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.
|
* SOLR-12416: When creating a time routed alias, the router.autoDeleteAge option wasn't considered.
|
||||||
(Joachim Sauer via David Smiley)
|
(Joachim Sauer via David Smiley)
|
||||||
|
|
||||||
|
* SOLR-12450: Don't allow referal to external resources in various config files.
|
||||||
|
(Yuyang Xiao, Uwe Schindler)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -16,20 +16,22 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.solr.handler.extraction;
|
package org.apache.solr.handler.extraction;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import java.beans.BeanInfo;
|
import java.beans.BeanInfo;
|
||||||
import java.beans.Introspector;
|
import java.beans.Introspector;
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
import java.beans.PropertyEditor;
|
import java.beans.PropertyEditor;
|
||||||
import java.beans.PropertyEditorManager;
|
import java.beans.PropertyEditorManager;
|
||||||
import java.io.InputStream;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.solr.core.SolrResourceLoader;
|
import org.apache.solr.core.SolrResourceLoader;
|
||||||
|
import org.apache.solr.util.SafeXMLParsing;
|
||||||
import org.apache.tika.parser.ParseContext;
|
import org.apache.tika.parser.ParseContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
@ -37,6 +39,8 @@ import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
public class ParseContextConfig {
|
public class ParseContextConfig {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
private final Map<Class<?>, Object> entries = new HashMap<>();
|
private final Map<Class<?>, Object> entries = new HashMap<>();
|
||||||
|
|
||||||
/** Creates an empty Config without any settings (used as placeholder). */
|
/** 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 {
|
private static Document loadConfigFile(SolrResourceLoader resourceLoader, String parseContextConfigLoc) throws Exception {
|
||||||
try (InputStream in = resourceLoader.openResource(parseContextConfigLoc)) {
|
return SafeXMLParsing.parseConfigXML(log, resourceLoader, parseContextConfigLoc);
|
||||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in, parseContextConfigLoc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extract(Element element, SolrResourceLoader loader) throws Exception {
|
private void extract(Element element, SolrResourceLoader loader) throws Exception {
|
||||||
|
|
|
@ -18,14 +18,12 @@
|
||||||
package org.apache.solr.schema;
|
package org.apache.solr.schema;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
import javax.xml.xpath.XPathExpressionException;
|
import javax.xml.xpath.XPathExpressionException;
|
||||||
|
@ -38,8 +36,10 @@ import org.apache.lucene.search.SortField;
|
||||||
import org.apache.lucene.util.BytesRefBuilder;
|
import org.apache.lucene.util.BytesRefBuilder;
|
||||||
import org.apache.solr.common.EnumFieldValue;
|
import org.apache.solr.common.EnumFieldValue;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.core.SolrResourceLoader;
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
|
import org.apache.solr.util.SafeXMLParsing;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
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.
|
* Abstract Field type for support of string values with custom sort order.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractEnumField extends PrimitiveFieldType {
|
public abstract class AbstractEnumField extends PrimitiveFieldType {
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
|
||||||
protected EnumMapping enumMapping;
|
protected EnumMapping enumMapping;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,6 +68,8 @@ public abstract class AbstractEnumField extends PrimitiveFieldType {
|
||||||
* @lucene.internal
|
* @lucene.internal
|
||||||
*/
|
*/
|
||||||
public static final class EnumMapping {
|
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_ENUMS_CONFIG = "enumsConfig";
|
||||||
public static final String PARAM_ENUM_NAME = "enumName";
|
public static final String PARAM_ENUM_NAME = "enumName";
|
||||||
public static final Integer DEFAULT_VALUE = -1;
|
public static final Integer DEFAULT_VALUE = -1;
|
||||||
|
@ -105,67 +106,49 @@ public abstract class AbstractEnumField extends PrimitiveFieldType {
|
||||||
ftName + ": No enum name was configured.");
|
ftName + ": No enum name was configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream is = null;
|
final SolrResourceLoader loader = schema.getResourceLoader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
is = schema.getResourceLoader().openResource(enumsConfigFile);
|
log.debug("Reloading enums config file from {}", enumsConfigFile);
|
||||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
Document doc = SafeXMLParsing.parseConfigXML(log, loader, enumsConfigFile);
|
||||||
try {
|
final XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||||
final Document doc = dbf.newDocumentBuilder().parse(is);
|
final XPath xpath = xpathFactory.newXPath();
|
||||||
final XPathFactory xpathFactory = XPathFactory.newInstance();
|
final String xpathStr = String.format(Locale.ROOT, "/enumsConfig/enum[@name='%s']", enumName);
|
||||||
final XPath xpath = xpathFactory.newXPath();
|
final NodeList nodes = (NodeList) xpath.evaluate(xpathStr, doc, XPathConstants.NODESET);
|
||||||
final String xpathStr = String.format(Locale.ROOT, "/enumsConfig/enum[@name='%s']", enumName);
|
final int nodesLength = nodes.getLength();
|
||||||
final NodeList nodes = (NodeList) xpath.evaluate(xpathStr, doc, XPathConstants.NODESET);
|
if (nodesLength == 0) {
|
||||||
final int nodesLength = nodes.getLength();
|
String exceptionMessage = String.format
|
||||||
if (nodesLength == 0) {
|
(Locale.ENGLISH, "%s: No enum configuration found for enum '%s' in %s.",
|
||||||
String exceptionMessage = String.format
|
ftName, enumName, enumsConfigFile);
|
||||||
(Locale.ENGLISH, "%s: No enum configuration found for enum '%s' in %s.",
|
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);
|
ftName, enumName, enumsConfigFile);
|
||||||
throw new SolrException(SolrException.ErrorCode.NOT_FOUND, exceptionMessage);
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||||
}
|
}
|
||||||
if (nodesLength > 1) {
|
if (enumStringToIntMap.containsKey(valueStr)) {
|
||||||
if (log.isWarnEnabled())
|
final String exceptionMessage = String.format
|
||||||
log.warn("{}: More than one enum configuration found for enum '{}' in {}. The last one was taken.",
|
(Locale.ENGLISH, "%s: A duplicated definition was found for value '%s' in enum '%s' in %s.",
|
||||||
ftName, enumName, enumsConfigFile);
|
ftName, valueStr, enumName, enumsConfigFile);
|
||||||
}
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, exceptionMessage);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
enumIntToStringMap.put(i, valueStr);
|
||||||
|
enumStringToIntMap.put(valueStr, i);
|
||||||
}
|
}
|
||||||
catch (ParserConfigurationException | XPathExpressionException | SAXException e) {
|
} catch (IOException | SAXException | XPathExpressionException e) {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
|
||||||
ftName + ": Error parsing enums config.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
ftName + ": Error while opening enums config.", e);
|
ftName + ": Error while parsing enums config.", e);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (is != null) {
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((enumStringToIntMap.size() == 0) || (enumIntToStringMap.size() == 0)) {
|
if ((enumStringToIntMap.size() == 0) || (enumIntToStringMap.size() == 0)) {
|
||||||
|
|
|
@ -17,22 +17,21 @@
|
||||||
|
|
||||||
package org.apache.solr.schema;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.lucene.analysis.util.ResourceLoader;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.util.SafeXMLParsing;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
@ -46,6 +45,7 @@ import org.xml.sax.SAXException;
|
||||||
*/
|
*/
|
||||||
class FileExchangeRateProvider implements ExchangeRateProvider {
|
class FileExchangeRateProvider implements ExchangeRateProvider {
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
protected static final String PARAM_CURRENCY_CONFIG = "currencyConfig";
|
protected static final String PARAM_CURRENCY_CONFIG = "currencyConfig";
|
||||||
|
|
||||||
// Exchange rate map, maps Currency Code -> Currency Code -> Rate
|
// Exchange rate map, maps Currency Code -> Currency Code -> Rate
|
||||||
|
@ -159,71 +159,49 @@ class FileExchangeRateProvider implements ExchangeRateProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean reload() throws SolrException {
|
public boolean reload() throws SolrException {
|
||||||
InputStream is = null;
|
|
||||||
Map<String, Map<String, Double>> tmpRates = new HashMap<>();
|
Map<String, Map<String, Double>> tmpRates = new HashMap<>();
|
||||||
try {
|
log.debug("Reloading exchange rates from file {}", currencyConfigFile);
|
||||||
log.debug("Reloading exchange rates from file "+this.currencyConfigFile);
|
|
||||||
|
|
||||||
is = loader.openResource(currencyConfigFile);
|
try {
|
||||||
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
Document doc = SafeXMLParsing.parseConfigXML(log, loader, currencyConfigFile);
|
||||||
try {
|
XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||||
dbf.setXIncludeAware(true);
|
XPath xpath = xpathFactory.newXPath();
|
||||||
dbf.setNamespaceAware(true);
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Parse exchange rates.
|
||||||
Document doc = dbf.newDocumentBuilder().parse(is);
|
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
|
||||||
XPathFactory xpathFactory = XPathFactory.newInstance();
|
|
||||||
XPath xpath = xpathFactory.newXPath();
|
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.
|
if (from == null || to == null || rate == null) {
|
||||||
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < nodes.getLength(); i++) {
|
String fromCurrency = from.getNodeValue();
|
||||||
Node rateNode = nodes.item(i);
|
String toCurrency = to.getNodeValue();
|
||||||
NamedNodeMap attributes = rateNode.getAttributes();
|
Double exchangeRate;
|
||||||
Node from = attributes.getNamedItem("from");
|
|
||||||
Node to = attributes.getNamedItem("to");
|
if (null == CurrencyFieldType.getCurrency(fromCurrency)) {
|
||||||
Node rate = attributes.getNamedItem("rate");
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'from' currency not supported in this JVM: " + fromCurrency);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} catch (SAXException | XPathExpressionException | ParserConfigurationException | IOException e) {
|
if (null == CurrencyFieldType.getCurrency(toCurrency)) {
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error parsing currency config.", e);
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Specified 'to' currency not supported in this JVM: " + toCurrency);
|
||||||
}
|
|
||||||
} 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();
|
|
||||||
}
|
}
|
||||||
} 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
|
// Atomically swap in the new rates map, if it loaded successfully
|
||||||
this.rates = tmpRates;
|
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