diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 08449e13..82fed5c3 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -37,3 +37,7 @@ Andrey Somov (asomov@github) * Contributed #101: Use latest SnakeYAML version 1.23 and get rid of deprecated methods (2.10.0) +Tanguy Leroux (tlrx@github) + +* Reported #90: Exception when decoding Jackson-encoded `Base64` binary value in YAML + (2.10.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3d221487..b4923b29 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -10,9 +10,11 @@ Modules: 2.10.0 (not yet released) -#101: Use latest SnakeYAML version 1.23 and get rid of deprecated methods +#90: Exception when decoding Jackson-encoded `Base64` binary value in YAML + (reported by Tanguy L) +#101: (yaml) Use latest SnakeYAML version 1.23 and get rid of deprecated methods (contributed by Andrey S) -#108: Add new `CsvParser.Feature.ALLOW_COMMENTS` to replace deprecated +#108: (yaml) Add new `CsvParser.Feature.ALLOW_COMMENTS` to replace deprecated `JsonParser.Feature.ALLOW_YAML_COMMENTS` 2.9.9 (not yet released) 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 5cbd47c3..d57f8bf3 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 @@ -835,7 +835,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)); } @@ -855,4 +856,42 @@ protected ScalarEvent _scalarEvent(String value, DumperOptions.ScalarStyle 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/failing/ObjectId123Test.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/failing/ObjectId123Test.java deleted file mode 100644 index e044c32c..00000000 --- a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/failing/ObjectId123Test.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fasterxml.jackson.dataformat.yaml.failing; - -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.*; - -//for [dataformats-text#123], problem with YAML, Object Ids -public class ObjectId123Test extends ModuleTestBase -{ - private final ObjectMapper MAPPER = newObjectMapper(); - - public void testObjectIdUsingNative() throws Exception - { - final String YAML_CONTENT = - "foo: &foo1\n" + - " value: bar\n" + - "boo: *foo1\n"; - ScratchModel result = MAPPER.readValue(YAML_CONTENT, ScratchModel.class); - assertNotNull(result); - assertNotNull(result.foo); - assertNotNull(result. boo); - assertSame(result.foo, result.boo); - } - - static class ScratchModel { - public StringHolder foo; - public StringHolder boo; - } - -// @JsonIdentityInfo(generator = ObjectIdGenerators.None.class) - @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class) - static class StringHolder { - public String value; - - @Override - public String toString() { - return value; - } - } -} 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); + + } }