This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5252-external in repository https://gitbox.apache.org/repos/asf/struts.git
commit 6658c6360e771a793ab261e5b4d3ed9dfb6720d3 Author: Lukasz Lenart <[email protected]> AuthorDate: Thu Oct 27 13:35:10 2022 +0200 WW-5252 Disables parsing external entities --- .../com/opensymphony/xwork2/util/DomHelper.java | 130 ++++++++++++--------- .../org/apache/struts2/views/xslt/XSLTResult.java | 60 +++++++--- core/src/main/resources/author.dtd | 22 ++++ .../opensymphony/xwork2/util/DomHelperTest.java | 56 ++++----- 4 files changed, 173 insertions(+), 95 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java b/core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java index f1021dc37..b79c3c03a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/DomHelper.java @@ -28,9 +28,17 @@ import org.apache.struts2.StrutsException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.xml.sax.*; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.TransformerFactory; @@ -48,28 +56,24 @@ import java.util.Map; public class DomHelper { private static final Logger LOG = LogManager.getLogger(DomHelper.class); - - public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; public static Location getLocationObject(Element element) { return LocationAttributes.getLocation(element); } - /** * Creates a W3C Document that remembers the location of each element in * the source file. The location of element nodes can then be retrieved * using the {@link #getLocationObject(Element)} method. * * @param inputSource the inputSource to read the document from - * * @return the W3C Document */ public static Document parse(InputSource inputSource) { return parse(inputSource, null); } - - + + /** * Creates a W3C Document that remembers the location of each element in * the source file. The location of element nodes can then be retrieved @@ -77,17 +81,16 @@ public class DomHelper { * * @param inputSource the inputSource to read the document from * @param dtdMappings a map of DTD names and public ids - * * @return the W3C Document */ public static Document parse(InputSource inputSource, Map<String, String> dtdMappings) { - + SAXParserFactory factory = null; String parserProp = System.getProperty("xwork.saxParserFactory"); if (parserProp != null) { try { ObjectFactory objectFactory = ActionContext.getContext().getContainer().getInstance(ObjectFactory.class); - Class clazz = objectFactory.getClassInstance(parserProp); + Class<?> clazz = objectFactory.getClassInstance(parserProp); factory = (SAXParserFactory) clazz.newInstance(); } catch (Exception e) { LOG.error("Unable to load saxParserFactory set by system property 'xwork.saxParserFactory': {}", parserProp, e); @@ -98,6 +101,13 @@ public class DomHelper { factory = SAXParserFactory.newInstance(); } + try { + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (ParserConfigurationException | SAXNotRecognizedException | SAXNotSupportedException e) { + throw new StrutsException("Unable to disable resolving external entities!", e); + } + factory.setValidating((dtdMappings != null)); factory.setNamespaceAware(true); @@ -107,22 +117,22 @@ public class DomHelper { } catch (Exception ex) { throw new StrutsException("Unable to create SAX parser", ex); } - - + + DOMBuilder builder = new DOMBuilder(); // Enhance the sax stream with location information ContentHandler locationHandler = new LocationAttributes.Pipe(builder); - + try { parser.parse(inputSource, new StartHandler(locationHandler, dtdMappings)); } catch (Exception ex) { throw new StrutsException(ex); } - + return builder.getDocument(); } - + /** * The <code>DOMBuilder</code> is a utility class that will generate a W3C * DOM Document from SAX events. @@ -130,27 +140,35 @@ public class DomHelper { * @author <a href="mailto:[email protected]">Carsten Ziegeler</a> */ static public class DOMBuilder implements ContentHandler { - - /** The default transformer factory shared by all instances */ + + /** + * The default transformer factory shared by all instances + */ protected static SAXTransformerFactory FACTORY; - - /** The transformer factory */ + + /** + * The transformer factory + */ protected SAXTransformerFactory factory; - - /** The result */ + + /** + * The result + */ protected DOMResult result; - - /** The parentNode */ + + /** + * The parentNode + */ protected Node parentNode; - + protected ContentHandler nextHandler; - + static { String parserProp = System.getProperty("xwork.saxTransformerFactory"); if (parserProp != null) { try { ObjectFactory objectFactory = ActionContext.getContext().getContainer().getInstance(ObjectFactory.class); - Class clazz = objectFactory.getClassInstance(parserProp); + Class<?> clazz = objectFactory.getClassInstance(parserProp); FACTORY = (SAXTransformerFactory) clazz.newInstance(); } catch (Exception e) { LOG.error("Unable to load SAXTransformerFactory set by system property 'xwork.saxTransformerFactory': {}", parserProp, e); @@ -158,7 +176,7 @@ public class DomHelper { } if (FACTORY == null) { - FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance(); + FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance(); } } @@ -168,15 +186,16 @@ public class DomHelper { public DOMBuilder() { this((Node) null); } - + /** * Construct a new instance of this DOMBuilder. + * * @param factory the SAX transformer factory */ public DOMBuilder(SAXTransformerFactory factory) { this(factory, null); } - + /** * Constructs a new instance that appends nodes to the given parent node. * @@ -185,19 +204,19 @@ public class DomHelper { public DOMBuilder(Node parentNode) { this(null, parentNode); } - + /** * Construct a new instance of this DOMBuilder. * - * @param factory the SAX transformer factory + * @param factory the SAX transformer factory * @param parentNode the parent node */ public DOMBuilder(SAXTransformerFactory factory, Node parentNode) { - this.factory = factory == null? FACTORY: factory; + this.factory = factory == null ? FACTORY : factory; this.parentNode = parentNode; setup(); } - + /** * Setup this instance transformer and result objects. */ @@ -215,7 +234,7 @@ public class DomHelper { throw new StrutsException("Fatal-Error: Unable to get transformer handler", local); } } - + /** * Return the newly built Document. * @@ -230,60 +249,61 @@ public class DomHelper { return this.result.getNode().getOwnerDocument(); } } - + public void setDocumentLocator(Locator locator) { nextHandler.setDocumentLocator(locator); } - + public void startDocument() throws SAXException { nextHandler.startDocument(); } - + public void endDocument() throws SAXException { nextHandler.endDocument(); } - + public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { nextHandler.startElement(uri, loc, raw, attrs); } - + public void endElement(String arg0, String arg1, String arg2) throws SAXException { nextHandler.endElement(arg0, arg1, arg2); } - + public void startPrefixMapping(String arg0, String arg1) throws SAXException { nextHandler.startPrefixMapping(arg0, arg1); } - + public void endPrefixMapping(String arg0) throws SAXException { nextHandler.endPrefixMapping(arg0); } - + public void characters(char[] arg0, int arg1, int arg2) throws SAXException { nextHandler.characters(arg0, arg1, arg2); } - + public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { nextHandler.ignorableWhitespace(arg0, arg1, arg2); } - + public void processingInstruction(String arg0, String arg1) throws SAXException { nextHandler.processingInstruction(arg0, arg1); } - + public void skippedEntity(String arg0) throws SAXException { nextHandler.skippedEntity(arg0); } } - + public static class StartHandler extends DefaultHandler { - - private ContentHandler nextHandler; - private Map<String, String> dtdMappings; - + + private final ContentHandler nextHandler; + private final Map<String, String> dtdMappings; + /** * Create a filter that is chained to another handler. - * @param next the next handler in the chain. + * + * @param next the next handler in the chain. * @param dtdMappings map of DTD mappings */ public StartHandler(ContentHandler next, Map<String, String> dtdMappings) { @@ -295,12 +315,12 @@ public class DomHelper { public void setDocumentLocator(Locator locator) { nextHandler.setDocumentLocator(locator); } - + @Override public void startDocument() throws SAXException { nextHandler.startDocument(); } - + @Override public void endDocument() throws SAXException { nextHandler.endDocument(); @@ -345,7 +365,7 @@ public class DomHelper { public void skippedEntity(String arg0) throws SAXException { nextHandler.skippedEntity(arg0); } - + @Override public InputSource resolveEntity(String publicId, String systemId) { if (dtdMappings != null && dtdMappings.containsKey(publicId)) { @@ -356,7 +376,7 @@ public class DomHelper { } return null; } - + @Override public void warning(SAXParseException exception) { } diff --git a/core/src/main/java/org/apache/struts2/views/xslt/XSLTResult.java b/core/src/main/java/org/apache/struts2/views/xslt/XSLTResult.java index d310a6db9..31690362c 100644 --- a/core/src/main/java/org/apache/struts2/views/xslt/XSLTResult.java +++ b/core/src/main/java/org/apache/struts2/views/xslt/XSLTResult.java @@ -18,7 +18,6 @@ */ package org.apache.struts2.views.xslt; -import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.inject.Inject; @@ -32,7 +31,15 @@ import org.apache.struts2.StrutsConstants; import org.apache.struts2.StrutsException; import javax.servlet.http.HttpServletResponse; -import javax.xml.transform.*; +import javax.xml.XMLConstants; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; @@ -49,10 +56,14 @@ public class XSLTResult implements Result { private static final long serialVersionUID = 6424691441777176763L; - /** Log instance for this result. */ + /** + * Log instance for this result. + */ private static final Logger LOG = LogManager.getLogger(XSLTResult.class); - /** 'stylesheetLocation' parameter. Points to the xsl. */ + /** + * 'stylesheetLocation' parameter. Points to the xsl. + */ public static final String DEFAULT_PARAM = "stylesheetLocation"; /** @@ -66,22 +77,34 @@ public class XSLTResult implements Result { // Configurable Parameters - /** Determines whether or not the result should allow caching. */ + /** + * Determines whether or not the result should allow caching. + */ protected boolean noCache; - /** Indicates the location of the xsl template. */ + /** + * Indicates the location of the xsl template. + */ private String stylesheetLocation; - /** Indicates the property name patterns which should be exposed to the xml. */ + /** + * Indicates the property name patterns which should be exposed to the xml. + */ private String matchingPattern; - /** Indicates the property name patterns which should be excluded from the xml. */ + /** + * Indicates the property name patterns which should be excluded from the xml. + */ private String excludingPattern; - /** Indicates the ognl expression representing the bean which is to be exposed as xml. */ + /** + * Indicates the ognl expression representing the bean which is to be exposed as xml. + */ private String exposedValue; - /** Indicates the status to return in the response */ + /** + * Indicates the status to return in the response + */ private int status = 200; private String encoding = "UTF-8"; @@ -96,7 +119,7 @@ public class XSLTResult implements Result { this(); setStylesheetLocation(stylesheetLocation); } - + @Inject(StrutsConstants.STRUTS_XSLT_NOCACHE) public void setNoCache(String xsltNoCache) { this.noCache = BooleanUtils.toBoolean(xsltNoCache); @@ -124,7 +147,7 @@ public class XSLTResult implements Result { public void setStatus(String status) { try { - this.status = Integer.valueOf(status); + this.status = Integer.parseInt(status); } catch (NumberFormatException e) { throw new IllegalArgumentException("Status value not number " + e.getMessage(), e); } @@ -175,7 +198,8 @@ public class XSLTResult implements Result { templates = getTemplates(location); transformer = templates.newTransformer(); } else { - transformer = TransformerFactory.newInstance().newTransformer(); + TransformerFactory factory = createTransformerFactory(); + transformer = factory.newTransformer(); } transformer.setURIResolver(getURIResolver()); @@ -217,6 +241,14 @@ public class XSLTResult implements Result { } } + protected TransformerFactory createTransformerFactory() { + TransformerFactory factory = TransformerFactory.newInstance(); + LOG.debug("Disables parsing external entities"); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + return factory; + } + protected ErrorListener buildErrorListener() { return new ErrorListener() { @@ -282,6 +314,6 @@ public class XSLTResult implements Result { } protected Source getDOMSourceForStack(Object value) throws IllegalAccessException, InstantiationException { - return new DOMSource(getAdapterFactory().adaptDocument("result", value) ); + return new DOMSource(getAdapterFactory().adaptDocument("result", value)); } } diff --git a/core/src/main/resources/author.dtd b/core/src/main/resources/author.dtd new file mode 100644 index 000000000..4521075a0 --- /dev/null +++ b/core/src/main/resources/author.dtd @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * 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. + */ +--> +<!ENTITY writer "Dock"> diff --git a/core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java b/core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java index 330dfe35f..4c56f5759 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/DomHelperTest.java @@ -32,42 +32,46 @@ import java.io.StringReader; */ public class DomHelperTest extends TestCase { - private String xml = "<!DOCTYPE foo [\n" + - "<!ELEMENT foo (bar)>\n" + - "<!ELEMENT bar (#PCDATA)>\n" + - "]>\n" + - "<foo>\n" + - " <bar/>\n" + - "</foo>\n"; - - public void testParse() throws Exception { + private final String xml = "<!DOCTYPE foo [<!ELEMENT foo (bar)><!ELEMENT bar (#PCDATA)>]>\n<foo>\n<bar/>\n</foo>\n"; + + public void testParse() { InputSource in = new InputSource(new StringReader(xml)); in.setSystemId("foo://bar"); - + Document doc = DomHelper.parse(in); assertNotNull(doc); - assertTrue("Wrong root node", - "foo".equals(doc.getDocumentElement().getNodeName())); - + assertEquals("Wrong root node", "foo", doc.getDocumentElement().getNodeName()); + NodeList nl = doc.getElementsByTagName("bar"); - assertTrue(nl.getLength() == 1); - - - + assertEquals(1, nl.getLength()); } - - public void testGetLocationObject() throws Exception { + + public void testGetLocationObject() { InputSource in = new InputSource(new StringReader(xml)); in.setSystemId("foo://bar"); - + Document doc = DomHelper.parse(in); - + NodeList nl = doc.getElementsByTagName("bar"); - - Location loc = DomHelper.getLocationObject((Element)nl.item(0)); - + + Location loc = DomHelper.getLocationObject((Element) nl.item(0)); + assertNotNull(loc); - assertTrue("Should be line 6, was "+loc.getLineNumber(), - 6==loc.getLineNumber()); + assertEquals("Should be line 3, was " + loc.getLineNumber(), 3, loc.getLineNumber()); + } + + public void testExternalEntities() { + String dtdFile = getClass().getResource("/author.dtd").getPath(); + String xml = "<!DOCTYPE foo [<!ELEMENT foo (bar)><!ELEMENT bar (#PCDATA)><!ENTITY writer SYSTEM \"file://" + dtdFile + "\">]><foo><bar>&writer;</bar></foo>"; + InputSource in = new InputSource(new StringReader(xml)); + in.setSystemId("foo://bar"); + + Document doc = DomHelper.parse(in); + assertNotNull(doc); + assertEquals("Wrong root node", "foo", doc.getDocumentElement().getNodeName()); + + NodeList nl = doc.getElementsByTagName("bar"); + assertEquals(1, nl.getLength()); + assertNull(nl.item(0).getNodeValue()); } }
