diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index b308518f..d0ae2b34 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -12,6 +12,8 @@ Modules: #63: (yaml) `null` Object Id serialized as anchor for YAML (reported by jflefebvre06@github) +#90: (yaml) Exception when decoding Jackson-encoded `Base64` binary value in YAML + (reported by Tanguy L) #122: (csv) `readValues(null)` causes infinite loop (reported by andyeko@github) #123: (yaml) YAML Anchor, reference fails with simple example diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index afdac184..21f6cbfc 100644 --- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -833,7 +833,8 @@ private void _writeScalarBinary(Base64Variant b64variant, if (b64variant == Base64Variants.getDefaultVariant()) { b64variant = Base64Variants.MIME; } - String encoded = b64variant.encode(data); + final String lf = _lf(); + String encoded = _base64encode(b64variant, data, lf); _emitter.emit(new ScalarEvent(null, TAG_BINARY, EXPLICIT_TAGS, encoded, null, null, STYLE_BASE64)); } @@ -853,4 +854,42 @@ protected ScalarEvent _scalarEvent(String value, Character style) return new ScalarEvent(anchor, yamlTag, NO_TAGS, value, null, null, style); } + + // // // 26-Feb-2019, tatu: Copied temporarily (for 2.10) from `Base64Variant` to prevent + // // // hard dependency for same minor version + + private String _base64encode(final Base64Variant b64v, final byte[] input, final String linefeed) + { + final int inputEnd = input.length; + final StringBuilder sb = new StringBuilder(inputEnd + (inputEnd >> 2) + (inputEnd >> 3)); + + int chunksBeforeLF = b64v.getMaxLineLength() >> 2; + + int inputPtr = 0; + int safeInputEnd = inputEnd-3; + + while (inputPtr <= safeInputEnd) { + int b24 = ((int) input[inputPtr++]) << 8; + b24 |= ((int) input[inputPtr++]) & 0xFF; + b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF); + b64v.encodeBase64Chunk(sb, b24); + if (--chunksBeforeLF <= 0) { + sb.append(linefeed); + chunksBeforeLF = b64v.getMaxLineLength() >> 2; + } + } + int inputLeft = inputEnd - inputPtr; + if (inputLeft > 0) { + int b24 = ((int) input[inputPtr++]) << 16; + if (inputLeft == 2) { + b24 |= (((int) input[inputPtr++]) & 0xFF) << 8; + } + b64v.encodeBase64Partial(sb, b24, inputLeft); + } + return sb.toString(); + } + + protected String _lf() { + return _outputOptions.getLineBreak().getString(); + } } diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java index 0b70b4fd..d9121204 100644 --- a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java +++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java @@ -1,10 +1,14 @@ package com.fasterxml.jackson.dataformat.yaml.deser; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; +import java.util.Random; import org.junit.Assert; +import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.dataformat.yaml.ModuleTestBase; @@ -41,4 +45,29 @@ public void testBinaryViaTree() throws Exception final byte[] expectedFileHeader = new byte[]{'G', 'I', 'F', '8', '9', 'a'}; Assert.assertArrayEquals(expectedFileHeader, actualFileHeader); } + + // [dataformats-text#90] + public void testReadLongBinary() throws Exception { + final byte[] data = new byte[1000]; + new Random(1234).nextBytes(data); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + try (JsonGenerator gen = MAPPER.getFactory().createGenerator(os)) { + gen.writeStartObject(); + gen.writeBinaryField("data", data); + gen.writeEndObject(); + gen.close(); + } + + try (JsonParser parser = MAPPER.getFactory().createParser(os.toByteArray())) { + assertEquals(JsonToken.START_OBJECT, parser.nextToken()); + assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals("data", parser.currentName()); + assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken()); + Assert.assertArrayEquals(data, parser.getBinaryValue()); + assertEquals(JsonToken.END_OBJECT, parser.nextToken()); + assertNull(parser.nextToken()); + } + } } diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java index da8b86fe..89e977f1 100644 --- a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java +++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java @@ -1,7 +1,11 @@ package com.fasterxml.jackson.dataformat.yaml.ser; +import java.io.StringWriter; +import java.util.Arrays; + import org.junit.Assert; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -26,4 +30,28 @@ public void testBinaryViaTree() throws Exception final byte[] b = data.binaryValue(); Assert.assertArrayEquals(srcPayload, b); } + + public void testWriteLongBinary() throws Exception { + final int length = 200; + final byte[] data = new byte[length]; + Arrays.fill(data, (byte) 1); + + StringWriter w = new StringWriter(); + + try (JsonGenerator gen = MAPPER.getFactory().createGenerator(w)) { + gen.writeStartObject(); + gen.writeBinaryField("array", data); + gen.writeEndObject(); + gen.close(); + } + + String yaml = w.toString(); + Assert.assertEquals("---\n" + + "array: !!binary |-\n" + + " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" + + " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" + + " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" + + " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\n", yaml); + + } }