You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2020/01/11 18:30:26 UTC
[freemarker] 01/02: Forward ported from 2.3-gae: FREEMARKER-124:
Changed DefaultMemberAccessPolicy to block more methods. This caused some
reworkings in other classes too: - Constructor member selectors (in a
whitelist or blacklist) don't match constructors in subclasses anymore. -
Added BlacklistMemberAccessPolicy (by refactoring
WhitelistMemberAccessPolicy to extend
MemberSelectorListMemberAccessPolicy). - Improved JavaDoc comments in
related classes
This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 3
in repository https://gitbox.apache.org/repos/asf/freemarker.git
commit aad5477771df4a7e375945c584bca644fb9f2721
Author: ddekany <dd...@apache.org>
AuthorDate: Sat Jan 11 19:27:35 2020 +0100
Forward ported from 2.3-gae: FREEMARKER-124: Changed DefaultMemberAccessPolicy to block more methods.
This caused some reworkings in other classes too:
- Constructor member selectors (in a whitelist or blacklist) don't match constructors in subclasses anymore.
- Added BlacklistMemberAccessPolicy (by refactoring WhitelistMemberAccessPolicy to extend MemberSelectorListMemberAccessPolicy).
- Improved JavaDoc comments in related classes
---
.../apache/freemarker/core/ConfigurationTest.java | 28 +
.../model/impl/DefaultMemberAccessPolicyTest.java | 143 +++++
...DefaultObjectWrapperMemberAccessPolicyTest.java | 208 +++++---
...ava => MemberSelectorListAccessPolicyTest.java} | 116 +++-
.../freemarker/core/ProcessingConfiguration.java | 6 +
.../model/impl/BlacklistMemberAccessPolicy.java | 48 ++
.../core/model/impl/ConstructorMatcher.java | 5 +
.../core/model/impl/DefaultMemberAccessPolicy.java | 204 +++++---
.../freemarker/core/model/impl/FieldMatcher.java | 5 +
.../core/model/impl/MemberAccessPolicy.java | 23 +-
.../freemarker/core/model/impl/MemberMatcher.java | 14 +-
...a => MemberSelectorListMemberAccessPolicy.java} | 127 +++--
.../freemarker/core/model/impl/MethodMatcher.java | 5 +
.../model/impl/WhitelistMemberAccessPolicy.java | 388 +-------------
.../model/impl/DefaultMemberAccessPolicy-rules | 582 +++++++++++++++++++++
.../core/model/impl/unsafeMethods.properties | 98 ----
16 files changed, 1289 insertions(+), 711 deletions(-)
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 708bae8..56d5390 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -25,6 +25,7 @@ import static org.apache.freemarker.test.hamcerst.Matchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
@@ -46,10 +47,14 @@ import java.util.TimeZone;
import java.util.TreeSet;
import org.apache.freemarker.core.Configuration.*;
+import org.apache.freemarker.core.model.TemplateHashModel;
import org.apache.freemarker.core.model.TemplateStringModel;
import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.MemberAccessPolicy;
+import org.apache.freemarker.core.model.impl.MemberSelectorListMemberAccessPolicy;
import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
import org.apache.freemarker.core.model.impl.SimpleString;
+import org.apache.freemarker.core.model.impl.WhitelistMemberAccessPolicy;
import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
import org.apache.freemarker.core.outputformat.OutputFormat;
import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
@@ -728,6 +733,29 @@ public class ConfigurationTest {
assertEquals(1000L * 60 * 60 * 2, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
}
+ public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY =
+ new WhitelistMemberAccessPolicy(MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
+ ImmutableList.<String>of(
+ File.class.getName() + ".getName()",
+ File.class.getName() + ".isFile()"),
+ ConfigurationTest.class.getClassLoader()));
+
+ @Test
+ public void testMemberAccessPolicySetting() throws TemplateException {
+ Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .setting(
+ "objectWrapper",
+ "DefaultObjectWrapper(3.0.0, "
+ + "memberAccessPolicy="
+ + ConfigurationTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
+ + ")")
+ .build();
+ TemplateHashModel m = (TemplateHashModel) cfg.getObjectWrapper().wrap(new File("x"));
+ assertNotNull(m.get("getName"));
+ assertNotNull(m.get("isFile"));
+ assertNull(m.get("delete"));
+ }
+
@Test
public void testGetSettingNamesAreSorted() throws Exception {
List<String> names = new ArrayList<>(Builder.getSettingNames());
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
new file mode 100644
index 0000000..42cd905
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+
+import org.apache.freemarker.core.Configuration;
+import org.junit.Test;
+
+public class DefaultMemberAccessPolicyTest {
+
+ private static final DefaultMemberAccessPolicy POLICY =
+ DefaultMemberAccessPolicy.getInstance(Configuration.VERSION_3_0_0);
+
+ @Test
+ public void testWhitelistRuleWithNoMembers() throws NoSuchMethodException {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(ProtectionDomain.class);
+ assertFalse(classPolicy.isMethodExposed(ProtectionDomain.class.getMethod("getClassLoader")));
+ }
+
+ @Test
+ public void testWhitelistRuleWithSomeMembers() throws NoSuchMethodException {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(URL.class);
+ assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("openStream")));
+ assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("getContent", Class[].class)));
+ assertTrue(classPolicy.isMethodExposed(URL.class.getMethod("getHost")));
+ assertTrue(classPolicy.isMethodExposed(URL.class.getMethod("sameFile", URL.class)));
+ }
+
+ @Test
+ public void testWhitelistRuleOnSubclass() throws NoSuchMethodException {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(CustomClassLoader.class);
+ assertFalse(classPolicy.isMethodExposed(CustomClassLoader.class.getMethod("loadClass", String.class)));
+ assertFalse(classPolicy.isMethodExposed(CustomClassLoader.class.getMethod("m1")));
+ }
+
+ @Test
+ public void testBlacklistUnlistedRule() throws NoSuchMethodException {
+ for (Class<?> testedClass : Arrays.asList(
+ Object.class, Thread.class, ThreadSubclass.class, ProtectionDomain.class, CustomClassLoader.class,
+ UserClass.class)) {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(testedClass);
+ assertFalse(classPolicy.isMethodExposed(testedClass.getMethod("wait")));
+ assertTrue(testedClass.getName(), classPolicy.isMethodExposed(testedClass.getMethod("toString")));
+ }
+
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(UserClass.class);
+ assertTrue(classPolicy.isMethodExposed(UserClass.class.getMethod("foo")));
+ }
+
+ @Test
+ public void testBlacklistUnlistedRuleOnSubclass() throws NoSuchMethodException {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(ThreadSubclass.class);
+ assertFalse(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("run")));
+ assertTrue(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("getName")));
+ assertTrue(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("m1")));
+ }
+
+ @Test
+ public void testWellKnownUnsafeMethodsAreBanned() throws NoSuchMethodException {
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(Class.class);
+ assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("forName", String.class)));
+ assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("newInstance")));
+ assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getClassLoader")));
+ assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getResourceAsStream", String.class)));
+ assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getResource", String.class)));
+ assertTrue(classPolicy.isMethodExposed(Class.class.getMethod("getProtectionDomain"))); // Allowed
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(ProtectionDomain.class);
+ assertFalse(classPolicy.isMethodExposed(ProtectionDomain.class.getMethod("getClassLoader")));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(ClassLoader.class);
+ assertFalse(classPolicy.isMethodExposed(ClassLoader.class.getMethod("loadClass", String.class)));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(Method.class);
+ assertFalse(classPolicy.isMethodExposed(Method.class.getMethod("invoke", Object.class, Object[].class)));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(Constructor.class);
+ assertFalse(classPolicy.isMethodExposed(Constructor.class.getMethod("newInstance", Object[].class)));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(Field.class);
+ assertFalse(classPolicy.isMethodExposed(Field.class.getMethod("get", Object.class)));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(Object.class);
+ assertTrue(classPolicy.isMethodExposed(Field.class.getMethod("getClass"))); // Allowed by design
+ assertFalse(classPolicy.isMethodExposed(Field.class.getMethod("wait")));
+ }
+ {
+ ClassMemberAccessPolicy classPolicy = POLICY.forClass(URL.class);
+ assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("openConnection")));
+ }
+ }
+
+ public static class ThreadSubclass extends Thread {
+ @Override
+ public void run() {
+ super.run();
+ }
+
+ public void m1() {}
+ }
+
+ public static class UserClass {
+ public void foo() {
+ }
+ }
+
+ public static class CustomClassLoader extends ClassLoader {
+ public void m1() {}
+ }
+
+}
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
index 7d4826c..4747f3e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
@@ -43,16 +43,18 @@ import com.google.common.collect.ImmutableMap;
public class DefaultObjectWrapperMemberAccessPolicyTest {
+ private final DefaultObjectWrapper dow
+ = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+
@Test
public void testMethodsWithDefaultMemberAccessPolicy() throws TemplateException {
- DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
- TemplateHashModel objM = (TemplateHashModel) ow.wrap(new C());
+ TemplateHashModel objM = (TemplateHashModel) dow.wrap(new C());
assertNotNull(objM.get("m1"));
- assertEquals("m2(true)", exec(ow, objM.get("m2"), true));
- assertEquals("staticM()", exec(ow, objM.get("staticM")));
+ assertEquals("m2(true)", exec(dow, objM.get("m2"), true));
+ assertEquals("staticM()", exec(dow, objM.get("staticM")));
- assertEquals("x", getHashValue(ow, objM, "x"));
+ assertEquals("x", getHashValue(dow, objM, "x"));
assertNotNull(objM.get("getX"));
assertNotNull(objM.get("setX"));
@@ -60,12 +62,9 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
assertNull(objM.get("notify"));
- // Because it was overridden, we allow it historically.
- assertNotNull(objM.get("run"));
-
- assertEquals("safe wait(1)", exec(ow, objM.get("wait"), 1L));
+ assertEquals("safe wait(1)", exec(dow, objM.get("wait"), 1L));
try {
- exec(ow, objM.get("wait")); // 0 arg overload is not visible, a it's "unsafe"
+ exec(dow, objM.get("wait")); // 0 arg overload is not visible, a it's "unsafe"
fail();
} catch (TemplateException e) {
assertThat(e.getMessage(), containsString("wait(int)"));
@@ -74,8 +73,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
@Test
public void testFieldsWithDefaultMemberAccessPolicy() throws TemplateException {
- DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
- TemplateHashModel objM = (TemplateHashModel) ow.wrap(new C());
+ TemplateHashModel objM = (TemplateHashModel) dow.wrap(new C());
assertFieldsNotExposed(objM);
}
@@ -89,34 +87,39 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
assertNull(objM.get("nonPublicField1"));
assertNull(objM.get("nonPublicField2"));
- // Strangely, public static fields are banned historically, while static methods aren't.
+ // Strangely, static fields are banned historically, while static methods aren't.
assertNull(objM.get("STATIC_FIELD"));
}
@Test
public void testGenericGetWithDefaultMemberAccessPolicy() throws TemplateException {
- DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
+ TemplateHashModel objM = (TemplateHashModel) dow.wrap(new CWithGenericGet());
- TemplateHashModel objM = (TemplateHashModel) ow.wrap(new CWithGenericGet());
+ assertEquals("get(x)", getHashValue(dow, objM, "x"));
+ }
+
+ @Test
+ public void testBlacklistRuleWithDefaultMemberAccessPolicy() throws TemplateException {
+ TemplateHashModel objM = (TemplateHashModel) dow.wrap(new CThread());
- assertEquals("get(x)", getHashValue(ow, objM, "x"));
+ assertNull(getHashValue(dow, objM, "run")); // blacklisted in Thread
+ assertNotNull(getHashValue(dow, objM, "m1")); // As Thread doesn't use whitelisted rule
+ assertNotNull(getHashValue(dow, objM, "toString"));
}
@Test
public void testConstructorsWithDefaultMemberAccessPolicy() throws TemplateException {
- DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
- assertNonPublicConstructorNotExposed(ow);
+ assertNonPublicConstructorNotExposed(dow);
- assertEquals(CWithConstructor.class,
- ow.newInstance(CWithConstructor.class, new TemplateModel[0], null)
- .getClass());
+ assertEquals(CWithConstructor.class, dow.newInstance(CWithConstructor.class, new TemplateModel[0], null)
+ .getClass());
assertEquals(CWithOverloadedConstructor.class,
- ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[0], null)
+ dow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[0], null)
.getClass());
assertEquals(CWithOverloadedConstructor.class,
- ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] {new SimpleNumber(1)}, null)
+ dow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] { new SimpleNumber(1) }, null)
.getClass());
}
@@ -167,8 +170,10 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testMethodsWithCustomMemberAccessPolicy() throws TemplateException {
DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
+ @Override
public boolean isMethodExposed(Method method) {
String name = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
@@ -177,10 +182,12 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
&& (paramTypes.length == 0 || paramTypes[0].equals(boolean.class)));
}
+ @Override
public boolean isConstructorExposed(Constructor<?> constructor) {
return true;
}
+ @Override
public boolean isFieldExposed(Field field) {
return true;
}
@@ -209,16 +216,20 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
owb.setExposeFields(true);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
+ @Override
public boolean isMethodExposed(Method method) {
return true;
}
+ @Override
public boolean isConstructorExposed(Constructor<?> constructor) {
return true;
}
+ @Override
public boolean isFieldExposed(Field field) {
return field.getName().equals("publicField1")
|| field.getName().equals("nonPublicField1");
@@ -239,16 +250,20 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testGenericGetWithCustomMemberAccessPolicy() throws TemplateException {
DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
+ @Override
public boolean isMethodExposed(Method method) {
return false;
}
+ @Override
public boolean isConstructorExposed(Constructor<?> constructor) {
return true;
}
+ @Override
public boolean isFieldExposed(Field field) {
return true;
}
@@ -265,17 +280,21 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testConstructorsWithCustomMemberAccessPolicy() throws TemplateException {
DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
+ @Override
public boolean isMethodExposed(Method method) {
return true;
}
+ @Override
public boolean isConstructorExposed(Constructor<?> constructor) {
return constructor.getDeclaringClass() == CWithOverloadedConstructor.class
&& constructor.getParameterTypes().length == 1;
}
+ @Override
public boolean isFieldExposed(Field field) {
return true;
}
@@ -302,54 +321,55 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
assertEquals(CWithOverloadedConstructor.class,
- ow.newInstance(CWithOverloadedConstructor.class,
- new TemplateModel[] {new SimpleNumber(1)}, null).getClass());
+ ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] { new SimpleNumber(1) }, null)
+ .getClass());
}
@Test
public void testMemberAccessPolicyAndApiBI() throws IOException, TemplateException {
- DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
- .memberAccessPolicy(new MemberAccessPolicy() {
+ DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+ owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+ return new ClassMemberAccessPolicy() {
@Override
- public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
- return new ClassMemberAccessPolicy() {
- public boolean isMethodExposed(Method method) {
- return method.getName().equals("size");
- }
-
- public boolean isConstructorExposed(Constructor<?> constructor) {
- return true;
- }
-
- public boolean isFieldExposed(Field field) {
- return true;
- }
- };
+ public boolean isMethodExposed(Method method) {
+ return method.getName().equals("size");
}
- })
- .build();
- Map<String, Object> dataModel = ImmutableMap.<String, Object>of("m", ImmutableMap.of("k", "v"));
+ @Override
+ public boolean isConstructorExposed(Constructor<?> constructor) {
+ return true;
+ }
- String templateSource = "size=${m?api.size()} get=${(m?api.get('k'))!'hidden'}";
+ @Override
+ public boolean isFieldExposed(Field field) {
+ return true;
+ }
+ };
+ }
+ });
+ DefaultObjectWrapper ow = owb.build();
+ Map<String, Object> dataModel = ImmutableMap.of("m", ImmutableMap.of("k", "v"));
+
+ String templateSourceCode = "size=${m?api.size()} get=${(m?api.get('k'))!'hidden'}";
{
Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
.objectWrapper(ow)
.apiBuiltinEnabled(true)
.build();
- Template template = new Template(null, templateSource, cfg);
+ Template template = new Template(null, templateSourceCode, cfg);
StringWriter out = new StringWriter();
template.process(dataModel, out);
assertEquals("size=1 get=hidden", out.toString());
}
-
{
Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
.objectWrapper(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build())
.apiBuiltinEnabled(true)
.build();
- Template template = new Template(null, templateSource, cfg);
+ Template template = new Template(null, templateSourceCode, cfg);
StringWriter out = new StringWriter();
template.process(dataModel, out);
assertEquals("size=1 get=v", out.toString());
@@ -357,53 +377,62 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
@Test
- public void testMemberAccessPolicyAndNewBI() throws IOException, TemplateException {
- DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
- .memberAccessPolicy(new MemberAccessPolicy() {
- public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
- return new ClassMemberAccessPolicy() {
- public boolean isMethodExposed(Method method) {
- return true;
- }
-
- public boolean isConstructorExposed(Constructor<?> constructor) {
- return constructor.getDeclaringClass().equals(CustomModel.class);
- }
-
- public boolean isFieldExposed(Field field) {
- return true;
- }
- };
- }
- })
- .build();
-
- String templateSource = "${'" + CustomModel.class.getName() + "'?new()} "
+ public void testMemberAccessPolicyAndNewBI() throws IOException, TemplateException, NoSuchMethodException {
+ DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+ owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ @Override
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+ return new ClassMemberAccessPolicy() {
+ @Override
+ public boolean isMethodExposed(Method method) {
+ return true;
+ }
+
+ @Override
+ public boolean isConstructorExposed(Constructor<?> constructor) {
+ return constructor.getDeclaringClass().equals(CustomModel.class);
+ }
+
+ @Override
+ public boolean isFieldExposed(Field field) {
+ return true;
+ }
+ };
+ }
+ });
+ DefaultObjectWrapper ow = owb.build();
+
+ String templateSourceCode = "${'" + CustomModel.class.getName() + "'?new()} "
+ "<#attempt>${'" + OtherCustomModel.class.getName() + "'?new()}<#recover>failed</#attempt>";
{
Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
.objectWrapper(ow)
.apiBuiltinEnabled(true)
.build();
- Template template = new Template(null, templateSource, cfg);
+ Template template = new Template(null, templateSourceCode, cfg);
+
StringWriter out = new StringWriter();
template.process(null, out);
assertEquals("1 failed", out.toString());
}
-
{
- Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
- Template template = new Template(null, templateSource, cfg);
+ DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+ MemberAccessPolicy pol = dow.getMemberAccessPolicy();
+ ClassMemberAccessPolicy cpol = pol.forClass(CustomModel.class);
+ assertTrue(cpol.isConstructorExposed(CustomModel.class.getConstructor()));
+
+ Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .objectWrapper(dow)
+ .apiBuiltinEnabled(true)
+ .build();
+ Template template = new Template(null, templateSourceCode, cfg);
+
StringWriter out = new StringWriter();
template.process(null, out);
assertEquals("1 2", out.toString());
}
}
- private static DefaultObjectWrapper createDefaultMemberAccessPolicyObjectWrapper() {
- return new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
- }
-
private static Object getHashValue(ObjectWrapperAndUnwrapper ow, TemplateHashModel objM, String key)
throws TemplateException {
return ow.unwrap(objM.get(key));
@@ -413,7 +442,8 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
assertThat(objM, instanceOf(TemplateFunctionModel.class));
TemplateModel[] argModels = new TemplateModel[args.length];
for (int i = 0; i < args.length; i++) {
- argModels[i] = ow.wrap(args[i]);
+ Object arg = args[i];
+ argModels[i] = ow.wrap(arg);
}
Object returnValue = ((TemplateFunctionModel) objM).execute(argModels, null, null);
return unwrap(ow, returnValue);
@@ -423,7 +453,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
return returnValue instanceof TemplateModel ? ow.unwrap((TemplateModel) returnValue) : returnValue;
}
- public static class C extends Thread {
+ public static class C {
public static final int STATIC_FIELD = 1;
public int publicField1 = 1;
public int publicField2 = 2;
@@ -471,18 +501,13 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public String wait(int otherOverload) {
return "safe wait(" + otherOverload + ")";
}
-
- @Override
- public void run() {
- return;
- }
}
public static class CExtended extends C {
public int publicField3 = 3;
}
- public static class CWithGenericGet extends Thread {
+ public static class CWithGenericGet {
public String get(String key) {
return "get(" + key + ")";
}
@@ -493,6 +518,13 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
}
+ public static class CThread extends Thread {
+ @Override
+ public void run() {}
+
+ public void m1() {}
+ }
+
public static class CWithOverloadedConstructor implements TemplateModel {
public CWithOverloadedConstructor() {
}
@@ -502,14 +534,18 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
public static class CustomModel implements TemplateNumberModel {
+ @Override
public Number getAsNumber() {
return 1;
}
}
public static class OtherCustomModel implements TemplateNumberModel {
+ @Override
public Number getAsNumber() {
return 2;
}
}
+
}
+
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
similarity index 84%
rename from freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java
rename to freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
index 9ed20be..1e667b2 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
@@ -25,12 +25,9 @@ import static org.junit.Assert.*;
import java.io.Serializable;
import java.util.Arrays;
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateHashModel;
import org.junit.Test;
-public class WhitelistMemberAccessPolicyTest {
+public class MemberSelectorListAccessPolicyTest {
@Test
public void testEmpty() throws NoSuchMethodException, NoSuchFieldException {
@@ -91,7 +88,7 @@ public class WhitelistMemberAccessPolicyTest {
assertFalse(c1Policy.isConstructorExposed(C1.class.getConstructor(int.class)));
assertTrue(c2Policy.isConstructorExposed(C2.class.getConstructor(int.class)));
- assertTrue(c3Policy.isConstructorExposed(C3.class.getConstructor(int.class)));
+ assertFalse(c3Policy.isConstructorExposed(C3.class.getConstructor(int.class))); // Not inherited
assertFalse(c1Policy.isMethodExposed(C1.class.getMethod("m1")));
assertTrue(c2Policy.isMethodExposed(C2.class.getMethod("m1")));
@@ -262,6 +259,75 @@ public class WhitelistMemberAccessPolicyTest {
}
@Test
+ public void testBlacklist1() throws NoSuchMethodException, NoSuchFieldException {
+ BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+ C1.class.getName() + ".m1()",
+ C1.class.getName() + ".f1",
+ C1.class.getName() + "." + C1.class.getSimpleName() + "()"
+ );
+
+ for (Class<?> cl : new Class[] { C1.class, C2.class, C3.class }) {
+ ClassMemberAccessPolicy classPolicy = policy.forClass(cl);
+ assertFalse(classPolicy.isMethodExposed(cl.getMethod("m1")));
+ assertTrue(classPolicy.isMethodExposed(cl.getMethod("m2", int.class)));
+ assertTrue(classPolicy.isMethodExposed(cl.getMethod("m3")));
+ assertFalse(classPolicy.isFieldExposed(cl.getField("f1")));
+ assertTrue(classPolicy.isFieldExposed(cl.getField("f2")));
+ if (cl != C2.class) {
+ assertEquals(cl != C1.class, classPolicy.isConstructorExposed(cl.getConstructor()));
+ }
+ assertTrue(classPolicy.isConstructorExposed(cl.getConstructor(int.class)));
+ }
+ }
+
+ @Test
+ public void testBlacklist2() throws NoSuchMethodException, NoSuchFieldException {
+ BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+ C2.class.getName() + ".m1()",
+ C2.class.getName() + ".f1",
+ C2.class.getName() + "." + C2.class.getSimpleName() + "(int)"
+ );
+
+ {
+ Class<C1> lc = C1.class;
+ ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+ assertTrue(classPolicy.isMethodExposed(lc.getMethod("m1")));
+ assertTrue(classPolicy.isFieldExposed(lc.getField("f1")));
+ assertTrue(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+ }
+
+ {
+ Class<C2> lc = C2.class;
+ ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+ assertFalse(classPolicy.isMethodExposed(lc.getMethod("m1")));
+ assertFalse(classPolicy.isFieldExposed(lc.getField("f1")));
+ assertFalse(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+ }
+
+ {
+ Class<C3> lc = C3.class;
+ ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+ assertFalse(classPolicy.isMethodExposed(lc.getMethod("m1")));
+ assertFalse(classPolicy.isFieldExposed(lc.getField("f1")));
+ assertTrue(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+ }
+ }
+
+ @Test
+ public void testBlacklistIgnoredAnnotation() throws NoSuchMethodException, NoSuchFieldException {
+ BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+ CAnnotationsTest1.class.getName() + ".m5()",
+ CAnnotationsTest1.class.getName() + ".f5",
+ CAnnotationsTest1.class.getName() + "." + CAnnotationsTest1.class.getSimpleName() + "()"
+ );
+
+ ClassMemberAccessPolicy classPolicy = policy.forClass(CAnnotationsTest1.class);
+ assertFalse(classPolicy.isMethodExposed(CAnnotationsTest1.class.getMethod("m5")));
+ assertFalse(classPolicy.isFieldExposed(CAnnotationsTest1.class.getField("f5")));
+ assertFalse(classPolicy.isConstructorExposed(CAnnotationsTest1.class.getConstructor()));
+ }
+
+ @Test
public void memberSelectorParserIgnoresWhitespace() throws NoSuchMethodException {
WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
(CArrayArgs.class.getName() + ".m1(java.lang.String)").replace(".", "\n\t. "),
@@ -306,7 +372,7 @@ public class WhitelistMemberAccessPolicyTest {
newWhitelistMemberAccessPolicy("java.util.Date.toString(");
fail();
} catch (IllegalArgumentException e) {
- assertThat(e.getMessage(), containsString("missing closing ')'"));
+ assertThat(e.getMessage(), containsString("should end with ')'"));
}
try {
newWhitelistMemberAccessPolicy("java.util.Date.m(com.x-y)");
@@ -360,32 +426,18 @@ public class WhitelistMemberAccessPolicyTest {
CAnnotationsTest2.class.getConstructor(int.class, int.class, int.class, int.class)));
}
- public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY =
- newWhitelistMemberAccessPolicy(
- C1.class.getName() + ".m1()",
- C1.class.getName() + ".m3()");
-
- @Test
- public void stringBasedConfigurationTest() throws TemplateException {
- Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
- .setting(
- "objectWrapper",
- "DefaultObjectWrapper(3.0.0, " +
- "memberAccessPolicy="
- + WhitelistMemberAccessPolicyTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
- + ")")
- .build();
- TemplateHashModel m = (TemplateHashModel) cfg.getObjectWrapper().wrap(new C1());
- assertNotNull(m.get("m1"));
- assertNull(m.get("m2"));
- assertNotNull(m.get("m3"));
- }
-
private static WhitelistMemberAccessPolicy newWhitelistMemberAccessPolicy(String... memberSelectors) {
return new WhitelistMemberAccessPolicy(
- WhitelistMemberAccessPolicy.MemberSelector.parse(
+ MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
Arrays.asList(memberSelectors),
- WhitelistMemberAccessPolicyTest.class.getClassLoader()));
+ MemberSelectorListAccessPolicyTest.class.getClassLoader()));
+ }
+
+ private static BlacklistMemberAccessPolicy newBlacklistMemberAccessPolicy(String... memberSelectors) {
+ return new BlacklistMemberAccessPolicy(
+ MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
+ Arrays.asList(memberSelectors),
+ MemberSelectorListAccessPolicyTest.class.getClassLoader()));
}
public static class C1 {
@@ -421,6 +473,10 @@ public class WhitelistMemberAccessPolicyTest {
super(x);
}
+ @Override
+ public void m2(int x) {
+ }
+
public void m2(boolean x) {
}
@@ -448,6 +504,7 @@ public class WhitelistMemberAccessPolicyTest {
}
public static class E1 implements I1Sub {
+ @Override
public void m1() {
}
@@ -550,6 +607,7 @@ public class WhitelistMemberAccessPolicyTest {
@TemplateAccessible
public void m4() {}
+ @Override
public void m5() {}
public void m6() {}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index 230dfed..e3215cf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -33,6 +33,8 @@ import java.util.TimeZone;
import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.impl.MemberAccessPolicy;
import org.apache.freemarker.core.pluggablebuiltin.TruncateBuiltinAlgorithm;
import org.apache.freemarker.core.pluggablebuiltin.impl.DefaultTruncateBuiltinAlgorithm;
import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
@@ -508,6 +510,10 @@ public interface ProcessingConfiguration {
* called to resolve the <code>"com.example.SomeClassName"</code> string to a class. The default value is {@link
* TemplateClassResolver#UNRESTRICTED}. If you allow users to upload templates, it's important to use a
* custom restrictive {@link TemplateClassResolver} or {@link TemplateClassResolver#ALLOW_NOTHING}.
+ *
+ * <p>Note that the {@link MemberAccessPolicy} used by the {@link ObjectWrapper} also influences what constructors
+ * are available. Allowing the resolution of the class here is not enough in itself, as the
+ * {@link MemberAccessPolicy} has to allow exposing the particular constructor you try to call as well.
*/
TemplateClassResolver getNewBuiltinClassResolver();
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
new file mode 100644
index 0000000..5bb0d62
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Collection;
+
+/**
+ * Blacklist-based member access policy, that is, members that are matched by the listing will not be accessible, all
+ * others will be. Note that {@link DefaultObjectWrapper} and its subclasses doesn't discover all members on the first
+ * place, and the {@link MemberAccessPolicy} just removes from that set of members, never adds to it.
+ *
+ * <p>This class is rarely useful in itself, and mostly meant to be used when composing a {@link MemberAccessPolicy}
+ * from other {@link MemberAccessPolicy}-es. If you are serious about security, never use this alone; consider using
+ * {@link WhitelistMemberAccessPolicy} as part of your solution.
+ *
+ * <p>See more about the rules at {@link MemberSelectorListMemberAccessPolicy}. Unlike
+ * {@link WhitelistMemberAccessPolicy}, {@link BlacklistMemberAccessPolicy} doesn't have annotations that can be used
+ * to add members to the member selector list.
+ */
+public class BlacklistMemberAccessPolicy extends MemberSelectorListMemberAccessPolicy {
+
+ /**
+ * @param memberSelectors
+ * List of member selectors; see {@link MemberSelectorListMemberAccessPolicy} class-level documentation for
+ * more.
+ */
+ public BlacklistMemberAccessPolicy(Collection<MemberSelector> memberSelectors) {
+ super(memberSelectors, ListType.BLACKLIST, null);
+ }
+
+}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
index 0300fd4..6d90470 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
@@ -29,4 +29,9 @@ final class ConstructorMatcher extends MemberMatcher<Constructor<?>, ExecutableM
protected ExecutableMemberSignature toMemberSignature(Constructor<?> member) {
return new ExecutableMemberSignature(member);
}
+
+ @Override
+ protected boolean matchInUpperBoundTypeSubtypes() {
+ return false;
+ }
}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
index 6899efe..4713f2a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
@@ -19,88 +19,36 @@
package org.apache.freemarker.core.model.impl;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.util.HashMap;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
+import java.util.List;
import java.util.Set;
-import java.util.StringTokenizer;
import org.apache.freemarker.core.Version;
import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core.util._ClassUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.freemarker.core.model.impl.MemberSelectorListMemberAccessPolicy.MemberSelector;
/**
- * Legacy black list based member access policy, used only to keep old behavior, as it can't provide meaningful safety.
- * Do not use it if you allow untrusted users to edit templates!
+ * Member access policy, used to implement default behavior; it can't provide safety in practice, if you allow
+ * untrusted users to edit templates! Use {@link WhitelistMemberAccessPolicy} if you need stricter control.
*/
public final class DefaultMemberAccessPolicy implements MemberAccessPolicy {
- private static final Logger LOG = LoggerFactory.getLogger(DefaultMemberAccessPolicy.class);
- private static final String UNSAFE_METHODS_PROPERTIES = "unsafeMethods.properties";
- private static final Set<Method> UNSAFE_METHODS = createUnsafeMethodsSet();
-
- private static Set<Method> createUnsafeMethodsSet() {
- try {
- Properties props = _ClassUtils.loadProperties(DefaultObjectWrapper.class, UNSAFE_METHODS_PROPERTIES);
- Set<Method> set = new HashSet<>(props.size() * 4 / 3, 1f);
- Map<String, Class<?>> primClasses = createPrimitiveClassesMap();
- for (Object key : props.keySet()) {
- try {
- set.add(parseMethodSpec((String) key, primClasses));
- } catch (ClassNotFoundException | NoSuchMethodException e) {
- LOG.debug("Failed to get unsafe method", e);
- }
- }
- return set;
- } catch (Exception e) {
- throw new RuntimeException("Could not load unsafe method set", e);
- }
- }
-
- private static Method parseMethodSpec(String methodSpec, Map<String, Class<?>> primClasses)
- throws ClassNotFoundException,
- NoSuchMethodException {
- int brace = methodSpec.indexOf('(');
- int dot = methodSpec.lastIndexOf('.', brace);
- Class<?> clazz = _ClassUtils.forName(methodSpec.substring(0, dot));
- String methodName = methodSpec.substring(dot + 1, brace);
- String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1);
- StringTokenizer tok = new StringTokenizer(argSpec, ",");
- int argcount = tok.countTokens();
- Class<?>[] argTypes = new Class[argcount];
- for (int i = 0; i < argcount; i++) {
- String argClassName = tok.nextToken();
- argTypes[i] = primClasses.get(argClassName);
- if (argTypes[i] == null) {
- argTypes[i] = _ClassUtils.forName(argClassName);
- }
- }
- return clazz.getMethod(methodName, argTypes);
- }
-
- private static Map<String, Class<?>> createPrimitiveClassesMap() {
- Map<String, Class<?>> map = new HashMap<>();
- map.put("boolean", Boolean.TYPE);
- map.put("byte", Byte.TYPE);
- map.put("char", Character.TYPE);
- map.put("short", Short.TYPE);
- map.put("int", Integer.TYPE);
- map.put("long", Long.TYPE);
- map.put("float", Float.TYPE);
- map.put("double", Double.TYPE);
- return map;
- }
-
- private DefaultMemberAccessPolicy() {
- }
private static final DefaultMemberAccessPolicy INSTANCE = new DefaultMemberAccessPolicy();
+ private final Set<Class<?>> whitelistRuleFinalClasses;
+ private final Set<Class<?>> whitelistRuleNonFinalClasses;
+ private final WhitelistMemberAccessPolicy whitelistMemberAccessPolicy;
+ private final BlacklistMemberAccessPolicy blacklistMemberAccessPolicy;
+
/**
* Returns the singleton that's compatible with the given incompatible improvements version.
*/
@@ -111,24 +59,124 @@ public final class DefaultMemberAccessPolicy implements MemberAccessPolicy {
return INSTANCE;
}
- public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
- return CLASS_MEMBER_ACCESS_POLICY_INSTANCE;
- }
+ private DefaultMemberAccessPolicy() {
+ try {
+ ClassLoader classLoader = DefaultMemberAccessPolicy.class.getClassLoader();
- private static final BacklistClassMemberAccessPolicy CLASS_MEMBER_ACCESS_POLICY_INSTANCE
- = new BacklistClassMemberAccessPolicy();
- private static class BacklistClassMemberAccessPolicy implements ClassMemberAccessPolicy {
+ whitelistRuleFinalClasses = new HashSet<>();
+ whitelistRuleNonFinalClasses = new HashSet<>();
+ Set<Class<?>> typesWithBlacklistUnlistedRule = new HashSet<>();
+ List<MemberSelector> whitelistMemberSelectors = new ArrayList<>();
+ for (String line : loadMemberSelectorFileLines()) {
+ line = line.trim();
+ if (!MemberSelector.isIgnoredLine(line)) {
+ if (line.startsWith("@")) {
+ String[] lineParts = line.split("\\s+");
+ if (lineParts.length != 2) {
+ throw new IllegalStateException("Malformed @ line: " + line);
+ }
+ String typeName = lineParts[1];
+ Class<?> upperBoundType;
+ try {
+ upperBoundType = classLoader.loadClass(typeName);
+ } catch (ClassNotFoundException e) {
+ upperBoundType = null;
+ }
+ String rule = lineParts[0].substring(1);
+ if (rule.equals("whitelistPolicyIfAssignable")) {
+ if (upperBoundType != null) {
+ Set<Class<?>> targetSet =
+ (upperBoundType.getModifiers() & Modifier.FINAL) != 0
+ ? whitelistRuleFinalClasses
+ : whitelistRuleNonFinalClasses;
+ targetSet.add(upperBoundType);
+ }
+ } else if (rule.equals("blacklistUnlistedMembers")) {
+ if (upperBoundType != null) {
+ typesWithBlacklistUnlistedRule.add(upperBoundType);
+ }
+ } else {
+ throw new IllegalStateException("Unhandled rule: " + rule);
+ }
+ } else {
+ MemberSelector memberSelector =
+ MemberSelector.parse(line, classLoader);
+ Class<?> upperBoundType = memberSelector.getUpperBoundType();
+ if (upperBoundType != null) {
+ if (!whitelistRuleFinalClasses.contains(upperBoundType)
+ && !whitelistRuleNonFinalClasses.contains(upperBoundType)
+ && !typesWithBlacklistUnlistedRule.contains(upperBoundType)) {
+ throw new IllegalStateException("Type without rule: " + upperBoundType.getName());
+ }
+ // We always do the same, as "blacklistUnlistedMembers" is also defined via a whitelist:
+ whitelistMemberSelectors.add(memberSelector);
+ }
+ }
+ }
+ }
+
+ whitelistMemberAccessPolicy = new WhitelistMemberAccessPolicy(whitelistMemberSelectors);
- public boolean isMethodExposed(Method method) {
- return !UNSAFE_METHODS.contains(method);
+ // Generate blacklists based on the whitelist and the members of "blacklistUnlistedMembers" types:
+ List<MemberSelector> blacklistMemberSelectors = new ArrayList<MemberSelector>();
+ for (Class<?> blacklistUnlistedRuleType : typesWithBlacklistUnlistedRule) {
+ ClassMemberAccessPolicy classPolicy = whitelistMemberAccessPolicy.forClass(blacklistUnlistedRuleType);
+ for (Method method : blacklistUnlistedRuleType.getMethods()) {
+ if (!classPolicy.isMethodExposed(method)) {
+ blacklistMemberSelectors.add(new MemberSelector(blacklistUnlistedRuleType, method));
+ }
+ }
+ for (Constructor<?> constructor : blacklistUnlistedRuleType.getConstructors()) {
+ if (!classPolicy.isConstructorExposed(constructor)) {
+ blacklistMemberSelectors.add(new MemberSelector(blacklistUnlistedRuleType, constructor));
+ }
+ }
+ for (Field field : blacklistUnlistedRuleType.getFields()) {
+ if (!classPolicy.isFieldExposed(field)) {
+ blacklistMemberSelectors.add(new MemberSelector(blacklistUnlistedRuleType, field));
+ }
+ }
+ }
+ blacklistMemberAccessPolicy = new BlacklistMemberAccessPolicy(blacklistMemberSelectors);
+ } catch (Exception e) {
+ throw new IllegalStateException("Couldn't init " + this.getClass().getName() + " instance", e);
}
+ }
- public boolean isConstructorExposed(Constructor<?> constructor) {
- return true;
+ private static List<String> loadMemberSelectorFileLines() throws IOException {
+ List<String> whitelist = new ArrayList<String>();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(
+ DefaultMemberAccessPolicy.class.getResourceAsStream("DefaultMemberAccessPolicy-rules"),
+ StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ whitelist.add(line);
+ }
}
- public boolean isFieldExposed(Field field) {
+ return whitelist;
+ }
+
+ @Override
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+ if (isTypeWithWhitelistRule(contextClass)) {
+ return whitelistMemberAccessPolicy.forClass(contextClass);
+ } else {
+ return blacklistMemberAccessPolicy.forClass(contextClass);
+ }
+ }
+
+ private boolean isTypeWithWhitelistRule(Class<?> contextClass) {
+ if (whitelistRuleFinalClasses.contains(contextClass)) {
return true;
}
+ for (Class<?> nonFinalClass : whitelistRuleNonFinalClasses) {
+ if (nonFinalClass.isAssignableFrom(contextClass)) {
+ return true;
+ }
+ }
+ return false;
}
+
}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
index dda187f..a61d54c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
@@ -29,4 +29,9 @@ final class FieldMatcher extends MemberMatcher<Field, String> {
protected String toMemberSignature(Field member) {
return member.getName();
}
+
+ @Override
+ protected boolean matchInUpperBoundTypeSubtypes() {
+ return true;
+ }
}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
index c5fa8b6..c00dbde 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
@@ -19,24 +19,29 @@
package org.apache.freemarker.core.model.impl;
+import org.apache.freemarker.core.Environment;
import org.apache.freemarker.core.model.ObjectWrapper;
import org.apache.freemarker.core.model.TemplateModel;
/**
- * Implement this to specify what class members are accessible from templates.
+ * Implement this to restrict what class members (methods, fields, constructors) are accessible from templates.
+ * Note, however, that {@link DefaultObjectWrapper} and its subclasses doesn't discover all members on the first place,
+ * and the {@link MemberAccessPolicy} just removes from that set of members, never adds to it. Practically speaking,
+ * it's the last filter in the chain.
*
- * <p>The instance is usually set via {@link DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)} (or if
- * you use {@link DefaultObjectWrapper}, with
- * {@link DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)}).
+ * <p>{@link MemberAccessPolicy}-s meant to be used inside {@link ObjectWrapper}-s, and their existence is transparent
+ * for the rest of the system. The instance is usually set via
+ * {@link DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)} (or if you use
+ * {@link DefaultObjectWrapper}, with {@link DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)}).
*
* <p>As {@link DefaultObjectWrapper}, and its subclasses like {@link DefaultObjectWrapper}, only discover public
- * members, it's pointless to whitelist non-public members. An {@link MemberAccessPolicy} is a filter applied to
- * the set of members that {@link DefaultObjectWrapper} intends to expose on the first place. (Also, while public members
- * declared in non-public classes are discovered by {@link DefaultObjectWrapper}, Java reflection will not allow accessing those
- * normally, so generally it's not useful to whitelist those either.)
+ * members, it's pointless to whitelist non-public members. (Also, while public members declared in non-public classes
+ * are discovered by {@link DefaultObjectWrapper}, Java reflection will not allow accessing those normally, so generally
+ * it's not useful to whitelist those either.)
*
* <p>Note that if you add {@link TemplateModel}-s directly to the data-model, those are not wrapped by the
- * {@link ObjectWrapper}, and so the {@link MemberAccessPolicy} won't affect those.
+ * {@link ObjectWrapper} (from {@link Environment#getObjectWrapper()}), and so the {@link MemberAccessPolicy} won't
+ * affect those.
*
* <p>Implementations must be thread-safe, and instances generally should be singletons on JVM level. FreeMarker
* caches its class metadata in a global (static, JVM-scope) cache for shared use, and the {@link MemberAccessPolicy}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
index 76080a1..c1fbe7c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
@@ -43,6 +43,8 @@ abstract class MemberMatcher<M extends Member, S> {
*/
protected abstract S toMemberSignature(M member);
+ protected abstract boolean matchInUpperBoundTypeSubtypes();
+
/**
* Adds a member that this {@link MemberMatcher} will match.
*
@@ -83,7 +85,17 @@ abstract class MemberMatcher<M extends Member, S> {
S memberSignature = toMemberSignature(member);
Types upperBoundTypes = signaturesToUpperBoundTypes.get(memberSignature);
- return upperBoundTypes != null && containsTypeOrSuperType(upperBoundTypes, contextClass);
+ return upperBoundTypes != null
+ && (matchInUpperBoundTypeSubtypes()
+ ? containsTypeOrSuperType(upperBoundTypes, contextClass)
+ : containsExactType(upperBoundTypes, contextClass));
+ }
+
+ private static boolean containsExactType(Types types, Class<?> c) {
+ if (c == null) {
+ return false;
+ }
+ return types.set.contains(c);
}
private static boolean containsTypeOrSuperType(Types types, Class<?> c) {
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
similarity index 76%
copy from freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
copy to freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
index 64ef7a9..48e48e4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
@@ -19,6 +19,7 @@
package org.apache.freemarker.core.model.impl;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -27,59 +28,64 @@ import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
-import org.apache.freemarker.core.model.ObjectWrapper;
import org.apache.freemarker.core.util._ClassUtils;
import org.apache.freemarker.core.util._NullArgumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Whitelist-based member access policy, that is, only members that you have explicitly whitelisted will be accessible.
- * The whitelist content is application specific, and can be significant work to put together, but it's the only way
- * you can achieve any practical safety if you don't fully trust the users who can edit templates. Of course, this only
- * can deal with the {@link ObjectWrapper} aspect of safety; please check the Manual to see what else is needed. Also,
- * since this is related to security, read the documentation of {@link MemberAccessPolicy}, to know about the
- * pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
+ * Superclass for member-selector-list-based member access policies, like {@link WhitelistMemberAccessPolicy}.
*
- * <p>There are two ways you can add members to the whitelist:
+ * <p>There are two ways you can add members to the member selector list:
* <ul>
* <li>Via a list of member selectors passed to the constructor
- * <li>Via {@link TemplateAccessible} annotation
+ * <li>Via annotation (concrete type depends on subclass)
* </ul>
*
- * <p>When a member is whitelisted, it's identified by the following data (with the example of
- * {@code com.example.MyClass.myMethod(int, int)} being whitelisted):
+ * <p>Members are identified with the following data (with the example of
+ * {@code com.example.MyClass.myMethod(int, int)}):
* <ul>
* <li>Upper bound class ({@code com.example.MyClass} in the example)
* <li>Member name ({@code myMethod} in the example), except for constructors where it's unused
* <li>Parameter types ({@code int, int} in the example), except for fields where it's unused
* </ul>
*
- * <p>Once you have whitelisted a member in the upper bound class, it will be automatically whitelisted in all
- * subclasses of that, even if the whitelisted member is a field or constructor (which doesn't support overriding, but
- * it will be treated as such if the field name or constructor parameter types match).
- * It's called "upper bound" class, because the member will only be whitelisted in classes that are {@code instanceof}
- * the upper bound class. That restriction stands even if the member was inherited from another class or
- * interface, and it wasn't even overridden in the upper bound class; the member won't be whitelisted in the
- * class/interface where it was inherited from, if that type is more generic than the upper bound class.
+ * <p>If a method or field is matched in the upper bound type, it will be automatically matched in all subtypes of that.
+ * It's called "upper bound" type, because the member will only be matched in classes that are {@code instanceof}
+ * the upper bound class. That restriction stands even if the member was inherited from another type (class or
+ * interface), and it wasn't even overridden in the upper bound type; the member won't be matched in the
+ * type where it was inherited from, if that type is more generic than the upper bound type.
*
- * <p>Note that the return type of methods aren't used in any way. So if you whitelist {@code myMethod(int, int)}, and
- * it has multiple variants with different return types (which is possible on the bytecode level), then you have
- * whitelisted all variants of it.
+ * <p>The above inheritance rule doesn't apply to constructors. That's consistent with the fact constructors aren't
+ * inherited in Java (or pretty much any other language). So for example, if you add {@code com.example.A.A()} to
+ * the member selector list, and {@code B extends A}, then {@code com.example.B.B()} is still not matched by that list.
+ * If you want it to be matched, you have to add {@code com.example.B.B()} to list explicitly.
+ *
+ * <p>Note that the return type of methods aren't used in any way. If {@code myMethod(int, int)} has multiple variants
+ * with different return types (which is possible on the bytecode level) but the same parameter types, then all
+ * variants of it is matched, or none is. Similarly, the type of fields isn't used either, only the name of the field
+ * matters.
*/
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
- private static final Logger LOG = LoggerFactory.getLogger(WhitelistMemberAccessPolicy.class);
+public abstract class MemberSelectorListMemberAccessPolicy implements MemberAccessPolicy {
+ private static final Logger LOG = LoggerFactory.getLogger(MemberSelectorListMemberAccessPolicy.class);
+
+ enum ListType {
+ /** Only matched members will be exposed. */
+ WHITELIST,
+ /** Matched members will not be exposed. */
+ BLACKLIST
+ }
+ private final ListType listType;
private final MethodMatcher methodMatcher;
private final ConstructorMatcher constructorMatcher;
private final FieldMatcher fieldMatcher;
+ private final Class<? extends Annotation> matchAnnotation;
/**
- * A condition that matches some type members. See {@link WhitelistMemberAccessPolicy} documentation for more.
+ * A condition that matches some type members. See {@link MemberSelectorListMemberAccessPolicy} documentation for more.
* Exactly one of these will be non-{@code null}:
* {@link #getMethod()}, {@link #getConstructor()}, {@link #getField()}, {@link #getException()}.
- *
- * @since 2.3.30
*/
public final static class MemberSelector {
private final Class<?> upperBoundType;
@@ -261,7 +267,7 @@ public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
if (hasArgList) {
if (cleanedStr.charAt(cleanedStr.length() - 1) != ')') {
- throw new IllegalArgumentException("Malformed whitelist entry (missing closing ')'): "
+ throw new IllegalArgumentException("Malformed whitelist entry (should end with ')'): "
+ memberSelectorString);
}
String argsSpec = cleanedStr.substring(postMemberNameIdx + 1, cleanedStr.length() - 1);
@@ -309,19 +315,46 @@ public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
}
/**
- * Convenience method to parse all member selectors in the collection; see {@link #parse(String, ClassLoader)}.
+ * Convenience method to parse all member selectors in the collection (see {@link #parse(String, ClassLoader)}),
+ * while also filtering out blank and comment lines; see {@link #parse(String, ClassLoader)},
+ * and {@link #isIgnoredLine(String)}.
*/
public static List<MemberSelector> parse(Collection<String> memberSelectors,
ClassLoader classLoader) {
List<MemberSelector> parsedMemberSelectors = new ArrayList<>(memberSelectors.size());
for (String memberSelector : memberSelectors) {
- parsedMemberSelectors.add(parse(memberSelector, classLoader));
+ if (!isIgnoredLine(memberSelector)) {
+ parsedMemberSelectors.add(parse(memberSelector, classLoader));
+ }
}
return parsedMemberSelectors;
}
+
+ /**
+ * A line is ignored if it's blank or a comment. A line is be blank if it doesn't contain non-whitespace
+ * character. A line is a comment if it starts with {@code #}, or {@code //} (ignoring any amount of
+ * preceding whitespace).
+ */
+ public static boolean isIgnoredLine(String line) {
+ String trimmedLine = line.trim();
+ return trimmedLine.length() == 0 || trimmedLine.startsWith("#") || trimmedLine.startsWith("//");
+ }
}
- public WhitelistMemberAccessPolicy(Collection<MemberSelector> memberSelectors) {
+ /**
+ * @param memberSelectors
+ * List of member selectors; see {@link MemberSelectorListMemberAccessPolicy} class-level documentation for
+ * more.
+ * @param listType
+ * Decides the "color" of the list
+ * @param matchAnnotation
+ */
+ MemberSelectorListMemberAccessPolicy(
+ Collection<MemberSelector> memberSelectors, ListType listType,
+ Class<? extends Annotation> matchAnnotation) {
+ this.listType = listType;
+ this.matchAnnotation = matchAnnotation;
+
methodMatcher = new MethodMatcher();
constructorMatcher = new ConstructorMatcher();
fieldMatcher = new FieldMatcher();
@@ -345,29 +378,47 @@ public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
}
@Override
- public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
+ public final ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
@Override
public boolean isMethodExposed(Method method) {
- return methodMatcher.matches(contextClass, method)
- || _MethodUtils.getInheritableAnnotation(contextClass, method, TemplateAccessible.class) != null;
+ return matchResultToIsExposedResult(
+ methodMatcher.matches(contextClass, method)
+ || matchAnnotation != null
+ && _MethodUtils.getInheritableAnnotation(contextClass, method, matchAnnotation)
+ != null);
}
@Override
public boolean isConstructorExposed(Constructor<?> constructor) {
- return constructorMatcher.matches(contextClass, constructor)
- || _MethodUtils.getInheritableAnnotation(contextClass, constructor, TemplateAccessible.class)
- != null;
+ return matchResultToIsExposedResult(
+ constructorMatcher.matches(contextClass, constructor)
+ || matchAnnotation != null
+ && _MethodUtils.getInheritableAnnotation(contextClass, constructor, matchAnnotation)
+ != null);
}
@Override
public boolean isFieldExposed(Field field) {
- return fieldMatcher.matches(contextClass, field)
- || _MethodUtils.getInheritableAnnotation(contextClass, field, TemplateAccessible.class) != null;
+ return matchResultToIsExposedResult(
+ fieldMatcher.matches(contextClass, field)
+ || matchAnnotation != null
+ && _MethodUtils.getInheritableAnnotation(contextClass, field, matchAnnotation)
+ != null);
}
};
}
+ private boolean matchResultToIsExposedResult(boolean matches) {
+ if (listType == ListType.WHITELIST) {
+ return matches;
+ }
+ if (listType == ListType.BLACKLIST) {
+ return !matches;
+ }
+ throw new AssertionError();
+ }
+
private static boolean isWellFormedClassName(String s) {
if (s.length() == 0) {
return false;
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
index ce66f4c..88711d6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
@@ -33,4 +33,9 @@ final class MethodMatcher extends MemberMatcher<Method, ExecutableMemberSignatur
protected ExecutableMemberSignature toMemberSignature(Method member) {
return new ExecutableMemberSignature(member);
}
+
+ @Override
+ protected boolean matchInUpperBoundTypeSubtypes() {
+ return true;
+ }
}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
index 64ef7a9..25e4801 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
@@ -19,390 +19,34 @@
package org.apache.freemarker.core.model.impl;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
-import java.util.StringTokenizer;
import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.util._ClassUtils;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
- * Whitelist-based member access policy, that is, only members that you have explicitly whitelisted will be accessible.
- * The whitelist content is application specific, and can be significant work to put together, but it's the only way
- * you can achieve any practical safety if you don't fully trust the users who can edit templates. Of course, this only
- * can deal with the {@link ObjectWrapper} aspect of safety; please check the Manual to see what else is needed. Also,
- * since this is related to security, read the documentation of {@link MemberAccessPolicy}, to know about the
- * pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
+ * Whitelist-based member access policy, that is, only members that are matched by the listing will be exposed.
+ * Note that {@link DefaultObjectWrapper} and its subclasses doesn't discover all members on the first place, and the
+ * {@link MemberAccessPolicy} just removes from that set of members, never adds to it.
*
- * <p>There are two ways you can add members to the whitelist:
- * <ul>
- * <li>Via a list of member selectors passed to the constructor
- * <li>Via {@link TemplateAccessible} annotation
- * </ul>
+ * <p>The whitelist content is usually application specific, and can be significant work to put together, but it's the
+ * only way you can achieve any practical safety when you don't fully trust the users who can edit templates.
*
- * <p>When a member is whitelisted, it's identified by the following data (with the example of
- * {@code com.example.MyClass.myMethod(int, int)} being whitelisted):
- * <ul>
- * <li>Upper bound class ({@code com.example.MyClass} in the example)
- * <li>Member name ({@code myMethod} in the example), except for constructors where it's unused
- * <li>Parameter types ({@code int, int} in the example), except for fields where it's unused
- * </ul>
+ * <p>See more about the rules at {@link MemberSelectorListMemberAccessPolicy}.
+ * {@link TemplateAccessible} annotation may be used to add members to the whitelist.
*
- * <p>Once you have whitelisted a member in the upper bound class, it will be automatically whitelisted in all
- * subclasses of that, even if the whitelisted member is a field or constructor (which doesn't support overriding, but
- * it will be treated as such if the field name or constructor parameter types match).
- * It's called "upper bound" class, because the member will only be whitelisted in classes that are {@code instanceof}
- * the upper bound class. That restriction stands even if the member was inherited from another class or
- * interface, and it wasn't even overridden in the upper bound class; the member won't be whitelisted in the
- * class/interface where it was inherited from, if that type is more generic than the upper bound class.
- *
- * <p>Note that the return type of methods aren't used in any way. So if you whitelist {@code myMethod(int, int)}, and
- * it has multiple variants with different return types (which is possible on the bytecode level), then you have
- * whitelisted all variants of it.
+ * <p>Of course, this only can deal with the {@link ObjectWrapper} aspect of safety; please check the Manual to see what
+ * else is needed. Also, since this is related to security, read the documentation of {@link MemberAccessPolicy}, to
+ * know about the pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
*/
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
- private static final Logger LOG = LoggerFactory.getLogger(WhitelistMemberAccessPolicy.class);
-
- private final MethodMatcher methodMatcher;
- private final ConstructorMatcher constructorMatcher;
- private final FieldMatcher fieldMatcher;
+public class WhitelistMemberAccessPolicy extends MemberSelectorListMemberAccessPolicy {
/**
- * A condition that matches some type members. See {@link WhitelistMemberAccessPolicy} documentation for more.
- * Exactly one of these will be non-{@code null}:
- * {@link #getMethod()}, {@link #getConstructor()}, {@link #getField()}, {@link #getException()}.
- *
- * @since 2.3.30
+ * @param memberSelectors
+ * List of member selectors; see {@link MemberSelectorListMemberAccessPolicy} class-level documentation for
+ * more.
*/
- public final static class MemberSelector {
- private final Class<?> upperBoundType;
- private final Method method;
- private final Constructor<?> constructor;
- private final Field field;
- private final Exception exception;
- private final String exceptionMemberSelectorString;
-
- /**
- * Use if you want to match methods similar to the specified one, in types that are {@code instanceof} of
- * the specified upper bound type. When methods are matched, only the name and the parameter types matter.
- */
- public MemberSelector(Class<?> upperBoundType, Method method) {
- _NullArgumentException.check("upperBoundType", upperBoundType);
- _NullArgumentException.check("method", method);
- this.upperBoundType = upperBoundType;
- this.method = method;
- this.constructor = null;
- this.field = null;
- this.exception = null;
- this.exceptionMemberSelectorString = null;
- }
-
- /**
- * Use if you want to match constructors similar to the specified one, in types that are {@code instanceof} of
- * the specified upper bound type. When constructors are matched, only the parameter types matter.
- */
- public MemberSelector(Class<?> upperBoundType, Constructor<?> constructor) {
- _NullArgumentException.check("upperBoundType", upperBoundType);
- _NullArgumentException.check("constructor", constructor);
- this.upperBoundType = upperBoundType;
- this.method = null;
- this.constructor = constructor;
- this.field = null;
- this.exception = null;
- this.exceptionMemberSelectorString = null;
- }
-
- /**
- * Use if you want to match fields similar to the specified one, in types that are {@code instanceof} of
- * the specified upper bound type. When fields are matched, only the name matters.
- */
- public MemberSelector(Class<?> upperBoundType, Field field) {
- _NullArgumentException.check("upperBoundType", upperBoundType);
- _NullArgumentException.check("field", field);
- this.upperBoundType = upperBoundType;
- this.method = null;
- this.constructor = null;
- this.field = field;
- this.exception = null;
- this.exceptionMemberSelectorString = null;
- }
-
- /**
- * Used to store the result of a parsing that's failed for a reason that we can skip on runtime (typically,
- * when a missing class or member was referred).
- *
- * @param upperBoundType {@code null} if resolving the upper bound type itself failed.
- * @param exception Not {@code null}
- * @param exceptionMemberSelectorString Not {@code null}; the selector whose resolution has failed, used in
- * the log message.
- */
- public MemberSelector(Class<?> upperBoundType, Exception exception, String exceptionMemberSelectorString) {
- _NullArgumentException.check("exception", exception);
- _NullArgumentException.check("exceptionMemberSelectorString", exceptionMemberSelectorString);
- this.upperBoundType = upperBoundType;
- this.method = null;
- this.constructor = null;
- this.field = null;
- this.exception = exception;
- this.exceptionMemberSelectorString = exceptionMemberSelectorString;
- }
-
- /**
- * Maybe {@code null} if {@link #getException()} is non-{@code null}.
- */
- public Class<?> getUpperBoundType() {
- return upperBoundType;
- }
-
- /**
- * Maybe {@code null};
- * set if the selector matches methods similar to the returned one, and there was no exception.
- */
- public Method getMethod() {
- return method;
- }
-
- /**
- * Maybe {@code null};
- * set if the selector matches constructors similar to the returned one, and there was no exception.
- */
- public Constructor<?> getConstructor() {
- return constructor;
- }
-
- /**
- * Maybe {@code null};
- * set if the selector matches fields similar to the returned one, and there was no exception.
- */
- public Field getField() {
- return field;
- }
-
- /**
- * Maybe {@code null}
- */
- public Exception getException() {
- return exception;
- }
-
- /**
- * Maybe {@code null}
- */
- public String getExceptionMemberSelectorString() {
- return exceptionMemberSelectorString;
- }
-
- /**
- * Parses a member selector that was specified with a string.
- *
- * @param classLoader
- * Used to resolve class names in the member selectors. Generally you want to pick a class that belongs to
- * you application (not to a 3rd party library, like FreeMarker), and then call
- * {@link Class#getClassLoader()} on that. Note that the resolution of the classes is not lazy, and so the
- * {@link ClassLoader} won't be stored after this method returns.
- * @param memberSelectorString
- * Describes the member (method, constructor, field) which you want to whitelist. Starts with the full
- * qualified name of the member, like {@code com.example.MyClass.myMember}. Unless it's a field, the
- * name is followed by comma separated list of the parameter types inside parentheses, like in
- * {@code com.example.MyClass.myMember(java.lang.String, boolean)}. The parameter type names must be
- * also full qualified names, except primitive type names. Array types must be indicated with one or
- * more {@code []}-s after the type name. Varargs arguments shouldn't be marked with {@code ...}, but with
- * {@code []}. In the member name, like {@code com.example.MyClass.myMember}, the class refers to the so
- * called "upper bound class". Regarding that and inheritance rules see the class level documentation.
- *
- * @return The {@link MemberSelector}, which might has non-{@code null} {@link MemberSelector#exception}.
- */
- public static MemberSelector parse(String memberSelectorString, ClassLoader classLoader) {
- if (memberSelectorString.contains("<") || memberSelectorString.contains(">")
- || memberSelectorString.contains("...") || memberSelectorString.contains(";")) {
- throw new IllegalArgumentException(
- "Malformed whitelist entry (shouldn't contain \"<\", \">\", \"...\", or \";\"): "
- + memberSelectorString);
- }
- String cleanedStr = memberSelectorString.trim().replaceAll("\\s*([\\.,\\(\\)\\[\\]])\\s*", "$1");
-
- int postMemberNameIdx;
- boolean hasArgList;
- {
- int openParenIdx = cleanedStr.indexOf('(');
- hasArgList = openParenIdx != -1;
- postMemberNameIdx = hasArgList ? openParenIdx : cleanedStr.length();
- }
-
- final int postClassDotIdx = cleanedStr.lastIndexOf('.', postMemberNameIdx);
- if (postClassDotIdx == -1) {
- throw new IllegalArgumentException("Malformed whitelist entry (missing dot): " + memberSelectorString);
- }
-
- Class<?> upperBoundClass;
- String upperBoundClassStr = cleanedStr.substring(0, postClassDotIdx);
- if (!isWellFormedClassName(upperBoundClassStr)) {
- throw new IllegalArgumentException("Malformed whitelist entry (malformed upper bound class name): "
- + memberSelectorString);
- }
- try {
- upperBoundClass = classLoader.loadClass(upperBoundClassStr);
- } catch (ClassNotFoundException e) {
- return new MemberSelector(null, e, cleanedStr);
- }
-
- String memberName = cleanedStr.substring(postClassDotIdx + 1, postMemberNameIdx);
- if (!isWellFormedJavaIdentifier(memberName)) {
- throw new IllegalArgumentException(
- "Malformed whitelist entry (malformed member name): " + memberSelectorString);
- }
-
- if (hasArgList) {
- if (cleanedStr.charAt(cleanedStr.length() - 1) != ')') {
- throw new IllegalArgumentException("Malformed whitelist entry (missing closing ')'): "
- + memberSelectorString);
- }
- String argsSpec = cleanedStr.substring(postMemberNameIdx + 1, cleanedStr.length() - 1);
- StringTokenizer tok = new StringTokenizer(argsSpec, ",");
- int argCount = tok.countTokens();
- Class<?>[] argTypes = new Class[argCount];
- for (int i = 0; i < argCount; i++) {
- String argClassName = tok.nextToken();
- int arrayDimensions = 0;
- while (argClassName.endsWith("[]")) {
- arrayDimensions++;
- argClassName = argClassName.substring(0, argClassName.length() - 2);
- }
- Class<?> argClass;
- Class<?> primArgClass = _ClassUtils.resolveIfPrimitiveTypeName(argClassName);
- if (primArgClass != null) {
- argClass = primArgClass;
- } else {
- if (!isWellFormedClassName(argClassName)) {
- throw new IllegalArgumentException(
- "Malformed whitelist entry (malformed argument class name): " + memberSelectorString);
- }
- try {
- argClass = classLoader.loadClass(argClassName);
- } catch (ClassNotFoundException | SecurityException e) {
- return new MemberSelector(upperBoundClass, e, cleanedStr);
- }
- }
- argTypes[i] = _ClassUtils.getArrayClass(argClass, arrayDimensions);
- }
- try {
- return memberName.equals(upperBoundClass.getSimpleName())
- ? new MemberSelector(upperBoundClass, upperBoundClass.getConstructor(argTypes))
- : new MemberSelector(upperBoundClass, upperBoundClass.getMethod(memberName, argTypes));
- } catch (NoSuchMethodException | SecurityException e) {
- return new MemberSelector(upperBoundClass, e, cleanedStr);
- }
- } else {
- try {
- return new MemberSelector(upperBoundClass, upperBoundClass.getField(memberName));
- } catch (NoSuchFieldException | SecurityException e) {
- return new MemberSelector(upperBoundClass, e, cleanedStr);
- }
- }
- }
-
- /**
- * Convenience method to parse all member selectors in the collection; see {@link #parse(String, ClassLoader)}.
- */
- public static List<MemberSelector> parse(Collection<String> memberSelectors,
- ClassLoader classLoader) {
- List<MemberSelector> parsedMemberSelectors = new ArrayList<>(memberSelectors.size());
- for (String memberSelector : memberSelectors) {
- parsedMemberSelectors.add(parse(memberSelector, classLoader));
- }
- return parsedMemberSelectors;
- }
- }
-
public WhitelistMemberAccessPolicy(Collection<MemberSelector> memberSelectors) {
- methodMatcher = new MethodMatcher();
- constructorMatcher = new ConstructorMatcher();
- fieldMatcher = new FieldMatcher();
- for (MemberSelector memberSelector : memberSelectors) {
- Class<?> upperBoundClass = memberSelector.upperBoundType;
- if (memberSelector.exception != null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Member selector ignored due to error: " + memberSelector.getExceptionMemberSelectorString(),
- memberSelector.exception);
- }
- } else if (memberSelector.constructor != null) {
- constructorMatcher.addMatching(upperBoundClass, memberSelector.constructor);
- } else if (memberSelector.method != null) {
- methodMatcher.addMatching(upperBoundClass, memberSelector.method);
- } else if (memberSelector.field != null) {
- fieldMatcher.addMatching(upperBoundClass, memberSelector.field);
- } else {
- throw new AssertionError();
- }
- }
- }
-
- @Override
- public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
- return new ClassMemberAccessPolicy() {
- @Override
- public boolean isMethodExposed(Method method) {
- return methodMatcher.matches(contextClass, method)
- || _MethodUtils.getInheritableAnnotation(contextClass, method, TemplateAccessible.class) != null;
- }
-
- @Override
- public boolean isConstructorExposed(Constructor<?> constructor) {
- return constructorMatcher.matches(contextClass, constructor)
- || _MethodUtils.getInheritableAnnotation(contextClass, constructor, TemplateAccessible.class)
- != null;
- }
-
- @Override
- public boolean isFieldExposed(Field field) {
- return fieldMatcher.matches(contextClass, field)
- || _MethodUtils.getInheritableAnnotation(contextClass, field, TemplateAccessible.class) != null;
- }
- };
- }
-
- private static boolean isWellFormedClassName(String s) {
- if (s.length() == 0) {
- return false;
- }
- int identifierStartIdx = 0;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (i == identifierStartIdx) {
- if (!Character.isJavaIdentifierStart(c)) {
- return false;
- }
- } else if (c == '.' && i != s.length() - 1) {
- identifierStartIdx = i + 1;
- } else {
- if (!Character.isJavaIdentifierPart(c)) {
- return false;
- }
- }
- }
- return true;
- }
-
- private static boolean isWellFormedJavaIdentifier(String s) {
- if (s.length() == 0) {
- return false;
- }
- if (!Character.isJavaIdentifierStart(s.charAt(0))) {
- return false;
- }
- for (int i = 1; i < s.length(); i++) {
- if (!Character.isJavaIdentifierPart(s.charAt(i))) {
- return false;
- }
- }
- return true;
+ super(memberSelectors, ListType.WHITELIST, TemplateAccessible.class);
}
-}
+}
\ No newline at end of file
diff --git a/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy-rules b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy-rules
new file mode 100644
index 0000000..474b831
--- /dev/null
+++ b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy-rules
@@ -0,0 +1,582 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Used by DefaultMemberAccessPolicy.
+# It does NOT provide enough safety if template authors aren't as trusted as the developers; you need to use a custom
+# whitelist then (see WhitelistMemberAccessPolicy).
+
+# Each member entry must have a upper bound type that already has a rule defined. The rules are associated with the
+# upper bound type in the lines starting with @. The possible rules are:
+# - whitelistPolicyIfAssignable: Members of the type and of its subtypes can only access members that were whitelisted.
+# Thus, if you extend a such type, and add a new method, it won't be exposed, as it wasn't whitelisted.
+# - blacklistUnlistedMembers: Members of the type that are not listed will be blacklisted. Once a member was blacklisted,
+# it will be blacklisted in subtypes as well. If you extend a type that has tris rule, and add a new method, it will
+# be exposed, as it wasn't blacklisted.
+
+@blacklistUnlistedMembers java.lang.Object
+# Disallowed since 2.3.0: java.lang.Object.wait(long)
+# Disallowed since 2.3.0: java.lang.Object.wait(long,int)
+# Disallowed since 2.3.0: java.lang.Object.wait()
+java.lang.Object.equals(java.lang.Object)
+java.lang.Object.toString()
+java.lang.Object.hashCode()
+java.lang.Object.getClass()
+# Disallowed since 2.3.0: java.lang.Object.notify()
+# Disallowed since 2.3.0: java.lang.Object.notifyAll()
+
+@blacklistUnlistedMembers java.lang.Thread
+java.lang.Thread.getName()
+# Disallowed since 2.3.0, since 2.3.30 even when overridden: java.lang.Thread.run()
+java.lang.Thread.isInterrupted()
+# Disallowed since 2.3.30: java.lang.Thread.currentThread()
+# Disallowed since 2.3.30: java.lang.Thread.onSpinWait()
+# Disallowed since 2.3.0: java.lang.Thread.join(long,int)
+# Disallowed since 2.3.0: java.lang.Thread.join(long)
+# Disallowed since 2.3.0: java.lang.Thread.join()
+java.lang.Thread.getThreadGroup()
+# Disallowed since 2.3.0: java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
+java.lang.Thread.holdsLock(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.Thread.getStackTrace()
+java.lang.Thread.checkAccess()
+# Disallowed since 2.3.30: java.lang.Thread.dumpStack()
+# Disallowed since 2.3.0: java.lang.Thread.setPriority(int)
+# Disallowed since 2.3.0: java.lang.Thread.setDaemon(boolean)
+# Disallowed since 2.3.0: java.lang.Thread.start()
+# Disallowed since 2.3.0: java.lang.Thread.sleep(long)
+# Disallowed since 2.3.0: java.lang.Thread.sleep(long,int)
+java.lang.Thread.isDaemon()
+java.lang.Thread.getPriority()
+# Disallowed since 2.3.0: java.lang.Thread.getContextClassLoader()
+# Disallowed since 2.3.0: java.lang.Thread.resume()
+# Disallowed since 2.3.0: java.lang.Thread.interrupt()
+java.lang.Thread.activeCount()
+# Disallowed since 2.3.30: java.lang.Thread.enumerate(java.lang.Thread[])
+java.lang.Thread.isAlive()
+# Disallowed since 2.3.30: java.lang.Thread.setDefaultUncaughtExceptionHandler(java.lang.Thread$UncaughtExceptionHandler)
+# Disallowed since 2.3.30: java.lang.Thread.getUncaughtExceptionHandler()
+# Disallowed since 2.3.30: java.lang.Thread.yield()
+# Disallowed since 2.3.0: java.lang.Thread.stop()
+java.lang.Thread.interrupted()
+# Disallowed since 2.3.0: java.lang.Thread.suspend()
+# Disallowed since 2.3.0: java.lang.Thread.setName(java.lang.String)
+java.lang.Thread.countStackFrames()
+# Disallowed since 2.3.30: java.lang.Thread.getAllStackTraces()
+java.lang.Thread.getId()
+java.lang.Thread.getState()
+# Disallowed since 2.3.30: java.lang.Thread.getDefaultUncaughtExceptionHandler()
+# Disallowed since 2.3.30: java.lang.Thread.setUncaughtExceptionHandler(java.lang.Thread$UncaughtExceptionHandler)
+
+@whitelistPolicyIfAssignable java.lang.ThreadGroup
+java.lang.ThreadGroup.getName()
+# Disallowed since 2.3.30: java.lang.ThreadGroup.list()
+java.lang.ThreadGroup.getParent()
+java.lang.ThreadGroup.checkAccess()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.setDaemon(boolean)
+java.lang.ThreadGroup.isDaemon()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.resume()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.interrupt()
+java.lang.ThreadGroup.getMaxPriority()
+java.lang.ThreadGroup.activeCount()
+# Disallowed since 2.3.30: java.lang.ThreadGroup.enumerate(java.lang.ThreadGroup[],boolean)
+# Disallowed since 2.3.30: java.lang.ThreadGroup.enumerate(java.lang.ThreadGroup[])
+# Disallowed since 2.3.30: java.lang.ThreadGroup.enumerate(java.lang.Thread[])
+# Disallowed since 2.3.30: java.lang.ThreadGroup.enumerate(java.lang.Thread[],boolean)
+# Disallowed since 2.3.30: java.lang.ThreadGroup.uncaughtException(java.lang.Thread,java.lang.Throwable)
+# Disallowed since 2.3.0: java.lang.ThreadGroup.stop()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.suspend()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.setMaxPriority(int)
+java.lang.ThreadGroup.activeGroupCount()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.destroy()
+java.lang.ThreadGroup.isDestroyed()
+java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup)
+# Disallowed since 2.3.0: java.lang.ThreadGroup.allowThreadSuspension(boolean)
+
+@whitelistPolicyIfAssignable java.lang.Runtime
+# Disallowed since 2.3.30: java.lang.Runtime.getRuntime()
+# Disallowed since 2.3.0: java.lang.Runtime.exit(int)
+# Disallowed since 2.3.30: java.lang.Runtime.runFinalization()
+java.lang.Runtime.version()
+# Disallowed since 2.3.0: java.lang.Runtime.loadLibrary(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Runtime.gc()
+# Disallowed since 2.3.0: java.lang.Runtime.load(java.lang.String)
+java.lang.Runtime.freeMemory()
+java.lang.Runtime.maxMemory()
+java.lang.Runtime.availableProcessors()
+# Disallowed since 2.3.0: java.lang.Runtime.halt(int)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String[])
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String,java.lang.String[],java.io.File)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String[],java.lang.String[])
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String[],java.lang.String[],java.io.File)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String,java.lang.String[])
+# Disallowed since 2.3.0: java.lang.Runtime.addShutdownHook(java.lang.Thread)
+# Disallowed since 2.3.0: java.lang.Runtime.removeShutdownHook(java.lang.Thread)
+java.lang.Runtime.totalMemory()
+# Disallowed since 2.3.0: java.lang.Runtime.traceInstructions(boolean)
+# Disallowed since 2.3.0: java.lang.Runtime.traceMethodCalls(boolean)
+
+@whitelistPolicyIfAssignable java.lang.System
+# Disallowed since 2.3.0: java.lang.System.exit(int)
+# Disallowed since 2.3.0: java.lang.System.runFinalization()
+# Disallowed since 2.3.0: java.lang.System.runFinalizersOnExit(boolean)
+java.lang.System.getProperty(java.lang.String)
+java.lang.System.getProperty(java.lang.String,java.lang.String)
+java.lang.System.identityHashCode(java.lang.Object)
+java.lang.System.currentTimeMillis()
+java.lang.System.nanoTime()
+# Disallowed since 2.3.30: java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)
+# Disallowed since 2.3.30: java.lang.System.getSecurityManager()
+java.lang.System.mapLibraryName(java.lang.String)
+# Disallowed since 2.3.0: java.lang.System.loadLibrary(java.lang.String)
+# Disallowed since 2.3.30: java.lang.System.console()
+# Disallowed since 2.3.30: java.lang.System.inheritedChannel()
+# Disallowed since 2.3.0: java.lang.System.setSecurityManager(java.lang.SecurityManager)
+java.lang.System.lineSeparator()
+# Disallowed since 2.3.0: java.lang.System.setProperty(java.lang.String,java.lang.String)
+java.lang.System.getenv(java.lang.String)
+java.lang.System.getenv()
+# Disallowed since 2.3.30: java.lang.System.getLogger(java.lang.String,java.util.ResourceBundle)
+# Disallowed since 2.3.30: java.lang.System.getLogger(java.lang.String)
+# Disallowed since 2.3.30: java.lang.System.gc()
+# Disallowed since 2.3.0: java.lang.System.setIn(java.io.InputStream)
+# Disallowed since 2.3.0: java.lang.System.setOut(java.io.PrintStream)
+# Disallowed since 2.3.0: java.lang.System.setErr(java.io.PrintStream)
+java.lang.System.getProperties()
+# Disallowed since 2.3.0: java.lang.System.setProperties(java.util.Properties)
+# Disallowed since 2.3.0: java.lang.System.clearProperty(java.lang.String)
+# Disallowed since 2.3.0: java.lang.System.load(java.lang.String)
+
+@whitelistPolicyIfAssignable java.lang.ClassLoader
+java.lang.ClassLoader.getName()
+# Disallowed since 2.3.30: java.lang.ClassLoader.loadClass(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getPlatformClassLoader()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getSystemClassLoader()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getSystemResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getSystemResource(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getResource(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getResources(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getDefinedPackage(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.resources(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.isRegisteredAsParallelCapable()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getSystemResources(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getParent()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getUnnamedModule()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getDefinedPackages()
+# Disallowed since 2.3.30: java.lang.ClassLoader.setDefaultAssertionStatus(boolean)
+# Disallowed since 2.3.30: java.lang.ClassLoader.setPackageAssertionStatus(java.lang.String,boolean)
+# Disallowed since 2.3.30: java.lang.ClassLoader.setClassAssertionStatus(java.lang.String,boolean)
+# Disallowed since 2.3.30: java.lang.ClassLoader.clearAssertionStatus()
+
+@whitelistPolicyIfAssignable java.security.ProtectionDomain
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getClassLoader()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getCodeSource()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.implies(java.security.Permission)
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getPermissions()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getPrincipals()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.staticPermissionsOnly()
+
+@whitelistPolicyIfAssignable java.lang.Class
+java.lang.Class.getName()
+# Disallowed since 2.3.30: java.lang.Class.forName(java.lang.Module,java.lang.String)
+# Disallowed since 2.3.0: java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
+# Disallowed since 2.3.0: java.lang.Class.forName(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Class.getModule()
+java.lang.Class.getProtectionDomain()
+java.lang.Class.isAssignableFrom(java.lang.Class)
+java.lang.Class.isInstance(java.lang.Object)
+java.lang.Class.getModifiers()
+java.lang.Class.isInterface()
+java.lang.Class.isArray()
+java.lang.Class.isPrimitive()
+java.lang.Class.getSuperclass()
+java.lang.Class.cast(java.lang.Object)
+java.lang.Class.componentType()
+java.lang.Class.componentType()
+java.lang.Class.describeConstable()
+java.lang.Class.getComponentType()
+java.lang.Class.isAnnotation()
+java.lang.Class.isEnum()
+java.lang.Class.getTypeParameters()
+# Disallowed since 2.3.0: java.lang.Class.getClassLoader()
+# Disallowed since 2.3.0: java.lang.Class.newInstance()
+java.lang.Class.getInterfaces()
+java.lang.Class.getEnclosingClass()
+java.lang.Class.getSimpleName()
+java.lang.Class.getCanonicalName()
+# Disallowed since 2.3.30: java.lang.Class.getResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Class.getResource(java.lang.String)
+java.lang.Class.getPackageName()
+java.lang.Class.desiredAssertionStatus()
+java.lang.Class.getMethod(java.lang.String,java.lang.Class[])
+java.lang.Class.isAnnotationPresent(java.lang.Class)
+java.lang.Class.descriptorString()
+java.lang.Class.arrayType()
+java.lang.Class.toGenericString()
+java.lang.Class.isSynthetic()
+java.lang.Class.getGenericSuperclass()
+java.lang.Class.getPackage()
+java.lang.Class.getGenericInterfaces()
+# Disallowed since 2.3.30: java.lang.Class.getSigners()
+java.lang.Class.getEnclosingMethod()
+java.lang.Class.getEnclosingConstructor()
+java.lang.Class.getDeclaringClass()
+java.lang.Class.getTypeName()
+java.lang.Class.isAnonymousClass()
+java.lang.Class.isLocalClass()
+java.lang.Class.isMemberClass()
+java.lang.Class.getClasses()
+java.lang.Class.getFields()
+java.lang.Class.getMethods()
+java.lang.Class.getConstructors()
+java.lang.Class.getField(java.lang.String)
+java.lang.Class.getConstructor(java.lang.Class[])
+java.lang.Class.getDeclaredClasses()
+java.lang.Class.getDeclaredFields()
+java.lang.Class.getDeclaredMethods()
+java.lang.Class.getDeclaredConstructors()
+java.lang.Class.getDeclaredField(java.lang.String)
+java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[])
+java.lang.Class.getDeclaredConstructor(java.lang.Class[])
+java.lang.Class.getEnumConstants()
+java.lang.Class.asSubclass(java.lang.Class)
+java.lang.Class.getAnnotation(java.lang.Class)
+java.lang.Class.getAnnotationsByType(java.lang.Class)
+java.lang.Class.getAnnotations()
+java.lang.Class.getDeclaredAnnotation(java.lang.Class)
+java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.Class.getDeclaredAnnotations()
+java.lang.Class.getAnnotatedSuperclass()
+java.lang.Class.getAnnotatedInterfaces()
+java.lang.Class.getNestHost()
+java.lang.Class.isNestmateOf(java.lang.Class)
+java.lang.Class.getNestMembers()
+
+@whitelistPolicyIfAssignable java.lang.Package
+java.lang.Package.getName()
+java.lang.Package.isAnnotationPresent(java.lang.Class)
+java.lang.Package.getPackage(java.lang.String)
+java.lang.Package.getAnnotation(java.lang.Class)
+java.lang.Package.getAnnotationsByType(java.lang.Class)
+java.lang.Package.getAnnotations()
+java.lang.Package.getDeclaredAnnotation(java.lang.Class)
+java.lang.Package.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.Package.getDeclaredAnnotations()
+java.lang.Package.getPackages()
+java.lang.Package.isSealed()
+java.lang.Package.isSealed(java.net.URL)
+java.lang.Package.getSpecificationTitle()
+java.lang.Package.getSpecificationVersion()
+java.lang.Package.getSpecificationVendor()
+java.lang.Package.getImplementationTitle()
+java.lang.Package.getImplementationVersion()
+java.lang.Package.getImplementationVendor()
+java.lang.Package.isCompatibleWith(java.lang.String)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Method
+# Disallowed since 2.3.0: java.lang.reflect.Method.invoke(java.lang.Object,java.lang.Object[])
+java.lang.reflect.Method.getName()
+java.lang.reflect.Method.getModifiers()
+java.lang.reflect.Method.getTypeParameters()
+java.lang.reflect.Method.getReturnType()
+java.lang.reflect.Method.getParameterTypes()
+java.lang.reflect.Method.toGenericString()
+java.lang.reflect.Method.isSynthetic()
+java.lang.reflect.Method.getDeclaringClass()
+java.lang.reflect.Method.getAnnotation(java.lang.Class)
+java.lang.reflect.Method.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.Method.setAccessible(boolean)
+java.lang.reflect.Method.isVarArgs()
+java.lang.reflect.Method.getParameterCount()
+java.lang.reflect.Method.getParameterAnnotations()
+java.lang.reflect.Method.getGenericParameterTypes()
+java.lang.reflect.Method.getGenericExceptionTypes()
+java.lang.reflect.Method.isDefault()
+java.lang.reflect.Method.getGenericReturnType()
+java.lang.reflect.Method.getExceptionTypes()
+java.lang.reflect.Method.isBridge()
+java.lang.reflect.Method.getDefaultValue()
+java.lang.reflect.Method.getAnnotatedReturnType()
+java.lang.reflect.Method.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Method.getAnnotatedParameterTypes()
+java.lang.reflect.Method.getParameters()
+java.lang.reflect.Method.getAnnotatedReceiverType()
+java.lang.reflect.Method.getAnnotatedExceptionTypes()
+java.lang.reflect.Method.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Method.getAnnotations()
+java.lang.reflect.Method.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Method.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: java.lang.reflect.Method.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Method.trySetAccessible()
+java.lang.reflect.Method.isAccessible()
+java.lang.reflect.Method.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Constructor
+java.lang.reflect.Constructor.getName()
+java.lang.reflect.Constructor.getModifiers()
+java.lang.reflect.Constructor.getTypeParameters()
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.newInstance(java.lang.Object[])
+java.lang.reflect.Constructor.getParameterTypes()
+java.lang.reflect.Constructor.toGenericString()
+java.lang.reflect.Constructor.isSynthetic()
+java.lang.reflect.Constructor.getDeclaringClass()
+java.lang.reflect.Constructor.getAnnotation(java.lang.Class)
+java.lang.reflect.Constructor.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.setAccessible(boolean)
+java.lang.reflect.Constructor.isVarArgs()
+java.lang.reflect.Constructor.getParameterCount()
+java.lang.reflect.Constructor.getParameterAnnotations()
+java.lang.reflect.Constructor.getGenericParameterTypes()
+java.lang.reflect.Constructor.getGenericExceptionTypes()
+java.lang.reflect.Constructor.getExceptionTypes()
+java.lang.reflect.Constructor.getAnnotatedReturnType()
+java.lang.reflect.Constructor.getAnnotatedReceiverType()
+java.lang.reflect.Constructor.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Constructor.getAnnotatedParameterTypes()
+java.lang.reflect.Constructor.getParameters()
+java.lang.reflect.Constructor.getAnnotatedExceptionTypes()
+java.lang.reflect.Constructor.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Constructor.getAnnotations()
+java.lang.reflect.Constructor.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Constructor.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.trySetAccessible()
+java.lang.reflect.Constructor.isAccessible()
+java.lang.reflect.Constructor.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Field
+java.lang.reflect.Field.getName()
+java.lang.reflect.Field.getModifiers()
+# Disallowed since 2.3.30: java.lang.reflect.Field.get(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getBoolean(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getByte(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getShort(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getChar(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getInt(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getLong(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getFloat(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getDouble(java.lang.Object)
+java.lang.reflect.Field.toGenericString()
+java.lang.reflect.Field.isSynthetic()
+java.lang.reflect.Field.getDeclaringClass()
+java.lang.reflect.Field.getAnnotation(java.lang.Class)
+java.lang.reflect.Field.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Field.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setAccessible(boolean)
+java.lang.reflect.Field.getGenericType()
+java.lang.reflect.Field.getType()
+# Disallowed since 2.3.0: java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setByte(java.lang.Object,byte)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setChar(java.lang.Object,char)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setShort(java.lang.Object,short)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setInt(java.lang.Object,int)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setLong(java.lang.Object,long)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setFloat(java.lang.Object,float)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setDouble(java.lang.Object,double)
+java.lang.reflect.Field.isEnumConstant()
+java.lang.reflect.Field.getAnnotatedType()
+java.lang.reflect.Field.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Field.getAnnotations()
+java.lang.reflect.Field.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Field.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Field.trySetAccessible()
+java.lang.reflect.Field.isAccessible()
+java.lang.reflect.Field.canAccess(java.lang.Object)
+
+@blacklistUnlistedMembers java.lang.reflect.AccessibleObject
+java.lang.reflect.AccessibleObject.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotation(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotations()
+java.lang.reflect.AccessibleObject.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.AccessibleObject.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.AccessibleObject.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.AccessibleObject.setAccessible(boolean)
+# Disallowed since 2.3.0: java.lang.reflect.AccessibleObject.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.30: java.lang.reflect.AccessibleObject.trySetAccessible()
+java.lang.reflect.AccessibleObject.isAccessible()
+java.lang.reflect.AccessibleObject.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Member
+java.lang.reflect.Member.getName()
+java.lang.reflect.Member.getModifiers()
+java.lang.reflect.Member.isSynthetic()
+java.lang.reflect.Member.getDeclaringClass()
+
+@whitelistPolicyIfAssignable java.lang.reflect.GenericDeclaration
+java.lang.reflect.GenericDeclaration.getTypeParameters()
+java.lang.reflect.GenericDeclaration.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotation(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotations()
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Executable
+java.lang.reflect.Executable.getName()
+java.lang.reflect.Executable.getModifiers()
+java.lang.reflect.Executable.getTypeParameters()
+java.lang.reflect.Executable.getParameterTypes()
+java.lang.reflect.Executable.toGenericString()
+java.lang.reflect.Executable.isSynthetic()
+java.lang.reflect.Executable.getDeclaringClass()
+java.lang.reflect.Executable.getAnnotation(java.lang.Class)
+java.lang.reflect.Executable.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Executable.getDeclaredAnnotations()
+java.lang.reflect.Executable.isVarArgs()
+java.lang.reflect.Executable.getAnnotatedParameterTypes()
+java.lang.reflect.Executable.getParameterCount()
+java.lang.reflect.Executable.getParameterAnnotations()
+java.lang.reflect.Executable.getGenericParameterTypes()
+java.lang.reflect.Executable.getGenericExceptionTypes()
+java.lang.reflect.Executable.getExceptionTypes()
+java.lang.reflect.Executable.getAnnotatedReturnType()
+java.lang.reflect.Executable.getParameters()
+java.lang.reflect.Executable.getAnnotatedReceiverType()
+java.lang.reflect.Executable.getAnnotatedExceptionTypes()
+java.lang.reflect.Executable.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Executable.getAnnotations()
+java.lang.reflect.Executable.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Executable.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: java.lang.reflect.Executable.setAccessible(boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Executable.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Executable.trySetAccessible()
+java.lang.reflect.Executable.isAccessible()
+java.lang.reflect.Executable.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.TypeVariable
+java.lang.reflect.TypeVariable.getName()
+java.lang.reflect.TypeVariable.getBounds()
+java.lang.reflect.TypeVariable.getGenericDeclaration()
+java.lang.reflect.TypeVariable.getAnnotatedBounds()
+java.lang.reflect.TypeVariable.getTypeName()
+java.lang.reflect.TypeVariable.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotation(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotations()
+java.lang.reflect.TypeVariable.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.TypeVariable.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.TypeVariable.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.AnnotatedType
+java.lang.reflect.AnnotatedType.getType()
+java.lang.reflect.AnnotatedType.getAnnotatedOwnerType()
+java.lang.reflect.AnnotatedType.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotation(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotations()
+java.lang.reflect.AnnotatedType.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.AnnotatedType.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.AnnotatedType.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Type
+java.lang.reflect.Type.getTypeName()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Parameter
+java.lang.reflect.Parameter.getName()
+java.lang.reflect.Parameter.getModifiers()
+java.lang.reflect.Parameter.isSynthetic()
+java.lang.reflect.Parameter.getAnnotation(java.lang.Class)
+java.lang.reflect.Parameter.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Parameter.getAnnotations()
+java.lang.reflect.Parameter.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Parameter.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.Parameter.getDeclaredAnnotations()
+java.lang.reflect.Parameter.getType()
+java.lang.reflect.Parameter.getAnnotatedType()
+java.lang.reflect.Parameter.getParameterizedType()
+java.lang.reflect.Parameter.isVarArgs()
+java.lang.reflect.Parameter.isNamePresent()
+java.lang.reflect.Parameter.getDeclaringExecutable()
+java.lang.reflect.Parameter.isImplicit()
+java.lang.reflect.Parameter.isAnnotationPresent(java.lang.Class)
+
+@whitelistPolicyIfAssignable java.lang.annotation.Annotation
+java.lang.annotation.Annotation.annotationType()
+
+@whitelistPolicyIfAssignable java.lang.constant.ClassDesc
+java.lang.constant.ClassDesc.isArray()
+java.lang.constant.ClassDesc.isPrimitive()
+java.lang.constant.ClassDesc.componentType()
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.of(java.lang.String)
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.of(java.lang.String,java.lang.String)
+java.lang.constant.ClassDesc.packageName()
+java.lang.constant.ClassDesc.descriptorString()
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.ofDescriptor(java.lang.String)
+java.lang.constant.ClassDesc.arrayType()
+java.lang.constant.ClassDesc.arrayType()
+java.lang.constant.ClassDesc.arrayType(int)
+java.lang.constant.ClassDesc.displayName()
+java.lang.constant.ClassDesc.isClassOrInterface()
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.nested(java.lang.String,java.lang.String[])
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.nested(java.lang.String)
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup)
+
+@whitelistPolicyIfAssignable java.net.URL
+# Disallowed since 2.3.30: java.net.URL.openStream()
+java.net.URL.getHost()
+java.net.URL.getPort()
+java.net.URL.getDefaultPort()
+java.net.URL.sameFile(java.net.URL)
+java.net.URL.toExternalForm()
+# Disallowed since 2.3.30: java.net.URL.openConnection()
+# Disallowed since 2.3.30: java.net.URL.openConnection(java.net.Proxy)
+# Disallowed since 2.3.30: java.net.URL.getContent()
+# Disallowed since 2.3.30: java.net.URL.getContent(java.lang.Class[])
+java.net.URL.getProtocol()
+java.net.URL.getAuthority()
+java.net.URL.getFile()
+java.net.URL.getRef()
+java.net.URL.getQuery()
+java.net.URL.getPath()
+java.net.URL.getUserInfo()
+java.net.URL.toURI()
+# Disallowed since 2.3.30: java.net.URL.setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory)
+
+@whitelistPolicyIfAssignable java.lang.constant.ClassDesc
+
+@whitelistPolicyIfAssignable java.net.URI
+java.net.URI.getRawAuthority()
+java.net.URI.compareTo(java.lang.Object)
+java.net.URI.compareTo(java.net.URI)
+java.net.URI.isAbsolute()
+java.net.URI.resolve(java.net.URI)
+java.net.URI.resolve(java.lang.String)
+java.net.URI.normalize()
+java.net.URI.getScheme()
+java.net.URI.isOpaque()
+java.net.URI.getRawFragment()
+java.net.URI.getRawQuery()
+java.net.URI.getRawPath()
+java.net.URI.getHost()
+java.net.URI.getPort()
+java.net.URI.create(java.lang.String)
+java.net.URI.getAuthority()
+java.net.URI.getQuery()
+java.net.URI.getPath()
+java.net.URI.getUserInfo()
+java.net.URI.toURL()
+java.net.URI.relativize(java.net.URI)
+java.net.URI.getRawSchemeSpecificPart()
+java.net.URI.parseServerAuthority()
+java.net.URI.getSchemeSpecificPart()
+java.net.URI.getRawUserInfo()
+java.net.URI.getFragment()
+java.net.URI.toASCIIString()
diff --git a/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
deleted file mode 100644
index 05c1981..0000000
--- a/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
+++ /dev/null
@@ -1,98 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-java.lang.Object.wait()
-java.lang.Object.wait(long)
-java.lang.Object.wait(long,int)
-java.lang.Object.notify()
-java.lang.Object.notifyAll()
-
-java.lang.Class.getClassLoader()
-java.lang.Class.newInstance()
-java.lang.Class.forName(java.lang.String)
-java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
-
-java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)
-
-java.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)
-
-java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
-java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
-java.lang.reflect.Field.setByte(java.lang.Object,byte)
-java.lang.reflect.Field.setChar(java.lang.Object,char)
-java.lang.reflect.Field.setDouble(java.lang.Object,double)
-java.lang.reflect.Field.setFloat(java.lang.Object,float)
-java.lang.reflect.Field.setInt(java.lang.Object,int)
-java.lang.reflect.Field.setLong(java.lang.Object,long)
-java.lang.reflect.Field.setShort(java.lang.Object,short)
-
-java.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)
-java.lang.reflect.AccessibleObject.setAccessible(boolean)
-
-java.lang.Thread.destroy()
-java.lang.Thread.getContextClassLoader()
-java.lang.Thread.interrupt()
-java.lang.Thread.join()
-java.lang.Thread.join(long)
-java.lang.Thread.join(long,int)
-java.lang.Thread.resume()
-java.lang.Thread.run()
-java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
-java.lang.Thread.setDaemon(boolean)
-java.lang.Thread.setName(java.lang.String)
-java.lang.Thread.setPriority(int)
-java.lang.Thread.sleep(long)
-java.lang.Thread.sleep(long,int)
-java.lang.Thread.start()
-java.lang.Thread.stop()
-java.lang.Thread.stop(java.lang.Throwable)
-java.lang.Thread.suspend()
-
-java.lang.ThreadGroup.allowThreadSuspension(boolean)
-java.lang.ThreadGroup.destroy()
-java.lang.ThreadGroup.interrupt()
-java.lang.ThreadGroup.resume()
-java.lang.ThreadGroup.setDaemon(boolean)
-java.lang.ThreadGroup.setMaxPriority(int)
-java.lang.ThreadGroup.stop()
-java.lang.Thread.suspend()
-
-java.lang.Runtime.addShutdownHook(java.lang.Thread)
-java.lang.Runtime.exec(java.lang.String)
-java.lang.Runtime.exec([Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exit(int)
-java.lang.Runtime.halt(int)
-java.lang.Runtime.load(java.lang.String)
-java.lang.Runtime.loadLibrary(java.lang.String)
-java.lang.Runtime.removeShutdownHook(java.lang.Thread)
-java.lang.Runtime.traceInstructions(boolean)
-java.lang.Runtime.traceMethodCalls(boolean)
-
-java.lang.System.exit(int)
-java.lang.System.load(java.lang.String)
-java.lang.System.loadLibrary(java.lang.String)
-java.lang.System.runFinalizersOnExit(boolean)
-java.lang.System.setErr(java.io.PrintStream)
-java.lang.System.setIn(java.io.InputStream)
-java.lang.System.setOut(java.io.PrintStream)
-java.lang.System.setProperties(java.util.Properties)
-java.lang.System.setProperty(java.lang.String,java.lang.String)
-java.lang.System.setSecurityManager(java.lang.SecurityManager)