Skip to content

Commit 66c070d

Browse files
committed
Merge branch '3.2' into 3.x
2 parents 6b4a8b4 + af62141 commit 66c070d

6 files changed

Lines changed: 114 additions & 3 deletions

File tree

release-notes/CREDITS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@ Rob Spoor (@robtimus)
4545
* Reported #1561: Javadoc for 3.1.0 doesn't work (build with JDK 21)
4646
(3.1.2)
4747

48-
4948
Revanth Meesala (@revanthmeesala)
5049
* Contributed #1642: Fix maxDocumentLength bypass in async parser single-feedInput()
5150
case [GHSA-2c4j-63jj-9fqr]
5251
(3.1.6)
5352

53+
@tinyb0y
54+
* Contributed #1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser
55+
[GHSA-649p-m576-vr99]
56+
(3.1.6)
57+
5458
seonwoojung (@seonwooj0810)
5559
* Contributed #707: Add `JsonReadFeature.ALLOW_HEXADECIMAL_NUMBERS`
5660
for JSON5-style hexadecimal integer literals

release-notes/CREDITS-2.x

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,8 @@ Revanth Meesala (@revanthmeesala)
535535
* Contributed #1642: Fix maxDocumentLength bypass in async parser single-feedInput()
536536
case [GHSA-2c4j-63jj-9fqr]
537537
(2.18.10)
538+
539+
@tinyb0y
540+
* Contributed #1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser
541+
[GHSA-649p-m576-vr99]
542+
(2.18.10)

release-notes/VERSION

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ JSON library.
2929
#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case
3030
[GHSA-2c4j-63jj-9fqr]
3131
(fix by Revanth M)
32+
#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [GHSA-649p-m576-vr99]
33+
(fix by @tinyb0y)
3234

3335
3.2.1 (10-Jul-2026)
3436

@@ -67,6 +69,8 @@ No changes since 3.2.0
6769
#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case
6870
[GHSA-2c4j-63jj-9fqr]
6971
(fix by Revanth M)
72+
#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [GHSA-649p-m576-vr99]
73+
(fix by @tinyb0y)
7074

7175
3.1.5 (07-Jul-2025)
7276

release-notes/VERSION-2.x

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,27 @@ a pure JSON library.
2121
(requested by @kilink)
2222
(contributed by @seonwooj0810)
2323

24+
2.22.2 (not yet released)
25+
26+
#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case
27+
[GHSA-2c4j-63jj-9fqr]
28+
(fix by Revanth M)
29+
#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [GHSA-649p-m576-vr99]
30+
(fix by @tinyb0y)
31+
2432
2.22.1 (07-Jul-2026)
2533
2.22.0 (03-Jun-2026)
2634

2735
No changes since 2.21
2836

37+
2.21.6 (not yet released)
38+
39+
#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case
40+
[GHSA-2c4j-63jj-9fqr]
41+
(fix by Revanth M)
42+
#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [GHSA-649p-m576-vr99]
43+
(fix by @tinyb0y)
44+
2945
2.21.5 (06-Jul-2026)
3046

3147
No changes since 2.21.4
@@ -162,6 +178,8 @@ No changes since 2.19.1
162178
#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case
163179
[GHSA-2c4j-63jj-9fqr]
164180
(fix by Revanth M)
181+
#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [GHSA-649p-m576-vr99]
182+
(fix by @tinyb0y)
165183

166184
2.18.9 (07-Jul-2026)
167185

src/main/java/tools/jackson/core/json/ReaderBasedJsonParser.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,15 @@ private String _parseName2(int startPtr, int hash, int endChar) throws JacksonEx
19481948
*/
19491949
char[] outBuf = _textBuffer.getCurrentSegment();
19501950
int outPtr = _textBuffer.getCurrentSegmentSize();
1951+
// 28-Jul-2026, tinyb0y: [core#1643] Track total length accumulated so far so we
1952+
// can validate against `maxNameLength` incrementally, same as byte-based
1953+
// parsers already do via `ParserBase._growNameDecodeBuffer()`. Without this,
1954+
// only the much larger `maxStringLength` bound (enforced inside
1955+
// `TextBuffer.finishCurrentSegment()`) applies until the whole name has
1956+
// already been buffered.
1957+
// Note: only updated when segment gets full (at which point `outPtr` is
1958+
// always exactly `outBuf.length`), to keep the per-character loop tight.
1959+
int totalLen = 0;
19511960

