diff --git a/src/main/java/tools/jackson/dataformat/xml/deser/FromXmlParser.java b/src/main/java/tools/jackson/dataformat/xml/deser/FromXmlParser.java
index 2d8fb94b..722eb644 100644
--- a/src/main/java/tools/jackson/dataformat/xml/deser/FromXmlParser.java
+++ b/src/main/java/tools/jackson/dataformat/xml/deser/FromXmlParser.java
@@ -209,7 +209,7 @@ public FromXmlParser(ObjectReadContext readCtxt, IOContext ioCtxt,
parserFeatures, xmlFeatures,
xmlReader,
new XmlTokenStream(xmlReader, ioCtxt.contentReference(),
- xmlFeatures, nameProcessor),
+ xmlFeatures, nameProcessor, ioCtxt.streamReadConstraints()),
nameProcessor,
nameForTextElement);
}
diff --git a/src/main/java/tools/jackson/dataformat/xml/deser/XmlTokenStream.java b/src/main/java/tools/jackson/dataformat/xml/deser/XmlTokenStream.java
index 705ca8a6..baa40923 100644
--- a/src/main/java/tools/jackson/dataformat/xml/deser/XmlTokenStream.java
+++ b/src/main/java/tools/jackson/dataformat/xml/deser/XmlTokenStream.java
@@ -10,6 +10,7 @@
import org.codehaus.stax2.XMLStreamLocation2;
import org.codehaus.stax2.XMLStreamReader2;
+import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.TokenStreamLocation;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.ContentReference;
@@ -86,6 +87,15 @@ public class XmlTokenStream
protected XmlNameProcessor _nameProcessor;
+ /**
+ * Read constraints used to enforce {@link StreamReadConstraints#validateStringLength}
+ * on element text / attribute values and {@link StreamReadConstraints#validateNameLength}
+ * on element / attribute names.
+ *
+ * @since 3.3
+ */
+ protected final StreamReadConstraints _streamReadConstraints;
+
/*
/**********************************************************************
/* Parsing state
@@ -193,6 +203,17 @@ public class XmlTokenStream
public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
int formatFeatures, XmlNameProcessor nameProcessor)
+ {
+ this(xmlReader, sourceRef, formatFeatures, nameProcessor,
+ StreamReadConstraints.defaults());
+ }
+
+ /**
+ * @since 3.3
+ */
+ public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
+ int formatFeatures, XmlNameProcessor nameProcessor,
+ StreamReadConstraints streamReadConstraints)
{
_sourceReference = sourceRef;
_formatFeatures = formatFeatures;
@@ -202,6 +223,7 @@ public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
// 04-Dec-2023, tatu: [dataformat-xml#618] Need further customized adapter:
_xmlReader = Stax2JacksonReaderAdapter.wrapIfNecessary(xmlReader);
_nameProcessor = nameProcessor;
+ _streamReadConstraints = streamReadConstraints;
}
/**
@@ -343,7 +365,15 @@ public void skipEndElement() throws IOException, XMLStreamException
public int getCurrentToken() { return _currentState; }
- public String getText() { return _textValue; }
+ public String getText() {
+ // Honor `StreamReadConstraints.maxStringLength` for element text and
+ // attribute values (idempotent: cheap length check even when this accessor
+ // is called more than once for the same value)
+ if (_textValue != null) {
+ _streamReadConstraints.validateStringLength(_textValue.length());
+ }
+ return _textValue;
+ }
/**
* Accessor for local name of current named event (that is,
@@ -847,6 +877,8 @@ private static boolean _isXsiNilTrue(String value) {
* @since 2.14
*/
protected void _decodeElementName(String namespaceURI, String localName) {
+ // Honor `StreamReadConstraints.maxNameLength` for element names
+ _streamReadConstraints.validateNameLength(localName.length());
// 31-Jan-2024, tatu: [dataformat-xml#634] Need to convert 'xsi:type'?
// (not 100% sure if needed for elements but let's do for now)
if (_cfgProcessXsiType) {
@@ -867,6 +899,8 @@ protected void _decodeElementName(String namespaceURI, String localName) {
* @since 2.14
*/
protected void _decodeAttributeName(String namespaceURI, String localName) {
+ // Honor `StreamReadConstraints.maxNameLength` for attribute names
+ _streamReadConstraints.validateNameLength(localName.length());
// 31-Jan-2024, tatu: [dataformat-xml#634] Need to convert 'xsi:type'?
if (_cfgProcessXsiType) {
if (localName.equals("type") && XSI_NAMESPACE.equals(namespaceURI)) {
diff --git a/src/test/java/tools/jackson/dataformat/xml/dos/StringAndNameLengthTest.java b/src/test/java/tools/jackson/dataformat/xml/dos/StringAndNameLengthTest.java
new file mode 100644
index 00000000..f1beda8b
--- /dev/null
+++ b/src/test/java/tools/jackson/dataformat/xml/dos/StringAndNameLengthTest.java
@@ -0,0 +1,117 @@
+package tools.jackson.dataformat.xml.dos;
+
+import org.junit.jupiter.api.Test;
+
+import tools.jackson.core.JsonParser;
+import tools.jackson.core.StreamReadConstraints;
+import tools.jackson.core.exc.StreamConstraintsException;
+
+import tools.jackson.dataformat.xml.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+// Verifies that `StreamReadConstraints.maxStringLength` / `maxNameLength` are
+// honored when reading XML (element text, attribute values, and element /
+// attribute / root names), matching the JSON backend.
+public class StringAndNameLengthTest extends XmlTestUtil
+{
+ private XmlMapper mapperWithStringLimit(int max) {
+ return mapperBuilder(XmlFactory.builder()
+ .streamReadConstraints(StreamReadConstraints.builder()
+ .maxStringLength(max).build())
+ .build()).build();
+ }
+
+ private XmlMapper mapperWithNameLimit(int max) {
+ return mapperBuilder(XmlFactory.builder()
+ .streamReadConstraints(StreamReadConstraints.builder()
+ .maxNameLength(max).build())
+ .build()).build();
+ }
+
+ private void drain(XmlMapper mapper, String xml) throws Exception {
+ try (JsonParser p = mapper.createParser(xml)) {
+ while (p.nextToken() != null) {
+ p.getString();
+ }
+ }
+ }
+
+ @Test
+ public void testElementTextLength() throws Exception
+ {
+ final XmlMapper mapper = mapperWithStringLimit(100);
+ final String value = "x".repeat(5000);
+ try {
+ drain(mapper, "" + value + "");
+ fail("expected StreamConstraintsException");
+ } catch (StreamConstraintsException e) {
+ assertTrue(e.getMessage().contains("String value length (5000) exceeds the maximum allowed"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAttributeValueLength() throws Exception
+ {
+ final XmlMapper mapper = mapperWithStringLimit(100);
+ final String value = "x".repeat(5000);
+ try {
+ drain(mapper, "");
+ fail("expected StreamConstraintsException");
+ } catch (StreamConstraintsException e) {
+ assertTrue(e.getMessage().contains("String value length (5000) exceeds the maximum allowed"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testElementNameLength() throws Exception
+ {
+ final XmlMapper mapper = mapperWithNameLimit(50);
+ final String name = "n".repeat(5000);
+ try {
+ drain(mapper, "<" + name + ">v" + name + ">");
+ fail("expected StreamConstraintsException");
+ } catch (StreamConstraintsException e) {
+ assertTrue(e.getMessage().contains("Name length (5000) exceeds the maximum allowed"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAttributeNameLength() throws Exception
+ {
+ final XmlMapper mapper = mapperWithNameLimit(50);
+ final String name = "n".repeat(5000);
+ try {
+ drain(mapper, "");
+ fail("expected StreamConstraintsException");
+ } catch (StreamConstraintsException e) {
+ assertTrue(e.getMessage().contains("Name length (5000) exceeds the maximum allowed"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRootElementNameLength() throws Exception
+ {
+ final XmlMapper mapper = mapperWithNameLimit(50);
+ final String name = "n".repeat(5000);
+ try {
+ drain(mapper, "<" + name + ">v" + name + ">");
+ fail("expected StreamConstraintsException");
+ } catch (StreamConstraintsException e) {
+ assertTrue(e.getMessage().contains("Name length (5000) exceeds the maximum allowed"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ // Values within the configured limits must parse unchanged.
+ @Test
+ public void testWithinLimits() throws Exception
+ {
+ drain(mapperWithStringLimit(100), "hello world");
+ drain(mapperWithNameLimit(50), "text");
+ }
+}