Added DOMNodeSupport and JythonSupport boolean properties to DefaultObjectWrapper. This allows disabling the special wrapping of DOM nodes and Jython classes. This might be desirable for security reasons.
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index c0337db..eb44ce6 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -73,8 +73,10 @@
private boolean useAdaptersForContainers;
private boolean forceLegacyNonListCollections;
private boolean iterableSupport;
+ private boolean domNodeSupport;
+ private boolean jythonSupport;
private final boolean useAdapterForEnumerations;
-
+
/**
* Creates a new instance with the incompatible-improvements-version specified in
* {@link Configuration#DEFAULT_INCOMPATIBLE_IMPROVEMENTS}.
@@ -134,6 +136,8 @@
&& getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_26;
forceLegacyNonListCollections = dowDowCfg.getForceLegacyNonListCollections();
iterableSupport = dowDowCfg.getIterableSupport();
+ domNodeSupport = dowDowCfg.getDOMNodeSupport();
+ jythonSupport = dowDowCfg.getJythonSupport();
finalizeConstruction(writeProtected);
}
@@ -255,9 +259,10 @@
* Called for an object that isn't considered to be of a "basic" Java type, like for an application specific type,
* or for a W3C DOM node. In its default implementation, W3C {@link Node}-s will be wrapped as {@link NodeModel}-s
* (allows DOM tree traversal), Jython objects will be delegated to the {@code JythonWrapper}, others will be
- * wrapped using {@link BeansWrapper#wrap(Object)}. Note that if {@link #getMemberAccessPolicy()} doesn't return
- * a {@link DefaultMemberAccessPolicy} or {@link LegacyDefaultMemberAccessPolicy}, then Jython wrapper will be
- * skipped for security reasons.
+ * wrapped using {@link BeansWrapper#wrap(Object)}. However, these can be turned off with the
+ * {@link #setDOMNodeSupport(boolean)} and {@link #setJythonSupport(boolean)}. Note that if
+ * {@link #getMemberAccessPolicy()} doesn't return a {@link DefaultMemberAccessPolicy} or
+ * {@link LegacyDefaultMemberAccessPolicy}, then Jython wrapper will be skipped for security reasons.
*
* <p>
* When you override this method, you should first decide if you want to wrap the object in a custom way (and if so
@@ -265,16 +270,20 @@
* behavior is fine with you).
*/
protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException {
- if (obj instanceof Node) {
+ if (domNodeSupport && obj instanceof Node) {
return wrapDomNode(obj);
}
- MemberAccessPolicy memberAccessPolicy = getMemberAccessPolicy();
- if (memberAccessPolicy instanceof DefaultMemberAccessPolicy
- || memberAccessPolicy instanceof LegacyDefaultMemberAccessPolicy) {
- if (JYTHON_WRAPPER != null && JYTHON_OBJ_CLASS.isInstance(obj)) {
- return JYTHON_WRAPPER.wrap(obj);
+
+ if (jythonSupport) {
+ MemberAccessPolicy memberAccessPolicy = getMemberAccessPolicy();
+ if (memberAccessPolicy instanceof DefaultMemberAccessPolicy
+ || memberAccessPolicy instanceof LegacyDefaultMemberAccessPolicy) {
+ if (JYTHON_WRAPPER != null && JYTHON_OBJ_CLASS.isInstance(obj)) {
+ return JYTHON_WRAPPER.wrap(obj);
+ }
}
}
+
return super.wrap(obj);
}
@@ -389,6 +398,51 @@
}
/**
+ * Getter pair of {@link #setDOMNodeSupport(boolean)}; see there.
+ *
+ * @since 2.3.31
+ */
+ public final boolean getDOMNodeSupport() {
+ return domNodeSupport;
+ }
+
+ /**
+ * Enables wrapping {@link Node}-s on a special way (as described in the "XML Processing Guide" in the Manual);
+ * defaults to {@code true}.. If this is {@code true}, {@link Node}+s will be wrapped like any other generic object.
+ *
+ * @see #handleUnknownType(Object)
+ *
+ * @since 2.3.31
+ */
+ public void setDOMNodeSupport(boolean domNodeSupport) {
+ checkModifiable();
+ this.domNodeSupport = domNodeSupport;
+ }
+
+ /**
+ * Getter pair of {@link #setJythonSupport(boolean)}; see there.
+ *
+ * @since 2.3.31
+ */
+ public final boolean getJythonSupport() {
+ return jythonSupport;
+ }
+
+ /**
+ * Enables wrapping Jython objects in a special way; defaults to {@code true}. If this is {@code false}, they will
+ * be wrapped like any other generic object. Note that Jython wrapping is legacy feature, and might by disabled by
+ * the selected {@link MemberAccessPolicy}, even if this is {@code true}; see {@link #handleUnknownType(Object)}.
+ *
+ * @see #handleUnknownType(Object)
+ *
+ * @since 2.3.31
+ */
+ public void setJythonSupport(boolean jythonSupport) {
+ checkModifiable();
+ this.jythonSupport = jythonSupport;
+ }
+
+ /**
* Returns the lowest version number that is equivalent with the parameter version.
*
* @since 2.3.22
@@ -416,8 +470,12 @@
}
}
- return "useAdaptersForContainers=" + useAdaptersForContainers + ", forceLegacyNonListCollections="
- + forceLegacyNonListCollections + ", iterableSupport=" + iterableSupport + bwProps;
+ return "useAdaptersForContainers=" + useAdaptersForContainers
+ + ", forceLegacyNonListCollections=" + forceLegacyNonListCollections
+ + ", iterableSupport=" + iterableSupport
+ + ", domNodeSupport=" + domNodeSupport
+ + ", jythonSupport=" + jythonSupport
+ + bwProps;
}
}
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
index ff474fa..a9575bc 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
@@ -35,6 +35,8 @@
private boolean useAdaptersForContainers;
private boolean forceLegacyNonListCollections;
private boolean iterableSupport;
+ private boolean domNodeSupport;
+ private boolean jythonSupport;
protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements) {
super(DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements), true);
@@ -43,6 +45,8 @@
"freemarker.configuration", "DefaultObjectWrapper");
useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22;
forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.VERSION_INT_2_4_0;
+ domNodeSupport = true;
+ jythonSupport = true;
}
/** See {@link DefaultObjectWrapper#getUseAdaptersForContainers()}. */
@@ -65,6 +69,26 @@
this.forceLegacyNonListCollections = legacyNonListCollectionWrapping;
}
+ /** See {@link DefaultObjectWrapper#getDOMNodeSupport()}. */
+ public boolean getDOMNodeSupport() {
+ return domNodeSupport;
+ }
+
+ /** See {@link DefaultObjectWrapper#setDOMNodeSupport(boolean)}. */
+ public void setDOMNodeSupport(boolean domNodeSupport) {
+ this.domNodeSupport = domNodeSupport;
+ }
+
+ /** See {@link DefaultObjectWrapper#getJythonSupport()}. */
+ public boolean getJythonSupport() {
+ return jythonSupport;
+ }
+
+ /** See {@link DefaultObjectWrapper#setJythonSupport(boolean)}. */
+ public void setJythonSupport(boolean jythonSupport) {
+ this.jythonSupport = jythonSupport;
+ }
+
/**
* See {@link DefaultObjectWrapper#getIterableSupport()}.
*
@@ -90,6 +114,8 @@
result = result * prime + (useAdaptersForContainers ? 1231 : 1237);
result = result * prime + (forceLegacyNonListCollections ? 1231 : 1237);
result = result * prime + (iterableSupport ? 1231 : 1237);
+ result = result * prime + (domNodeSupport ? 1231 : 1237);
+ result = result * prime + (jythonSupport ? 1231 : 1237);
return result;
}
@@ -99,7 +125,9 @@
final DefaultObjectWrapperConfiguration thatDowCfg = (DefaultObjectWrapperConfiguration) that;
return useAdaptersForContainers == thatDowCfg.getUseAdaptersForContainers()
&& forceLegacyNonListCollections == thatDowCfg.forceLegacyNonListCollections
- && iterableSupport == thatDowCfg.iterableSupport;
+ && iterableSupport == thatDowCfg.iterableSupport
+ && domNodeSupport == thatDowCfg.domNodeSupport
+ && jythonSupport == thatDowCfg.jythonSupport;
}
}
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index a83ec5d..9d9123e 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -29179,18 +29179,18 @@
<para>If you are using the default object wrapper class
(<literal>freemarker.template.DefaultObjectWrapper</literal>),
or a subclass of it, you should disable the XML (DOM) wrapping
- feature of it, by overriding <literal>wrapDomNode(Object
- obj)</literal> so that it does this: <literal>return
- getModelFactory(obj.getClass()).create(obj, this);</literal>.
- The problem with the XML wrapping feature, which wraps
- <literal>org.w3c.dom.Node</literal> objects on special way to
- make them easier to work with in templates, is that this
- facility by design lets template authors evaluate arbitrary
- XPath expressions, and XPath can do too much in certain
- setups. If you really need the XML wrapping facility, review
- carefully what XPath expressions are possible in your setup.
- Also, be sure you don't use the long deprecated, and more
- dangerous <literal>freemarker.ext.xml</literal> package, only
+ feature of it, by setting its
+ <literal>DOMNodeSupport</literal> property to
+ <literal>false</literal>. The problem with the XML wrapping
+ feature, which wraps <literal>org.w3c.dom.Node</literal>
+ objects on special way to make them easier to work with in
+ templates, is that this facility by design lets template
+ authors evaluate arbitrary XPath expressions, and XPath can do
+ too much in certain setups. If you really need the XML
+ wrapping facility, review carefully what XPath expressions are
+ possible in your setup. Also, be sure you don't use the long
+ deprecated, and more dangerous
+ <literal>freemarker.ext.xml</literal> package, only
<literal>freemarker.ext.dom</literal>. Also, note that when
using the XML wrapping feature, not allowing
<literal>org.w3c.dom.Node</literal> methods in the
@@ -29419,6 +29419,16 @@
<itemizedlist>
<listitem>
+ <para>Added <literal>DOMNodeSupport</literal> and
+ <literal>JythonSupport</literal> <literal>boolean</literal>
+ properties to <literal>DefaultObjectWrapper</literal>. This
+ allows disabling the special wrapping of DOM nodes and Jython
+ classes. This might be desirable <link
+ linkend="faq_template_uploading_security">for security
+ reasons</link>.</para>
+ </listitem>
+
+ <listitem>
<para><link
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-145">FREEMARKER-145</link>:
Fixed bug where methods with "overloaded" return type may become
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index b973358..d9924f1 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -46,7 +46,9 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import org.hamcrest.Matchers;
import org.junit.Test;
+import org.python.core.PyString;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -58,6 +60,7 @@
import freemarker.ext.beans.EnumerationModel;
import freemarker.ext.beans.HashAdapter;
import freemarker.ext.beans.WhitelistMemberAccessPolicy;
+import freemarker.ext.jython.JythonSequenceModel;
import freemarker.ext.util.WrapperTemplateModel;
public class DefaultObjectWrapperTest {
@@ -1033,11 +1036,49 @@
@Test
public void testCanWrapDOM() throws SAXException, IOException, ParserConfigurationException,
TemplateModelException {
+ assertTrue(OW22.wrap(createDocument()) instanceof TemplateNodeModel);
+ }
+
+ @Test
+ public void testDisabledDOMNodeWrapping() throws SAXException, IOException, ParserConfigurationException,
+ TemplateModelException {
+ Document doc = createDocument();
+ DefaultObjectWrapperBuilder dowB = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_31);
+ dowB.setDOMNodeSupport(false);
+ DefaultObjectWrapper ow = dowB.build();
+
+ testDisabledDomWrappingInternal(doc, ow);
+
+ ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_31);
+ ow.setDOMNodeSupport(false);
+ testDisabledDomWrappingInternal(doc, ow);
+ }
+
+ private void testDisabledDomWrappingInternal(Document doc, DefaultObjectWrapper ow) throws TemplateModelException {
+ TemplateModel model = ow.wrap(doc);
+ assertFalse(model instanceof TemplateNodeModel);
+ assertTrue(model instanceof TemplateHashModel);
+ assertNotNull(((TemplateHashModel) model).get("getDoctype"));
+ assertNotNull(((TemplateHashModel) model).get("class"));
+ }
+
+ @Test
+ public void testDisabledJythonWrapping() throws SAXException, IOException, ParserConfigurationException,
+ TemplateModelException {
+ PyString pyString = new PyString("foo");
+
+ DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_31);
+ assertThat(ow.wrap(pyString), Matchers.instanceOf(JythonSequenceModel.class));
+
+ ow.setJythonSupport(false);
+ assertThat(ow.wrap(pyString), not(Matchers.instanceOf(JythonSequenceModel.class)));
+ }
+
+ private Document createDocument() throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>"));
- Document doc = db.parse(is);
- assertTrue(OW22.wrap(doc) instanceof TemplateNodeModel);
+ return db.parse(is);
}
@Test