mirror of https://github.com/apache/lucene.git
SOLR-12450: Don't allow referal to external resources in various config files
This commit is contained in:
parent
2519025fda
commit
e21d4937e0
|
@ -382,6 +382,9 @@ Bug Fixes
|
|||
* SOLR-12416: When creating a time routed alias, the router.autoDeleteAge option wasn't considered.
|
||||
(Joachim Sauer via David Smiley)
|
||||
|
||||
* SOLR-12450: Don't allow referal to external resources in various config files.
|
||||
(Yuyang Xiao, Uwe Schindler)
|
||||
|
||||
Optimizations
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -16,20 +16,22 @@
|
|||
*/
|
||||
package org.apache.solr.handler.extraction;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.beans.PropertyEditorManager;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.apache.solr.util.SafeXMLParsing;
|
||||
import org.apache.tika.parser.ParseContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
|
@ -37,6 +39,8 @@ import org.w3c.dom.Node;
|
|||
import org.w3c.dom.NodeList;
|
||||
|
||||
public class ParseContextConfig {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private final Map<Class<?>, Object> entries = new HashMap<>();
|
||||
|
||||
/** Creates an empty Config without any settings (used as placeholder). */
|
||||
|
@ -54,9 +58,7 @@ public class ParseContextConfig {
|
|||
}
|
||||
|
||||
private static Document loadConfigFile(SolrResourceLoader resourceLoader, String parseContextConfigLoc) throws Exception {
|
||||
try (InputStream in = resourceLoader.openResource(parseContextConfigLoc)) {
|
||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in, parseContextConfigLoc);
|
||||
}
|
||||
return SafeXMLParsing.parseConfigXML(log, resourceLoader, parseContextConfigLoc);
|
||||
}
|
||||
|
||||
private void extract(Element element, SolrResourceLoader loader) throws Exception {
|
||||
|
|
|
@ -18,14 +18,12 @@
|
|||
package org.apache.solr.schema;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
|
@ -38,8 +36,10 @@ import org.apache.lucene.search.SortField;
|
|||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.apache.solr.common.EnumFieldValue;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.apache.solr.response.TextResponseWriter;
|
||||
import org.apache.solr.search.QParser;
|
||||
import org.apache.solr.util.SafeXMLParsing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
|
@ -51,7 +51,6 @@ import org.xml.sax.SAXException;
|
|||
* Abstract Field type for support of string values with custom sort order.
|
||||
*/
|
||||
public abstract class AbstractEnumField extends PrimitiveFieldType {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
protected EnumMapping enumMapping;
|
||||
|
||||
@Override
|
||||
|
@ -69,6 +68,8 @@ public abstract class AbstractEnumField extends PrimitiveFieldType {
|
|||
* @lucene.internal
|
||||
*/
|
||||
public static final class EnumMapping {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public static final String PARAM_ENUMS_CONFIG = "enumsConfig";
|
||||
public static final String PARAM_ENUM_NAME = "enumName";
|
||||
public static final Integer DEFAULT_VALUE = -1;
|
||||
|
@ -105,67 +106,49 @@ public abstract class AbstractEnumField extends PrimitiveFieldType {
|
|||
ftName + ": No enum name was configured.");
|
||||
}
|
||||
|
||||
InputStream is = null;
|
||||
|
||||
final SolrResourceLoader loader = schema.getResourceLoader();
|
||||
try {
|
||||
is = schema.getResourceLoader().openResource(enumsConfigFile);
|
||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
final Document doc = dbf.newDocumentBuilder().parse(is);
|
||||
final XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
final XPath xpath = xpathFactory.newXPath();
|
||||
final String xpathStr = String.format(Locale.ROOT, "/enumsConfig/enum[@name='%s']", enumName);
|
||||
final NodeList nodes = (NodeList) xpath.evaluate(xpathStr, doc, XPathConstants.NODESET);
|
||||
final int nodesLength = nodes.getLength();
|
||||
if (nodesLength == 0) {
|
||||
String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: No enum configuration found for enum '%s' in %s.",
|
||||
log.debug("Reloading enums config file from {}", enumsConfigFile);
|
||||
Document doc = SafeXMLParsing.parseConfigXML(log, loader, enumsConfigFile);
|
||||
final XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
final XPath xpath = xpathFactory.newXPath();
|
||||
final String xpathStr = String.format(Locale.ROOT, "/enumsConfig/enum[@name='%s']", enumName);
|
||||
final NodeList nodes = (NodeList) xpath.evaluate(xpathStr, doc, XPathConstants.NODESET);
|
||||
final int nodesLength = nodes.getLength();
|
||||
if (nodesLength == 0) {
|
||||
String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: No enum configuration found for enum '%s' in %s.",
|
||||
ftName, enumName, enumsConfigFile);
|
||||
throw new SolrException(SolrException.ErrorCode.NOT_FOUND, exceptionMessage);
|
||||
}
|
||||
if (nodesLength > 1) {
|
||||
if (log.isWarnEnabled())
|
||||
log.warn("{}: More than one enum configuration found for enum '{}' in {}. The last one was taken.",
|
||||
ftName, enumName, enumsConfigFile);
|
||||
}
|
||||
final Node enumNode = nodes.item(nodesLength - 1);
|
||||
final NodeList valueNodes = (NodeList) xpath.evaluate("value", enumNode, XPathConstants.NODESET);
|
||||
for (int i = 0; i < valueNodes.getLength(); i++) {
|
||||
final Node valueNode = valueNodes.item(i);
|
||||
final String valueStr = valueNode.getTextContent();
|
||||
if ((valueStr == null) || (valueStr.length() == 0)) {
|
||||
final String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: A value was defined with an no value in enum '%s' in %s.",
|
||||
ftName, enumName, enumsConfigFile);
|
||||
throw new SolrException(SolrException.ErrorCode.NOT_FOUND, exceptionMessage);
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||
}
|
||||
if (nodesLength > 1) {
|
||||
if (log.isWarnEnabled())
|
||||
log.warn("{}: More than one enum configuration found for enum '{}' in {}. The last one was taken.",
|
||||
ftName, enumName, enumsConfigFile);
|
||||
}
|
||||
final Node enumNode = nodes.item(nodesLength - 1);
|
||||
final NodeList valueNodes = (NodeList) xpath.evaluate("value", enumNode, XPathConstants.NODESET);
|
||||
for (int i = 0; i < valueNodes.getLength(); i++) {
|
||||
final Node valueNode = valueNodes.item(i);
|
||||
final String valueStr = valueNode.getTextContent();
|
||||
if ((valueStr == null) || (valueStr.length() == 0)) {
|
||||
final String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: A value was defined with an no value in enum '%s' in %s.",
|
||||
ftName, enumName, enumsConfigFile);
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||
}
|
||||
if (enumStringToIntMap.containsKey(valueStr)) {
|
||||
final String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: A duplicated definition was found for value '%s' in enum '%s' in %s.",
|
||||
ftName, valueStr, enumName, enumsConfigFile);
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||
}
|
||||
enumIntToStringMap.put(i, valueStr);
|
||||
enumStringToIntMap.put(valueStr, i);
|
||||
if (enumStringToIntMap.containsKey(valueStr)) {
|
||||
final String exceptionMessage = String.format
|
||||
(Locale.ENGLISH, "%s: A duplicated definition was found for value '%s' in enum '%s' in %s.",
|
||||
ftName, valueStr, enumName, enumsConfigFile);
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||
}
|
||||
enumIntToStringMap.put(i, valueStr);
|
||||
enumStringToIntMap.put(valueStr, i);
|
||||
}
|
||||
catch (ParserConfigurationException | XPathExpressionException | SAXException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||
ftName + ": Error parsing enums config.", e);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException | SAXException | XPathExpressionException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||
ftName + ": Error while opening enums config.", e);
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ftName + ": Error while parsing enums config.", e);
|
||||
}
|
||||
|
||||
if ((enumStringToIntMap.size() == 0) || (enumIntToStringMap.size() == 0)) {
|
||||
|
|
|
@ -17,22 +17,21 @@
|
|||
|
||||
package org.apache.solr.schema;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.lucene.analysis.util.ResourceLoader;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.util.SafeXMLParsing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
|
@ -46,6 +45,7 @@ import org.xml.sax.SAXException;
|
|||
*/
|
||||
class FileExchangeRateProvider implements ExchangeRateProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
protected static final String PARAM_CURRENCY_CONFIG = "currencyConfig";
|
||||
|
||||
// Exchange rate map, maps Currency Code -> Currency Code -> Rate
|
||||
|
@ -159,71 +159,49 @@ class FileExchangeRateProvider implements ExchangeRateProvider {
|
|||
|
||||
@Override
|
||||
public boolean reload() throws SolrException {
|
||||
InputStream is = null;
|
||||
Map<String, Map<String, Double>> tmpRates = new HashMap<>();
|
||||
try {
|
||||
log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
|
||||
log.debug("Reloading exchange rates from file {}", currencyConfigFile);
|
||||
|
||||
is = loader.openResource(currencyConfigFile);
|
||||
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
dbf.setXIncludeAware(true);
|
||||
dbf.setNamespaceAware(true);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
|
||||
}
|
||||
try {
|
||||
Document doc = SafeXMLParsing.parseConfigXML(log, loader, currencyConfigFile);
|
||||
XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
XPath xpath = xpathFactory.newXPath();
|
||||
|
||||
try {
|
||||
Document doc = dbf.newDocumentBuilder().parse(is);
|
||||
XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
XPath xpath = xpathFactory.newXPath();
|
||||
// Parse exchange rates.
|
||||
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
|
||||
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node rateNode = nodes.item(i);
|
||||
NamedNodeMap attributes = rateNode.getAttributes();
|
||||
Node from = attributes.getNamedItem("from");
|
||||
Node to = attributes.getNamedItem("to");
|
||||
Node rate = attributes.getNamedItem("rate");
|
||||
|
||||
// Parse exchange rates.
|
||||
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
|
||||
if (from == null || to == null || rate == null) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node rateNode = nodes.item(i);
|
||||
NamedNodeMap attributes = rateNode.getAttributes();
|
||||
Node from = attributes.getNamedItem("from");
|
||||
Node to = attributes.getNamedItem("to");
|
||||
Node rate = attributes.getNamedItem("rate");
|
||||
|
||||
if (from == null || to == null || rate == null) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
|
||||
}
|
||||
|
||||
String fromCurrency = from.getNodeValue();
|
||||
String toCurrency = to.getNodeValue();
|
||||
Double exchangeRate;
|
||||
|
||||
if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
|
||||
}
|
||||
if (null == CurrencyFieldType.getCurrency(toCurrency)) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
|
||||
}
|
||||
|
||||
try {
|
||||
exchangeRate = Double.parseDouble(rate.getNodeValue());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
|
||||
}
|
||||
|
||||
addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
|
||||
String fromCurrency = from.getNodeValue();
|
||||
String toCurrency = to.getNodeValue();
|
||||
Double exchangeRate;
|
||||
|
||||
if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
|
||||
}
|
||||
} catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while opening Currency configuration file "+currencyConfigFile, e);
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
if (null == CurrencyFieldType.getCurrency(toCurrency)) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
try {
|
||||
exchangeRate = Double.parseDouble(rate.getNodeValue());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not parse exchange rate: " + rateNode, e);
|
||||
}
|
||||
|
||||
addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
|
||||
}
|
||||
} catch (SAXException | IOException | XPathExpressionException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error while parsing currency configuration file "+currencyConfigFile, e);
|
||||
}
|
||||
// Atomically swap in the new rates map, if it loaded successfully
|
||||
this.rates = tmpRates;
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.solr.util;
|
||||
|
||||
import java.io.FilterReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.commons.io.input.CloseShieldInputStream;
|
||||
import org.apache.lucene.analysis.util.ResourceLoader;
|
||||
import org.apache.solr.common.EmptyEntityResolver;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.util.SuppressForbidden;
|
||||
import org.apache.solr.common.util.XMLErrorLogger;
|
||||
import org.slf4j.Logger;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Some utility methods for parsing XML in a safe way. This class can be used to parse XML
|
||||
* coming from network (completely untrusted) or it can load a config file from a
|
||||
* {@link ResourceLoader}. In this case it allows external entities and xincludes, but only
|
||||
* referring to files reachable by the loader.
|
||||
*/
|
||||
@SuppressForbidden(reason = "This class uses XML APIs directly that should not be used anywhere else in Solr code")
|
||||
public final class SafeXMLParsing {
|
||||
|
||||
public static final String SYSTEMID_UNTRUSTED = "untrusted://stream";
|
||||
|
||||
private SafeXMLParsing() {}
|
||||
|
||||
/** Parses a config file from ResourceLoader. Xinclude and external entities are enabled, but cannot escape the resource loader. */
|
||||
public static Document parseConfigXML(Logger log, ResourceLoader loader, String file) throws SAXException, IOException {
|
||||
try (InputStream in = loader.openResource(file)) {
|
||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setValidating(false);
|
||||
dbf.setNamespaceAware(true);
|
||||
trySetDOMFeature(dbf, XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
try {
|
||||
dbf.setXIncludeAware(true);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
|
||||
}
|
||||
|
||||
final DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
db.setEntityResolver(new SystemIdResolver(loader));
|
||||
db.setErrorHandler(new XMLErrorLogger(log));
|
||||
return db.parse(in, SystemIdResolver.createSystemIdFromResourceName(file));
|
||||
} catch (ParserConfigurationException pce) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser cannot be configured", pce);
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses the given InputStream as XML, disabling any external entities with secure processing enabled.
|
||||
* The given InputStream is not closed. */
|
||||
public static Document parseUntrustedXML(Logger log, InputStream in) throws SAXException, IOException {
|
||||
return getUntrustedDocumentBuilder(log).parse(new CloseShieldInputStream(in), SYSTEMID_UNTRUSTED);
|
||||
}
|
||||
|
||||
/** Parses the given InputStream as XML, disabling any external entities with secure processing enabled.
|
||||
* The given Reader is not closed. */
|
||||
public static Document parseUntrustedXML(Logger log, Reader reader) throws SAXException, IOException {
|
||||
final InputSource is = new InputSource(new FilterReader(reader) {
|
||||
@Override public void close() {}
|
||||
});
|
||||
is.setSystemId(SYSTEMID_UNTRUSTED);
|
||||
return getUntrustedDocumentBuilder(log).parse(is);
|
||||
}
|
||||
|
||||
public static Document parseUntrustedXML(Logger log, String xml) throws SAXException, IOException {
|
||||
return parseUntrustedXML(log, new StringReader(xml));
|
||||
}
|
||||
|
||||
private static DocumentBuilder getUntrustedDocumentBuilder(Logger log) {
|
||||
try {
|
||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setValidating(false);
|
||||
dbf.setNamespaceAware(true);
|
||||
trySetDOMFeature(dbf, XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
|
||||
final DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
db.setEntityResolver(EmptyEntityResolver.SAX_INSTANCE);
|
||||
db.setErrorHandler(new XMLErrorLogger(log));
|
||||
return db;
|
||||
} catch (ParserConfigurationException pce) {
|
||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser cannot be configured", pce);
|
||||
}
|
||||
}
|
||||
|
||||
private static void trySetDOMFeature(DocumentBuilderFactory factory, String feature, boolean enabled) {
|
||||
try {
|
||||
factory.setFeature(feature, enabled);
|
||||
} catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.solr.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.lucene.analysis.util.ResourceLoader;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
public class TestSafeXMLParsing extends LuceneTestCase {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public void testUntrusted() throws Exception {
|
||||
// TODO: Fix the underlying EmptyEntityResolver to not replace external entities by nothing and instead throw exception:
|
||||
Document doc = SafeXMLParsing.parseUntrustedXML(log, "<!DOCTYPE test [\n" +
|
||||
"<!ENTITY internalTerm \"foobar\">\n" +
|
||||
"<!ENTITY externalTerm SYSTEM \"foo://bar.xyz/external\">\n" +
|
||||
"]>\n" +
|
||||
"<test>&internalTerm;&externalTerm;</test>");
|
||||
assertEquals("foobar", doc.getDocumentElement().getTextContent());
|
||||
}
|
||||
|
||||
InputStream getStringStream(String xml) {
|
||||
return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void testConfig() throws Exception {
|
||||
final ResourceLoader loader = new ResourceLoader() {
|
||||
@Override
|
||||
public InputStream openResource(String resource) throws IOException {
|
||||
switch (resource) {
|
||||
case "source1.xml":
|
||||
return getStringStream("<!DOCTYPE test [\n" +
|
||||
"<!ENTITY externalTerm SYSTEM \"foo://bar.xyz/external\">\n" +
|
||||
"]>\n" +
|
||||
"<test>&externalTerm;</test>");
|
||||
case "source2.xml":
|
||||
return getStringStream("<!DOCTYPE test [\n" +
|
||||
"<!ENTITY externalTerm SYSTEM \"./include1.xml\">\n" +
|
||||
"]>\n" +
|
||||
"<test>&externalTerm;</test>");
|
||||
case "source3.xml":
|
||||
return getStringStream("<foo xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n" +
|
||||
" <xi:include href=\"./include2.xml\"/>\n" +
|
||||
"</foo>");
|
||||
case "include1.xml":
|
||||
return getStringStream("Make XML Great Again!™");
|
||||
case "include2.xml":
|
||||
return getStringStream("<bar>Make XML Great Again!™</bar>");
|
||||
}
|
||||
throw new IOException("Resource not found: " + resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Class<? extends T> findClass(String cname, Class<T> expectedType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T newInstance(String cname, Class<T> expectedType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
IOException ioe = expectThrows(IOException.class, () -> {
|
||||
SafeXMLParsing.parseConfigXML(log, loader, "source1.xml");
|
||||
});
|
||||
assertTrue(ioe.getMessage().contains("Cannot resolve absolute systemIDs"));
|
||||
|
||||
Document doc = SafeXMLParsing.parseConfigXML(log, loader, "source2.xml");
|
||||
assertEquals("Make XML Great Again!™", doc.getDocumentElement().getTextContent());
|
||||
|
||||
doc = SafeXMLParsing.parseConfigXML(log, loader, "source3.xml");
|
||||
assertEquals("Make XML Great Again!™", doc.getDocumentElement().getTextContent().trim());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue