11package tools .jackson .core .unittest .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
1012import tools .jackson .core .async .ByteArrayFeeder ;
1113import tools .jackson .core .exc .StreamConstraintsException ;
1214import tools .jackson .core .json .JsonFactory ;
15+ import tools .jackson .core .json .JsonReadFeature ;
1316import tools .jackson .core .unittest .*;
1417
1518import 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