This is an automated email from the ASF dual-hosted git repository.
bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 3eb4059f544 NIFI-16452 - Add configurable XML entity-size limits to
EvaluateXPath (#11799)
3eb4059f544 is described below
commit 3eb4059f544c766be0f148203eb0d3b7ede1d692
Author: Pierre Villard <[email protected]>
AuthorDate: Mon Oct 12 19:52:55 2026 +0200
NIFI-16452 - Add configurable XML entity-size limits to EvaluateXPath
(#11799)
---
.../parsers/StandardDocumentProvider.java | 38 ++++++
.../parsers/StandardDocumentProviderTest.java | 75 +++++++++++
.../nifi/processors/standard/EvaluateXPath.java | 29 +++-
.../processors/standard/TestEvaluateXPath.java | 147 +++++++++++++++++++++
4 files changed, 288 insertions(+), 1 deletion(-)
diff --git
a/nifi-commons/nifi-xml-processing/src/main/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProvider.java
b/nifi-commons/nifi-xml-processing/src/main/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProvider.java
index 3abe3c6cf8c..3f3fbc02220 100644
---
a/nifi-commons/nifi-xml-processing/src/main/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProvider.java
+++
b/nifi-commons/nifi-xml-processing/src/main/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProvider.java
@@ -35,12 +35,20 @@ import javax.xml.validation.Schema;
* Standard implementation of Document Provider with secure processing enabled
*/
public class StandardDocumentProvider implements DocumentProvider {
+ private static final String MAXIMUM_GENERAL_ENTITY_SIZE_LIMIT =
"jdk.xml.maxGeneralEntitySizeLimit";
+
+ private static final String MAXIMUM_TOTAL_ENTITY_SIZE_LIMIT =
"jdk.xml.totalEntitySizeLimit";
+
private boolean namespaceAware;
private Schema schema;
private ErrorHandler errorHandler;
+ private Integer maximumGeneralEntitySizeLimit;
+
+ private Integer maximumTotalEntitySizeLimit;
+
/**
* Set Error Handler
*
@@ -68,6 +76,24 @@ public class StandardDocumentProvider implements
DocumentProvider {
this.schema = schema;
}
+ /**
+ * Set maximum size limit for a single general entity or null to use the
effective JVM XML processing configuration
+ *
+ * @param maximumGeneralEntitySizeLimit maximum general entity size limit
+ */
+ public void setMaximumGeneralEntitySizeLimit(final Integer
maximumGeneralEntitySizeLimit) {
+ this.maximumGeneralEntitySizeLimit = maximumGeneralEntitySizeLimit;
+ }
+
+ /**
+ * Set maximum cumulative size limit for all entities or null to use the
effective JVM XML processing configuration
+ *
+ * @param maximumTotalEntitySizeLimit maximum total entity size limit
+ */
+ public void setMaximumTotalEntitySizeLimit(final Integer
maximumTotalEntitySizeLimit) {
+ this.maximumTotalEntitySizeLimit = maximumTotalEntitySizeLimit;
+ }
+
@Override
public Document newDocument() {
final DocumentBuilderFactory documentBuilderFactory =
getDocumentBuilderFactory();
@@ -75,6 +101,7 @@ public class StandardDocumentProvider implements
DocumentProvider {
try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,
ProcessingFeature.SECURE_PROCESSING.isEnabled());
documentBuilderFactory.setFeature(ProcessingFeature.DISALLOW_DOCTYPE_DECL.getFeature(),
ProcessingFeature.DISALLOW_DOCTYPE_DECL.isEnabled());
+ configureEntitySizeLimits(documentBuilderFactory);
final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
documentBuilder.setErrorHandler(errorHandler);
@@ -98,6 +125,7 @@ public class StandardDocumentProvider implements
DocumentProvider {
try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,
ProcessingFeature.SECURE_PROCESSING.isEnabled());
documentBuilderFactory.setFeature(ProcessingFeature.DISALLOW_DOCTYPE_DECL.getFeature(),
isDisallowDocumentTypeDeclaration());
+ configureEntitySizeLimits(documentBuilderFactory);
final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
documentBuilder.setErrorHandler(errorHandler);
@@ -123,4 +151,14 @@ public class StandardDocumentProvider implements
DocumentProvider {
return documentBuilderFactory;
}
+
+ private void configureEntitySizeLimits(final DocumentBuilderFactory
documentBuilderFactory) {
+ if (maximumGeneralEntitySizeLimit != null) {
+
documentBuilderFactory.setAttribute(MAXIMUM_GENERAL_ENTITY_SIZE_LIMIT,
maximumGeneralEntitySizeLimit.toString());
+ }
+
+ if (maximumTotalEntitySizeLimit != null) {
+
documentBuilderFactory.setAttribute(MAXIMUM_TOTAL_ENTITY_SIZE_LIMIT,
maximumTotalEntitySizeLimit.toString());
+ }
+ }
}
diff --git
a/nifi-commons/nifi-xml-processing/src/test/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProviderTest.java
b/nifi-commons/nifi-xml-processing/src/test/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProviderTest.java
index e770dcca447..90c41df7aaf 100644
---
a/nifi-commons/nifi-xml-processing/src/test/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProviderTest.java
+++
b/nifi-commons/nifi-xml-processing/src/test/java/org/apache/nifi/xml/processing/parsers/StandardDocumentProviderTest.java
@@ -26,11 +26,14 @@ import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import javax.xml.validation.Schema;
import javax.xml.validation.ValidatorHandler;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -64,11 +67,16 @@ class StandardDocumentProviderTest {
final Document document = provider.parse(inputStream);
assertNotNull(document);
}
+
+ assertEscapedCharacterReferenceLimitBehavior(new
StandardDocumentProvider());
+ assertEscapedCharacterReferenceLimitBehavior(new
DocumentTypeAllowedStandardDocumentProvider());
}
@Test
void testParseDocumentTypeDeclarationException() throws IOException {
final StandardDocumentProvider provider = new
StandardDocumentProvider();
+ provider.setMaximumGeneralEntitySizeLimit(1024);
+ provider.setMaximumTotalEntitySizeLimit(1024);
try (final InputStream inputStream =
ResourceProvider.getStandardDocumentDocType()) {
assertParsingException(inputStream, provider);
@@ -78,6 +86,8 @@ class StandardDocumentProviderTest {
@Test
void testParseExternalEntityException() throws IOException {
final StandardDocumentProvider provider = new
StandardDocumentProvider();
+ provider.setMaximumGeneralEntitySizeLimit(1024);
+ provider.setMaximumTotalEntitySizeLimit(1024);
assertParsingException(provider);
}
@@ -90,6 +100,8 @@ class StandardDocumentProviderTest {
provider.setNamespaceAware(true);
provider.setSchema(schema);
provider.setErrorHandler(errorHandler);
+ provider.setMaximumGeneralEntitySizeLimit(1024);
+ provider.setMaximumTotalEntitySizeLimit(1024);
assertParsingException(provider);
}
@@ -104,4 +116,67 @@ class StandardDocumentProviderTest {
final ProcessingException processingException =
assertThrows(ProcessingException.class, () -> provider.parse(inputStream));
assertInstanceOf(SAXParseException.class,
processingException.getCause());
}
+
+ private void assertEscapedCharacterReferenceLimitBehavior(final
StandardDocumentProvider provider) {
+ provider.setMaximumGeneralEntitySizeLimit(64);
+
+ ProcessingException processingException =
assertThrows(ProcessingException.class,
+ () ->
provider.parse(getEscapedCharacterReferenceDocument(128)));
+ assertInstanceOf(SAXParseException.class,
processingException.getCause());
+
+ provider.setMaximumGeneralEntitySizeLimit(null);
+
+ Document document =
provider.parse(getEscapedCharacterReferenceDocument(128));
+ assertEquals(getEscapedCharacterReferenceValue(128),
document.getDocumentElement().getElementsByTagName("subNode").item(0).getAttributes().getNamedItem("value").getNodeValue());
+
+ provider.setMaximumTotalEntitySizeLimit(64);
+
+ processingException = assertThrows(ProcessingException.class,
+ () ->
provider.parse(getEscapedCharacterReferenceDocument(128)));
+ assertInstanceOf(SAXParseException.class,
processingException.getCause());
+
+ provider.setMaximumTotalEntitySizeLimit(null);
+
+ document = provider.parse(getEscapedCharacterReferenceDocument(128));
+ assertEquals(getEscapedCharacterReferenceValue(128),
document.getDocumentElement().getElementsByTagName("subNode").item(0).getAttributes().getNamedItem("value").getNodeValue());
+ }
+
+ private InputStream getEscapedCharacterReferenceDocument(final int
characterLength) {
+ final String document = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <standard>
+ <subNode value="%s"/>
+ </standard>
+
""".formatted(getEscapedCharacterReferenceMarkup(characterLength));
+ return new
ByteArrayInputStream(document.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private String getEscapedCharacterReferenceMarkup(final int
characterLength) {
+ final String[] escapedCharacters = {"<", ">", "&"};
+ final StringBuilder builder = new StringBuilder(characterLength * 4);
+
+ for (int i = 0; i < characterLength; i++) {
+ builder.append(escapedCharacters[i % escapedCharacters.length]);
+ }
+
+ return builder.toString();
+ }
+
+ private String getEscapedCharacterReferenceValue(final int
characterLength) {
+ final char[] expandedCharacters = {'<', '>', '&'};
+ final StringBuilder builder = new StringBuilder(characterLength);
+
+ for (int i = 0; i < characterLength; i++) {
+ builder.append(expandedCharacters[i % expandedCharacters.length]);
+ }
+
+ return builder.toString();
+ }
+
+ private static class DocumentTypeAllowedStandardDocumentProvider extends
StandardDocumentProvider {
+ @Override
+ protected boolean isDisallowDocumentTypeDeclaration() {
+ return false;
+ }
+ }
}
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EvaluateXPath.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EvaluateXPath.java
index a76ab9c9f7a..e28727a77c1 100644
---
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EvaluateXPath.java
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EvaluateXPath.java
@@ -43,6 +43,7 @@ import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
import
org.apache.nifi.processors.standard.xml.DocumentTypeAllowedDocumentProvider;
import org.apache.nifi.xml.processing.ProcessingException;
import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
@@ -131,10 +132,32 @@ public class EvaluateXPath extends AbstractProcessor {
.defaultValue("false")
.build();
+ public static final PropertyDescriptor MAXIMUM_GENERAL_ENTITY_SIZE = new
PropertyDescriptor.Builder()
+ .name("Maximum General Entity Size")
+ .description("""
+ The maximum number of characters permitted in a single
general entity. \
+ When not set, the effective JVM XML processing
configuration is used. \
+ Increasing this limit can increase memory consumption when
parsing XML with entities.""")
+ .required(false)
+ .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
+ .build();
+
+ public static final PropertyDescriptor MAXIMUM_TOTAL_ENTITY_SIZE = new
PropertyDescriptor.Builder()
+ .name("Maximum Total Entity Size")
+ .description("""
+ The maximum cumulative number of characters permitted
across all entities. \
+ When not set, the effective JVM XML processing
configuration is used. \
+ Increasing this limit can increase memory consumption when
parsing XML with entities.""")
+ .required(false)
+ .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
+ .build();
+
private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS =
List.of(
DESTINATION,
RETURN_TYPE,
- VALIDATE_DTD
+ VALIDATE_DTD,
+ MAXIMUM_GENERAL_ENTITY_SIZE,
+ MAXIMUM_TOTAL_ENTITY_SIZE
);
public static final Relationship REL_MATCH = new Relationship.Builder()
@@ -261,6 +284,8 @@ public class EvaluateXPath extends AbstractProcessor {
}
final boolean validatingDeclaration =
context.getProperty(VALIDATE_DTD).asBoolean();
+ final Integer maximumGeneralEntitySize =
context.getProperty(MAXIMUM_GENERAL_ENTITY_SIZE).asInteger();
+ final Integer maximumTotalEntitySize =
context.getProperty(MAXIMUM_TOTAL_ENTITY_SIZE).asInteger();
final StandardTransformProvider transformProvider = new
StandardTransformProvider();
transformProvider.setIndent(true);
@@ -275,6 +300,8 @@ public class EvaluateXPath extends AbstractProcessor {
final StandardDocumentProvider documentProvider =
validatingDeclaration
? new DocumentTypeAllowedDocumentProvider()
: new StandardDocumentProvider();
+
documentProvider.setMaximumGeneralEntitySizeLimit(maximumGeneralEntitySize);
+
documentProvider.setMaximumTotalEntitySizeLimit(maximumTotalEntitySize);
final Document document = documentProvider.parse(in);
sourceRef.set(new DOMSource(document));
}
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEvaluateXPath.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEvaluateXPath.java
index d88ac43e849..f5d2926616b 100644
---
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEvaluateXPath.java
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEvaluateXPath.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.processors.standard;
+import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.PropertyMigrationResult;
import org.apache.nifi.util.TestRunner;
@@ -35,6 +36,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestEvaluateXPath {
+ private static final int LARGE_ESCAPED_CHARACTER_REFERENCE_LENGTH =
100_001;
+
+ private static final int HIGH_ENTITY_SIZE_LIMIT = 200_000;
+
+ private static final String ESCAPED_ENTITY_XPATH =
"string-length(/*:bundle/node/subNode[1]/@value)";
+
+ private static final String ESCAPED_VALUE_XPATH =
"string(/*:bundle/node/subNode[1]/@value)";
+
private static final Path XML_SNIPPET =
Paths.get("src/test/resources/TestXml/xml-snippet.xml");
private static final Path XML_SNIPPET_EMBEDDED_DOCTYPE =
Paths.get("src/test/resources/TestXml/xml-snippet-embedded-doctype.xml");
private static final Path XML_SNIPPET_NONEXISTENT_DOCTYPE =
Paths.get("src/test/resources/TestXml/xml-snippet-external-doctype.xml");
@@ -172,6 +181,9 @@ public class TestEvaluateXPath {
testRunner.assertAllFlowFilesTransferred(EvaluateXPath.REL_MATCH, 1);
final MockFlowFile out =
testRunner.getFlowFilesForRelationship(EvaluateXPath.REL_MATCH).getFirst();
out.assertContentEquals("Hello");
+
+ assertEntitySizeLimitConfigurationAcrossDocumentProviderPaths();
+ assertLargeEscapedCharacterReferencePayloadWithConfiguredLimits();
}
@Test
@@ -179,6 +191,8 @@ public class TestEvaluateXPath {
testRunner.setProperty(EvaluateXPath.DESTINATION,
EvaluateXPath.DESTINATION_CONTENT);
testRunner.setProperty(EvaluateXPath.RETURN_TYPE,
EvaluateXPath.RETURN_TYPE_STRING);
testRunner.setProperty(EvaluateXPath.VALIDATE_DTD, "false");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
"1024");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
"1024");
testRunner.setProperty("some.property",
"/*:bundle/node/subNode[1]/value/text()");
testRunner.enqueue(XML_SNIPPET_EMBEDDED_DOCTYPE);
@@ -191,6 +205,9 @@ public class TestEvaluateXPath {
public void testFailureForExternalDocTypeWithDocTypeValidationEnabled()
throws IOException {
testRunner.setProperty(EvaluateXPath.DESTINATION,
EvaluateXPath.DESTINATION_CONTENT);
testRunner.setProperty(EvaluateXPath.RETURN_TYPE,
EvaluateXPath.RETURN_TYPE_STRING);
+ testRunner.setProperty(EvaluateXPath.VALIDATE_DTD, "true");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
"1024");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
"1024");
testRunner.setProperty("some.property",
"/*:bundle/node/subNode[1]/value/text()");
testRunner.enqueue(XML_SNIPPET_NONEXISTENT_DOCTYPE);
@@ -204,6 +221,8 @@ public class TestEvaluateXPath {
testRunner.setProperty(EvaluateXPath.DESTINATION,
EvaluateXPath.DESTINATION_CONTENT);
testRunner.setProperty(EvaluateXPath.RETURN_TYPE,
EvaluateXPath.RETURN_TYPE_STRING);
testRunner.setProperty(EvaluateXPath.VALIDATE_DTD, "false");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
"1024");
+ testRunner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
"1024");
testRunner.setProperty("some.property",
"/*:bundle/node/subNode[1]/value/text()");
testRunner.enqueue(XML_SNIPPET_NONEXISTENT_DOCTYPE);
@@ -219,4 +238,132 @@ public class TestEvaluateXPath {
final PropertyMigrationResult propertyMigrationResult =
testRunner.migrateProperties();
assertEquals(expectedRenamed,
propertyMigrationResult.getPropertiesRenamed());
}
+
+ private void
assertEntitySizeLimitConfigurationAcrossDocumentProviderPaths() {
+ final byte[] payload = getEscapedCharacterReferenceXml(128);
+ final String expectedContent = getEscapedCharacterReferenceValue(128);
+ final TestRunner disallowDtdRunner = newEntityLimitTestRunner(false,
ESCAPED_VALUE_XPATH);
+ final TestRunner allowDtdRunner = newEntityLimitTestRunner(true,
ESCAPED_VALUE_XPATH);
+
+ assertInvalidEntitySizeProperties(disallowDtdRunner);
+ assertInvalidEntitySizeProperties(allowDtdRunner);
+
+
disallowDtdRunner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE, "32");
+ assertFailure(disallowDtdRunner, payload);
+ assertMatch(allowDtdRunner, payload, expectedContent);
+
+
disallowDtdRunner.removeProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE);
+ assertMatch(disallowDtdRunner, payload, expectedContent);
+
+ allowDtdRunner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
"32");
+ assertFailure(allowDtdRunner, payload);
+
allowDtdRunner.removeProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE);
+ assertMatch(allowDtdRunner, payload, expectedContent);
+
+ allowDtdRunner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
"32");
+ assertFailure(allowDtdRunner, payload);
+ assertMatch(disallowDtdRunner, payload, expectedContent);
+
+ allowDtdRunner.removeProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE);
+ assertMatch(allowDtdRunner, payload, expectedContent);
+
+ disallowDtdRunner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
"32");
+ assertFailure(disallowDtdRunner, payload);
+
disallowDtdRunner.removeProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE);
+ assertMatch(disallowDtdRunner, payload, expectedContent);
+ }
+
+ private void
assertLargeEscapedCharacterReferencePayloadWithConfiguredLimits() {
+ final byte[] payload =
getEscapedCharacterReferenceXml(LARGE_ESCAPED_CHARACTER_REFERENCE_LENGTH);
+
+ assertLargeEscapedCharacterReferenceBehavior(false, payload);
+ assertLargeEscapedCharacterReferenceBehavior(true, payload);
+ }
+
+ private void assertLargeEscapedCharacterReferenceBehavior(final boolean
allowDtd, final byte[] payload) {
+ final TestRunner runner = newEntityLimitTestRunner(allowDtd,
ESCAPED_ENTITY_XPATH);
+
+ runner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
Integer.toString(LARGE_ESCAPED_CHARACTER_REFERENCE_LENGTH - 1));
+ runner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
Integer.toString(LARGE_ESCAPED_CHARACTER_REFERENCE_LENGTH - 1));
+ assertFailure(runner, payload);
+
+ runner.setProperty(EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
Integer.toString(HIGH_ENTITY_SIZE_LIMIT));
+ runner.setProperty(EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE,
Integer.toString(HIGH_ENTITY_SIZE_LIMIT));
+ assertMatch(runner, payload,
Integer.toString(LARGE_ESCAPED_CHARACTER_REFERENCE_LENGTH));
+ }
+
+ private void assertInvalidEntitySizeProperties(final TestRunner runner) {
+ final PropertyDescriptor[] descriptors = {
+ EvaluateXPath.MAXIMUM_GENERAL_ENTITY_SIZE,
+ EvaluateXPath.MAXIMUM_TOTAL_ENTITY_SIZE
+ };
+ final String[] invalidValues = {"0", "-1", "abc", "2147483648"};
+
+ for (final PropertyDescriptor descriptor : descriptors) {
+ for (final String invalidValue : invalidValues) {
+ runner.setProperty(descriptor, invalidValue);
+ runner.assertNotValid();
+ runner.removeProperty(descriptor);
+ runner.assertValid();
+ }
+ }
+ }
+
+ private TestRunner newEntityLimitTestRunner(final boolean allowDtd, final
String xpathExpression) {
+ final TestRunner runner = TestRunners.newTestRunner(new
EvaluateXPath());
+ runner.setProperty(EvaluateXPath.DESTINATION,
EvaluateXPath.DESTINATION_CONTENT);
+ runner.setProperty(EvaluateXPath.RETURN_TYPE,
EvaluateXPath.RETURN_TYPE_STRING);
+ runner.setProperty(EvaluateXPath.VALIDATE_DTD,
Boolean.toString(allowDtd));
+ runner.setProperty("some.property", xpathExpression);
+ return runner;
+ }
+
+ private void assertFailure(final TestRunner runner, final byte[] payload) {
+ runner.enqueue(payload);
+ runner.run();
+ runner.assertTransferCount(EvaluateXPath.REL_FAILURE, 1);
+ runner.clearTransferState();
+ }
+
+ private void assertMatch(final TestRunner runner, final byte[] payload,
final String expectedContent) {
+ runner.enqueue(payload);
+ runner.run();
+ runner.assertTransferCount(EvaluateXPath.REL_MATCH, 1);
+
runner.getFlowFilesForRelationship(EvaluateXPath.REL_MATCH).getFirst().assertContentEquals(expectedContent);
+ runner.clearTransferState();
+ }
+
+ private byte[] getEscapedCharacterReferenceXml(final int characterLength) {
+ final String document = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <bundle>
+ <node>
+ <subNode value="%s"/>
+ </node>
+ </bundle>
+
""".formatted(getEscapedCharacterReferenceMarkup(characterLength));
+ return document.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private String getEscapedCharacterReferenceMarkup(final int
characterLength) {
+ final String[] escapedCharacters = {"<", ">", "&"};
+ final StringBuilder builder = new StringBuilder(characterLength * 4);
+
+ for (int characterIndex = 0; characterIndex < characterLength;
characterIndex++) {
+ builder.append(escapedCharacters[characterIndex %
escapedCharacters.length]);
+ }
+
+ return builder.toString();
+ }
+
+ private String getEscapedCharacterReferenceValue(final int
characterLength) {
+ final char[] expandedCharacters = {'<', '>', '&'};
+ final StringBuilder builder = new StringBuilder(characterLength);
+
+ for (int characterIndex = 0; characterIndex < characterLength;
characterIndex++) {
+ builder.append(expandedCharacters[characterIndex %
expandedCharacters.length]);
+ }
+
+ return builder.toString();
+ }
}