Skip to content

Commit 3b36832

Browse files
committed
Merge branch '2.19'
2 parents 60953f0 + 796f49c commit 3b36832

5 files changed

Lines changed: 91 additions & 87 deletions

File tree

release-notes/CREDITS-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ Yanming Zhou (@quaff)
477477
Fawzi Essam (@iifawzi)
478478
* Contributed #633: Allow skipping `RS` CTRL-CHAR to support JSON Text Sequences
479479
(2.19.0)
480+
* Contributed #1144: `FilteringParserDelegate` can go into an infinite loop if underlying
481+
parser is non-blocking
482+
(2.19.0)
480483
481484
Eduard Gomoliako (@Gems)
482485
* Contributed #1356: Make `JsonGenerator::writeTypePrefix` method to not write a

release-notes/VERSION-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ a pure JSON library.
1919
#633: Allow skipping `RS` CTRL-CHAR to support JSON Text Sequences
2020
(requested by Yanming Z)
2121
(contributed by Fawzi E)
22+
#1144: `FilteringParserDelegate` can go into an infinite loop if underlying
23+
parser is non-blocking
24+
(reported by @simonbasle)
25+
(contributed by Fawzi E)
2226
#1328: Optimize handling of `JsonPointer.head()`
2327
#1356: Make `JsonGenerator::writeTypePrefix` method to not write a
2428
`WRAPPER_ARRAY` when `typeIdDef.id == null`

