CVE-2022-46751 don't parse doctypes and access external entities by default

This commit is contained in:
Stefan Bodewig 2023-01-22 17:30:23 +01:00
parent 6ac4f36c9e
commit 2be17bc18b
12 changed files with 606 additions and 68 deletions

View File

@ -23,6 +23,8 @@ In order to work as you want, Ivy sometimes needs some settings. Actually, Ivy c
Settings are specified through an XML file, usually called `ivysettings.xml`. To configure Ivy from Ant, you just have to use the link:use/settings{outfilesuffix}[settings] datatype with the path of your settings file.
In addition certain link:systemproperties{outfilesuffix}[Java system properties] affect the XML parsing behavior of Ivy.
Here is an example of the settings file:
[source, xml]

View File

@ -0,0 +1,67 @@
////
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
https://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.
////
= Java System Properties Affecting Ivy
== XML Parser Settings
Starting with Ivy 2.5.2 Ivy's XML parser can be controlled via the use
of two newly introduced system properties.
If you want to restore the default behavior of Ivy 2.5.1 and earlier
you need to set `ivy.xml.allow-doctype-processing` to `true` and
`ivy.xml.external-resources` to `ALL`.
=== `ivy.xml.allow-doctype-processing`
This system property accepts `true` or `false` as values. When set to
`false` Ivy will not allow any processing of doctype declarations at
all, while setting it to `true` enables it.
The default is to allow doctype processing if and only if Ivy is
parsing a Maven POM file.
=== `ivy.xml.external-resources`
This system property controls if external resources are read during
doctype processing - and if so, where they can be loadad from. The
value of this system property is only ever used if
`ivy.xml.allow-doctype-processing` is not `false`.
The accepted values are
* `PROHIBIT` makes Ivy fail if any doctype tries to load an external
resource.
* `IGNORE` makes Ivy ignore any external resource that the doctype
declaration wants to load.
* `LOCAL_ONLY` allows external resources to be loaded via `file:` or
`jar:file` URIs only.
* `ALL` allows external resources to be loaded from any URI.
The default behavior is to not allow doctype processing at all, but if
it is enabled the value `PROHIBIT` is assumed unless the property has
been set explicitly.
When reading Maven POMs a specific internal system id is recognized as
resource and will be loaded from a resource shipping with the Ivy
distribution in order to deal with invalid POM files accepted by
Apache Maven - and the default value for this property is
`IGNORE`in that case. See
link:https://issues.apache.org/jira/browse/IVY-921[IVY-921] for
details.

View File

