Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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, "<a>" + value + "</a>");
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, "<a b='" + 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 testElementNameLength() throws Exception
{
final XmlMapper mapper = mapperWithNameLimit(50);
final String name = "n".repeat(5000);
try {
drain(mapper, "<a><" + name + ">v</" + name + "></a>");
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, "<a " + name + "='v'/>");
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), "<a b='short'>hello world</a>");
drain(mapperWithNameLimit(50), "<parent attr='v'><child>text</child></parent>");
}
}
Loading