Skip to content

Commit 54f78a9

Browse files
authored
apply constraint to nesting depth (#943)
1 parent 566fff4 commit 54f78a9

11 files changed

+166
-50
lines changed

src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java

+14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public abstract class JsonStreamContext
5252
*/
5353
protected int _index;
5454

55+
/**
56+
* The nesting depth is a count of objects and arrays that have not
57+
* been closed, `{` and `[` respectively.
58+
*/
59+
protected int _nestingDepth;
60+
5561
/*
5662
/**********************************************************
5763
/* Life-cycle
@@ -118,6 +124,14 @@ protected JsonStreamContext(int type, int index) {
118124
*/
119125
public final boolean inObject() { return _type == TYPE_OBJECT; }
120126

127+
/**
128+
* The nesting depth is a count of objects and arrays that have not
129+
* been closed, `{` and `[` respectively.
130+
*/
131+
public final int getNestingDepth() {
132+
return _nestingDepth;
133+
}
134+
121135
/**
122136
* @return Type description String
123137
*

src/main/java/com/fasterxml/jackson/core/StreamReadConstraints.java

+64-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public class StreamReadConstraints
2525
{
2626
private static final long serialVersionUID = 1L;
2727

28+
/**
29+
* Default setting for maximum depth: see {@link Builder#maxNestingDepth(int)} for details.
30+
*/
31+
public static final int DEFAULT_MAX_DEPTH = 1000;
32+
2833
/**
2934
* Default setting for maximum number length: see {@link Builder#maxNumberLength(int)} for details.
3035
*/
@@ -35,16 +40,37 @@ public class StreamReadConstraints
3540
*/
3641
public static final int DEFAULT_MAX_STRING_LEN = 1_000_000;
3742

43+
protected final int _maxNestingDepth;
3844
protected final int _maxNumLen;
3945
protected final int _maxStringLen;
4046

4147
private static final StreamReadConstraints DEFAULT =
42-
new StreamReadConstraints(DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
48+
new StreamReadConstraints(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
4349

4450
public static final class Builder {
51+
private int maxNestingDepth;
4552
private int maxNumLen;
4653
private int maxStringLen;
4754

55+
/**
56+
* Sets the maximum nesting depth. The depth is a count of objects and arrays that have not
57+
* been closed, `{` and `[` respectively.
58+
*
59+
* @param maxNestingDepth the maximum depth
60+
*
61+
* @return this builder
62+
* @throws IllegalArgumentException if the maxNestingDepth is set to a negative value
63+
*
64+
* @since 2.15
65+
*/
66+
public Builder maxNestingDepth(final int maxNestingDepth) {
67+
if (maxNestingDepth < 0) {
68+
throw new IllegalArgumentException("Cannot set maxNestingDepth to a negative value");
69+
}
70+
this.maxNestingDepth = maxNestingDepth;
71+
return this;
72+
}
73+
4874
/**
4975
* Sets the maximum number length (in chars or bytes, depending on input context).
5076
* The default is 1000.
@@ -90,21 +116,23 @@ public Builder maxStringLength(final int maxStringLen) {
90116
}
91117

92118
Builder() {
93-
this(DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
119+
this(DEFAULT_MAX_DEPTH, DEFAULT_MAX_NUM_LEN, DEFAULT_MAX_STRING_LEN);
94120
}
95121

96-
Builder(final int maxNumLen, final int maxStringLen) {
122+
Builder(final int maxNestingDepth, final int maxNumLen, final int maxStringLen) {
123+
this.maxNestingDepth = maxNestingDepth;
97124
this.maxNumLen = maxNumLen;
98125
this.maxStringLen = maxStringLen;
99126
}
100127

101128
Builder(StreamReadConstraints src) {
129+
maxNestingDepth = src._maxNestingDepth;
102130
maxNumLen = src._maxNumLen;
103131
maxStringLen = src._maxStringLen;
104132
}
105133

106134
public StreamReadConstraints build() {
107-
return new StreamReadConstraints(maxNumLen, maxStringLen);
135+
return new StreamReadConstraints(maxNestingDepth, maxNumLen, maxStringLen);
108136
}
109137
}
110138

@@ -114,7 +142,8 @@ public StreamReadConstraints build() {
114142
/**********************************************************************
115143
*/
116144

117-
StreamReadConstraints(final int maxNumLen, final int maxStringLen) {
145+
StreamReadConstraints(final int maxNestingDepth, final int maxNumLen, final int maxStringLen) {
146+
_maxNestingDepth = maxNestingDepth;
118147
_maxNumLen = maxNumLen;
119148
_maxStringLen = maxStringLen;
120149
}
@@ -141,6 +170,16 @@ public Builder rebuild() {
141170
/**********************************************************************
142171
*/
143172

173+
/**
174+
* Accessor for maximum depth.
175+
* see {@link Builder#maxNestingDepth(int)} for details.
176+
*
177+
* @return Maximum allowed depth
178+
*/
179+
public int getMaxNestingDepth() {
180+
return _maxNestingDepth;
181+
}
182+
144183
/**
145184
* Accessor for maximum length of numbers to decode.
146185
* see {@link Builder#maxNumberLength(int)} for details.
@@ -169,7 +208,7 @@ public int getMaxStringLength() {
169208

170209
/**
171210
* Convenience method that can be used to verify that a floating-point
172-
* number of specified length does not exceed maximum specific by this
211+
* number of specified length does not exceed maximum specified by this
173212
* constraints object: if it does, a
174213
* {@link StreamConstraintsException}
175214
* is thrown.
@@ -223,4 +262,23 @@ public void validateStringLength(int length) throws StreamConstraintsException
223262
length, _maxStringLen));
224263
}
225264
}
265+
266+
/**
267+
* Convenience method that can be used to verify that the
268+
* nesting depth does not exceed the maximum specified by this
269+
* constraints object: if it does, a
270+
* {@link StreamConstraintsException}
271+
* is thrown.
272+
*
273+
* @param depth count of unclosed objects and arrays
274+
*
275+
* @throws StreamConstraintsException If depth exceeds maximum
276+
*/
277+
public void validateNestingDepth(int depth) throws StreamConstraintsException
278+
{
279+
if (depth > _maxNestingDepth) {
280+
throw new StreamConstraintsException(String.format("Depth (%d) exceeds the maximum allowed nesting depth (%d)",
281+
depth, _maxNestingDepth));
282+
}
283+
}
226284
}

src/main/java/com/fasterxml/jackson/core/base/ParserBase.java

+10
Original file line numberDiff line numberDiff line change
@@ -1536,4 +1536,14 @@ protected void loadMoreGuaranteed() throws IOException {
15361536

15371537
// Can't declare as deprecated, for now, but shouldn't be needed
15381538
protected void _finishString() throws IOException { }
1539+
1540+
protected final void createChildArrayContext(final int lineNr, final int colNr) throws IOException {
1541+
_parsingContext = _parsingContext.createChildArrayContext(lineNr, colNr);
1542+
_streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth());
1543+
}
1544+
1545+
protected final void createChildObjectContext(final int lineNr, final int colNr) throws IOException {
1546+
_parsingContext = _parsingContext.createChildObjectContext(lineNr, colNr);
1547+
_streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth());
1548+
}
15391549
}

src/main/java/com/fasterxml/jackson/core/json/JsonReadContext.java

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public JsonReadContext(JsonReadContext parent, DupDetector dups, int type, int l
6161
_lineNr = lineNr;
6262
_columnNr = colNr;
6363
_index = -1;
64+
_nestingDepth = parent == null ? 0 : parent._nestingDepth + 1;
6465
}
6566

6667
/**

src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java

+15-15
Original file line numberDiff line numberDiff line change
@@ -752,13 +752,13 @@ public final JsonToken nextToken() throws IOException
752752
break;
753753
case '[':
754754
if (!inObject) {
755-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
755+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
756756
}
757757
t = JsonToken.START_ARRAY;
758758
break;
759759
case '{':
760760
if (!inObject) {
761-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
761+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
762762
}
763763
t = JsonToken.START_OBJECT;
764764
break;
@@ -817,7 +817,7 @@ public final JsonToken nextToken() throws IOException
817817
return t;
818818
}
819819

820-
private final JsonToken _nextAfterName()
820+
private final JsonToken _nextAfterName() throws IOException
821821
{
822822
_nameCopied = false; // need to invalidate if it was copied
823823
JsonToken t = _nextToken;
@@ -827,9 +827,9 @@ private final JsonToken _nextAfterName()
827827

828828
// Also: may need to start new context?
829829
if (t == JsonToken.START_ARRAY) {
830-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
830+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
831831
} else if (t == JsonToken.START_OBJECT) {
832-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
832+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
833833
}
834834
return (_currToken = t);
835835
}
@@ -1165,10 +1165,10 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
11651165
}
11661166
switch (i) {
11671167
case '[':
1168-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1168+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
11691169
return (_currToken = JsonToken.START_ARRAY);
11701170
case '{':
1171-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1171+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
11721172
return (_currToken = JsonToken.START_OBJECT);
11731173
case 't':
11741174
_matchToken("true", 1);
@@ -1235,9 +1235,9 @@ public final String nextTextValue() throws IOException
12351235
return _textBuffer.contentsAsString();
12361236
}
12371237
if (t == JsonToken.START_ARRAY) {
1238-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1238+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
12391239
} else if (t == JsonToken.START_OBJECT) {
1240-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1240+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
12411241
}
12421242
return null;
12431243
}
@@ -1258,9 +1258,9 @@ public final int nextIntValue(int defaultValue) throws IOException
12581258
return getIntValue();
12591259
}
12601260
if (t == JsonToken.START_ARRAY) {
1261-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1261+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
12621262
} else if (t == JsonToken.START_OBJECT) {
1263-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1263+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
12641264
}
12651265
return defaultValue;
12661266
}
@@ -1281,9 +1281,9 @@ public final long nextLongValue(long defaultValue) throws IOException
12811281
return getLongValue();
12821282
}
12831283
if (t == JsonToken.START_ARRAY) {
1284-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1284+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
12851285
} else if (t == JsonToken.START_OBJECT) {
1286-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1286+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
12871287
}
12881288
return defaultValue;
12891289
}
@@ -1307,9 +1307,9 @@ public final Boolean nextBooleanValue() throws IOException
13071307
return Boolean.FALSE;
13081308
}
13091309
if (t == JsonToken.START_ARRAY) {
1310-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1310+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
13111311
} else if (t == JsonToken.START_OBJECT) {
1312-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1312+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
13131313
}
13141314
return null;
13151315
}

src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java

+13-13
Original file line numberDiff line numberDiff line change
@@ -716,10 +716,10 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
716716
}
717717
switch (i) {
718718
case '[':
719-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
719+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
720720
return (_currToken = JsonToken.START_ARRAY);
721721
case '{':
722-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
722+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
723723
return (_currToken = JsonToken.START_OBJECT);
724724
case 't':
725725
_matchToken("true", 1);
@@ -754,17 +754,17 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException
754754
return (_currToken = _handleUnexpectedValue(i));
755755
}
756756

757-
private final JsonToken _nextAfterName()
757+
private final JsonToken _nextAfterName() throws IOException
758758
{
759759
_nameCopied = false; // need to invalidate if it was copied
760760
JsonToken t = _nextToken;
761761
_nextToken = null;
762762

763763
// Also: may need to start new context?
764764
if (t == JsonToken.START_ARRAY) {
765-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
765+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
766766
} else if (t == JsonToken.START_OBJECT) {
767-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
767+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
768768
}
769769
return (_currToken = t);
770770
}
@@ -908,9 +908,9 @@ public String nextTextValue() throws IOException
908908
return _textBuffer.contentsAsString();
909909
}
910910
if (t == JsonToken.START_ARRAY) {
911-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
911+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
912912
} else if (t == JsonToken.START_OBJECT) {
913-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
913+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
914914
}
915915
return null;
916916
}
@@ -930,9 +930,9 @@ public int nextIntValue(int defaultValue) throws IOException
930930
return getIntValue();
931931
}
932932
if (t == JsonToken.START_ARRAY) {
933-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
933+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
934934
} else if (t == JsonToken.START_OBJECT) {
935-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
935+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
936936
}
937937
return defaultValue;
938938
}
@@ -952,9 +952,9 @@ public long nextLongValue(long defaultValue) throws IOException
952952
return getLongValue();
953953
}
954954
if (t == JsonToken.START_ARRAY) {
955-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
955+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
956956
} else if (t == JsonToken.START_OBJECT) {
957-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
957+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
958958
}
959959
return defaultValue;
960960
}
@@ -977,9 +977,9 @@ public Boolean nextBooleanValue() throws IOException
977977
return Boolean.FALSE;
978978
}
979979
if (t == JsonToken.START_ARRAY) {
980-
_parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
980+
createChildArrayContext(_tokenInputRow, _tokenInputCol);
981981
} else if (t == JsonToken.START_OBJECT) {
982-
_parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
982+
createChildObjectContext(_tokenInputRow, _tokenInputCol);
983983
}
984984
return null;
985985
}

0 commit comments

Comments
 (0)