freemarker.core.OptInTemplateClassResolver Maven / Gradle / Ivy
Go to download
Google App Engine compliant variation of FreeMarker.
FreeMarker is a "template engine"; a generic tool to generate text output based on templates.
package freemarker.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;
/**
* A {@link TemplateClassResolver} that resolves only the classes whose name
* was specified in the constructor.
*/
public class OptInTemplateClassResolver implements TemplateClassResolver {
private final Set/**/ allowedClasses;
private final List/**/ trustedTemplatePrefixes;
private final Set/**/ trustedTemplateNames;
/**
* Creates a new instance.
*
* @param allowedClasses the {@link Set} of {@link String}-s that contains
* the full-qualified names of the allowed classes.
* Can be null
(means not class is allowed).
* @param trustedTemplates the {@link List} of {@link String}-s that contains
* template names (i.e., template root directory relative paths)
* and prefix patterns (like "include/*"
) of templates
* for which {@link TemplateClassResolver#SAFER_RESOLVER} will be
* used (which is not as safe as {@link OptInTemplateClassResolver}).
* The list items need not start with "/"
(if they are, it
* will be removed). List items ending with "*"
are treated
* as prefixes (i.e. "foo*"
matches "foobar"
,
* "foo/bar/baaz"
, "foowhatever/bar/baaz"
,
* etc.). The "*"
has no special meaning anywhere else.
* The matched template name is the name (template root directory
* relative path) of the template that directly (lexically) contains the
* operation (like ?new
) that wants to get the class. Thus,
* if a trusted template includes a non-trusted template, the
* allowedClasses
restriction will apply in the included
* template.
* This parameter can be null
(means no trusted templates).
*/
public OptInTemplateClassResolver(
Set allowedClasses, List trustedTemplates) {
this.allowedClasses = allowedClasses != null ? allowedClasses : Collections.EMPTY_SET;
if (trustedTemplates != null) {
trustedTemplateNames = new HashSet();
trustedTemplatePrefixes = new ArrayList();
Iterator it = trustedTemplates.iterator();
while (it.hasNext()) {
String li = (String) it.next();
if (li.startsWith("/")) li = li.substring(1);
if (li.endsWith("*")) {
trustedTemplatePrefixes.add(li.substring(0, li.length() - 1));
} else {
trustedTemplateNames.add(li);
}
}
} else {
trustedTemplateNames = Collections.EMPTY_SET;
trustedTemplatePrefixes = Collections.EMPTY_LIST;
}
}
public Class resolve(String className, Environment env, Template template)
throws TemplateException {
String templateName = safeGetTemplateName(template);
if (templateName != null
&& (trustedTemplateNames.contains(templateName)
|| hasMatchingPrefix(templateName))) {
return TemplateClassResolver.SAFER_RESOLVER.resolve(className, env, template);
} else {
if (!allowedClasses.contains(className)) {
throw new TemplateException(
"Instantiating " + className + " is not allowed in the " +
"template for security reasons. (If you meet this problem " +
"when using ?new in a template, you may want to look " +
"at the \"" + Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY +
"\" setting in the FreeMarker configuration.)",
env);
} else {
try {
return ClassUtil.forName(className);
} catch (ClassNotFoundException e) {
throw new TemplateException(e, env);
}
}
}
}
/**
* Extract the template name from the template object which will be matched
* against the trusted template names and pattern.
*/
protected String safeGetTemplateName(Template template) {
if (template == null) return null;
String name = template.getName();
if (name == null) return null;
// Detect exploits, return null if one is suspected:
String decodedName = name;
if (decodedName.indexOf('%') != -1) {
decodedName = StringUtil.replace(decodedName, "%2e", ".", false, false);
decodedName = StringUtil.replace(decodedName, "%2E", ".", false, false);
decodedName = StringUtil.replace(decodedName, "%2f", "/", false, false);
decodedName = StringUtil.replace(decodedName, "%2F", "/", false, false);
decodedName = StringUtil.replace(decodedName, "%5c", "\\", false, false);
decodedName = StringUtil.replace(decodedName, "%5C", "\\", false, false);
}
int dotDotIdx = decodedName.indexOf("..");
if (dotDotIdx != -1) {
int before = dotDotIdx - 1 >= 0 ? decodedName.charAt(dotDotIdx - 1) : -1;
int after = dotDotIdx + 2 < decodedName.length() ? decodedName.charAt(dotDotIdx + 2) : -1;
if ((before == -1 || before == '/' || before == '\\')
&& (after == -1 || after == '/' || after == '\\')) {
return null;
}
}
return name.startsWith("/") ? name.substring(1) : name;
}
private boolean hasMatchingPrefix(String name) {
for (int i = 0; i < trustedTemplatePrefixes.size(); i++) {
String prefix = (String) trustedTemplatePrefixes.get(i);
if (name.startsWith(prefix)) return true;
}
return false;
}
}