diff --git a/java/org/apache/catalina/Context.java b/java/org/apache/catalina/Context.java
index 03e087bc784c..21517a07cb47 100644
--- a/java/org/apache/catalina/Context.java
+++ b/java/org/apache/catalina/Context.java
@@ -674,6 +674,25 @@ public void setSessionCookiePathUsesTrailingSlash(
public void setTldNamespaceAware(boolean tldNamespaceAware);
+ /**
+ * Will the parsing of web.xml, web-fragment.xml, *.tld, *.jspx, *.tagx and
+ * tagplugin.xml files for this Context block the use of external entities?
+ *
+ * @return true if access to external entities is blocked
+ */
+ public boolean getXmlBlockExternal();
+
+
+ /**
+ * Controls whether the parsing of web.xml, web-fragment.xml, *.tld, *.jspx,
+ * *.tagx and tagplugin.xml files for this Context will block the use of
+ * external entities.
+ *
+ * @param xmlBlockExternal true to block external entities
+ */
+ public void setXmlBlockExternal(boolean xmlBlockExternal);
+
+
/**
* Will the parsing of *.tld files for this Context be performed by a
* validating parser?
diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java
index 56694e76a2e1..78210d306f8b 100644
--- a/java/org/apache/catalina/Globals.java
+++ b/java/org/apache/catalina/Globals.java
@@ -316,4 +316,15 @@ public final class Globals {
*/
public static final String JASPER_XML_VALIDATION_TLD_INIT_PARAM =
"org.apache.jasper.XML_VALIDATE_TLD";
+
+
+ /**
+ * Name of the ServletContext init-param that determines if the JSP engine
+ * will block external entities from being used in *.tld, *.jspx, *.tagx and
+ * tagplugin.xml files.
+ *
+ * This must be kept in sync with org.apache.jasper.Constants
+ */
+ public static final String JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM =
+ "org.apache.jasper.XML_BLOCK_EXTERNAL";
}
diff --git a/java/org/apache/catalina/ant/ValidatorTask.java b/java/org/apache/catalina/ant/ValidatorTask.java
index 4c2272f60f13..68f229974f62 100644
--- a/java/org/apache/catalina/ant/ValidatorTask.java
+++ b/java/org/apache/catalina/ant/ValidatorTask.java
@@ -21,6 +21,7 @@
import java.io.FileInputStream;
import java.io.InputStream;
+import org.apache.catalina.Globals;
import org.apache.catalina.startup.Constants;
import org.apache.tomcat.util.descriptor.DigesterFactory;
import org.apache.tomcat.util.digester.Digester;
@@ -87,7 +88,10 @@ public void execute() throws BuildException {
Thread.currentThread().setContextClassLoader
(ValidatorTask.class.getClassLoader());
- Digester digester = DigesterFactory.newDigester(true, true, null);
+ // Called through trusted manager interface. If running under a
+ // SecurityManager assume that untrusted applications may be deployed.
+ Digester digester = DigesterFactory.newDigester(
+ true, true, null, Globals.IS_SECURITY_ENABLED);
try {
file = file.getCanonicalFile();
InputStream stream =
diff --git a/java/org/apache/catalina/core/ApplicationContext.java b/java/org/apache/catalina/core/ApplicationContext.java
index 504b5c2365be..96dd3ec78ce0 100644
--- a/java/org/apache/catalina/core/ApplicationContext.java
+++ b/java/org/apache/catalina/core/ApplicationContext.java
@@ -320,12 +320,20 @@ public String getContextPath() {
*/
@Override
public String getInitParameter(final String name) {
- // Special handling for XML validation as the context setting must
+ // Special handling for XML settings as the context setting must
// always override anything that might have been set by an application.
if (Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM.equals(name) &&
context.getTldValidation()) {
return "true";
}
+ if (Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM.equals(name)) {
+ if (context.getXmlBlockExternal()) {
+ return "true";
+ } else if (Globals.IS_SECURITY_ENABLED) {
+ // System admin has explicitly changed the default
+ return "false";
+ }
+ }
return parameters.get(name);
}
@@ -338,11 +346,14 @@ public String getInitParameter(final String name) {
public Enumeration getInitParameterNames() {
Set names = new HashSet();
names.addAll(parameters.keySet());
- // Special handling for XML validation as this attribute will always be
- // available if validation has been enabled on the context
+ // Special handling for XML settings as these attributes will always be
+ // available if they have been set on the context
if (context.getTldValidation()) {
names.add(Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM);
}
+ if (context.getXmlBlockExternal() || Globals.IS_SECURITY_ENABLED) {
+ names.add(Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM);
+ }
return Collections.enumeration(names);
}
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index 531424081742..f7d0a364caef 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -698,6 +698,13 @@ public StandardContext() {
protected int cacheMaxSize = 10240; // 10 MB
+
+ /**
+ * Attribute used to turn on/off the use of external entities.
+ */
+ private boolean xmlBlockExternal = Globals.IS_SECURITY_ENABLED;
+
+
/**
* Cache object max size in KB.
*/
@@ -6624,6 +6631,18 @@ public void setTldNamespaceAware(boolean tldNamespaceAware){
}
+ @Override
+ public void setXmlBlockExternal(boolean xmlBlockExternal) {
+ this.xmlBlockExternal = xmlBlockExternal;
+ }
+
+
+ @Override
+ public boolean getXmlBlockExternal() {
+ return xmlBlockExternal;
+ }
+
+
@Override
public void setTldValidation(boolean tldValidation){
this.tldValidation = tldValidation;
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index 80080e086b6f..4c8213c46361 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -516,14 +516,16 @@ protected void authenticatorConfig() {
public void createWebXmlDigester(boolean namespaceAware,
boolean validation) {
+ boolean blockExternal = context.getXmlBlockExternal();
+
webRuleSet = new WebRuleSet(false);
webDigester = DigesterFactory.newDigester(validation,
- namespaceAware, webRuleSet);
+ namespaceAware, webRuleSet, blockExternal);
webDigester.getParser();
webFragmentRuleSet = new WebRuleSet(true);
webFragmentDigester = DigesterFactory.newDigester(validation,
- namespaceAware, webFragmentRuleSet);
+ namespaceAware, webFragmentRuleSet, blockExternal);
webFragmentDigester.getParser();
}
diff --git a/java/org/apache/catalina/startup/FailedContext.java b/java/org/apache/catalina/startup/FailedContext.java
index 3ece2a9951b0..f8459efbdc6d 100644
--- a/java/org/apache/catalina/startup/FailedContext.java
+++ b/java/org/apache/catalina/startup/FailedContext.java
@@ -427,6 +427,11 @@ public void setXmlValidation(boolean xmlValidation) { /* NO-OP */ }
@Override
public void setTldValidation(boolean tldValidation) { /* NO-OP */ }
+ @Override
+ public boolean getXmlBlockExternal() { return true; }
+ @Override
+ public void setXmlBlockExternal(boolean xmlBlockExternal) { /* NO-OP */ }
+
@Override
public boolean getTldValidation() { return false; }
diff --git a/java/org/apache/catalina/startup/TldConfig.java b/java/org/apache/catalina/startup/TldConfig.java
index 8127c0b56389..cd114fbaa86d 100644
--- a/java/org/apache/catalina/startup/TldConfig.java
+++ b/java/org/apache/catalina/startup/TldConfig.java
@@ -85,20 +85,21 @@ public final class TldConfig implements LifecycleListener {
* Create (if necessary) and return a Digester configured to process the
* tld.
*/
- private static Digester createTldDigester(boolean validation) {
+ private static Digester createTldDigester(boolean validation,
+ boolean blockExternal) {
Digester digester = null;
if (!validation) {
if (tldDigesters[0] == null) {
tldDigesters[0] = DigesterFactory.newDigester(validation,
- true, new TldRuleSet());
+ true, new TldRuleSet(), blockExternal);
tldDigesters[0].getParser();
}
digester = tldDigesters[0];
} else {
if (tldDigesters[1] == null) {
tldDigesters[1] = DigesterFactory.newDigester(validation,
- true, new TldRuleSet());
+ true, new TldRuleSet(), blockExternal);
tldDigesters[1].getParser();
}
digester = tldDigesters[1];
@@ -567,7 +568,8 @@ public void lifecycleEvent(LifecycleEvent event) {
private void init() {
if (tldDigester == null){
- tldDigester = createTldDigester(context.getTldValidation());
+ tldDigester = createTldDigester(context.getTldValidation(),
+ context.getXmlBlockExternal());
}
}
diff --git a/java/org/apache/jasper/Constants.java b/java/org/apache/jasper/Constants.java
index 75838dc2a9ed..142c66242b37 100644
--- a/java/org/apache/jasper/Constants.java
+++ b/java/org/apache/jasper/Constants.java
@@ -241,4 +241,13 @@ public class Constants {
*/
public static final String XML_VALIDATION_TLD_INIT_PARAM =
"org.apache.jasper.XML_VALIDATE_TLD";
+
+ /**
+ * Name of the ServletContext init-param that determines if the XML parsers
+ * will block the resolution of external entities.
+ *
+ * This must be kept in sync with org.apache.catalina.Globals
+ */
+ public static final String XML_BLOCK_EXTERNAL_INIT_PARAM =
+ "org.apache.jasper.XML_BLOCK_EXTERNAL";
}
diff --git a/java/org/apache/jasper/JspC.java b/java/org/apache/jasper/JspC.java
index eac77846a309..9c88b18bc7eb 100644
--- a/java/org/apache/jasper/JspC.java
+++ b/java/org/apache/jasper/JspC.java
@@ -127,6 +127,7 @@ public class JspC extends Task implements Options {
protected static final String SWITCH_SMAP = "-smap";
protected static final String SWITCH_DUMP_SMAP = "-dumpsmap";
protected static final String SWITCH_VALIDATE_TLD = "-validateTld";
+ protected static final String SWITCH_BLOCK_EXTERNAL = "-blockExternal";
protected static final String SHOW_SUCCESS ="-s";
protected static final String LIST_ERRORS = "-l";
protected static final int INC_WEBXML = 10;
@@ -158,6 +159,7 @@ public class JspC extends Task implements Options {
protected boolean trimSpaces = false;
protected boolean genStringAsCharArray = false;
protected boolean validateTld;
+ protected boolean blockExternal;
protected boolean xpoweredBy;
protected boolean mappedFile = false;
protected boolean poolingEnabled = true;
@@ -367,6 +369,8 @@ public void setArgs(String[] arg) throws JasperException {
smapDumped = true;
} else if (tok.equals(SWITCH_VALIDATE_TLD)) {
setValidateTld(true);
+ } else if (tok.equals(SWITCH_BLOCK_EXTERNAL)) {
+ setBlockExternal(true);
} else {
if (tok.startsWith("-")) {
throw new JasperException("Unrecognized option: " + tok +
@@ -854,6 +858,14 @@ public boolean isValidateTld() {
return validateTld;
}
+ public void setBlockExternal( boolean b ) {
+ this.blockExternal = b;
+ }
+
+ public boolean isBlockExternal() {
+ return blockExternal;
+ }
+
public void setListErrors( boolean b ) {
listErrors = b;
}
@@ -1435,6 +1447,9 @@ protected void initServletContext() {
if (isValidateTld()) {
context.setInitParameter(Constants.XML_VALIDATION_TLD_INIT_PARAM, "true");
}
+ if (isBlockExternal()) {
+ context.setInitParameter(Constants.XML_BLOCK_EXTERNAL_INIT_PARAM, "true");
+ }
rctxt = new JspRuntimeContext(context, this);
jspConfig = new JspConfig(context);
diff --git a/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java b/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java
index 91af39e90bda..562f365c1e04 100644
--- a/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java
+++ b/java/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java
@@ -23,6 +23,7 @@
import java.util.Set;
import java.util.Vector;
+import javax.servlet.ServletContext;
import javax.servlet.jsp.tagext.FunctionInfo;
import javax.servlet.jsp.tagext.TagFileInfo;
import javax.servlet.jsp.tagext.TagInfo;
@@ -124,11 +125,21 @@ public ImplicitTagLibraryInfo(JspCompilationContext ctxt,
pi.addDependant(path, ctxt.getLastModified(path));
}
+ ServletContext servletContext = ctxt.getServletContext();
boolean validate = Boolean.parseBoolean(
- ctxt.getServletContext().getInitParameter(
+ servletContext.getInitParameter(
Constants.XML_VALIDATION_TLD_INIT_PARAM));
-
- ParserUtils pu = new ParserUtils(validate);
+ String blockExternalString =
+ servletContext.getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
+
+ ParserUtils pu = new ParserUtils(validate, blockExternal);
TreeNode tld = pu.parseXMLDocument(uri, in);
if (tld.findAttribute("version") != null) {
diff --git a/java/org/apache/jasper/compiler/JspConfig.java b/java/org/apache/jasper/compiler/JspConfig.java
index 570a6d5e342f..f74bdce1c668 100644
--- a/java/org/apache/jasper/compiler/JspConfig.java
+++ b/java/org/apache/jasper/compiler/JspConfig.java
@@ -80,10 +80,18 @@ private void processWebDotXml() throws JasperException {
boolean validate = Boolean.parseBoolean(
ctxt.getInitParameter(Constants.XML_VALIDATION_TLD_INIT_PARAM));
+ String blockExternalString =
+ ctxt.getInitParameter(Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
TreeNode webApp = null;
if (webXml.getInputSource() != null) {
- ParserUtils pu = new ParserUtils(validate);
+ ParserUtils pu = new ParserUtils(validate, blockExternal);
webApp = pu.parseXMLDocument(webXml.getSystemId(),
webXml.getInputSource());
}
diff --git a/java/org/apache/jasper/compiler/JspDocumentParser.java b/java/org/apache/jasper/compiler/JspDocumentParser.java
index 08c2477b8292..d42f1405742c 100644
--- a/java/org/apache/jasper/compiler/JspDocumentParser.java
+++ b/java/org/apache/jasper/compiler/JspDocumentParser.java
@@ -30,8 +30,11 @@
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
+import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
+import org.apache.tomcat.util.descriptor.DigesterFactory;
+import org.apache.tomcat.util.descriptor.LocalResolver;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
@@ -39,6 +42,7 @@
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;
+import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.AttributesImpl;
/**
@@ -91,6 +95,7 @@ class JspDocumentParser
private boolean inDTD;
private boolean isValidating;
+ private final EntityResolver2 entityResolver;
private ErrorDispatcher err;
private boolean isTagFile;
@@ -119,6 +124,20 @@ public JspDocumentParser(
this.isTagFile = isTagFile;
this.directivesOnly = directivesOnly;
this.isTop = true;
+
+ String blockExternalString = ctxt.getServletContext().getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
+
+ this.entityResolver = new LocalResolver(
+ DigesterFactory.SERVLET_API_PUBLIC_IDS,
+ DigesterFactory.SERVLET_API_SYSTEM_IDS,
+ blockExternal);
}
/*
@@ -239,12 +258,29 @@ private void addInclude(Node parent, List files) throws SAXException {
}
}
+
+ @Override
+ public InputSource getExternalSubset(String name, String baseURI)
+ throws SAXException, IOException {
+ return entityResolver.getExternalSubset(name, baseURI);
+ }
+
+
+
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ return entityResolver.resolveEntity(publicId, systemId);
+ }
+
+
@Override
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
throws SAXException, IOException {
- return null;
+ return entityResolver.resolveEntity(name, publicId, baseURI, systemId);
}
+
/*
* Receives notification of the start of an element.
*
diff --git a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
index 36fe86ff8d6d..e83015894bd7 100644
--- a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
+++ b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
@@ -31,6 +31,7 @@
import java.util.Map;
import java.util.Vector;
+import javax.servlet.ServletContext;
import javax.servlet.jsp.tagext.FunctionInfo;
import javax.servlet.jsp.tagext.PageData;
import javax.servlet.jsp.tagext.TagAttributeInfo;
@@ -213,12 +214,20 @@ private void parseTLD(String uri, InputStream in, JarResource jarResource)
Vector tagFileVector = new Vector();
Hashtable functionTable = new Hashtable();
- boolean validate = Boolean.parseBoolean(
- ctxt.getServletContext().getInitParameter(
- Constants.XML_VALIDATION_TLD_INIT_PARAM));
+ ServletContext servletContext = ctxt.getServletContext();
+ boolean validate = Boolean.parseBoolean(servletContext.getInitParameter(
+ Constants.XML_VALIDATION_TLD_INIT_PARAM));
+ String blockExternalString = servletContext.getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
// Create an iterator over the child elements of our element
- ParserUtils pu = new ParserUtils(validate);
+ ParserUtils pu = new ParserUtils(validate, blockExternal);
TreeNode tld = pu.parseXMLDocument(uri, in);
// Check to see if the root element contains a 'version'
diff --git a/java/org/apache/jasper/compiler/TagPluginManager.java b/java/org/apache/jasper/compiler/TagPluginManager.java
index c61419486726..7fb6d805cc34 100644
--- a/java/org/apache/jasper/compiler/TagPluginManager.java
+++ b/java/org/apache/jasper/compiler/TagPluginManager.java
@@ -25,6 +25,7 @@
import javax.servlet.ServletContext;
+import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.tagplugin.TagPlugin;
import org.apache.jasper.compiler.tagplugin.TagPluginContext;
@@ -119,8 +120,18 @@ private void init(ErrorDispatcher err) throws JasperException {
private void loadTagPlugins(ErrorDispatcher err, InputStream is)
throws JasperException {
- TreeNode root =
- (new ParserUtils(false)).parseXMLDocument(TAG_PLUGINS_XML, is);
+ String blockExternalString = ctxt.getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
+
+ ParserUtils pu = new ParserUtils(false, blockExternal);
+
+ TreeNode root = pu.parseXMLDocument(TAG_PLUGINS_XML, is);
if (root == null) {
return;
}
diff --git a/java/org/apache/jasper/compiler/TldLocationsCache.java b/java/org/apache/jasper/compiler/TldLocationsCache.java
index f2ccbd1cdab3..f8a68c00700e 100644
--- a/java/org/apache/jasper/compiler/TldLocationsCache.java
+++ b/java/org/apache/jasper/compiler/TldLocationsCache.java
@@ -289,10 +289,20 @@ private void tldScanWebXml() throws Exception {
boolean validate = Boolean.parseBoolean(
ctxt.getInitParameter(
Constants.XML_VALIDATION_TLD_INIT_PARAM));
-
+ String blockExternalString = ctxt.getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
+
// Parse the web application deployment descriptor
+ ParserUtils pu = new ParserUtils(validate, blockExternal);
+
TreeNode webtld = null;
- webtld = new ParserUtils(validate).parseXMLDocument(webXml.getSystemId(),
+ webtld = pu.parseXMLDocument(webXml.getSystemId(),
webXml.getInputSource());
// Allow taglib to be an element of the root or jsp-config (JSP2.0)
@@ -498,9 +508,17 @@ private void tldScanStream(String resourcePath, String entryName,
boolean validate = Boolean.parseBoolean(
ctxt.getInitParameter(
Constants.XML_VALIDATION_TLD_INIT_PARAM));
-
- TreeNode tld = new ParserUtils(validate).parseXMLDocument(
- resourcePath, stream);
+ String blockExternalString = ctxt.getInitParameter(
+ Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
+ boolean blockExternal;
+ if (blockExternalString == null) {
+ blockExternal = Constants.IS_SECURITY_ENABLED;
+ } else {
+ blockExternal = Boolean.parseBoolean(blockExternalString);
+ }
+
+ ParserUtils pu = new ParserUtils(validate, blockExternal);
+ TreeNode tld = pu.parseXMLDocument(resourcePath, stream);
TreeNode uriNode = tld.findChild("uri");
if (uriNode != null) {
String body = uriNode.getBody();
diff --git a/java/org/apache/jasper/xmlparser/ParserUtils.java b/java/org/apache/jasper/xmlparser/ParserUtils.java
index 94a2502b3b5f..668cfa4d2c8f 100644
--- a/java/org/apache/jasper/xmlparser/ParserUtils.java
+++ b/java/org/apache/jasper/xmlparser/ParserUtils.java
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.jasper.xmlparser;
import java.io.IOException;
@@ -24,9 +23,11 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.Localizer;
import org.apache.tomcat.util.descriptor.DigesterFactory;
+import org.apache.tomcat.util.descriptor.LocalResolver;
import org.apache.tomcat.util.descriptor.XmlErrorHandler;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
@@ -49,7 +50,6 @@
* @author Craig R. McClanahan
* @version $Id$
*/
-
public class ParserUtils {
/**
@@ -60,12 +60,25 @@ public class ParserUtils {
/**
* An entity resolver for use when parsing XML documents.
*/
- static EntityResolver entityResolver = DigesterFactory.SERVLET_API_RESOLVER;
+ static EntityResolver entityResolver;
+
+ private final EntityResolver entityResolverInstance;
private final boolean validating;
public ParserUtils(boolean validating) {
+ this(validating, Constants.IS_SECURITY_ENABLED);
+ }
+
+ public ParserUtils(boolean validating, boolean blockExternal) {
this.validating = validating;
+ if (entityResolver == null) {
+ this.entityResolverInstance = new LocalResolver(
+ DigesterFactory.SERVLET_API_PUBLIC_IDS,
+ DigesterFactory.SERVLET_API_SYSTEM_IDS, blockExternal);
+ } else {
+ this.entityResolverInstance = entityResolver;
+ }
}
// --------------------------------------------------------- Public Methods
@@ -92,7 +105,7 @@ public TreeNode parseXMLDocument(String location, InputSource is)
factory.setNamespaceAware(true);
factory.setValidating(validating);
DocumentBuilder builder = factory.newDocumentBuilder();
- builder.setEntityResolver(entityResolver);
+ builder.setEntityResolver(entityResolverInstance);
builder.setErrorHandler(errorHandler);
document = builder.parse(is);
} catch (ParserConfigurationException ex) {
diff --git a/java/org/apache/tomcat/util/descriptor/DigesterFactory.java b/java/org/apache/tomcat/util/descriptor/DigesterFactory.java
index e094d8f1b061..bf28a09714b5 100644
--- a/java/org/apache/tomcat/util/descriptor/DigesterFactory.java
+++ b/java/org/apache/tomcat/util/descriptor/DigesterFactory.java
@@ -17,6 +17,7 @@
package org.apache.tomcat.util.descriptor;
import java.net.URL;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -24,6 +25,7 @@
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.digester.RuleSet;
+import org.xml.sax.ext.EntityResolver2;
/**
* Wrapper class around the Digester that hide Digester's initialization
@@ -32,10 +34,16 @@
public class DigesterFactory {
/**
- * A resolver for the resources packaged in servlet-api.jar
+ * Mapping of well-known public IDs used by the Servlet API to the matching
+ * local resource.
*/
- public static final LocalResolver SERVLET_API_RESOLVER;
+ public static final Map SERVLET_API_PUBLIC_IDS;
+ /**
+ * Mapping of well-known system IDs used by the Servlet API to the matching
+ * local resource.
+ */
+ public static final Map SERVLET_API_SYSTEM_IDS;
static {
Map publicIds = new HashMap();
@@ -81,7 +89,8 @@ public class DigesterFactory {
addSelf(systemIds, "javaee_web_services_1_3.xsd");
addSelf(systemIds, "javaee_web_services_client_1_3.xsd");
- SERVLET_API_RESOLVER = new LocalResolver(publicIds, systemIds);
+ SERVLET_API_PUBLIC_IDS = Collections.unmodifiableMap(publicIds);
+ SERVLET_API_SYSTEM_IDS = Collections.unmodifiableMap(systemIds);
}
private static void addSelf(Map ids, String id) {
@@ -103,15 +112,19 @@ private static String idFor(String url) {
* @param xmlValidation turn on/off xml validation
* @param xmlNamespaceAware turn on/off namespace validation
* @param rule an instance of RuleSet
used for parsing the xml.
+ * @param blockExternal turn on/off the blocking of external resources
*/
public static Digester newDigester(boolean xmlValidation,
boolean xmlNamespaceAware,
- RuleSet rule) {
+ RuleSet rule,
+ boolean blockExternal) {
Digester digester = new Digester();
digester.setNamespaceAware(xmlNamespaceAware);
digester.setValidating(xmlValidation);
digester.setUseContextClassLoader(true);
- digester.setEntityResolver(SERVLET_API_RESOLVER);
+ EntityResolver2 resolver = new LocalResolver(SERVLET_API_PUBLIC_IDS,
+ SERVLET_API_SYSTEM_IDS, blockExternal);
+ digester.setEntityResolver(resolver);
if (rule != null) {
digester.addRuleSet(rule);
}
diff --git a/java/org/apache/tomcat/util/descriptor/LocalResolver.java b/java/org/apache/tomcat/util/descriptor/LocalResolver.java
index 55edf3d834ac..fb191459690d 100644
--- a/java/org/apache/tomcat/util/descriptor/LocalResolver.java
+++ b/java/org/apache/tomcat/util/descriptor/LocalResolver.java
@@ -16,6 +16,7 @@
*/
package org.apache.tomcat.util.descriptor;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
@@ -23,6 +24,7 @@
import java.net.URL;
import java.util.Map;
+import org.apache.tomcat.util.res.StringManager;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;
@@ -32,22 +34,30 @@
*/
public class LocalResolver implements EntityResolver2 {
+ private static final StringManager sm =
+ StringManager.getManager(Constants.PACKAGE_NAME);
+
private final Map publicIds;
private final Map systemIds;
-
+ private final boolean blockExternal;
/**
* Constructor providing mappings of public and system identifiers to local
* resources. Each map contains a mapping from a well-known identifier to a
* URL for a local resource path.
*
- * @param publicIds mapping of public identifiers to local resources
- * @param systemIds mapping of system identifiers to local resources
+ * @param publicIds mapping of well-known public identifiers to local
+ * resources
+ * @param systemIds mapping of well-known system identifiers to local
+ * resources
+ * @param blockExternal are external resources blocked that are not
+ * well-known
*/
public LocalResolver(Map publicIds,
- Map systemIds) {
+ Map systemIds, boolean blockExternal) {
this.publicIds = publicIds;
this.systemIds = systemIds;
+ this.blockExternal = blockExternal;
}
@@ -60,63 +70,77 @@ public InputSource resolveEntity(String publicId, String systemId)
@Override
public InputSource resolveEntity(String name, String publicId,
- String baseURI, String systemId) throws SAXException, IOException {
-
- String resolved = resolve(publicId, systemId, baseURI);
- if (resolved == null) {
- return null;
- }
+ String base, String systemId) throws SAXException, IOException {
- InputSource is = new InputSource(resolved);
- is.setPublicId(publicId);
- return is;
- }
-
-
- @Override
- public InputSource getExternalSubset(String name, String baseURI)
- throws SAXException, IOException {
- return null;
- }
-
-
- private String resolve(String publicId, String systemId, String baseURI) {
- // try resolving using the publicId
+ // First try resolving using the publicId
String resolved = publicIds.get(publicId);
if (resolved != null) {
- return resolved;
+ InputSource is = new InputSource(resolved);
+ is.setPublicId(publicId);
+ return is;
}
- // try resolving using the systemId
+ // If there is no systemId, can't try anything else
if (systemId == null) {
- return null;
+ throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity",
+ name, publicId, systemId, base));
}
- systemId = resolve(baseURI, systemId);
+ // Try resolving with the supplied systemId
resolved = systemIds.get(systemId);
if (resolved != null) {
- return resolved;
+ InputSource is = new InputSource(resolved);
+ is.setPublicId(publicId);
+ return is;
}
- // fall back to the supplied systemId
- return systemId;
- }
-
-
- private static String resolve(String baseURI, String systemId) {
+ // Resolve the supplied systemId against the base
+ URI systemUri;
try {
- if (baseURI == null) {
- return systemId;
+ if (base == null) {
+ systemUri = new URI(systemId);
+ } else {
+ // Can't use URI.resolve() because "jar:..." URLs are not valid
+ // hierarchical URIs so resolve() does not work. new URL()
+ // delegates to the jar: stream handler and it manages to figure
+ // it out.
+ URI baseUri = new URI(base);
+ systemUri = new URL(baseUri.toURL(), systemId).toURI();
}
- URI systemUri = new URI(systemId);
- if (systemUri.isAbsolute()) {
- return systemId;
- }
- return new URL(new URL(baseURI), systemId).toString();
+ systemUri = systemUri.normalize();
} catch (URISyntaxException e) {
- return systemId;
- } catch (MalformedURLException e) {
- return systemId;
+ // May be caused by a | being used instead of a : in an absolute
+ // file URI on Windows.
+ if (blockExternal) {
+ // Absolute paths aren't allowed so block it
+ throw new MalformedURLException(e.getMessage());
+ } else {
+ // See if the URLHandler can resolve it
+ return new InputSource(systemId);
+ }
+ }
+ if (systemUri.isAbsolute()) {
+ // Try the resolved systemId
+ resolved = systemIds.get(systemUri.toString());
+ if (resolved != null) {
+ InputSource is = new InputSource(resolved);
+ is.setPublicId(publicId);
+ return is;
+ }
+ if (!blockExternal) {
+ InputSource is = new InputSource(systemUri.toString());
+ is.setPublicId(publicId);
+ return is;
+ }
}
+ throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity",
+ name, publicId, systemId, base));
+ }
+
+
+ @Override
+ public InputSource getExternalSubset(String name, String baseURI)
+ throws SAXException, IOException {
+ return null;
}
-}
\ No newline at end of file
+}
diff --git a/java/org/apache/tomcat/util/descriptor/LocalStrings.properties b/java/org/apache/tomcat/util/descriptor/LocalStrings.properties
index f7d68915ad01..2e42fc719368 100644
--- a/java/org/apache/tomcat/util/descriptor/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/descriptor/LocalStrings.properties
@@ -13,5 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+localResolver.unresolvedEntity=Could not resolve XML resource [{0}] with public ID [{1}], system ID [{2}] and base URI [{3}] to a known, local entity.
+
xmlErrorHandler.error=Non-fatal error [{0}] reported processing [{1}].
xmlErrorHandler.warning=Warning [{0}] reported processing [{1}].
diff --git a/test/javax/servlet/resources/TestSchemaValidation.java b/test/javax/servlet/resources/TestSchemaValidation.java
index 46ab8ae6f035..59ec8d546a9c 100644
--- a/test/javax/servlet/resources/TestSchemaValidation.java
+++ b/test/javax/servlet/resources/TestSchemaValidation.java
@@ -31,8 +31,8 @@ public class TestSchemaValidation {
@Test
public void testWebapp_2_2() throws Exception {
- Digester digester =
- DigesterFactory.newDigester(true, true, new WebRuleSet(false));
+ Digester digester = DigesterFactory.newDigester(
+ true, true, new WebRuleSet(false), true);
digester.push(new WebXml());
WebXml desc = (WebXml) digester.parse(
new File("test/webapp-2.2/WEB-INF/web.xml"));
@@ -42,8 +42,8 @@ public void testWebapp_2_2() throws Exception {
@Test
public void testWebapp_2_3() throws Exception {
- Digester digester =
- DigesterFactory.newDigester(true, true, new WebRuleSet(false));
+ Digester digester = DigesterFactory.newDigester(
+ true, true, new WebRuleSet(false), true);
digester.push(new WebXml());
WebXml desc = (WebXml) digester.parse(
new File("test/webapp-2.3/WEB-INF/web.xml"));
@@ -53,8 +53,8 @@ public void testWebapp_2_3() throws Exception {
@Test
public void testWebapp_2_4() throws Exception {
- Digester digester =
- DigesterFactory.newDigester(true, true, new WebRuleSet(false));
+ Digester digester = DigesterFactory.newDigester(
+ true, true, new WebRuleSet(false), true);
digester.push(new WebXml());
WebXml desc = (WebXml) digester.parse(
new File("test/webapp-2.4/WEB-INF/web.xml"));
@@ -63,8 +63,8 @@ public void testWebapp_2_4() throws Exception {
@Test
public void testWebapp_2_5() throws Exception {
- Digester digester =
- DigesterFactory.newDigester(true, true, new WebRuleSet(false));
+ Digester digester = DigesterFactory.newDigester(
+ true, true, new WebRuleSet(false), true);
digester.push(new WebXml());
WebXml desc = (WebXml) digester.parse(
new File("test/webapp-2.5/WEB-INF/web.xml"));
@@ -73,8 +73,8 @@ public void testWebapp_2_5() throws Exception {
@Test
public void testWebapp_3_0() throws Exception {
- Digester digester =
- DigesterFactory.newDigester(true, true, new WebRuleSet(false));
+ Digester digester = DigesterFactory.newDigester(
+ true, true, new WebRuleSet(false), true);
digester.push(new WebXml());
WebXml desc = (WebXml) digester.parse(
new File("test/webapp-3.0/WEB-INF/web.xml"));
diff --git a/test/org/apache/catalina/core/TesterContext.java b/test/org/apache/catalina/core/TesterContext.java
index 0c3a802defb7..3c802be07d74 100644
--- a/test/org/apache/catalina/core/TesterContext.java
+++ b/test/org/apache/catalina/core/TesterContext.java
@@ -635,6 +635,16 @@ public boolean getTldValidation() {
return false;
}
+ @Override
+ public boolean getXmlBlockExternal() {
+ return false;
+ }
+
+ @Override
+ public void setXmlBlockExternal(boolean xmlBlockExternal) {
+ // NO-OP
+ }
+
@Override
public boolean getTldNamespaceAware() {
return true;
diff --git a/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java b/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java
index b41606b4a872..cf8b6b388cc8 100644
--- a/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java
+++ b/test/org/apache/tomcat/util/descriptor/TestLocalResolver.java
@@ -16,6 +16,7 @@
*/
package org.apache.tomcat.util.descriptor;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -34,7 +35,7 @@ public class TestLocalResolver {
private final Map publicIds = new HashMap();
private final Map systemIds = new HashMap();
- private LocalResolver resolver = new LocalResolver(publicIds, systemIds);
+ private LocalResolver resolver = new LocalResolver(publicIds, systemIds, true);
private String WEB_22_LOCAL;
private String WEB_30_LOCAL;
private String WEBCOMMON_30_LOCAL;
@@ -53,25 +54,25 @@ public String urlFor(String id) {
return ServletContext.class.getResource(id).toExternalForm();
}
- @Test
- public void unknownNullIdIsNull() throws IOException, SAXException {
+ @Test(expected = FileNotFoundException.class)
+ public void unknownNullId() throws IOException, SAXException {
Assert.assertNull(resolver.resolveEntity(null, null));
}
- @Test
- public void unknownPublicIdIsNull() throws IOException, SAXException {
+ @Test(expected = FileNotFoundException.class)
+ public void unknownPublicId() throws IOException, SAXException {
Assert.assertNull(resolver.resolveEntity("unknown", null));
}
- @Test
- public void unknownSystemIdIsReturned() throws IOException, SAXException {
+ @Test(expected = FileNotFoundException.class)
+ public void unknownSystemId() throws IOException, SAXException {
InputSource source = resolver.resolveEntity(null, "unknown");
Assert.assertEquals(null, source.getPublicId());
Assert.assertEquals("unknown", source.getSystemId());
}
- @Test
- public void unknownSystemIdIsResolvedAgainstBaseURI()
+ @Test(expected = FileNotFoundException.class)
+ public void unknownRelativeSystemId()
throws IOException, SAXException {
InputSource source = resolver.resolveEntity(
null, null, "http://example.com/home.html", "unknown");
@@ -121,4 +122,4 @@ public void absoluteSystemIdOverridesBaseURI()
Assert.assertEquals(null, source.getPublicId());
Assert.assertEquals(WEB_30_LOCAL, source.getSystemId());
}
-}
\ No newline at end of file
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index ea776b3d4b97..125dc090e496 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -216,6 +216,12 @@
55851: Further fixes to enable SPNEGO authentication to work
with IBM JDKs. Based on a patch by Arunav Sanyal. (markt)
+
+ Add an option to the Context to control the blocking of XML external
+ entities when parsing XML configuration files and enable this blocking
+ by default when a security manager is used. The block is implemented via
+ a custom resolver to enable the logging of any blocked entities. (markt)
+
diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
index 15849f459f88..f0fe76e0b047 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -524,6 +524,16 @@
Context. If not specified, a standard default value will be used.
+
+ If the value of this flag is true
, the parsing of
+ web.xml
, web-fragment.xml
, *.tld
,
+ *.jspx
, *.tagx
and tagPlugins.xml
+ files for this web application will not permit external entities to be
+ loaded. If a SecurityManager
is configured then the default
+ value of this attribute will be true
, else the default
+ value will be false
.
+
+
If the value of this flag is true
, the parsing of
web.xml
and web-fragment.xml
files for this
diff --git a/webapps/docs/security-howto.xml b/webapps/docs/security-howto.xml
index 56dc11f7cab9..ef4d34a5aaf9 100644
--- a/webapps/docs/security-howto.xml
+++ b/webapps/docs/security-howto.xml
@@ -179,6 +179,9 @@
- The default value for the deployXML attribute of the
Host element is changed to
false
.
+ - The default value for the xmlBlockExternal attribute
+ of the Context element is changed to
true
.
+