This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-text.git
The following commit(s) were added to refs/heads/master by this push:
new 9656bdee Enable secure processing for the XML parser (#729)
9656bdee is described below
commit 9656bdeedc5fde6150b107b3a3eec2ab775b33b3
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Dec 3 06:07:10 2025 -0500
Enable secure processing for the XML parser (#729)
Secure processing for XPath evaluation is already enabled but doesn't
affect XML parsing.
---
.../commons/text/lookup/StringLookupFactory.java | 61 ++++++++---
.../commons/text/lookup/XmlStringLookup.java | 115 ++++++++++++++++-----
.../text/lookup/StringLookupFactoryTest.java | 13 +++
.../commons/text/lookup/XmlStringLookupTest.java | 65 ++++++++++--
.../apache/commons/text/document-entity-ref.xml | 26 +++++
.../org/apache/commons/text/xml-entity.txt | 17 +++
6 files changed, 242 insertions(+), 55 deletions(-)
diff --git
a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
index 2f163b8e..f172f588 100644
--- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
+++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
@@ -30,6 +30,7 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.text.StringSubstitutor;
@@ -1617,10 +1618,19 @@ public final class StringLookupFactory {
* if a lookup causes causes a path to resolve outside of these fences.
Otherwise, the result is unfenced to preserved behavior from previous versions.
* </p>
* <p>
- * We look up the value for the key in the format "DocumentPath:XPath".
+ * We looks up values in an XML document in the format {@code
"[secure=(true|false):]DocumentPath:XPath"}.
* </p>
* <p>
- * For example: "com/domain/document.xml:/path/to/node".
+ * For example:
+ * </p>
+ * <ul>
+ * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=false:com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=true:com/domain/document.xml:/path/to/node"}</li>
+ * </ul>
+ * <p>
+ * Secure processing is enabled by default. The secure boolean String
parsing follows the syntax defined by {@link Boolean#parseBoolean(String)}. The
secure
+ * value in the key overrides instance settings given in the constructor.
* </p>
* <p>
* Using a {@link StringLookup} from the {@link StringLookupFactory}:
@@ -1644,7 +1654,7 @@ public final class StringLookupFactory {
* @since 1.5
*/
public StringLookup xmlStringLookup() {
- return fences != null ?
xmlStringLookup(XmlStringLookup.DEFAULT_FEATURES, fences) :
XmlStringLookup.INSTANCE;
+ return fences != null ?
xmlStringLookup(XmlStringLookup.DEFAULT_XPATH_FEATURES, fences) :
XmlStringLookup.INSTANCE;
}
/**
@@ -1654,10 +1664,19 @@ public final class StringLookupFactory {
* if a lookup causes causes a path to resolve outside of these fences.
Otherwise, the result is unfenced to preserved behavior from previous versions.
* </p>
* <p>
- * We look up the value for the key in the format {@code
"DocumentPath:XPath"}.
+ * We looks up values in an XML document in the format {@code
"[secure=(true|false):]DocumentPath:XPath"}.
* </p>
* <p>
- * For example: {@code "com/domain/document.xml:/path/to/node"}.
+ * For example:
+ * </p>
+ * <ul>
+ * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=false:com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=true:com/domain/document.xml:/path/to/node"}</li>
+ * </ul>
+ * <p>
+ * Secure processing is enabled by default. The secure boolean String
parsing follows the syntax defined by {@link Boolean#parseBoolean(String)}. The
secure
+ * value in the key overrides instance settings given in the constructor.
* </p>
* <p>
* Using a {@link StringLookup} from the {@link StringLookupFactory}:
@@ -1677,13 +1696,14 @@ public final class StringLookupFactory {
* The examples above convert {@code
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML
document.
* </p>
*
- * @param xPathFactoryFeatures XPathFactory features to set.
+ * @param factoryFeatures DocumentBuilderFactory and XPathFactory features
to set.
* @return An XML StringLookup instance.
+ * @see DocumentBuilderFactory#setFeature(String, boolean)
* @see XPathFactory#setFeature(String, boolean)
* @since 1.11.0
*/
- public StringLookup xmlStringLookup(final Map<String, Boolean>
xPathFactoryFeatures) {
- return xmlStringLookup(xPathFactoryFeatures, fences);
+ public StringLookup xmlStringLookup(final Map<String, Boolean>
factoryFeatures) {
+ return xmlStringLookup(factoryFeatures, fences);
}
/**
@@ -1693,10 +1713,19 @@ public final class StringLookupFactory {
* if a lookup causes causes a path to resolve outside of these fences.
Otherwise, the result is unfenced to preserved behavior from previous versions.
* </p>
* <p>
- * We look up the value for the key in the format {@code
"DocumentPath:XPath"}.
+ * We looks up values in an XML document in the format {@code
"[secure=(true|false):]DocumentPath:XPath"}.
* </p>
* <p>
- * For example: {@code "com/domain/document.xml:/path/to/node"}.
+ * For example:
+ * </p>
+ * <ul>
+ * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=false:com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=true:com/domain/document.xml:/path/to/node"}</li>
+ * </ul>
+ * <p>
+ * Secure processing is enabled by default. The secure boolean String
parsing follows the syntax defined by {@link Boolean#parseBoolean(String)}. The
secure
+ * value in the key overrides instance settings given in the constructor.
* </p>
* <p>
* Using a {@link StringLookup} from the {@link StringLookupFactory}
fenced by the current directory ({@code Paths.get("")}):
@@ -1711,10 +1740,8 @@ public final class StringLookupFactory {
*
* <pre>
*
StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("com/domain/document.xml:/path/to/node");
- *
* // throws IllegalArgumentException
*
StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("/rootdir/foo/document.xml:/path/to/node");
- *
* // throws IllegalArgumentException
*
StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("../com/domain/document.xml:/path/to/node");
* </pre>
@@ -1726,12 +1753,14 @@ public final class StringLookupFactory {
* resolves in a fence.
* </p>
*
- * @param xPathFactoryFeatures XPathFactory features to set.
- * @param fences The fences guarding Path resolution.
+ * @param factoryFeatures DocumentBuilderFactory and XPathFactory features
to set.
+ * @param fences The fences guarding Path resolution.
* @return An XML StringLookup instance.
+ * @see DocumentBuilderFactory#setFeature(String, boolean)
+ * @see XPathFactory#setFeature(String, boolean)
* @since 1.12.0
*/
- public StringLookup xmlStringLookup(final Map<String, Boolean>
xPathFactoryFeatures, final Path... fences) {
- return new XmlStringLookup(xPathFactoryFeatures, fences);
+ public StringLookup xmlStringLookup(final Map<String, Boolean>
factoryFeatures, final Path... fences) {
+ return new XmlStringLookup(factoryFeatures, factoryFeatures, fences);
}
}
diff --git a/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
b/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
index a476cc07..aa873a00 100644
--- a/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
@@ -26,38 +26,60 @@ import java.util.Map.Entry;
import java.util.Objects;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang3.StringUtils;
-import org.xml.sax.InputSource;
+import org.w3c.dom.Document;
/**
- * Looks up keys from an XML document.
+ * Looks up values in an XML document in the format {@code
"[secure=(true|false):]DocumentPath:XPath"}.
* <p>
- * Looks up the value for a given key in the format "Document:XPath".
+ * For example:
* </p>
+ * <ul>
+ * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=false:com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=true:com/domain/document.xml:/path/to/node"}</li>
+ * </ul>
* <p>
- * For example: "com/domain/document.xml:/path/to/node".
+ * Secure processing is enabled by default. The secure boolean String parsing
follows the syntax defined by {@link Boolean#parseBoolean(String)}. The secure
+ * value in the key overrides instance settings given in the constructor.
* </p>
*
* @since 1.5
*/
final class XmlStringLookup extends AbstractPathFencedLookup {
+ /**
+ * Minimum number of key parts.
+ */
+ private static final int KEY_PARTS_MIN = 2;
+
+ /**
+ * Minimum number of key parts.
+ */
+ private static final int KEY_PARTS_MAX = 3;
+
/**
* Defines default XPath factory features.
*/
- static final Map<String, Boolean> DEFAULT_FEATURES;
+ static final Map<String, Boolean> DEFAULT_XPATH_FEATURES;
+ /**
+ * Defines default XML factory features.
+ */
+ static final Map<String, Boolean> DEFAULT_XML_FEATURES;
static {
- DEFAULT_FEATURES = new HashMap<>(1);
- DEFAULT_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING,
Boolean.TRUE);
+ DEFAULT_XPATH_FEATURES = new HashMap<>(1);
+ DEFAULT_XPATH_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING,
Boolean.TRUE);
+ DEFAULT_XML_FEATURES = new HashMap<>(1);
+ DEFAULT_XML_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING,
Boolean.TRUE);
}
-
/**
- * Defines the singleton for this class.
+ * Defines the singleton for this class with secure processing enabled.
*/
- static final XmlStringLookup INSTANCE = new
XmlStringLookup(DEFAULT_FEATURES, (Path[]) null);
+ static final XmlStringLookup INSTANCE = new
XmlStringLookup(DEFAULT_XML_FEATURES, DEFAULT_XPATH_FEATURES, (Path[]) null);
/**
* Defines XPath factory features.
@@ -65,23 +87,40 @@ final class XmlStringLookup extends
AbstractPathFencedLookup {
private final Map<String, Boolean> xPathFactoryFeatures;
/**
- * No need to build instances for now.
+ * Defines XML factory features.
+ */
+ private final Map<String, Boolean> xmlFactoryFeatures;
+
+ /**
+ * Constructs a new instance.
*
- * @param xPathFactoryFeatures XPathFactory features to set.
+ * @param xmlFactoryFeatures The {@link DocumentBuilderFactory} features
to set.
+ * @param xPathFactoryFeatures The {@link XPathFactory} features to set.
+ * @see DocumentBuilderFactory#setFeature(String, boolean)
* @see XPathFactory#setFeature(String, boolean)
*/
- XmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures, final
Path... fences) {
+ XmlStringLookup(final Map<String, Boolean> xmlFactoryFeatures, final
Map<String, Boolean> xPathFactoryFeatures, final Path... fences) {
super(fences);
+ this.xmlFactoryFeatures = Objects.requireNonNull(xmlFactoryFeatures,
"xmlFactoryFeatures");
this.xPathFactoryFeatures =
Objects.requireNonNull(xPathFactoryFeatures, "xPathFfactoryFeatures");
}
/**
- * Looks up the value for the key in the format "DocumentPath:XPath".
+ * Looks up a value for the key in the format {@code
"[secure=(true|false):]DocumentPath:XPath"}.
+ * <p>
+ * For example:
+ * </p>
+ * <ul>
+ * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=false:com/domain/document.xml:/path/to/node"}</li>
+ * <li>{@code "secure=true:com/domain/document.xml:/path/to/node"}</li>
+ * </ul>
* <p>
- * For example: "com/domain/document.xml:/path/to/node".
+ * Secure processing is enabled by default. The secure boolean String
parsing follows the syntax defined by {@link Boolean#parseBoolean(String)}. The
secure
+ * value in the key overrides instance settings given in the constructor.
* </p>
*
- * @param key the key to be looked up, may be null
+ * @param key the key to be looked up, may be null.
* @return The value associated with the key.
*/
@Override
@@ -91,22 +130,42 @@ final class XmlStringLookup extends
AbstractPathFencedLookup {
}
final String[] keys = key.split(SPLIT_STR);
final int keyLen = keys.length;
- if (keyLen != 2) {
- throw IllegalArgumentExceptions.format("Bad XML key format [%s];
expected format is DocumentPath:XPath.",
- key);
+ if (keyLen != KEY_PARTS_MIN && keyLen != KEY_PARTS_MAX) {
+ throw IllegalArgumentExceptions.format("Bad XML key format '%s';
the expected format is [secure=(true|false):]DocumentPath:XPath.", key);
}
- final String documentPath = keys[0];
- final String xpath = StringUtils.substringAfter(key, SPLIT_CH);
- try (InputStream inputStream =
Files.newInputStream(getPath(documentPath))) {
- final XPathFactory factory = XPathFactory.newInstance();
- for (final Entry<String, Boolean> p :
xPathFactoryFeatures.entrySet()) {
- factory.setFeature(p.getKey(), p.getValue());
+ final boolean isKeySecure = keyLen == KEY_PARTS_MAX;
+ final Boolean secure = isKeySecure ? parseSecureKey(keys, key) : null;
+ final String documentPath = isKeySecure ? keys[1] : keys[0];
+ final String xpath = StringUtils.substringAfterLast(key, SPLIT_CH);
+ final DocumentBuilderFactory dbFactory =
DocumentBuilderFactory.newInstance();
+ try {
+ for (final Entry<String, Boolean> p :
xmlFactoryFeatures.entrySet()) {
+ dbFactory.setFeature(p.getKey(), p.getValue());
+ }
+ if (secure != null) {
+ dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,
secure.booleanValue());
+ }
+ try (InputStream inputStream =
Files.newInputStream(getPath(documentPath))) {
+ final Document doc =
dbFactory.newDocumentBuilder().parse(inputStream);
+ final XPathFactory factory = XPathFactory.newInstance();
+ for (final Entry<String, Boolean> p :
xPathFactoryFeatures.entrySet()) {
+ factory.setFeature(p.getKey(), p.getValue());
+ }
+ if (secure != null) {
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,
secure.booleanValue());
+ }
+ return factory.newXPath().evaluate(xpath, doc);
}
- return factory.newXPath().evaluate(xpath, new
InputSource(inputStream));
} catch (final Exception e) {
- throw IllegalArgumentExceptions.format(e, "Error looking up XML
document [%s] and XPath [%s].",
- documentPath, xpath);
+ throw new IllegalArgumentException(e);
}
}
+ private Boolean parseSecureKey(final String[] args, final String key) {
+ final String[] secParts = args[0].split("=");
+ if (secParts.length != 2 && !Objects.equals(secParts[0], "secure")) {
+ throw IllegalArgumentExceptions.format("Bad XML key format '%s';
the expected format is [secure=(true|false):]DocumentPath:XPath.", key);
+ }
+ return Boolean.valueOf(secParts[1]);
+ }
}
diff --git
a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java
b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java
index 33897a74..5ffe49f3 100644
--- a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java
@@ -282,4 +282,17 @@ class StringLookupFactoryTest {
XmlStringLookupTest.assertLookup(stringLookupFactory.xmlStringLookup(features));
XmlStringLookupTest.assertLookup(stringLookupFactory.xmlStringLookup(new
HashMap<>()));
}
+
+ @Test
+ void testXmlStringLookupExternalEntityOff() {
+ assertThrows(IllegalArgumentException.class,
+ () ->
StringLookupFactory.INSTANCE.xmlStringLookup().apply(XmlStringLookupTest.DOC_DIR
+ "document-entity-ref.xml:/document/content"));
+ }
+
+ @Test
+ void testXmlStringLookupExternalEntityOn() {
+ final String key = XmlStringLookupTest.DOC_DIR +
"document-entity-ref.xml:/document/content";
+ assertEquals(XmlStringLookupTest.DATA,
StringLookupFactory.INSTANCE.xmlStringLookup(XmlStringLookupTest.EMPTY_MAP).apply(key).trim());
+ }
+
}
diff --git
a/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
b/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
index 40290f0b..d24c4090 100644
--- a/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
@@ -26,11 +26,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Map;
import javax.xml.XMLConstants;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringSubstitutor;
import org.junit.jupiter.api.Test;
/**
@@ -38,15 +41,18 @@ import org.junit.jupiter.api.Test;
*/
class XmlStringLookupTest {
+ static final String DATA = "Hello World!";
+ static final Map<String, Boolean> EMPTY_MAP = Collections.emptyMap();
private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); //
NOT "."
private static final Path ABSENT_PATH = Paths.get("does not exist at all");
- private static final String DOC_RELATIVE =
"src/test/resources/org/apache/commons/text/document.xml";
+ static final String DOC_DIR =
"src/test/resources/org/apache/commons/text/";
+ private static final String DOC_RELATIVE = DOC_DIR + "document.xml";
private static final String DOC_ROOT = "/document.xml";
static void assertLookup(final StringLookup xmlStringLookup) {
assertNotNull(xmlStringLookup);
assertInstanceOf(XmlStringLookup.class, xmlStringLookup);
- assertEquals("Hello World!", xmlStringLookup.apply(DOC_RELATIVE +
":/root/path/to/node"));
+ assertEquals(DATA, xmlStringLookup.apply(DOC_RELATIVE +
":/root/path/to/node"));
assertNull(xmlStringLookup.apply(null));
}
@@ -55,6 +61,44 @@ class XmlStringLookupTest {
assertThrows(IllegalArgumentException.class, () ->
XmlStringLookup.INSTANCE.apply("docName"));
}
+ @Test
+ void testExternalEntityOff() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new
XmlStringLookup(XmlStringLookup.DEFAULT_XML_FEATURES, EMPTY_MAP).apply(DOC_DIR
+ "document-entity-ref.xml:/document/content"));
+ }
+
+ @Test
+ void testExternalEntityOn() {
+ final String key = DOC_DIR +
"document-entity-ref.xml:/document/content";
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP,
EMPTY_MAP).apply(key).trim());
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP,
XmlStringLookup.DEFAULT_XPATH_FEATURES).apply(key).trim());
+ }
+
+ @Test
+ void testInterpolatorExternalEntityOff() {
+ final StringSubstitutor stringSubstitutor =
StringSubstitutor.createInterpolator();
+ assertThrows(IllegalArgumentException.class, () ->
stringSubstitutor.replace("${xml:" + DOC_DIR +
"document-entity-ref.xml:/document/content}"));
+ }
+
+ @Test
+ void testInterpolatorExternalEntityOffOverride() {
+ final StringSubstitutor stringSubstitutor =
StringSubstitutor.createInterpolator();
+ assertEquals(DATA, stringSubstitutor.replace("${xml:secure=false:" +
DOC_DIR + "document-entity-ref.xml:/document/content}").trim());
+ }
+
+ @Test
+ void testInterpolatorExternalEntityOn() {
+ final StringSubstitutor stringSubstitutor =
StringSubstitutor.createInterpolator();
+ assertThrows(IllegalArgumentException.class, () ->
stringSubstitutor.replace("${xml:" + DOC_DIR +
"document-entity-ref.xml:/document/content}"));
+ }
+
+ @Test
+ void testInterpolatorExternalEntityOnOverride() {
+ final StringSubstitutor stringSubstitutor =
StringSubstitutor.createInterpolator();
+ assertThrows(IllegalArgumentException.class,
+ () -> stringSubstitutor.replace("${xml:secure=true:" + DOC_DIR
+ "document-entity-ref.xml:/document/content}"));
+ }
+
@Test
void testMissingXPath() {
assertThrows(IllegalArgumentException.class, () ->
XmlStringLookup.INSTANCE.apply(DOC_RELATIVE + ":!JUNK!"));
@@ -63,20 +107,20 @@ class XmlStringLookupTest {
@Test
void testNoFeatures() {
final String xpath = "/root/path/to/node";
- assertEquals("Hello World!", new XmlStringLookup(new
HashMap<>()).apply(DOC_RELATIVE + ":" + xpath));
- assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(),
CURRENT_PATH).apply(DOC_RELATIVE + ":" + xpath));
- assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(),
CURRENT_PATH, ABSENT_PATH).apply(DOC_RELATIVE + ":" + xpath));
- assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(),
ABSENT_PATH, CURRENT_PATH).apply(DOC_RELATIVE + ":" + xpath));
- assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(new HashMap<>(), ABSENT_PATH).apply(DOC_ROOT + ":" + xpath));
- assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(new HashMap<>(), CURRENT_PATH).apply(DOC_ROOT + ":" + xpath));
- assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(new HashMap<>(), ABSENT_PATH, CURRENT_PATH).apply(DOC_ROOT +
":" + xpath));
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP,
EMPTY_MAP).apply(DOC_RELATIVE + ":" + xpath));
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP,
EMPTY_MAP).apply(DOC_RELATIVE + ":" + xpath));
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP, EMPTY_MAP,
CURRENT_PATH, ABSENT_PATH).apply(DOC_RELATIVE + ":" + xpath));
+ assertEquals(DATA, new XmlStringLookup(EMPTY_MAP, EMPTY_MAP,
ABSENT_PATH, CURRENT_PATH).apply(DOC_RELATIVE + ":" + xpath));
+ assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(EMPTY_MAP, EMPTY_MAP, ABSENT_PATH).apply(DOC_ROOT + ":" +
xpath));
+ assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(EMPTY_MAP, EMPTY_MAP, CURRENT_PATH).apply(DOC_ROOT + ":" +
xpath));
+ assertThrows(IllegalArgumentException.class, () -> new
XmlStringLookup(EMPTY_MAP, EMPTY_MAP, ABSENT_PATH, CURRENT_PATH).apply(DOC_ROOT
+ ":" + xpath));
}
@Test
void testNoFeaturesDefault() {
final HashMap<String, Boolean> features = new HashMap<>(1);
features.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
- assertLookup(new XmlStringLookup(features));
+ assertLookup(new XmlStringLookup(EMPTY_MAP, features));
}
@Test
@@ -94,5 +138,4 @@ class XmlStringLookupTest {
// does not blow up and gives some kind of string.
assertFalse(XmlStringLookup.INSTANCE.toString().isEmpty());
}
-
}
diff --git a/src/test/resources/org/apache/commons/text/document-entity-ref.xml
b/src/test/resources/org/apache/commons/text/document-entity-ref.xml
new file mode 100644
index 00000000..bfd36098
--- /dev/null
+++ b/src/test/resources/org/apache/commons/text/document-entity-ref.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ 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.
+ -->
+<!DOCTYPE document [
+ <!ENTITY ext SYSTEM
"src/test/resources/org/apache/commons/text/xml-entity.txt">
+]>
+<document>
+ <title>Example of an External Entity</title>
+ <content>
+ &ext;
+ </content>
+</document>
diff --git a/src/test/resources/org/apache/commons/text/xml-entity.txt
b/src/test/resources/org/apache/commons/text/xml-entity.txt
new file mode 100644
index 00000000..4f2fa758
--- /dev/null
+++ b/src/test/resources/org/apache/commons/text/xml-entity.txt
@@ -0,0 +1,17 @@
+<!--
+ 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.
+ -->
+Hello World!