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
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,14 @@ public boolean nextFieldName(SerializableString str) throws IOException
}
}
}
// 24-Jul-2026, tatu: [dataformats-binary#728] Cannot fall back to
// `nextToken()` here: we have already consumed one entry of
// expected-length Object (`expectMoreValues()` above) and
// `nextToken()` would consume another one. So decode name here.
// Note: cannot get `null` for end-of-input here since we are
// within Object, and `_handleEOF()` fails for that case.
return (_updateToken(_decodePropertyName()) == JsonToken.FIELD_NAME)
&& str.getValue().equals(getCurrentName());
}
// otherwise just fall back to default handling; should occur rarely
return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(getCurrentName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper;
import com.fasterxml.jackson.dataformat.cbor.testutil.ThrottledInputStream;

public abstract class CBORTestBase
extends junit.framework.TestCase
Expand Down Expand Up @@ -42,6 +43,32 @@ protected CBORParser cborParser(CBORFactory f, byte[] input) throws IOException
return f.createParser(input);
}

/**
* Helper for cases that need to verify handling both with fully buffered
* input (fixed {@code byte[]}) and with input that trickles in, one byte
* at a time, so that parser cannot rely on look-ahead within buffer.
*
* @since 2.18.10
*/
protected CBORParser cborParser(byte[] input, boolean throttled) throws IOException {
return cborParser(cborFactory(), input, throttled);
}

/**
* Variant that takes factory to use: needed when multiple parsers are to
* share symbol table (as they do when created by same factory).
*
* @since 2.18.10
*/
protected CBORParser cborParser(CBORFactory f, byte[] input, boolean throttled)
throws IOException
{
if (throttled) {
return cborParser(f, new ThrottledInputStream(input, 1));
}
return cborParser(f, input);
}

protected CBORParser cborParser(CBORFactory f, InputStream in) throws IOException {
return f.createParser(in);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.fasterxml.jackson.dataformat.cbor.parse;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import com.fasterxml.jackson.core.JsonGenerator;
Expand All @@ -11,14 +10,11 @@

import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
import com.fasterxml.jackson.dataformat.cbor.testutil.ThrottledInputStream;

// [dataformats-binary#727]: `nextFieldName(SerializableString)` used to confuse
// 5-bit length marker 23 (length as-is) with 24 ("1-byte length suffix follows")
public class NextFieldName727Test extends CBORTestBase
{
private final CBORFactory F = new CBORFactory();

// Name of exactly 23 bytes, starting with control character U+0017 (0x17):
// the mis-decoded length marker used to consume that first name byte as
// the length, which made a 23-character name of a different value match
Expand All @@ -39,8 +35,8 @@ public void testNoFalseMatchForLength23Name() throws Exception
final String actualName = "\u0017" + repeat('a', 22);
final SerializableString differentName = new SerializedString(repeat('a', 23));

for (boolean stream : new boolean[] { false, true }) {
try (JsonParser p = _parser(DOC, stream)) {
for (boolean throttled : new boolean[] { false, true }) {
try (JsonParser p = cborParser(DOC, throttled)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertFalse(p.nextFieldName(differentName));
assertToken(JsonToken.FIELD_NAME, p.currentToken());
Expand All @@ -52,7 +48,7 @@ public void testNoFalseMatchForLength23Name() throws Exception
}

// ... and the actual name of course still matches
try (JsonParser p = _parser(DOC, stream)) {
try (JsonParser p = cborParser(DOC, throttled)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertTrue(p.nextFieldName(new SerializedString(actualName)));
assertToken(JsonToken.FIELD_NAME, p.currentToken());
Expand All @@ -68,31 +64,35 @@ public void testNoFalseMatchForLength23Name() throws Exception
// fast path may see (inline length, 1-byte suffix, 2-byte suffix)
public void testNameLengths() throws Exception
{
// NOTE: same factory for all parsers on purpose, so that later parsers
// will find names decoded by earlier ones from (shared) symbol table
final CBORFactory f = cborFactory();

for (int len : new int[] { 1, 2, 3, 22, 23, 24, 25, 100, 255, 256, 1000 }) {
final String name = repeat('a', len - 1) + 'b';
final String differentName = repeat('a', len - 1) + 'c';

ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (JsonGenerator g = F.createGenerator(bytes)) {
try (JsonGenerator g = cborGenerator(f, bytes)) {
g.writeStartObject();
g.writeStringField(name, "v");
g.writeEndObject();
}
final byte[] doc = bytes.toByteArray();

for (boolean stream : new boolean[] { false, true }) {
try (JsonParser p = _parser(doc, stream)) {
for (boolean throttled : new boolean[] { false, true }) {
try (JsonParser p = cborParser(f, doc, throttled)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertTrue("Should match name of length "+len+" (stream? "+stream+")",
assertTrue("Should match name of length "+len+" (throttled? "+throttled+")",
p.nextFieldName(new SerializedString(name)));
assertEquals(name, p.currentName());
assertEquals("v", p.nextTextValue());
assertToken(JsonToken.END_OBJECT, p.nextToken());
assertNull(p.nextToken());
}
try (JsonParser p = _parser(doc, stream)) {
try (JsonParser p = cborParser(f, doc, throttled)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertFalse("Should not match different name of length "+len+" (stream? "+stream+")",
assertFalse("Should not match different name of length "+len+" (throttled? "+throttled+")",
p.nextFieldName(new SerializedString(differentName)));
assertToken(JsonToken.FIELD_NAME, p.currentToken());
assertEquals(name, p.currentName());
Expand Down Expand Up @@ -120,7 +120,7 @@ public void testNonCanonicalLengthEncoding() throws Exception
bytes.write(0xFF); // end indefinite-length Object
final byte[] DOC = bytes.toByteArray();

try (JsonParser p = _parser(DOC, false)) {
try (JsonParser p = cborParser(DOC)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertTrue(p.nextFieldName(new SerializedString(name)));
assertEquals(name, p.currentName());
Expand All @@ -129,22 +129,14 @@ public void testNonCanonicalLengthEncoding() throws Exception
assertNull(p.nextToken());
}

try (JsonParser p = _parser(DOC, false)) {
try (JsonParser p = cborParser(DOC)) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
assertFalse(p.nextFieldName(new SerializedString("abcdX")));
assertToken(JsonToken.FIELD_NAME, p.currentToken());
assertEquals(name, p.currentName());
}
}

private JsonParser _parser(byte[] doc, boolean stream) throws Exception {
if (!stream) {
return F.createParser(doc);
}
// read one byte at a time, to force the "not enough buffered" fallback
return F.createParser(new ThrottledInputStream(new ByteArrayInputStream(doc), 1));
}

private String repeat(char c, int count) {
StringBuilder sb = new StringBuilder(count);
for (int i = 0; i < count; ++i) {
Expand Down
Loading
Loading