Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java (working copy) @@ -0,0 +1,87 @@ +/* + * 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.jackrabbit.webdav.xml; + +import java.io.IOException; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Custom {@link DocumentBuilderFactory} extended for use in WebDAV. + */ +public class DavDocumentBuilderFactory { + + private static final Logger LOG = LoggerFactory.getLogger(DomUtil.class); + + private final DocumentBuilderFactory DEFAULT_FACTORY = createFactory(); + + private DocumentBuilderFactory BUILDER_FACTORY = DEFAULT_FACTORY; + + private DocumentBuilderFactory createFactory() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setCoalescing(true); + try { + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + LOG.warn("Secure XML processing is not supported", e); + } catch (AbstractMethodError e) { + LOG.warn("Secure XML processing is not supported", e); + } + return factory; + } + + public void setFactory(DocumentBuilderFactory documentBuilderFactory) { + LOG.debug("DocumentBuilderFactory changed to: " + documentBuilderFactory); + BUILDER_FACTORY = documentBuilderFactory != null ? documentBuilderFactory : DEFAULT_FACTORY; + } + + /** + * An entity resolver that does not allow external entity resolution. See + * RFC 4918, Section 20.6 + */ + private static final EntityResolver DEFAULT_ENTITY_RESOLVER = new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) throws IOException { + LOG.debug("Resolution of external entities in XML payload not supported - publicId: " + publicId + ", systemId: " + + systemId); + throw new IOException("This parser does not support resolution of external entities (publicId: " + publicId + + ", systemId: " + systemId + ")"); + } + }; + + public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { + DocumentBuilder db = BUILDER_FACTORY.newDocumentBuilder(); + if (BUILDER_FACTORY == DEFAULT_FACTORY) { + // if this is the default factory: set the default entity resolver as well + db.setEntityResolver(DEFAULT_ENTITY_RESOLVER); + } + db.setErrorHandler(new DefaultHandler()); + return db; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java (revision 1678308) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java (working copy) @@ -28,9 +28,7 @@ import org.w3c.dom.Text; import org.w3c.dom.NamedNodeMap; import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; -import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -56,27 +54,11 @@ private static Logger log = LoggerFactory.getLogger(DomUtil.class); /** - * Constant for DocumentBuilderFactory which is used + * Constant for DavDocumentBuilderFactory which is used * to create and parse DOM documents. */ - private static DocumentBuilderFactory BUILDER_FACTORY = createFactory(); + private static final DavDocumentBuilderFactory BUILDER_FACTORY = new DavDocumentBuilderFactory(); - private static DocumentBuilderFactory createFactory() { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setCoalescing(true); - try { - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - } catch (ParserConfigurationException e) { - log.warn("Secure XML processing is not supported", e); - } catch (AbstractMethodError e) { - log.warn("Secure XML processing is not supported", e); - } - return factory; - } - /** * Support the replacement of {@link #BUILDER_FACTORY}. This is useful * for injecting a customized BuilderFactory, for example with one that @@ -88,7 +70,7 @@ */ public static void setBuilderFactory( DocumentBuilderFactory documentBuilderFactory) { - BUILDER_FACTORY = documentBuilderFactory; + BUILDER_FACTORY.setFactory(documentBuilderFactory); } /** @@ -119,11 +101,6 @@ public static Document parseDocument(InputStream stream) throws ParserConfigurationException, SAXException, IOException { DocumentBuilder docBuilder = BUILDER_FACTORY.newDocumentBuilder(); - - // Set an error handler to prevent parsers from printing error messages - // to standard output! - docBuilder.setErrorHandler(new DefaultHandler()); - return docBuilder.parse(stream); } Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java =================================================================== --- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java (revision 0) +++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java (working copy) @@ -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.jackrabbit.webdav.xml; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import junit.framework.TestCase; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class ParserTest extends TestCase { + + // see + public void testBillionLaughs() throws UnsupportedEncodingException { + + String testBody = "" + "" + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + "]>" + "&lol9;"; + InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8")); + + try { + DomUtil.parseDocument(is); + fail("parsing this document should cause an exception"); + } catch (Exception expected) { + } + } + + public void testExternalEntities() throws IOException { + + String dname = "target"; + String fname = "test.xml"; + + File f = new File(dname, fname); + OutputStream os = new FileOutputStream(f); + os.write("testdata".getBytes()); + os.close(); + + String testBody = "\n" + + "]>\n&test;"; + InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8")); + + try { + Document d = DomUtil.parseDocument(is); + Element root = d.getDocumentElement(); + String text = DomUtil.getText(root); + fail("parsing this document should cause an exception, but the following external content was included: " + text); + } catch (Exception expected) { + } + } + + public void testCustomEntityResolver() throws ParserConfigurationException, SAXException, IOException { + + try { + DocumentBuilderFactory dbf = new DocumentBuilderFactory() { + + DocumentBuilderFactory def = DocumentBuilderFactory.newInstance(); + + @Override + public void setFeature(String name, boolean value) throws ParserConfigurationException { + def.setFeature(name, value); + } + + @Override + public void setAttribute(String name, Object value) throws IllegalArgumentException { + def.setAttribute(name, value); + } + + @Override + public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { + DocumentBuilder db = def.newDocumentBuilder(); + db.setEntityResolver(new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + if ("foo:test".equals(systemId)) { + return new InputSource(new ByteArrayInputStream("foo&bar".getBytes("UTF-8"))); + } else { + return null; + } + } + }); + return db; + } + + @Override + public boolean getFeature(String name) throws ParserConfigurationException { + return def.getFeature(name); + } + + @Override + public Object getAttribute(String name) throws IllegalArgumentException { + return def.getAttribute(name); + } + }; + + DomUtil.setBuilderFactory(dbf); + String testBody = "\n" + + "]>\n&test;"; + InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8")); + + Document d = DomUtil.parseDocument(is); + Element root = d.getDocumentElement(); + String text = DomUtil.getText(root); + assertEquals("custom entity resolver apparently not called", "foo&bar", text); + } finally { + DomUtil.setBuilderFactory(null); + } + } +} \ No newline at end of file Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java =================================================================== --- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java (revision 1678308) +++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java (working copy) @@ -33,6 +33,7 @@ TestSuite suite = new TestSuite("org.apache.jackrabbit.webdav.xml tests"); suite.addTestSuite(NamespaceTest.class); + suite.addTestSuite(ParserTest.class); return suite; }