@ -150,6 +150,11 @@
}
]
},
{
"id": "systemproperties",
"title": "System Properties",
"children": []
},
{
"id":"settings",
"title":"Settings Files",

View File

@ -28,8 +28,6 @@ import java.util.Set;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
@ -43,6 +41,7 @@ import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.retrieve.RetrieveOptions;
import org.apache.ivy.util.XMLHelper;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.xml.sax.SAXException;
@ -170,10 +169,8 @@ public class IvyArtifactReport extends IvyPostResolveTask {
}
private TransformerHandler createTransformerHandler(FileOutputStream fileOutputStream)
throws TransformerFactoryConfigurationError, TransformerConfigurationException {
SAXTransformerFactory transformerFact = (SAXTransformerFactory) SAXTransformerFactory
.newInstance();
TransformerHandler saxHandler = transformerFact.newTransformerHandler();
throws TransformerConfigurationException {
TransformerHandler saxHandler = XMLHelper.getTransformerHandler();
saxHandler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8");
saxHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
saxHandler.setResult(new StreamResult(fileOutputStream));

View File

@ -33,7 +33,6 @@ import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
@ -48,6 +47,7 @@ import org.apache.ivy.plugins.report.XmlReportOutputter;
import org.apache.ivy.plugins.report.XmlReportParser;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.XSLTProcess;
import org.apache.tools.ant.util.JAXPUtils;
@ -313,8 +313,7 @@ public class IvyReport extends IvyTask {
Source xsltSource = new StreamSource(xsltStream, JAXPUtils.getSystemId(style));
// create transformer
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(xsltSource);
Transformer transformer = XMLHelper.getTransformer(xsltSource);
// add standard parameters
transformer.setParameter("confs", conf);

View File

@ -32,8 +32,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.SAXParserFactory;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.cache.RepositoryCacheManager;
import org.apache.ivy.core.module.status.StatusManager;
@ -46,6 +44,7 @@ import org.apache.ivy.util.Checks;
import org.apache.ivy.util.Configurator;
import org.apache.ivy.util.FileResolver;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.apache.ivy.util.url.CredentialsStore;
import org.apache.ivy.util.url.TimeoutConstrainedURLHandler;
import org.apache.ivy.util.url.URLHandlerRegistry;
@ -151,10 +150,8 @@ public class XmlSettingsParser extends DefaultHandler {
@SuppressWarnings("deprecation")
private void doParse(URL settingsUrl) throws IOException, ParseException {
this.settings = settingsUrl;
try (InputStream stream = URLHandlerRegistry.getDefault().openStream(settingsUrl)) {
InputSource inSrc = new InputSource(stream);
inSrc.setSystemId(settingsUrl.toExternalForm());
SAXParserFactory.newInstance().newSAXParser().parse(settingsUrl.toExternalForm(), this);
try {
XMLHelper.parse(settingsUrl, null, this);
ivy.validate();
} catch (IOException e) {
throw e;

View File

@ -22,9 +22,7 @@ import java.text.ParseException;
import java.util.Set;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
@ -45,6 +43,7 @@ import org.apache.ivy.osgi.repo.ManifestAndLocation;
import org.apache.ivy.osgi.util.Version;
import org.apache.ivy.osgi.util.VersionRange;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
@ -53,12 +52,10 @@ public class OBRXMLWriter {
public static ContentHandler newHandler(OutputStream out, String encoding, boolean indent)
throws TransformerConfigurationException {
SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
TransformerHandler hd = tf.newTransformerHandler();
Transformer serializer = tf.newTransformer();
TransformerHandler hd = XMLHelper.getTransformerHandler();
hd.getTransformer().setOutputProperty(OutputKeys.ENCODING, encoding);
hd.getTransformer().setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no");
StreamResult stream = new StreamResult(out);
serializer.setOutputProperty(OutputKeys.ENCODING, encoding);
serializer.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no");
hd.setResult(stream);
return hd;
}

View File

@ -130,12 +130,13 @@ public class PomReader {
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if (systemId != null && systemId.endsWith("m2-entities.ent")) {
// IVY-921: return an InputSource for our local packaged m2-entities.ent file
return new InputSource(
PomReader.class.getResourceAsStream("m2-entities.ent"));
}
return null;
}
});
}, true, XMLHelper.ExternalResources.IGNORE);
projectElement = pomDomDoc.getDocumentElement();
if (!PROJECT.equals(projectElement.getNodeName())
&& !MODEL.equals(projectElement.getNodeName())) {

View File

@ -17,9 +17,13 @@
*/
package org.apache.ivy.plugins.parser.xml;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@ -80,6 +84,7 @@ import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.apache.ivy.util.extendable.ExtendableItemHelper;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import static org.apache.ivy.core.module.descriptor.Configuration.Visibility.getVisibility;
@ -216,6 +221,8 @@ public class XmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
protected static final List<String> ALLOWED_VERSIONS = Arrays.asList("1.0",
"1.1", "1.2", "1.3", "1.4", "2.0", "2.1", "2.2", "2.3", "2.4");
private static final String IVY_XSD_CONTENT;
/* how and what do we have to parse */
private ParserSettings settings;
@ -248,6 +255,40 @@ public class XmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
private Stack<ExtraInfoHolder> extraInfoStack = new Stack<>();
static {
String ivyXSDContent = null;
final InputStream is = Parser.class.getResourceAsStream("ivy.xsd");
if (is != null) {
final StringBuilder sb = new StringBuilder();
try {
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line = null;
while ((line = reader.readLine()) != null) {
if (sb.length() != 0) {
sb.append("\n");
}
sb.append(line);
}
} catch (UnsupportedEncodingException e) {
// ignore
ivyXSDContent = null;
} catch (IOException e) {
// ignore
ivyXSDContent = null;
}
} finally {
try {
is.close();
} catch (Exception e) {
// ignore
}
}
ivyXSDContent = sb.length() == 0 ? null : sb.toString();
}
IVY_XSD_CONTENT = ivyXSDContent;
}
public Parser(ModuleDescriptorParser parser, ParserSettings ivySettings) {
super(parser);
settings = ivySettings;
@ -268,10 +309,14 @@ public class XmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
public void parse() throws ParseException {
try {
URL schemaURL = validate ? getSchemaURL() : null;
XMLHelper.ExternalResources e =
validate && System.getProperty(XMLHelper.EXTERNAL_RESOURCES) == null
? XMLHelper.ExternalResources.IGNORE
: XMLHelper.ExternalResources.fromSystemProperty();
if (descriptorURL != null) {
XMLHelper.parse(descriptorURL, schemaURL, this);
XMLHelper.parse(descriptorURL, schemaURL, this, null, e);
} else {
XMLHelper.parse(descriptorInput, schemaURL, this, null);
XMLHelper.parse(descriptorInput, schemaURL, this, null, e);
}
checkConfigurations();
replaceConfigurationWildcards();
@ -296,6 +341,26 @@ public class XmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
}
}
@Override
public InputSource resolveEntity(final String publicId, final String systemId)
throws IOException, SAXException {
if (isApacheOrgIvyXSDSystemId(systemId) && IVY_XSD_CONTENT != null) {
// redirect the schema location to local file based ivy.xsd whose content
// we have already read and is available in-memory.
final InputSource source = new InputSource(new StringReader(IVY_XSD_CONTENT));
return source;
}
return super.resolveEntity(publicId, systemId);
}
private static boolean isApacheOrgIvyXSDSystemId(final String systemId) {
if (systemId == null) {
return false;
}
return systemId.equals("http://ant.apache.org/ivy/schemas/ivy.xsd")
|| systemId.equals("https://ant.apache.org/ivy/schemas/ivy.xsd");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {

View File

@ -27,9 +27,6 @@ import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.ivy.core.cache.ArtifactOrigin;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
@ -38,6 +35,7 @@ import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.DownloadStatus;
import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
import org.apache.ivy.util.DateUtil;
import org.apache.ivy.util.XMLHelper;
import org.apache.ivy.util.extendable.ExtendableItemHelper;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
@ -242,8 +240,7 @@ public class XmlReportParser {
}
public void parse() throws Exception {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
saxParser.parse(report, new XmlReportParserHandler());
XMLHelper.parse(report.toURI().toURL(), null, new XmlReportParserHandler());
}
private static boolean parseBoolean(String str) {

View File

@ -19,6 +19,7 @@ package org.apache.ivy.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@ -28,13 +29,24 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import org.apache.ivy.util.url.URLHandlerRegistry;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
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.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
@ -50,49 +62,38 @@ public abstract class XMLHelper {
static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
private static boolean canUseSchemaValidation = true;
private static final String XML_ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";
private static final String XML_ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
public static final String ALLOW_DOCTYPE_PROCESSING = "ivy.xml.allow-doctype-processing";
public static final String EXTERNAL_RESOURCES = "ivy.xml.external-resources";
private static Boolean canDisableExternalDtds = null;
private static SAXParser newSAXParser(URL schema, InputStream schemaStream,
boolean loadExternalDtds) throws ParserConfigurationException, SAXException {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
private static SAXParser newSAXParser(final URL schema, final InputStream schemaStream,
final boolean allowXmlDoctypeProcessing, final ExternalResources externalResources)
throws ParserConfigurationException, SAXException {
final SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
parserFactory.setValidating(canUseSchemaValidation && (schema != null));
if (!loadExternalDtds && canDisableExternalDtds(parserFactory)) {
parserFactory.setFeature(XERCES_LOAD_EXTERNAL_DTD, false);
}
SAXParser parser = parserFactory.newSAXParser();
parserFactory.setValidating(schema != null);
configureSafeFeatures(parserFactory, allowXmlDoctypeProcessing, externalResources);
if (canUseSchemaValidation && schema != null) {
SAXParser parser = parserFactory.newSAXParser();
if (schema != null) {
try {
parser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
parser.setProperty(JAXP_SCHEMA_SOURCE, schemaStream);
} catch (SAXNotRecognizedException ex) {
Message.warn("problem while setting JAXP validating property on SAXParser... "
+ "XML validation will not be done", ex);
canUseSchemaValidation = false;
parserFactory.setValidating(false);
parser = parserFactory.newSAXParser();
}
}
parser.getXMLReader().setFeature(XML_NAMESPACE_PREFIXES, true);
final XMLReader reader = parser.getXMLReader();
reader.setFeature(XML_NAMESPACE_PREFIXES, true);
reader.setProperty(XML_ACCESS_EXTERNAL_SCHEMA, externalResources.getAllowedProtocols());
reader.setProperty(XML_ACCESS_EXTERNAL_DTD, externalResources.getAllowedProtocols());
return parser;
}
private static boolean canDisableExternalDtds(SAXParserFactory parserFactory) {
if (canDisableExternalDtds == null) {
try {
parserFactory.getFeature(XERCES_LOAD_EXTERNAL_DTD);
canDisableExternalDtds = Boolean.TRUE;
} catch (Exception ex) {
canDisableExternalDtds = Boolean.FALSE;
}
}
return canDisableExternalDtds;
}
/**
* Convert an URL to a valid systemId according to RFC 2396.
*
@ -116,36 +117,58 @@ public abstract class XMLHelper {
parse(xmlURL, schema, handler, null);
}
@SuppressWarnings("deprecation")
public static void parse(URL xmlURL, URL schema, DefaultHandler handler, LexicalHandler lHandler)
throws SAXException, IOException, ParserConfigurationException {
parse(xmlURL, schema, handler, lHandler, ExternalResources.fromSystemProperty());
}
@SuppressWarnings("deprecation")
public static void parse(URL xmlURL, URL schema, DefaultHandler handler, LexicalHandler lHandler,
final ExternalResources externalResources)
throws SAXException, IOException, ParserConfigurationException {
try (InputStream xmlStream = URLHandlerRegistry.getDefault().openStream(xmlURL)) {
InputSource inSrc = new InputSource(xmlStream);
inSrc.setSystemId(toSystemId(xmlURL));
parse(inSrc, schema, handler, lHandler);
parse(inSrc, schema, handler, lHandler, externalResources);
}
}
public static void parse(InputStream xmlStream, URL schema, DefaultHandler handler,
LexicalHandler lHandler) throws SAXException, IOException, ParserConfigurationException {
parse(xmlStream, schema, handler, lHandler, ExternalResources.fromSystemProperty());
}
public static void parse(InputStream xmlStream, URL schema, DefaultHandler handler,
LexicalHandler lHandler, final ExternalResources externalResources)
throws SAXException, IOException, ParserConfigurationException {
parse(new InputSource(xmlStream), schema, handler, lHandler);
}
public static void parse(InputSource xmlStream, URL schema, DefaultHandler handler,
LexicalHandler lHandler) throws SAXException, IOException, ParserConfigurationException {
parse(xmlStream, schema, handler, lHandler, true);
parse(xmlStream, schema, handler, lHandler, ExternalResources.fromSystemProperty());
}
public static void parse(final InputSource xmlStream, final URL schema,
final DefaultHandler handler, final LexicalHandler lHandler,
final boolean loadExternalDtds) throws SAXException, IOException,
ParserConfigurationException {
parse(xmlStream, schema, handler, lHandler,
loadExternalDtds ? ExternalResources.LOCAL_ONLY : ExternalResources.PROHIBIT);
}
@SuppressWarnings("deprecation")
public static void parse(InputSource xmlStream, URL schema, DefaultHandler handler,
LexicalHandler lHandler, boolean loadExternalDtds) throws SAXException, IOException,
public static void parse(final InputSource xmlStream, final URL schema,
final DefaultHandler handler, final LexicalHandler lHandler,
final ExternalResources externalResources) throws SAXException, IOException,
ParserConfigurationException {
InputStream schemaStream = null;
try {
if (schema != null) {
schemaStream = URLHandlerRegistry.getDefault().openStream(schema);
}
SAXParser parser = XMLHelper.newSAXParser(schema, schemaStream, loadExternalDtds);
SAXParser parser = XMLHelper.newSAXParser(schema, schemaStream,
isXmlDoctypeProcessingAllowed(), externalResources);
if (lHandler != null) {
try {
@ -157,7 +180,10 @@ public abstract class XMLHelper {
}
}
parser.parse(xmlStream, handler);
DefaultHandler h = externalResources == ExternalResources.IGNORE
? new NoopEntityResolverDefaultHandler(handler)
: handler;
parser.parse(xmlStream, h);
} finally {
if (schemaStream != null) {
try {
@ -170,7 +196,7 @@ public abstract class XMLHelper {
}
public static boolean canUseSchemaValidation() {
return canUseSchemaValidation;
return true;
}
/**
@ -216,15 +242,33 @@ public abstract class XMLHelper {
public static Document parseToDom(InputSource source, EntityResolver entityResolver)
throws IOException, SAXException {
DocumentBuilder docBuilder = getDocBuilder(entityResolver);
return parseToDom(source, entityResolver, isXmlDoctypeProcessingAllowed(),
ExternalResources.fromSystemProperty());
}
public static Document parseToDom(InputSource source, EntityResolver entityResolver,
boolean allowXmlDoctypeProcessing, ExternalResources externalResources)
throws IOException, SAXException {
DocumentBuilder docBuilder = getDocBuilder(entityResolver, allowXmlDoctypeProcessing,
externalResources);
return docBuilder.parse(source);
}
public static DocumentBuilder getDocBuilder(EntityResolver entityResolver) {
return getDocBuilder(entityResolver, isXmlDoctypeProcessingAllowed(),
ExternalResources.fromSystemProperty());
}
public static DocumentBuilder getDocBuilder(EntityResolver entityResolver,
boolean allowXmlDoctypeProcessing, ExternalResources externalResources) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
configureSafeFeatures(factory, allowXmlDoctypeProcessing, externalResources);
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (externalResources == ExternalResources.IGNORE) {
entityResolver = new NoopEntityResolver(entityResolver);
}
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
@ -234,7 +278,335 @@ public abstract class XMLHelper {
}
}
public static Transformer getTransformer(Source source) throws TransformerConfigurationException {
TransformerFactory factory = getTransformerFactory();
return factory.newTransformer(source);
}
public static TransformerHandler getTransformerHandler() throws TransformerConfigurationException {
SAXTransformerFactory factory = getTransformerFactory();
return factory.newTransformerHandler();
}
public enum ExternalResources {
PROHIBIT(""),
// technically the URIs for IGNORE will never get resolved at all.
// "all" pacifies some version of Java that check the property before delegating to the EntityResolver (which is
// going to return an empty content anyway)
IGNORE("all"),
LOCAL_ONLY("file, jar:file"),
ALL("all");
private final String allowedProtocols;
private ExternalResources(String allowedProtocols) {
this.allowedProtocols = allowedProtocols;
}
private String getAllowedProtocols() {
return allowedProtocols;
}
public static ExternalResources fromSystemProperty() {
final String val = System.getProperty(EXTERNAL_RESOURCES);
if (val != null) {
if (val.equalsIgnoreCase("ignore")) {
return IGNORE;
}
if (val.equalsIgnoreCase("all")) {
return ALL;
}
if (val.equalsIgnoreCase("local-only") || val.equalsIgnoreCase("local_only")) {
return LOCAL_ONLY;
}
}
return PROHIBIT;
}
}
public static boolean isXmlDoctypeProcessingAllowed() {
return "true".equals(System.getProperty(ALLOW_DOCTYPE_PROCESSING));
}
private XMLHelper() {
}
private static SAXTransformerFactory getTransformerFactory() {
TransformerFactory factory = SAXTransformerFactory.newInstance();
configureSafeFeatures(factory);
return (SAXTransformerFactory) factory;
}
private static void configureSafeFeatures(final DocumentBuilderFactory factory,
final boolean allowXmlDoctypeProcessing, final ExternalResources externalResources) {
final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
trySetFeature(factory, DISALLOW_DOCTYPE_DECL, !allowXmlDoctypeProcessing);
// available since Java 6, as XMLConstants.FEATURE_SECURE_PROCESSING. We can't use Java 6
// at compile time, in current version, so inline the constant here
final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
trySetFeature(factory, FEATURE_SECURE_PROCESSING, true);
final String ALLOW_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";
trySetFeature(factory, ALLOW_EXTERNAL_GENERAL_ENTITIES, false);
final String ALLOW_EXTERNAL_PARAM_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
trySetFeature(factory, ALLOW_EXTERNAL_PARAM_ENTITIES, false);
final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
trySetFeature(factory, LOAD_EXTERNAL_DTD, externalResources != ExternalResources.PROHIBIT);
try {
factory.setXIncludeAware(false);
} catch (Exception e) {
// ignore
}
try {
factory.setExpandEntityReferences(false);
} catch (Exception e) {
// ignore
}
}
private static void configureSafeFeatures(final SAXParserFactory factory,
final boolean allowXmlDoctypeProcessing, final ExternalResources externalResources) {
final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
trySetFeature(factory, DISALLOW_DOCTYPE_DECL, !allowXmlDoctypeProcessing);
// available since Java 6, as XMLConstants.FEATURE_SECURE_PROCESSING. We can't use Java 6
// at compile time, in current version, so inline the constant here
final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
trySetFeature(factory, FEATURE_SECURE_PROCESSING, true);
final boolean allowEntities = externalResources == ExternalResources.LOCAL_ONLY
|| externalResources == ExternalResources.ALL;
final String ALLOW_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";
trySetFeature(factory, ALLOW_EXTERNAL_GENERAL_ENTITIES, allowEntities);
final String ALLOW_EXTERNAL_PARAM_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
trySetFeature(factory, ALLOW_EXTERNAL_PARAM_ENTITIES, allowEntities);
final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
trySetFeature(factory, LOAD_EXTERNAL_DTD, externalResources != ExternalResources.PROHIBIT);
try {
factory.setXIncludeAware(false);
} catch (Exception e) {
// ignore
}
}
private static void configureSafeFeatures(final TransformerFactory factory) {
// available since Java 7, as XMLConstants.ACCESS_EXTERNAL_DTD, ACCESS_EXTERNAL_SCHEMA and
// ACCESS_EXTERNAL_STYLESHEET respectively.
// We can't use Java 7 at compile time, in current version, so inline the constants here
trySetAttribute(factory, XML_ACCESS_EXTERNAL_DTD, "");
trySetAttribute(factory, XML_ACCESS_EXTERNAL_SCHEMA, "");
trySetAttribute(factory, "http://javax.xml.XMLConstants/property/accessExternalStylesheet", "");
}
private static boolean isFeatureSupported(final SAXParserFactory factory, final String feature) {
try {
factory.getFeature(feature);
return true;
} catch (ParserConfigurationException e) {
return false;
} catch (SAXNotRecognizedException e) {
return false;
} catch (SAXNotSupportedException e) {
return false;
}
}
private static boolean isFeatureSupported(final DocumentBuilderFactory factory, final String feature) {
try {
factory.getFeature(feature);
return true;
} catch (ParserConfigurationException e) {
return false;
}
}
private static boolean isAttributeSupported(final TransformerFactory factory, final String attribute) {
try {
factory.getAttribute(attribute);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
private static boolean trySetFeature(final DocumentBuilderFactory factory,
final String feature, final boolean val) {
if (!isFeatureSupported(factory, feature)) {
return false;
}
try {
factory.setFeature(feature, val);
return true;
} catch (ParserConfigurationException e) {
// log and continue
Message.warn("Failed to set feature " + feature + " on DocumentBuilderFactory", e);
return false;
}
}
private static boolean trySetFeature(final SAXParserFactory factory,
final String feature, final boolean val) {
if (!isFeatureSupported(factory, feature)) {
return false;
}
try {
factory.setFeature(feature, val);
return true;
} catch (ParserConfigurationException e) {
// log and continue
Message.warn("Failed to set feature " + feature + " on SAXParserFactory", e);
return false;
} catch (SAXNotRecognizedException e) {
// log and continue
Message.warn("Failed to set feature " + feature + " on SAXParserFactory", e);
return false;
} catch (SAXNotSupportedException e) {
// log and continue
Message.warn("Failed to set feature " + feature + " on SAXParserFactory", e);
return false;
}
}
private static boolean trySetAttribute(final TransformerFactory factory,
final String attribute, final String val) {
if (!isAttributeSupported(factory, attribute)) {
return false;
}
try {
factory.setAttribute(attribute, val);
return true;
} catch (IllegalArgumentException e) {
// log and continue
Message.warn("Failed to set attribute " + attribute + " on TransformerFactory", e);
return false;
}
}
private static final InputSource EMPTY_INPUT_SOURCE = new InputSource(new StringReader(""));
private static class NoopEntityResolver implements EntityResolver {
private EntityResolver wrapped;
private NoopEntityResolver(EntityResolver wrapped) {
this.wrapped = wrapped;
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (wrapped != null) {
InputSource s = wrapped.resolveEntity(publicId, systemId);
if (s != null) {
return s;
}
}
return EMPTY_INPUT_SOURCE;
}
}
private static class NoopEntityResolverDefaultHandler extends DefaultHandler {
private DefaultHandler wrapped;
private NoopEntityResolverDefaultHandler(DefaultHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (wrapped != null) {
InputSource s = wrapped.resolveEntity(publicId, systemId);
if (s != null) {
return s;
}
}
return EMPTY_INPUT_SOURCE;
}
@Override
public void notationDecl(String name, String publicId, String systemId) throws SAXException {
wrapped.notationDecl(name, publicId, systemId);
}
@Override
public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName)
throws SAXException {
wrapped.unparsedEntityDecl(name, publicId, systemId, notationName);
}
@Override
public void setDocumentLocator(Locator locator) {
wrapped.setDocumentLocator(locator);
}
@Override
public void startDocument() throws SAXException {
wrapped.startDocument();
}
@Override
public void endDocument() throws SAXException {
wrapped.endDocument();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
wrapped.startPrefixMapping(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
wrapped.endPrefixMapping(prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
wrapped.startElement(uri, localName, qName, attributes);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
wrapped.endElement(uri, localName, qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
wrapped.characters(ch, start, length);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
wrapped.ignorableWhitespace(ch, start, length);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
wrapped.processingInstruction(target, data);
}
@Override
public void skippedEntity(String name) throws SAXException {
wrapped.skippedEntity(name);
}
@Override
public void warning(SAXParseException e) throws SAXException {
wrapped.warning(e);
}
@Override
public void error(SAXParseException e) throws SAXException {
wrapped.error(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
wrapped.fatalError(e);
}
}
}

View File

@ -51,6 +51,7 @@ import org.apache.ivy.plugins.resolver.FileSystemResolver;
import org.apache.ivy.util.CacheCleaner;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.MockMessageLogger;
import org.apache.ivy.util.XMLHelper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -74,6 +75,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@ -309,6 +311,43 @@ public class ResolveTest {
@Test
public void testResolveWithXmlEntities() {
testResolveWithXmlEntities(null, 0);
testResolveWithXmlEntities("prohibit", 0);
testResolveWithXmlEntities("ignore", 0);
testResolveWithXmlEntities("local-only", 2);
testResolveWithXmlEntities("LOCAL_ONLY", 2);
testResolveWithXmlEntities("all", 2);
}
private void testResolveWithXmlEntities(String externalResourcesSystemProperty,
int expectedNumberOfDependencies) {
Ivy ivy = new Ivy();
Throwable th = null;
Properties p = System.getProperties();
try {
System.setProperties(new Properties());
System.setProperty(XMLHelper.ALLOW_DOCTYPE_PROCESSING, "true");
if (externalResourcesSystemProperty != null) {
System.setProperty(XMLHelper.EXTERNAL_RESOURCES, externalResourcesSystemProperty);
}
ivy.configure(new File("test/repositories/xml-entities/ivysettings.xml"));
ResolveReport report = ivy.resolve(new File("test/repositories/xml-entities/ivy.xml"),
getResolveOptions(new String[] {"*"}));
assertNotNull(report);
assertFalse(report.hasError());
assertNotNull(report.getDependencies());
assertEquals("number of dependencies while setting " + externalResourcesSystemProperty,
expectedNumberOfDependencies, report.getDependencies().size());
} catch (Throwable e) {
th = e;
} finally {
System.setProperties(p);
}
assertNull(th);
}
@Test
public void testResolveWithXmlEntitiesButNoSystemProperty() {
Ivy ivy = new Ivy();
Throwable th = null;
try {
@ -320,7 +359,7 @@ public class ResolveTest {
} catch (Throwable e) {
th = e;
}
assertNull(th);
assertNotNull(th);
}
@Test