Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: ['8', '11', '17', '21']
java_version: ['8', '17', '21']
include:
- java_version: '8'
release_build: 'R'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2620,7 +2620,8 @@ private final String _finishLongText(int len) throws IOException
}

/**
* Consumes as many ascii chars as possible in a tight loop. Returns the amount of bytes remaining.
* Consumes as many ascii chars as possible in a tight loop.
* Returns the amount of bytes remaining.
*/
private final int _finishLongTextAscii(int len) throws IOException
{
Expand All @@ -2646,7 +2647,10 @@ private final int _finishLongTextAscii(int len) throws IOException
--outPtr;
_inputPtr = inPtr - 1;
_textBuffer.setCurrentLength(outPtr);
return len - outPtr;
// `len` was already decremented for all previous iterations; subtract only
// the bytes consumed in THIS iteration (= _inputPtr), since
// _tryToLoadToHaveAtLeast always resets _inputPtr to 0 before the inner loop.
return len - _inputPtr;
}
_inputPtr = inPtr;
if (outPtr >= outBuf.length) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.fasterxml.jackson.dataformat.cbor.parse;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

// For [dataformats-binary#686]
public class CBORLongAsciiRead686Test
{
// TextBuffer segment sizes grow 1.5× per flip, starting at 200 chars (MIN=500):
// S0=200, S1=500, S2=750, S3=1125, S4=1687, S5=2530, S6=3795, S7=5692, S8=8538
// After 8 flips the segment is 8538 chars — the first one larger than the 8000-byte I/O buffer.
// Total chars consumed reaching S8: 200+500+750+1125+1687+2530+3795+5692 = 16279.
private static final int CHARS_TO_REACH_S8 = 16279;
private static final int IO_BUFFER_SIZE = 8000;

/**
* Parses a definite-length CBOR map {a: <16279 'a' bytes>, b: <8000 'a' bytes + 0xB7>, n: "x"}
* via InputStream and expects all tokens to be read without error.
* <p>
* Actual result on affected versions: JsonParseException "Unsupported major type (5)"
*/
@Test
public void testFinishLongTextAsciiDoesNotLeaveNonAsciiByte()
{
byte[] strA = new byte[CHARS_TO_REACH_S8];
Arrays.fill(strA, (byte) 'a');

// 0xC2 0xB7 = U+00B7 "·" (middle dot): a valid 2-byte UTF-8 sequence.
// 0xC2 is non-ASCII so _finishLongTextAscii exits, but it must leave len non-negative
// so that _finishLongText's while loop can still decode the sequence.
byte[] strB = new byte[IO_BUFFER_SIZE + 2];
Arrays.fill(strB, (byte) 'a');
strB[IO_BUFFER_SIZE] = (byte) 0xC2;
strB[IO_BUFFER_SIZE + 1] = (byte) 0xB7;

assertDoesNotThrow(() ->
new ObjectMapper(new CBORFactory())
.readValue(new ByteArrayInputStream(buildMap(strA, strB)), Object.class));
}

/**
* definite-length map(3): {a: strA, b: strB, n: "x"}
*/
private static byte[] buildMap(byte[] strA, byte[] strB) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(0xa3);
writeText(bos, new byte[]{'a'});
writeText(bos, strA);
writeText(bos, new byte[]{'b'});
writeText(bos, strB);
writeText(bos, new byte[]{'n'});
writeText(bos, new byte[]{'x'});
return bos.toByteArray();
}

private static void writeText(ByteArrayOutputStream bos, byte[] bytes) throws IOException
{
int n = bytes.length;
if (n <= 23) {
bos.write(0x60 | n);
} else if (n <= 0xFF) {
bos.write(0x78);
bos.write(n);
} else if (n <= 0xFFFF) {
bos.write(0x79);
bos.write((n >> 8) & 0xFF);
bos.write(n & 0xFF);
}
bos.write(bytes);
}
}
7 changes: 6 additions & 1 deletion release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -429,5 +429,10 @@ Vincent Eigenberger (@beseder1)
(2.20.1)

Yohei Kishimoto (@morokosi)
* Reported #599: (cbor) Unable to deserialize stringref-enabled CBOR with ignored properties
* Reported #599: (cbor) Unable to deserialize stringref-enabled CBOR with ignored propertie
(2.21.0)

Halil İbrahim Şener (@hisener)
* Fixed #686: `CBORParser._finishLongTextAscii` returns negative length when `TextBuffer`
segment > I/O buffer, leaving non-ASCII byte unconsumed
(2.21.3)
6 changes: 6 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Active maintainers:
=== Releases ===
------------------------------------------------------------------------

2.21.3 (not yet released)

#686: `CBORParser._finishLongTextAscii` returns negative length when `TextBuffer`
segment > I/O buffer, leaving non-ASCII byte unconsumed
(fixed by Halil İbrahim Ş)

2.21.2 (20-Mar-2026)

No changes since 2.21.1.
Expand Down