src/main/java/tools/jackson/core/filter/FilteringParserDelegate.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,66 @@ public class FilteringParserDelegate extends JsonParserDelegate
100100
* @param f Filter to use
101101
* @param inclusion Definition of inclusion criteria
102102
* @param allowMultipleMatches Whether to allow multiple matches
103+
*
104+
* @throws IllegalArgumentException if non-blocking (async) parser
105+
* (for which {@code parser.canParseAsync()} returns `true`) is
106+
* used -- doing so requires use of different constructor:
107+
* {@link #FilteringParserDelegate(JsonParser, TokenFilter, TokenFilter.Inclusion, boolean, boolean)}
103108
*/
104109
public FilteringParserDelegate(JsonParser p, TokenFilter f,
105110
TokenFilter.Inclusion inclusion, boolean allowMultipleMatches)
106111
{
107112
super(p);
113+
114+
_checkAsyncParser(p);
115+
116+
initializeFilters(f, inclusion, allowMultipleMatches);
117+
}
118+
119+
private static void _checkAsyncParser(JsonParser p) throws IllegalArgumentException {
120+
if (p.canParseAsync()) {
121+
throw new IllegalArgumentException(
122+
String.format(
123+
"%s is an asynchronous parser (canParseAsync() == true), " +
124+
"which requires explicit permission to be used: " +
125+
"to allow use, call constructor with `allowNonBlockingParser` passed as `true`",
126+
p.getClass().getSimpleName()
127+
)
128+
);
129+
}
130+
}
131+
132+
/**
133+
* @param p Parser to delegate calls to
134+
* @param f Filter to use
135+
* @param inclusion Definition of inclusion criteria
136+
* @param allowMultipleMatches Whether to allow multiple matches
137+
* @param allowNonBlockingParser If true, allows use of NonBlockingJsonParser: must
138+
* also feed all input to parser before calling nextToken on this delegate
139+
*
140+
* @throws IllegalArgumentException if NonBlockingJsonParser is used without explicit permission
141+
*/
142+
public FilteringParserDelegate(JsonParser p, TokenFilter f,
143+
TokenFilter.Inclusion inclusion, boolean allowMultipleMatches,
144+
boolean allowNonBlockingParser) {
145+
super(p);
146+
147+
if (!allowNonBlockingParser) {
148+
_checkAsyncParser(p);
149+
}
150+
151+
initializeFilters(f, inclusion, allowMultipleMatches);
152+
}
153+
154+
/**
155+
* Initializes filter-related fields
156+
*
157+
* @param f Filter to use
158+
* @param inclusion Definition of inclusion criteria
159+
* @param allowMultipleMatches Whether to allow multiple matches
160+
*/
161+
private void initializeFilters(TokenFilter f, TokenFilter.Inclusion inclusion, boolean allowMultipleMatches) {
108162
rootFilter = f;
109-
// and this is the currently active filter for root values
110163
_itemFilter = f;
111164
_headContext = TokenFilterContext.createRootContext(f);
112165
_inclusion = inclusion;
@@ -286,7 +339,9 @@ public JsonToken nextToken() throws JacksonException
286339
TokenFilter f;
287340

288341
switch (t.id()) {
289-
case ID_START_ARRAY:
342+
case JsonTokenId.ID_NOT_AVAILABLE:
343+
throw _constructReadException("`JsonToken.NOT_AVAILABLE` received: ensure all input is fed to the Parser before use");
344+
case ID_START_ARRAY:
290345
f = _itemFilter;
291346
if (f == TokenFilter.INCLUDE_ALL) {
292347
_headContext = _headContext.createChildArrayContext(f, null, true);

src/test/java/tools/jackson/core/unittest/json/async/AsyncTokenFilterTest.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import org.junit.jupiter.api.Test;
44

5-
import tools.jackson.core.JsonToken;
6-
import tools.jackson.core.ObjectReadContext;
5+
import tools.jackson.core.*;
76
import tools.jackson.core.exc.StreamReadException;
87
import tools.jackson.core.filter.FilteringParserDelegate;
8+
import tools.jackson.core.filter.JsonPointerBasedFilter;
99
import tools.jackson.core.filter.TokenFilter;
1010
import tools.jackson.core.filter.TokenFilter.Inclusion;
1111
import tools.jackson.core.json.JsonFactory;
@@ -28,13 +28,36 @@ public TokenFilter includeProperty(String name) {
2828
}
2929
};
3030

31+
@Test
32+
void filteredNonBlockingParserNotExplicitlyAllowed() throws Exception {
33+
NonBlockingByteArrayJsonParser nbParser = _nonBlockingParser();
34+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
35+
new FilteringParserDelegate(nbParser, TOKEN_FILTER, Inclusion.INCLUDE_ALL_AND_PATH, true)
36+
);
37+
38+
verifyException(exception, "NonBlockingByteArrayJsonParser");
39+
verifyException(exception, "(canParseAsync() == true)");
40+
}
41+
42+
@Test
43+
void filteringNonBlockingParserWithoutInputFed() throws Exception
44+
{
45+
NonBlockingByteArrayJsonParser nbParser = _nonBlockingParser();
46+
try (JsonParser filteringParser = new FilteringParserDelegate(nbParser,
47+
new JsonPointerBasedFilter("/second"),
48+
TokenFilter.Inclusion.ONLY_INCLUDE_ALL, false, true)) {
49+
StreamReadException e = assertThrows(StreamReadException.class, filteringParser::nextToken);
50+
verifyException(e, "JsonToken.NOT_AVAILABLE");
51+
}
52+
}
53+
3154
// Passes if (but only if) all content is actually available
3255
@Test
3356
public void testFilteredNonBlockingParserAllContent()
3457
{
3558
NonBlockingByteArrayJsonParser nbParser = _nonBlockingParser();
3659
FilteringParserDelegate filteredParser = new FilteringParserDelegate(nbParser,
37-
TOKEN_FILTER, Inclusion.INCLUDE_ALL_AND_PATH, true);
60+
TOKEN_FILTER, Inclusion.INCLUDE_ALL_AND_PATH, true, true);
3861
nbParser.feedInput(INPUT_BYTES, 0, INPUT_BYTES.length);
3962

4063
assertToken(JsonToken.START_OBJECT, filteredParser.nextToken());
@@ -53,7 +76,7 @@ public void testSkipChildrenFailOnSplit()
5376
{
5477
NonBlockingByteArrayJsonParser nbParser = _nonBlockingParser();
5578
FilteringParserDelegate filteredParser = new FilteringParserDelegate(nbParser,
56-
TOKEN_FILTER, Inclusion.INCLUDE_ALL_AND_PATH, true);
79+
TOKEN_FILTER, Inclusion.INCLUDE_ALL_AND_PATH, true, true);
5780
nbParser.feedInput(INPUT_BYTES, 0, 5);
5881

5982
assertToken(JsonToken.START_OBJECT, nbParser.nextToken());

src/test/java/tools/jackson/core/unittest/tofix/async/FilteringOnAsyncInfiniteLoop1144Test.java

Lines changed: 0 additions & 81 deletions
This file was deleted.

0 commit comments

Comments
 (0)