diff --git a/src/main/java/tools/jackson/dataformat/xml/XmlFactory.java b/src/main/java/tools/jackson/dataformat/xml/XmlFactory.java index 9754ea5b..d4bb6f35 100644 --- a/src/main/java/tools/jackson/dataformat/xml/XmlFactory.java +++ b/src/main/java/tools/jackson/dataformat/xml/XmlFactory.java @@ -484,6 +484,8 @@ protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCt sr = _xmlInputFactory.createXMLStreamReader(in); } catch (XMLStreamException e) { return StaxUtil.throwAsReadException(e, null); + } catch (ArrayIndexOutOfBoundsException e) { + return _reportBadXmlReaderCreation(e); } return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr)); } @@ -497,6 +499,8 @@ protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCt sr = _xmlInputFactory.createXMLStreamReader(r); } catch (XMLStreamException e) { return StaxUtil.throwAsReadException(e, null); + } catch (ArrayIndexOutOfBoundsException e) { + return _reportBadXmlReaderCreation(e); } return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr)); } @@ -519,6 +523,8 @@ protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCt } } catch (XMLStreamException e) { return StaxUtil.throwAsReadException(e, null); + } catch (ArrayIndexOutOfBoundsException e) { + return _reportBadXmlReaderCreation(e); } return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr)); } @@ -538,10 +544,25 @@ protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCt } } catch (XMLStreamException e) { return StaxUtil.throwAsReadException(e, null); + } catch (ArrayIndexOutOfBoundsException e) { + return _reportBadXmlReaderCreation(e); } return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr)); } + // Some non-Woodstox Stax implementations (for example SJSXP, [dataformat-xml#618]) + // parse the XML declaration eagerly while creating the stream reader, and can fail + // with unchecked exceptions on malformed input instead of `XMLStreamException`. + // Translate to the standard read-exception type so callers see a `JacksonException` + // here too, matching how `_initializeXmlReader` already handles the sibling case. + private T _reportBadXmlReaderCreation(ArrayIndexOutOfBoundsException e) { + throw new StreamReadException(null, + "Internal processing error by `XMLInputFactory` of type " + +_xmlInputFactory.getClass().getName() + +" when creating `XMLStreamReader` (consider using Woodstox instead): " + +e.getMessage(), e); + } + /** * Overridable method to allow using custom FromXmlParser sub-classes. */ diff --git a/src/test/java/tools/jackson/dataformat/xml/stream/BrokenReaderFactoryTest.java b/src/test/java/tools/jackson/dataformat/xml/stream/BrokenReaderFactoryTest.java new file mode 100644 index 00000000..9b53e94f --- /dev/null +++ b/src/test/java/tools/jackson/dataformat/xml/stream/BrokenReaderFactoryTest.java @@ -0,0 +1,76 @@ +package tools.jackson.dataformat.xml.stream; + +import java.io.*; + +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Source; + +import org.junit.jupiter.api.Test; + +import com.ctc.wstx.stax.WstxInputFactory; + +import tools.jackson.core.exc.StreamReadException; + +import tools.jackson.dataformat.xml.XmlFactory; +import tools.jackson.dataformat.xml.XmlMapper; +import tools.jackson.dataformat.xml.XmlTestUtil; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +// [dataformat-xml#618]: some non-Woodstox Stax impls (notably JDK-bundled SJSXP) +// parse the XML declaration while creating the stream reader and fail with unchecked +// exceptions on malformed input. XmlFactory already translates the reader.next() and +// _initializeXmlReader cases; this checks the createXMLStreamReader boundary too, so +// callers see a StreamReadException rather than a leaked ArrayIndexOutOfBoundsException. +public class BrokenReaderFactoryTest extends XmlTestUtil +{ + // Stands in for a non-Woodstox impl whose reader creation throws non-XMLStreamException + static class BrokenInputFactory extends WstxInputFactory { + @Override + public XMLStreamReader createXMLStreamReader(InputStream in) { + throw new ArrayIndexOutOfBoundsException("Index 5 out of bounds for length 4"); + } + + @Override + public XMLStreamReader createXMLStreamReader(Reader r) { + throw new ArrayIndexOutOfBoundsException("Index 5 out of bounds for length 4"); + } + + @Override + public XMLStreamReader createXMLStreamReader(Source s) { + throw new ArrayIndexOutOfBoundsException("Index 5 out of bounds for length 4"); + } + } + + private final XmlMapper MAPPER = new XmlMapper(new XmlFactory(new BrokenInputFactory())); + + private static final String DOC = "value"; + + @Test + public void testByteArrayInput() throws Exception { + StreamReadException e = assertThrows(StreamReadException.class, + () -> MAPPER.readTree(utf8Bytes(DOC))); + verifyException(e, "Internal processing error by `XMLInputFactory`"); + } + + @Test + public void testInputStreamInput() throws Exception { + StreamReadException e = assertThrows(StreamReadException.class, + () -> MAPPER.readTree(new ByteArrayInputStream(utf8Bytes(DOC)))); + verifyException(e, "Internal processing error by `XMLInputFactory`"); + } + + @Test + public void testReaderInput() throws Exception { + StreamReadException e = assertThrows(StreamReadException.class, + () -> MAPPER.readTree(new StringReader(DOC))); + verifyException(e, "Internal processing error by `XMLInputFactory`"); + } + + @Test + public void testStringInput() throws Exception { + StreamReadException e = assertThrows(StreamReadException.class, + () -> MAPPER.readTree(DOC)); + verifyException(e, "Internal processing error by `XMLInputFactory`"); + } +}