Skip to content

Commit c96d3a1

Browse files
juergwcopybara-github
authored andcommittedAug 31, 2022
Implement a stricter JSON Parser, using Gson's JsonReader.
The parser is identical to the TypeAdapters.JSON_ELEMENT parser, with the following modifications: - it rejects duplicated map entries. - does not support for JsonTreeReader, since we only use JsonReader. PiperOrigin-RevId: 471250043 Change-Id: I01a73414e694453779b6f57fd420caff06316456
1 parent 864f953 commit c96d3a1

File tree

5 files changed

+732
-0
lines changed

5 files changed

+732
-0
lines changed
 

‎BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ gen_maven_jar_rules(
156156
"//src/main/java/com/google/crypto/tink/internal:proto_parameters_serialization",
157157
"//src/main/java/com/google/crypto/tink/internal:serialization",
158158
"//src/main/java/com/google/crypto/tink/internal:serialization_registry",
159+
"//src/main/java/com/google/crypto/tink/internal:strict_json_parser",
159160
"//src/main/java/com/google/crypto/tink/internal:tink_bug_exception",
160161
"//src/main/java/com/google/crypto/tink/internal:util",
161162
"//src/main/java/com/google/crypto/tink/jwt:json_util",
@@ -460,6 +461,7 @@ gen_maven_jar_rules(
460461
"//src/main/java/com/google/crypto/tink/internal:proto_parameters_serialization-android",
461462
"//src/main/java/com/google/crypto/tink/internal:serialization-android",
462463
"//src/main/java/com/google/crypto/tink/internal:serialization_registry-android",
464+
"//src/main/java/com/google/crypto/tink/internal:strict_json_parser-android",
463465
"//src/main/java/com/google/crypto/tink/internal:tink_bug_exception-android",
464466
"//src/main/java/com/google/crypto/tink/internal:util-android",
465467
"//src/main/java/com/google/crypto/tink/jwt:json_util-android",

‎src/main/java/com/google/crypto/tink/internal/BUILD.bazel

+18
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,21 @@ java_library(
494494
srcs = ["BuildDispatchedCode.java"],
495495
deps = ["@maven//:com_google_code_findbugs_jsr305"],
496496
)
497+
498+
java_library(
499+
name = "strict_json_parser",
500+
srcs = ["StrictJsonParser.java"],
501+
deps = [
502+
"@maven//:com_google_code_findbugs_jsr305",
503+
"@maven//:com_google_code_gson_gson",
504+
],
505+
)
506+
507+
android_library(
508+
name = "strict_json_parser-android",
509+
srcs = ["StrictJsonParser.java"],
510+
deps = [
511+
"@maven//:com_google_code_findbugs_jsr305",
512+
"@maven//:com_google_code_gson_gson",
513+
],
514+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 2011 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.crypto.tink.internal;
18+
19+
import com.google.gson.JsonArray;
20+
import com.google.gson.JsonElement;
21+
import com.google.gson.JsonNull;
22+
import com.google.gson.JsonObject;
23+
import com.google.gson.JsonPrimitive;
24+
import com.google.gson.TypeAdapter;
25+
import com.google.gson.internal.LazilyParsedNumber;
26+
import com.google.gson.stream.JsonReader;
27+
import com.google.gson.stream.JsonToken;
28+
import com.google.gson.stream.JsonWriter;
29+
import java.io.IOException;
30+
import java.io.StringReader;
31+
import java.util.ArrayDeque;
32+
import java.util.Deque;
33+
import javax.annotation.Nullable;
34+
35+
/**
36+
* Implementation of a Strict JSON Parser.
37+
*
38+
* <p>The parsing is almost identical to TypeAdapters.JSON_ELEMENT, but it rejects duplicated map
39+
* keys.
40+
*/
41+
final class StrictJsonParser {
42+
43+
private static final TypeAdapter<JsonElement> JSON_ELEMENT =
44+
new TypeAdapter<JsonElement>() {
45+
/**
46+
* Tries to begin reading a JSON array or JSON object, returning {@code null} if the next
47+
* element is neither of those.
48+
*/
49+
@Nullable
50+
private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
51+
switch (peeked) {
52+
case BEGIN_ARRAY:
53+
in.beginArray();
54+
return new JsonArray();
55+
case BEGIN_OBJECT:
56+
in.beginObject();
57+
return new JsonObject();
58+
default:
59+
return null;
60+
}
61+
}
62+
63+
/** Reads a {@link JsonElement} which cannot have any nested elements */
64+
private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException {
65+
switch (peeked) {
66+
case STRING:
67+
// TODO(juerg): Add additional validation, as in jwt/JsonUtil.java.
68+
return new JsonPrimitive(in.nextString());
69+
case NUMBER:
70+
String number = in.nextString();
71+
return new JsonPrimitive(new LazilyParsedNumber(number));
72+
case BOOLEAN:
73+
return new JsonPrimitive(in.nextBoolean());
74+
case NULL:
75+
in.nextNull();
76+
return JsonNull.INSTANCE;
77+
default:
78+
// When read(JsonReader) is called with JsonReader in invalid state
79+
throw new IllegalStateException("Unexpected token: " + peeked);
80+
}
81+
}
82+
83+
@Override
84+
public JsonElement read(JsonReader in) throws IOException {
85+
// Either JsonArray or JsonObject
86+
JsonElement current;
87+
JsonToken peeked = in.peek();
88+
89+
current = tryBeginNesting(in, peeked);
90+
if (current == null) {
91+
return readTerminal(in, peeked);
92+
}
93+
94+
Deque<JsonElement> stack = new ArrayDeque<>();
95+
96+
while (true) {
97+
while (in.hasNext()) {
98+
String name = null;
99+
// Name is only used for JSON object members
100+
if (current instanceof JsonObject) {
101+
name = in.nextName();
102+
// TODO(juerg): Add additional validation, as in jwt/JsonUtil.java.
103+
}
104+
105+
peeked = in.peek();
106+
JsonElement value = tryBeginNesting(in, peeked);
107+
boolean isNesting = value != null;
108+
109+
if (value == null) {
110+
value = readTerminal(in, peeked);
111+
}
112+
113+
if (current instanceof JsonArray) {
114+
((JsonArray) current).add(value);
115+
} else {
116+
if (((JsonObject) current).has(name)) {
117+
throw new IOException("duplicate key: " + name);
118+
}
119+
((JsonObject) current).add(name, value);
120+
}
121+
122+
if (isNesting) {
123+
stack.addLast(current);
124+
current = value;
125+
}
126+
}
127+
128+
// End current element
129+
if (current instanceof JsonArray) {
130+
in.endArray();
131+
} else {
132+
in.endObject();
133+
}
134+
135+
if (stack.isEmpty()) {
136+
return current;
137+
} else {
138+
// Continue with enclosing element
139+
current = stack.removeLast();
140+
}
141+
}
142+
}
143+
144+
@Override
145+
public void write(JsonWriter out, JsonElement value) {
146+
throw new UnsupportedOperationException("write is not supported");
147+
}
148+
};
149+
150+
public static JsonElement parse(String json) throws IOException {
151+
try {
152+
JsonReader jsonReader = new JsonReader(new StringReader(json));
153+
jsonReader.setLenient(false);
154+
return JSON_ELEMENT.read(jsonReader);
155+
} catch (NumberFormatException e) {
156+
throw new IOException(e);
157+
}
158+
}
159+
160+
private StrictJsonParser() {}
161+
}

‎src/test/java/com/google/crypto/tink/internal/BUILD.bazel

+12
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,15 @@ java_test(
284284
"@maven//:junit_junit",
285285
],
286286
)
287+
288+
java_test(
289+
name = "StrictJsonParserTest",
290+
size = "small",
291+
srcs = ["StrictJsonParserTest.java"],
292+
deps = [
293+
"//src/main/java/com/google/crypto/tink/internal:strict_json_parser",
294+
"@maven//:com_google_code_gson_gson",
295+
"@maven//:com_google_truth_truth",
296+
"@maven//:junit_junit",
297+
],
298+
)

‎src/test/java/com/google/crypto/tink/internal/StrictJsonParserTest.java

+539
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.