blob: b64569d1d72462a53b35a598e56236d2f4959b69 [file] [log] [blame]
/*
* 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 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/*<String>*/ allowedClasses;
private final List/*<String>*/ trustedTemplatePrefixes;
private final Set/*<String>*/ 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 <code>null</code> (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 <code>"include/*"</code>) 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 <code>"/"</code> (if they are, it
* will be removed). List items ending with <code>"*"</code> are treated
* as prefixes (i.e. <code>"foo*"</code> matches <code>"foobar"</code>,
* <code>"foo/bar/baaz"</code>, <code>"foowhatever/bar/baaz"</code>,
* etc.). The <code>"*"</code> 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 <code>?new</code>) that wants to get the class. Thus,
* if a trusted template includes a non-trusted template, the
* <code>allowedClasses</code> restriction will apply in the included
* template.
* This parameter can be <code>null</code> (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 _MiscTemplateException(env,
"Instantiating ", className, " is not allowed in the template for security reasons. (If you "
+ "run into this problem when using ?new in a template, you may want to check the \"",
Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY,
"\" setting in the FreeMarker configuration.)");
} else {
try {
return ClassUtil.forName(className);
} catch (ClassNotFoundException e) {
throw new _MiscTemplateException(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;
}
}