19521961
while (true) {
19531962
if (_inputPtr >= _inputEnd) {
@@ -1979,6 +1988,8 @@ private String _parseName2(int startPtr, int hash, int endChar) throws JacksonEx
19791988

19801989
// Need more room?
19811990
if (outPtr >= outBuf.length) {
1991+
totalLen += outBuf.length;
1992+
_streamReadConstraints.validateNameLength(totalLen);
19821993
outBuf = _textBuffer.finishCurrentSegment();
19831994
outPtr = 0;
19841995
}
@@ -2208,6 +2219,9 @@ private String _handleOddName2(int startPtr, int hash, int[] codes) throws Jacks
22082219
char[] outBuf = _textBuffer.getCurrentSegment();
22092220
int outPtr = _textBuffer.getCurrentSegmentSize();
22102221
final int maxCode = codes.length;
2222+
// 28-Jul-2026, tinyb0y: [core#1643] Same incremental `maxNameLength` check as
2223+
// `_parseName2()` needs to apply to unquoted ("odd") names as well
2224+
int totalLen = 0;
22112225

22122226
while (true) {
22132227
if (_inputPtr >= _inputEnd) {
@@ -2231,6 +2245,8 @@ private String _handleOddName2(int startPtr, int hash, int[] codes) throws Jacks
22312245

22322246
// Need more room?
22332247
if (outPtr >= outBuf.length) {
2248+
totalLen += outBuf.length;
2249+
_streamReadConstraints.validateNameLength(totalLen);
22342250
outBuf = _textBuffer.finishCurrentSegment();
22352251
outPtr = 0;
22362252
}

src/test/java/tools/jackson/core/unittest/constraints/LargeNameReadTest.java

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package tools.jackson.core.unittest.constraints;
22

33
import java.io.IOException;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
46

57
import org.junit.jupiter.api.Test;
68

@@ -10,6 +12,7 @@
1012
import tools.jackson.core.async.ByteArrayFeeder;
1113
import tools.jackson.core.exc.StreamConstraintsException;
1214
import tools.jackson.core.json.JsonFactory;
15+
import tools.jackson.core.json.JsonReadFeature;
1316
import tools.jackson.core.unittest.*;
1417

1518
import static org.junit.jupiter.api.Assertions.fail;
@@ -23,6 +26,13 @@ class LargeNameReadTest extends JacksonCoreTestBase
2326
.streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build())
2427
.build();
2528

29+
// Factory that also allows non-standard name flavors ("apostrophe" and unquoted)
30+
private final JsonFactory JSON_F_NAME_100_ODD = JsonFactory.builder()
31+
.streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build())
32+
.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
33+
.configure(JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES, true)
34+
.build();
35+
2636
// Test name that is below default max name
2737
@Test
2838
void largeNameBytes() throws Exception {
@@ -72,6 +82,55 @@ private void _testLargeNameWithSmallLimitChars(JsonFactory jf) throws Exception
7282
}
7383
}
7484

85+
// [core#1643]: Reader-backed parser must reject an over-limit name promptly, the
86+
// same way byte-based input already does -- not only once the entire (possibly
87+
// huge) name has already been buffered.
88+
// (note: `String` / `char[]` input is not affected the same way, since the whole
89+
// document is already in memory and gets scanned in-place, without buffering)
90+
@Test
91+
void largeNameWithSmallLimitCharsFailsFast() throws Exception {
92+
// Name much larger than the configured limit: without incremental checking
93+
// the whole name gets buffered (bounded only by much bigger `maxStringLength`)
94+
// before failing, whereas the fix must reject within a segment fill or two.
95+
_testLargeNameFailsFast(JSON_F_NAME_100, "\"");
96+
}
97+
98+
// [core#1643]: ... and same goes for the non-standard name flavors, which are
99+
// decoded by different code paths ("apostrophe" and unquoted names)
100+
@Test
101+
void largeOddNameWithSmallLimitCharsFailsFast() throws Exception {
102+
_testLargeNameFailsFast(JSON_F_NAME_100_ODD, "'");
103+
_testLargeNameFailsFast(JSON_F_NAME_100_ODD, "");
104+
}
105+
106+
private void _testLargeNameFailsFast(JsonFactory jf, String nameQuote) throws Exception
107+
{
108+
final int nameLen = 1_000_000;
109+
final String doc = generateJSON(nameLen, nameQuote);
110+
try (JsonParser p = createParserUsingReader(jf, doc)) {
111+
consumeTokens(p);
112+
fail("expected StreamConstraintsException");
113+
} catch (StreamConstraintsException e) {
114+
verifyException(e, "Name length");
115+
// Length the exception reports tells us how much had been accumulated
116+
// before the check fired: needs to be small fraction of the whole name
117+
final int reportedLen = _reportedNameLength(e);
118+
final int maxExpected = nameLen >> 4;
119+
if (reportedLen > maxExpected) {
120+
fail("Should have failed before buffering "+maxExpected
121+
+" chars (limit is 100), but reported length was: "+reportedLen);
122+
}
123+
}
124+
}
125+
126+
private int _reportedNameLength(StreamConstraintsException e) {
127+
Matcher m = Pattern.compile("Name length \\((\\d+)\\)").matcher(e.getMessage());
128+
if (!m.find()) {
129+
fail("Could not find reported name length from message: "+e.getMessage());
130+
}
131+
return Integer.parseInt(m.group(1));
132+
}
133+
75134
@Test
76135
void largeNameWithSmallLimitAsync() throws Exception
77136
{
@@ -109,12 +168,17 @@ private void consumeTokens(JsonParser p) throws IOException {
109168
}
110169

111170
private String generateJSON(final int nameLen) {
171+
return generateJSON(nameLen, "\"");
172+
}
173+
174+
// @param nameQuote Quote character to use around name; empty String for unquoted name
175+
private String generateJSON(final int nameLen, final String nameQuote) {
112176
final StringBuilder sb = new StringBuilder();
113-
sb.append("{\"");
177+
sb.append("{").append(nameQuote);
114178
for (int i = 0; i < nameLen; i++) {
115179
sb.append("a");
116180
}
117-
sb.append("\":\"value\"}");
181+
sb.append(nameQuote).append(":\"value\"}");
118182
return sb.toString();
119183
}
120184
}

0 commit comments

Comments
 (0)