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 = {"&lt;", "&gt;", "&amp;"};
+        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 = {"&lt;", "&gt;", "&amp;"};
+        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();
+    }
 }

Reply via email to