Skip to content

Commit 265e2f4

Browse files
joerg1985diemol
andauthored
[java] Increased the max depth of new session payload (#12205)
Increased the max depth of new session payload Co-authored-by: Diego Molina <[email protected]>
1 parent ab6e4f8 commit 265e2f4

4 files changed

Lines changed: 86 additions & 49 deletions

File tree

java/src/org/openqa/selenium/json/Json.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ public class Json {
3838
private final JsonTypeCoercer fromJson = new JsonTypeCoercer();
3939

4040
public String toJson(Object toConvert) {
41+
return toJson(toConvert, JsonOutput.MAX_DEPTH);
42+
}
43+
44+
public String toJson(Object toConvert, int maxDepth) {
4145
try (Writer writer = new StringWriter();
42-
JsonOutput jsonOutput = newOutput(writer)) {
43-
jsonOutput.write(toConvert);
46+
JsonOutput jsonOutput = newOutput(writer)) {
47+
jsonOutput.write(toConvert, maxDepth);
4448
return writer.toString();
4549
} catch (IOException e) {
4650
throw new JsonException(e);

java/src/org/openqa/selenium/json/JsonOutput.java

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
public class JsonOutput implements Closeable {
5050
private static final Logger LOG = Logger.getLogger(JsonOutput.class.getName());
51-
private static final int MAX_DEPTH = 10;
51+
static final int MAX_DEPTH = 10;
5252

5353
private static final Predicate<Class<?>> GSON_ELEMENT;
5454

@@ -100,7 +100,7 @@ public class JsonOutput implements Closeable {
100100
ESCAPES = Collections.unmodifiableMap(builder);
101101
}
102102

103-
private final Map<Predicate<Class<?>>, SafeBiConsumer<Object, Integer>> converters;
103+
private final Map<Predicate<Class<?>>, DepthAwareConsumer> converters;
104104
private final Appendable appendable;
105105
private final Consumer<String> appender;
106106
private Deque<Node> stack;
@@ -126,32 +126,32 @@ public class JsonOutput implements Closeable {
126126

127127
// Order matters, since we want to handle null values first to avoid exceptions, and then then
128128
// common kinds of inputs next.
129-
Map<Predicate<Class<?>>, SafeBiConsumer<Object, Integer>> builder = new LinkedHashMap<>();
130-
builder.put(Objects::isNull, (obj, depth) -> append("null"));
131-
builder.put(CharSequence.class::isAssignableFrom, (obj, depth) -> append(asString(obj)));
132-
builder.put(Number.class::isAssignableFrom, (obj, depth) -> append(obj.toString()));
129+
Map<Predicate<Class<?>>, DepthAwareConsumer> builder = new LinkedHashMap<>();
130+
builder.put(Objects::isNull, (obj, maxDepth, depthRemaining) -> append("null"));
131+
builder.put(CharSequence.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString(obj)));
132+
builder.put(Number.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(obj.toString()));
133133
builder.put(
134-
Boolean.class::isAssignableFrom, (obj, depth) -> append((Boolean) obj ? "true" : "false"));
134+
Boolean.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append((Boolean) obj ? "true" : "false"));
135135
builder.put(
136136
Date.class::isAssignableFrom,
137-
(obj, depth) -> append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime()))));
137+
(obj, maxDepth, depthRemaining) -> append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime()))));
138138
builder.put(
139139
Instant.class::isAssignableFrom,
140-
(obj, depth) -> append(asString(DateTimeFormatter.ISO_INSTANT.format((Instant) obj))));
141-
builder.put(Enum.class::isAssignableFrom, (obj, depth) -> append(asString(obj)));
140+
(obj, maxDepth, depthRemaining) -> append(asString(DateTimeFormatter.ISO_INSTANT.format((Instant) obj))));
141+
builder.put(Enum.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString(obj)));
142142
builder.put(
143-
File.class::isAssignableFrom, (obj, depth) -> append(((File) obj).getAbsolutePath()));
144-
builder.put(URI.class::isAssignableFrom, (obj, depth) -> append(asString((obj).toString())));
143+
File.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(((File) obj).getAbsolutePath()));
144+
builder.put(URI.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString((obj).toString())));
145145
builder.put(
146146
URL.class::isAssignableFrom,
147-
(obj, depth) -> append(asString(((URL) obj).toExternalForm())));
148-
builder.put(UUID.class::isAssignableFrom, (obj, depth) -> append(asString(obj.toString())));
147+
(obj, maxDepth, depthRemaining) -> append(asString(((URL) obj).toExternalForm())));
148+
builder.put(UUID.class::isAssignableFrom, (obj, maxDepth, depthRemaining) -> append(asString(obj.toString())));
149149
builder.put(
150150
Level.class::isAssignableFrom,
151-
(obj, depth) -> append(asString(LogLevelMapping.getName((Level) obj))));
151+
(obj, maxDepth, depthRemaining) -> append(asString(LogLevelMapping.getName((Level) obj))));
152152
builder.put(
153153
GSON_ELEMENT,
154-
(obj, depth) -> {
154+
(obj, maxDepth, depthRemaining) -> {
155155
LOG.log(
156156
Level.WARNING,
157157
"Attempt to convert JsonElement from GSON. This functionality is deprecated. "
@@ -162,36 +162,36 @@ public class JsonOutput implements Closeable {
162162
// Special handling of asMap and toJson
163163
builder.put(
164164
cls -> getMethod(cls, "toJson") != null,
165-
(obj, depth) -> convertUsingMethod("toJson", obj, depth));
165+
(obj, maxDepth, depthRemaining) -> convertUsingMethod("toJson", obj, maxDepth, depthRemaining));
166166
builder.put(
167167
cls -> getMethod(cls, "asMap") != null,
168-
(obj, depth) -> convertUsingMethod("asMap", obj, depth));
168+
(obj, maxDepth, depthRemaining) -> convertUsingMethod("asMap", obj, maxDepth, depthRemaining));
169169
builder.put(
170170
cls -> getMethod(cls, "toMap") != null,
171-
(obj, depth) -> convertUsingMethod("toMap", obj, depth));
171+
(obj, maxDepth, depthRemaining) -> convertUsingMethod("toMap", obj, maxDepth, depthRemaining));
172172

173173
// And then the collection types
174174
builder.put(
175175
Collection.class::isAssignableFrom,
176-
(obj, depth) -> {
177-
if (depth < 1) {
176+
(obj, maxDepth, depthRemaining) -> {
177+
if (depthRemaining < 1) {
178178
throw new JsonException(
179-
"Reached the maximum depth of " + MAX_DEPTH + " while writing JSON");
179+
"Reached the maximum depth of " + maxDepth + " while writing JSON");
180180
}
181181
beginArray();
182182
((Collection<?>) obj)
183183
.stream()
184184
.filter(o -> (!(o instanceof Optional) || ((Optional<?>) o).isPresent()))
185-
.forEach(o -> write(o, depth - 1));
185+
.forEach(o -> write0(o, maxDepth, depthRemaining - 1));
186186
endArray();
187187
});
188188

189189
builder.put(
190190
Map.class::isAssignableFrom,
191-
(obj, depth) -> {
192-
if (depth < 1) {
191+
(obj, maxDepth, depthRemaining) -> {
192+
if (depthRemaining < 1) {
193193
throw new JsonException(
194-
"Reached the maximum depth of " + MAX_DEPTH + " while writing JSON");
194+
"Reached the maximum depth of " + maxDepth + " while writing JSON");
195195
}
196196
beginObject();
197197
((Map<?, ?>) obj)
@@ -200,45 +200,45 @@ public class JsonOutput implements Closeable {
200200
if (value instanceof Optional && !((Optional) value).isPresent()) {
201201
return;
202202
}
203-
name(String.valueOf(key)).write(value, depth - 1);
203+
name(String.valueOf(key)).write0(value, maxDepth, depthRemaining - 1);
204204
});
205205
endObject();
206206
});
207207
builder.put(
208208
Class::isArray,
209-
(obj, depth) -> {
210-
if (depth < 1) {
209+
(obj, maxDepth, depthRemaining) -> {
210+
if (depthRemaining < 1) {
211211
throw new JsonException(
212-
"Reached the maximum depth of " + MAX_DEPTH + " while writing JSON");
212+
"Reached the maximum depth of " + maxDepth + " while writing JSON");
213213
}
214214
beginArray();
215215
Stream.of((Object[]) obj)
216216
.filter(o -> (!(o instanceof Optional) || ((Optional<?>) o).isPresent()))
217-
.forEach(o -> write(o, depth - 1));
217+
.forEach(o -> write0(o, maxDepth, depthRemaining - 1));
218218
endArray();
219219
});
220220

221221
builder.put(
222222
Optional.class::isAssignableFrom,
223-
(obj, depth) -> {
223+
(obj, maxDepth, depthRemaining) -> {
224224
Optional<?> optional = (Optional<?>) obj;
225225
if (!optional.isPresent()) {
226226
append("null");
227227
return;
228228
}
229229

230-
write(optional.get(), depth);
230+
write0(optional.get(), maxDepth, depthRemaining);
231231
});
232232

233233
// Finally, attempt to convert as an object
234234
builder.put(
235235
cls -> true,
236-
(obj, depth) -> {
237-
if (depth < 1) {
236+
(obj, maxDepth, depthRemaining) -> {
237+
if (depthRemaining < 1) {
238238
throw new JsonException(
239-
"Reached the maximum depth of " + MAX_DEPTH + " while writing JSON");
239+
"Reached the maximum depth of " + maxDepth + " while writing JSON");
240240
}
241-
mapObject(obj, depth - 1);
241+
mapObject(obj, maxDepth, depthRemaining - 1);
242242
});
243243

244244
this.converters = Collections.unmodifiableMap(builder);
@@ -313,13 +313,17 @@ public JsonOutput write(Object value) {
313313
return write(value, MAX_DEPTH);
314314
}
315315

316-
public JsonOutput write(Object input, int depthRemaining) {
316+
public JsonOutput write(Object value, int maxDepth) {
317+
return write0(value, maxDepth, maxDepth);
318+
}
319+
320+
private JsonOutput write0(Object input, int maxDepth, int depthRemaining) {
317321
converters.entrySet().stream()
318322
.filter(entry -> entry.getKey().test(input == null ? null : input.getClass()))
319323
.findFirst()
320324
.map(Map.Entry::getValue)
321325
.orElseThrow(() -> new JsonException("Unable to write " + input))
322-
.consume(input, depthRemaining);
326+
.consume(input, maxDepth, depthRemaining);
323327

324328
return this;
325329
}
@@ -381,7 +385,7 @@ private Method getMethod(Class<?> clazz, String methodName) {
381385
}
382386
}
383387

384-
private JsonOutput convertUsingMethod(String methodName, Object toConvert, int depth) {
388+
private JsonOutput convertUsingMethod(String methodName, Object toConvert, int maxDepth, int depthRemaining) {
385389
try {
386390
Method method = getMethod(toConvert.getClass(), methodName);
387391
if (method == null) {
@@ -390,13 +394,13 @@ private JsonOutput convertUsingMethod(String methodName, Object toConvert, int d
390394
}
391395
Object value = method.invoke(toConvert);
392396

393-
return write(value, depth);
397+
return write0(value, maxDepth, depthRemaining);
394398
} catch (ReflectiveOperationException e) {
395399
throw new JsonException(e);
396400
}
397401
}
398402

399-
private void mapObject(Object toConvert, int depthRemaining) {
403+
private void mapObject(Object toConvert, int maxDepth, int depthRemaining) {
400404
if (toConvert instanceof Class) {
401405
write(((Class<?>) toConvert).getName());
402406
return;
@@ -420,7 +424,7 @@ private void mapObject(Object toConvert, int depthRemaining) {
420424
Object value = pd.getReadMethod().apply(toConvert);
421425
if (!Optional.empty().equals(value)) {
422426
name(pd.getName());
423-
write(value, depthRemaining - 1);
427+
write0(value, maxDepth, depthRemaining - 1);
424428
}
425429
}
426430
endObject();
@@ -479,7 +483,7 @@ public void write(String text) {
479483
}
480484

481485
@FunctionalInterface
482-
private interface SafeBiConsumer<T, U> {
483-
void consume(T t, U u);
486+
private interface DepthAwareConsumer {
487+
void consume(Object object, int maxDepth, int depthRemaining);
484488
}
485489
}

java/src/org/openqa/selenium/remote/NewSessionPayload.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public static NewSessionPayload create(Map<String, ?> source) {
123123
Require.precondition(
124124
source.containsKey("capabilities"), "New session payload must contain capabilities");
125125

126-
String json = new Json().toJson(Require.nonNull("Payload", source));
126+
String json = new Json().toJson(Require.nonNull("Payload", source), 100);
127127
return new NewSessionPayload(new StringReader(json));
128128
}
129129

java/test/org/openqa/selenium/json/JsonOutputTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ public String getCheese() {
719719
}
720720

721721
@Test
722-
void shouldRespectMaxDepth() {
722+
void shouldRespectDefaultMaxDepth() {
723723
StringBuilder builder = new StringBuilder();
724724

725725
JsonOutput jsonOutput = new Json().newOutput(builder);
@@ -738,6 +738,35 @@ void shouldRespectMaxDepth() {
738738
assertThatExceptionOfType(JsonException.class).isThrownBy(() -> jsonOutput.write(finalValue));
739739
}
740740

741+
@Test
742+
void shouldRespectCustomHigherMaxDepth() {
743+
shouldRespectMaxDepth(16);
744+
}
745+
746+
@Test
747+
void shouldRespectCustomLowerMaxDepth() {
748+
shouldRespectMaxDepth(8);
749+
}
750+
751+
void shouldRespectMaxDepth(int maxDepth) {
752+
StringBuilder builder = new StringBuilder();
753+
754+
JsonOutput jsonOutput = new Json().newOutput(builder);
755+
jsonOutput.beginArray();
756+
757+
Object value = emptyList();
758+
759+
for (int i = 0; i < maxDepth; i++) {
760+
jsonOutput.write(value, maxDepth);
761+
762+
value = singletonList(value);
763+
}
764+
765+
Object finalValue = value;
766+
767+
assertThatExceptionOfType(JsonException.class).isThrownBy(() -> jsonOutput.write(finalValue, maxDepth));
768+
}
769+
741770
private String convert(Object toConvert) {
742771
try (Writer writer = new StringWriter();
743772
JsonOutput jsonOutput = new Json().newOutput(writer)) {

0 commit comments

Comments
 (0)