From b5a358019f799d92b73f67eacb411ec966333b21 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 4 Sep 2026 14:50:16 +0200 Subject: [PATCH 1/3] Polishing --- .../util/xml/XMLEventStreamReader.java | 8 +++++--- .../util/xml/XMLEventStreamReaderTests.java | 16 +++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java index e554dbd6ebd..3e8725e3d76 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java @@ -34,9 +34,11 @@ import javax.xml.stream.events.XMLEvent; import org.jspecify.annotations.Nullable; /** - * Implementation of the {@link javax.xml.stream.XMLStreamReader} interface that wraps a - * {@link XMLEventReader}. Useful because the StAX {@link javax.xml.stream.XMLInputFactory} - * allows one to create an event reader from a stream reader, but not vice-versa. + * Implementation of the {@link javax.xml.stream.XMLStreamReader} interface that wraps an + * {@link XMLEventReader}. + * + *

Useful because the StAX {@link javax.xml.stream.XMLInputFactory} allows one + * to create an event reader from a stream reader, but not vice-versa. * * @author Arjen Poutsma * @since 3.0 diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java index 40ef3b6410d..9a261226c86 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java @@ -26,7 +26,6 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamResult; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.w3c.dom.Node; import org.xmlunit.util.Predicate; @@ -35,21 +34,24 @@ import org.springframework.core.testfixture.xml.XmlContent; import static org.assertj.core.api.Assertions.assertThat; +/** + * Tests for {@link XMLEventStreamReader}. + */ class XMLEventStreamReaderTests { private static final String XML = - "content" - ; + "content"; - private XMLEventStreamReader streamReader; + private final XMLEventStreamReader streamReader; - @BeforeEach - void createStreamReader() throws Exception { + + XMLEventStreamReaderTests() throws Exception { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(XML)); - streamReader = new XMLEventStreamReader(eventReader); + this.streamReader = new XMLEventStreamReader(eventReader); } + @Test void readAll() throws Exception { while (streamReader.hasNext()) { From 2b276311ebe715354a10610c102a09e4c9be89a7 Mon Sep 17 00:00:00 2001 From: junhyeong9812 Date: Sat, 13 Jun 2026 19:33:37 +0900 Subject: [PATCH 2/3] Honor sourceStart offset in AbstractXMLStreamReader#getTextCharacters Prior to this commit, AbstractXMLStreamReader.getTextCharacters(int sourceStart, char[], int, int) capped the copy length with Math.min(length, source.length), ignoring sourceStart. When sourceStart > 0 and sourceStart + length exceeds the text length, System.arraycopy read past the end of the source array and threw ArrayIndexOutOfBoundsException, contrary to the XMLStreamReader#getTextCharacters contract (copy up to length characters starting at sourceStart and return the number copied). To address that, this commit caps the length by the number of characters remaining from sourceStart. Closes gh-36914 Signed-off-by: junhyeong9812 --- .../util/xml/AbstractXMLStreamReader.java | 2 +- .../util/xml/XMLEventStreamReaderTests.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java index 1c92c458fcd..1bba07dc5f3 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java @@ -190,7 +190,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { @Override public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) { char[] source = getTextCharacters(); - length = Math.min(length, source.length); + length = Math.min(length, source.length - sourceStart); System.arraycopy(source, sourceStart, target, targetStart, length); return length; } diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java index 9a261226c86..0a517e7abea 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java @@ -21,6 +21,7 @@ import java.io.StringWriter; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stax.StAXSource; @@ -70,4 +71,23 @@ class XMLEventStreamReaderTests { assertThat(XmlContent.from(writer)).isSimilarTo(XML, nodeFilter); } + @Test // gh-36914 + void getTextCharactersHonorsSourceStart() throws Exception { + char[] target = new char[10]; + + advanceToCharacters(); + + // text node is "content" (7 chars); copy from index 4 with an oversized buffer + // getTextCharacters(sourceStart, ...) must not read past the source + int count = streamReader.getTextCharacters(4, target, 0, 10); + assertThat(count).isEqualTo(3); + assertThat(new String(target, 0, count)).isEqualTo("ent"); + } + + private void advanceToCharacters() throws Exception { + while (streamReader.getEventType() != XMLStreamConstants.CHARACTERS) { + streamReader.next(); + } + } + } From ea2a26206cc109c9dd5c280e3250b40c90848ee0 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:07:07 +0200 Subject: [PATCH 3/3] Polish contribution See gh-36914 --- .../util/xml/XMLEventStreamReaderTests.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java index 0a517e7abea..def40b8be90 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java +++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java @@ -28,6 +28,8 @@ import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamResult; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.w3c.dom.Node; import org.xmlunit.util.Predicate; @@ -71,17 +73,24 @@ class XMLEventStreamReaderTests { assertThat(XmlContent.from(writer)).isSimilarTo(XML, nodeFilter); } - @Test // gh-36914 - void getTextCharactersHonorsSourceStart() throws Exception { - char[] target = new char[10]; - + @ParameterizedTest // gh-36914 + @CsvSource(textBlock = """ + 0, content + 1, ontent + 4, ent + 6, t + 7, '' + """) + void getTextCharactersHonorsSourceStart(int sourceStart, String expected) throws Exception { advanceToCharacters(); - // text node is "content" (7 chars); copy from index 4 with an oversized buffer - // getTextCharacters(sourceStart, ...) must not read past the source - int count = streamReader.getTextCharacters(4, target, 0, 10); - assertThat(count).isEqualTo(3); - assertThat(new String(target, 0, count)).isEqualTo("ent"); + // text node is "content" (7 chars); request an oversized buffer to ensure + // getTextCharacters(sourceStart, ...) does not read past the source + char[] target = new char[10]; + int count = streamReader.getTextCharacters(sourceStart, target, 0, 10); + + assertThat(count).isEqualTo(expected.length()); + assertThat(new String(target, 0, count)).isEqualTo(expected); } private void advanceToCharacters() throws Exception {