11package com .fasterxml .jackson .core .constraints ;
22
33import java .io .IOException ;
4+ import java .util .regex .Matcher ;
5+ import java .util .regex .Pattern ;
46
57import org .junit .jupiter .api .Test ;
68
79import com .fasterxml .jackson .core .*;
810import com .fasterxml .jackson .core .exc .StreamConstraintsException ;
11+ import com .fasterxml .jackson .core .json .JsonReadFeature ;
912import com .fasterxml .jackson .core .json .async .NonBlockingJsonParser ;
1013
1114import static org .junit .jupiter .api .Assertions .fail ;
@@ -25,6 +28,13 @@ class LargeNameReadTest extends JUnit5TestBase
2528 .maxNameLength (100 ).build ());
2629 }
2730
31+ // Factory that also allows non-standard name flavors ("apostrophe" and unquoted)
32+ private final JsonFactory JSON_F_NAME_100_ODD = JsonFactory .builder ()
33+ .streamReadConstraints (StreamReadConstraints .builder ().maxNameLength (100 ).build ())
34+ .configure (JsonReadFeature .ALLOW_SINGLE_QUOTES , true )
35+ .configure (JsonReadFeature .ALLOW_UNQUOTED_FIELD_NAMES , true )
36+ .build ();
37+
2838 // Test name that is below default max name
2939 @ Test
3040 void largeNameBytes () throws Exception {
@@ -76,6 +86,56 @@ private void _testLargeNameWithSmallLimitChars(JsonFactory jf) throws Exception
7686 }
7787 }
7888
89+ // [core#1643]: Reader-backed parser must reject an over-limit name promptly, the
90+ // same way byte-based input already does -- not only once the entire (possibly
91+ // huge) name has already been buffered.
92+ // (note: `String` / `char[]` input is not affected the same way, since the whole
93+ // document is already in memory and gets scanned in-place, without buffering)
94+ @ Test
95+ void largeNameWithSmallLimitCharsFailsFast () throws Exception {
96+ // Name much larger than the configured limit: without incremental checking
97+ // the whole name gets buffered (bounded only by much bigger `maxStringLength`)
98+ // before failing, whereas the fix must reject within a segment fill or two.
99+ _testLargeNameFailsFast (JSON_F_NAME_100 , "\" " );
100+ _testLargeNameFailsFast (JSON_F_NAME_100_B , "\" " );
101+ }
102+
103+ // [core#1643]: ... and same goes for the non-standard name flavors, which are
104+ // decoded by different code paths ("apostrophe" and unquoted names)
105+ @ Test
106+ void largeOddNameWithSmallLimitCharsFailsFast () throws Exception {
107+ _testLargeNameFailsFast (JSON_F_NAME_100_ODD , "'" );
108+ _testLargeNameFailsFast (JSON_F_NAME_100_ODD , "" );
109+ }
110+
111+ private void _testLargeNameFailsFast (JsonFactory jf , String nameQuote ) throws Exception
112+ {
113+ final int nameLen = 1_000_000 ;
114+ final String doc = generateJSON (nameLen , nameQuote );
115+ try (JsonParser p = createParserUsingReader (jf , doc )) {
116+ consumeTokens (p );
117+ fail ("expected StreamConstraintsException" );
118+ } catch (StreamConstraintsException e ) {
119+ verifyException (e , "Name length" );
120+ // Length the exception reports tells us how much had been accumulated
121+ // before the check fired: needs to be small fraction of the whole name
122+ final int reportedLen = _reportedNameLength (e );
123+ final int maxExpected = nameLen >> 4 ;
124+ if (reportedLen > maxExpected ) {
125+ fail ("Should have failed before buffering " +maxExpected
126+ +" chars (limit is 100), but reported length was: " +reportedLen );
127+ }
128+ }
129+ }
130+
131+ private int _reportedNameLength (StreamConstraintsException e ) {
132+ Matcher m = Pattern .compile ("Name length \\ ((\\ d+)\\ )" ).matcher (e .getMessage ());
133+ if (!m .find ()) {
134+ fail ("Could not find reported name length from message: " +e .getMessage ());
135+ }
136+ return Integer .parseInt (m .group (1 ));
137+ }
138+
79139 @ Test
80140 void largeNameWithSmallLimitAsync () throws Exception
81141 {
@@ -115,12 +175,17 @@ private void consumeTokens(JsonParser p) throws IOException {
115175 }
116176
117177 private String generateJSON (final int nameLen ) {
178+ return generateJSON (nameLen , "\" " );
179+ }
180+
181+ // @param nameQuote Quote character to use around name; empty String for unquoted name
182+ private String generateJSON (final int nameLen , final String nameQuote ) {
118183 final StringBuilder sb = new StringBuilder ();
119- sb .append ("{\" " );
184+ sb .append ("{" ). append ( nameQuote );
120185 for (int i = 0 ; i < nameLen ; i ++) {
121186 sb .append ("a" );
122187 }
123- sb .append (" \ " :\" value\" }" );
188+ sb .append (nameQuote ). append ( ":\" value\" }" );
124189 return sb .toString ();
125190 }
126191}
0 commit comments