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);
+ }
+}