mirror of
https://github.com/apache/struts.git
synced 2026-08-06 07:06:58 +00:00
Merge pull request #628 from apache/WW-5252-external
[WW-5252] Disables parsing external entities
This commit is contained in:
@@ -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:cziegeler@apache.org">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) {
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -270,7 +302,7 @@ public class XSLTResult implements Result {
|
||||
|
||||
LOG.debug("Preparing XSLT stylesheet templates: {}", path);
|
||||
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
TransformerFactory factory = createTransformerFactory();
|
||||
factory.setURIResolver(getURIResolver());
|
||||
factory.setErrorListener(buildErrorListener());
|
||||
templates = factory.newTemplates(new StreamSource(resource.openStream()));
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user