diff --git a/src/main/java/tools/jackson/dataformat/xml/ser/ToXmlGenerator.java b/src/main/java/tools/jackson/dataformat/xml/ser/ToXmlGenerator.java index 1fde0228..2524e269 100644 --- a/src/main/java/tools/jackson/dataformat/xml/ser/ToXmlGenerator.java +++ b/src/main/java/tools/jackson/dataformat/xml/ser/ToXmlGenerator.java @@ -18,6 +18,7 @@ import tools.jackson.core.exc.StreamWriteException; import tools.jackson.core.io.IOContext; import tools.jackson.core.json.DupDetector; +import tools.jackson.core.util.ByteArrayBuilder; import tools.jackson.core.util.SimpleStreamWriteContext; import tools.jackson.dataformat.xml.XmlPrettyPrinter; import tools.jackson.dataformat.xml.XmlWriteFeature; @@ -1163,6 +1164,16 @@ public int writeBinary(Base64Variant b64variant, InputStream data, int dataLengt writeNull(); return 0; } + // A negative length means "length not known, read to the end of stream" per the + // JsonGenerator.writeBinary(...) contract (and as the JSON backend does). Stax2 + // needs the full buffer for attribute/typed writes anyway, so read it all and + // reuse the byte[] path; otherwise the negative length reaches a byte[] allocation + // or bounded read and escapes as a raw NegativeArraySizeException / IndexOutOfBoundsException. + if (dataLength < 0) { + byte[] full = _readAll(data); + writeBinary(b64variant, full, 0, full.length); + return full.length; + } _verifyValueWrite("write Binary value"); if (_nextName == null) { handleMissingName(); @@ -1237,7 +1248,24 @@ private byte[] toFullBuffer(byte[] data, int offset, int len) return result; } - private byte[] toFullBuffer(InputStream data, final int len) throws JacksonException + private byte[] _readAll(InputStream data) throws JacksonException + { + ByteArrayBuilder bb = new ByteArrayBuilder(); + byte[] tmp = new byte[4000]; + int count; + try { + while ((count = data.read(tmp)) != -1) { + if (count > 0) { + bb.write(tmp, 0, count); + } + } + } catch (IOException e) { + throw _wrapIOFailure(e); + } + return bb.toByteArray(); + } + + private byte[] toFullBuffer(InputStream data, final int len) throws JacksonException { byte[] result = new byte[len]; int offset = 0; diff --git a/src/test/java/tools/jackson/dataformat/xml/stream/BinaryUnknownLengthWriteTest.java b/src/test/java/tools/jackson/dataformat/xml/stream/BinaryUnknownLengthWriteTest.java new file mode 100644 index 00000000..69c91d48 --- /dev/null +++ b/src/test/java/tools/jackson/dataformat/xml/stream/BinaryUnknownLengthWriteTest.java @@ -0,0 +1,59 @@ +package tools.jackson.dataformat.xml.stream; + +import java.io.ByteArrayInputStream; +import java.io.StringWriter; + +import javax.xml.namespace.QName; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.Base64Variants; + +import tools.jackson.dataformat.xml.XmlMapper; +import tools.jackson.dataformat.xml.XmlTestUtil; +import tools.jackson.dataformat.xml.ser.ToXmlGenerator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [dataformat-xml] `JsonGenerator.writeBinary(InputStream, dataLength)` documents a +// negative `dataLength` as "length unknown, read to end" (the JSON backend honors it); +// verify the XML backend streams to end instead of failing with a raw runtime exception. +public class BinaryUnknownLengthWriteTest extends XmlTestUtil +{ + private final XmlMapper MAPPER = newMapper(); + + private final byte[] DATA = "hello, binary world".getBytes(); + // base64 of DATA + private final String ENCODED = "aGVsbG8sIGJpbmFyeSB3b3JsZA=="; + + @Test + public void testElementUnknownLength() throws Exception + { + StringWriter out = new StringWriter(); + try (ToXmlGenerator gen = (ToXmlGenerator) MAPPER.createGenerator(out)) { + gen.setNextName(new QName("root")); + gen.writeStartObject(); + gen.writeName("bin"); + gen.writeBinary(Base64Variants.MIME, new ByteArrayInputStream(DATA), -1); + gen.writeEndObject(); + } + String xml = removeSjsxpNamespace(out.toString()); + assertEquals("" + ENCODED + "", xml); + } + + @Test + public void testAttributeUnknownLength() throws Exception + { + StringWriter out = new StringWriter(); + try (ToXmlGenerator gen = (ToXmlGenerator) MAPPER.createGenerator(out)) { + gen.setNextName(new QName("root")); + gen.writeStartObject(); + gen.setNextIsAttribute(true); + gen.writeName("bin"); + gen.writeBinary(Base64Variants.MIME, new ByteArrayInputStream(DATA), -1); + gen.writeEndObject(); + } + String xml = removeSjsxpNamespace(out.toString()); + assertEquals("", xml); + } +}