diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index f813f4c9c..014a573c2 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -1190,6 +1190,14 @@ public void writeBytes(byte[] data, int offset, int len) throws IOException { /********************************************************** */ + private final static int MAX_SHORT_STRING_CHARS = 23; + // in case it's > 23 bytes + private final static int MAX_SHORT_STRING_BYTES = 23 * 3 + 2; + + private final static int MAX_MEDIUM_STRING_CHARS = 255; + // in case it's > 255 bytes + private final static int MAX_MEDIUM_STRING_BYTES = 255 * 3 + 3; + protected final void _writeString(String name) throws IOException { int len = name.length(); if (len == 0) { @@ -1203,7 +1211,7 @@ protected final void _writeString(String name) throws IOException { int actual = _encode(_outputTail + 1, name, len); final byte[] buf = _outputBuffer; int ix = _outputTail; - if (actual < MAX_SHORT_STRING_CHARS) { // fits in prefix byte + if (actual <= MAX_SHORT_STRING_CHARS) { // fits in prefix byte buf[ix++] = (byte) (PREFIX_TYPE_TEXT + actual); _outputTail = ix + actual; return; @@ -1225,14 +1233,6 @@ protected final void _writeString(String name) throws IOException { _writeString(cbuf, 0, len); } - private final static int MAX_SHORT_STRING_CHARS = 23; - // in case it's > 23 bytes - private final static int MAX_SHORT_STRING_BYTES = 23 * 3 + 2; - - private final static int MAX_MEDIUM_STRING_CHARS = 255; - // in case it's > 255 bytes - private final static int MAX_MEDIUM_STRING_BYTES = 255 * 3 + 3; - protected final void _ensureSpace(int needed) throws IOException { if ((_outputTail + needed + 3) > _outputEnd) { _flushBuffer(); @@ -1242,12 +1242,12 @@ protected final void _ensureSpace(int needed) throws IOException { protected final void _writeString(char[] text, int offset, int len) throws IOException { - if (len <= MAX_SHORT_STRING_CHARS) { // possibly short strings (not necessarily) + if (len <= MAX_SHORT_STRING_CHARS) { // possibly short string (not necessarily) _ensureSpace(MAX_SHORT_STRING_BYTES); // can afford approximate length int actual = _encode(_outputTail + 1, text, offset, offset + len); final byte[] buf = _outputBuffer; int ix = _outputTail; - if (actual < MAX_SHORT_STRING_CHARS) { // fits in prefix byte + if (actual <= MAX_SHORT_STRING_CHARS) { // fits in prefix byte buf[ix++] = (byte) (PREFIX_TYPE_TEXT + actual); _outputTail = ix + actual; return; @@ -1264,7 +1264,7 @@ protected final void _writeString(char[] text, int offset, int len) int actual = _encode(_outputTail + 2, text, offset, offset + len); final byte[] buf = _outputBuffer; int ix = _outputTail; - if (actual < MAX_MEDIUM_STRING_CHARS) { // fits as expected + if (actual <= MAX_MEDIUM_STRING_CHARS) { // fits as expected buf[ix++] = BYTE_STRING_1BYTE_LEN; buf[ix++] = (byte) actual; _outputTail = ix + actual; diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java index 28e7db736..af0ed4914 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java @@ -208,11 +208,11 @@ protected static String generateUnicodeString(int length, Random rnd) return sw.toString(); } - protected static String generateAsciiString(int length) { - return generateAsciiString(length, new Random(length)); + protected static String generateLongAsciiString(int length) { + return generateLongAsciiString(length, new Random(length)); } - protected static String generateAsciiString(int length, Random rnd) + protected static String generateLongAsciiString(int length, Random rnd) { StringBuilder sw = new StringBuilder(length+10); do { diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorShortStringTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorShortStringTest.java new file mode 100644 index 000000000..308d2b72a --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorShortStringTest.java @@ -0,0 +1,106 @@ +package com.fasterxml.jackson.dataformat.cbor; + +import java.io.ByteArrayOutputStream; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class GeneratorShortStringTest extends CBORTestBase +{ + public void testEmptyString() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + // First with String as input + gen.writeString(""); + gen.close(); + } + _verifyBytes(out.toByteArray(), CBORConstants.BYTE_EMPTY_STRING); + + // then as char[] + out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + gen.writeString(new char[0], 0, 0); + gen.close(); + } + _verifyBytes(out.toByteArray(), CBORConstants.BYTE_EMPTY_STRING); + } + + public void testShortTextAsString() throws Exception { + for (int len = 1; len <= 23; ++len) { + final String value = generateAsciiString(len); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + gen.writeString(value); + gen.close(); + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_TEXT + len), + value.getBytes("UTF-8")); + _verifyString(out.toByteArray(), value); + } + } + } + + public void testShortTextAsCharArray() throws Exception { + for (int len = 1; len <= 23; ++len) { + final String value = generateAsciiString(len); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + gen.writeString(value.toCharArray(), 0, len); + gen.close(); + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_TEXT + len), + value.getBytes("UTF-8")); + _verifyString(out.toByteArray(), value); + } + } + } + + public void testMediumTextAsString() throws Exception { + for (int len = 24; len <= 255; ++len) { + final String value = generateAsciiString(len); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + gen.writeString(value); + gen.close(); + _verifyBytes(out.toByteArray(), + CBORConstants.BYTE_STRING_1BYTE_LEN, (byte) len, + value.getBytes("UTF-8")); + _verifyString(out.toByteArray(), value); + } + } + } + + public void testMediumTextAsCharArray() throws Exception { + for (int len = 24; len <= 255; ++len) { + final String value = generateAsciiString(len); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (CBORGenerator gen = cborGenerator(out)) { + gen.writeString(value.toCharArray(), 0, len); + gen.close(); + _verifyBytes(out.toByteArray(), + CBORConstants.BYTE_STRING_1BYTE_LEN, (byte) len, + value.getBytes("UTF-8")); + _verifyString(out.toByteArray(), value); + } + } + } + + private String generateAsciiString(int len) { + StringBuilder sb = new StringBuilder(len); + while (--len >= 0) { + sb.append((char) ('0' + (len % 10))); + } + return sb.toString(); + } + + private void _verifyString(byte[] encoded, String value) throws Exception + { + try (JsonParser p = cborParser(encoded)) { + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals(value, p.getText()); + assertNull(p.nextToken()); + } + } +} diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java index 017f5b377..f83693cfd 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java @@ -286,29 +286,13 @@ public void testTrivialObject() throws Exception byte[] b = MAPPER.writeValueAsBytes(map); _verifyBytes(b, EXP); } - - public void testShortText() throws Exception - { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - CBORGenerator gen = cborGenerator(out); - gen.writeString(""); - gen.close(); - _verifyBytes(out.toByteArray(), CBORConstants.BYTE_EMPTY_STRING); - out = new ByteArrayOutputStream(); - gen = cborGenerator(out); - gen.writeString("abc"); - gen.close(); - _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_TEXT + 3), - (byte) 'a', (byte) 'b', (byte) 'c'); - } - public void testLongerText() throws Exception { // First, something with 8-bit length ByteArrayOutputStream out = new ByteArrayOutputStream(); CBORGenerator gen = cborGenerator(out); - final String SHORT_ASCII = generateAsciiString(240); + final String SHORT_ASCII = generateLongAsciiString(240); gen.writeString(SHORT_ASCII); gen.close(); byte[] b = SHORT_ASCII.getBytes("UTF-8"); diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 0e44d3118..c0d63174e 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -77,3 +77,7 @@ Michael Milkin (mmilkin@github) Guido Medina (guidomedina@github) * Reported #153: (smile) Unable to set a compression input/output decorator to a `SmileFactory` (2.9.8) + +Alexander Cyon (Sajjon@github) +* Reported #159: (cbor) Some short UTF Strings encoded using non-canonical form + (2.9.9) diff --git a/release-notes/VERSION b/release-notes/VERSION index 9f14ffed2..b91660147 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -8,6 +8,11 @@ Project: jackson-datatypes-binaryModules: === Releases === ------------------------------------------------------------------------ +2.9.9 (not yet released) + +#159: (cbor) Some short UTF Strings encoded using non-canonical form + (reported by Alexander C) + 2.9.8 (15-Dec-2018) #140: (protobuf) Stack overflow when generating Protobuf schema on class with