This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 50a92ec4496 Add a depth limit for JSON parsing mirroring what Jettison
does (#3149)
50a92ec4496 is described below
commit 50a92ec4496ef25bbd76a25ddc5f3e4a2e26fc28
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed May 27 15:13:15 2026 +0100
Add a depth limit for JSON parsing mirroring what Jettison does (#3149)
---
.../json/basic/JsonMapObjectReaderWriter.java | 30 +++++++++++++++--
.../json/basic/JsonMapObjectReaderWriterTest.java | 38 ++++++++++++++++++++++
2 files changed, 65 insertions(+), 3 deletions(-)
diff --git
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
index 3522a6a496c..7ea3ca1d926 100644
---
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
+++
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
@@ -30,6 +30,13 @@ import org.apache.cxf.helpers.IOUtils;
public class JsonMapObjectReaderWriter {
+ /**
+ * Maximum JSON nesting depth accepted by the parser, matching Jettison's
default
+ * {@code RECURSION_DEPTH_LIMIT} of 500. Payloads nested more deeply than
this
+ * throw an {@link java.io.UncheckedIOException} rather than exhausting
the JVM
+ * thread stack with unbounded recursion.
+ */
+ static final int MAX_RECURSION_DEPTH = 500;
private static final Set<Character> ESCAPED_CHARS;
private static final char DQUOTE = '"';
private static final char COMMA = ',';
@@ -185,6 +192,14 @@ public class JsonMapObjectReaderWriter {
return internalFromJsonAsList(name, theJson.substring(1,
theJson.length() - 1));
}
protected void readJsonObjectAsSettable(Settable values, String json) {
+ readJsonObjectAsSettable(values, json, 0);
+ }
+
+ private void readJsonObjectAsSettable(Settable values, String json, int
depth) {
+ if (depth > MAX_RECURSION_DEPTH) {
+ throw new UncheckedIOException(new IOException(
+ "JSON nesting depth exceeds maximum of " +
MAX_RECURSION_DEPTH));
+ }
for (int i = 0; i < json.length(); i++) {
if (Character.isWhitespace(json.charAt(i))) {
continue;
@@ -207,13 +222,13 @@ public class JsonMapObjectReaderWriter {
int closingIndex = getClosingIndex(json, OBJECT_START,
OBJECT_END, sepIndex + j);
String newJson = json.substring(sepIndex + j + 1,
closingIndex);
MapSettable nextMap = new MapSettable();
- readJsonObjectAsSettable(nextMap, newJson);
+ readJsonObjectAsSettable(nextMap, newJson, depth + 1);
values.put(name, nextMap.map);
i = closingIndex + 1;
} else if (json.charAt(sepIndex + j) == ARRAY_START) {
int closingIndex = getClosingIndex(json, ARRAY_START,
ARRAY_END, sepIndex + j);
String newJson = json.substring(sepIndex + j + 1,
closingIndex);
- values.put(name, internalFromJsonAsList(name, newJson));
+ values.put(name, internalFromJsonAsList(name, newJson, depth +
1));
i = closingIndex + 1;
} else {
int commaIndex = getCommaIndex(json, sepIndex + j);
@@ -224,7 +239,16 @@ public class JsonMapObjectReaderWriter {
}
}
+
protected List<Object> internalFromJsonAsList(String name, String json) {
+ return internalFromJsonAsList(name, json, 0);
+ }
+
+ private List<Object> internalFromJsonAsList(String name, String json, int
depth) {
+ if (depth > MAX_RECURSION_DEPTH) {
+ throw new UncheckedIOException(new IOException(
+ "JSON nesting depth exceeds maximum of " +
MAX_RECURSION_DEPTH));
+ }
List<Object> values = new LinkedList<>();
for (int i = 0; i < json.length(); i++) {
if (Character.isWhitespace(json.charAt(i))) {
@@ -233,7 +257,7 @@ public class JsonMapObjectReaderWriter {
if (json.charAt(i) == OBJECT_START) {
int closingIndex = getClosingIndex(json, OBJECT_START,
OBJECT_END, i);
MapSettable nextMap = new MapSettable();
- readJsonObjectAsSettable(nextMap, json.substring(i + 1,
closingIndex));
+ readJsonObjectAsSettable(nextMap, json.substring(i + 1,
closingIndex), depth + 1);
values.add(nextMap.map);
i = closingIndex + 1;
} else {
diff --git
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
index 77b20e410a0..a8583cce40e 100644
---
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
+++
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
@@ -392,4 +392,42 @@ public class JsonMapObjectReaderWriterTest {
}
}
+ /**
+ * Add a test to check an exception is thrown on parsing deeply nested
JSON structures that exceed the
+ * recursion depth limit.
+ */
+ @Test(expected = UncheckedIOException.class)
+ public void testDepthLimitExceededThrowsUncheckedIOException() {
+ int levels = JsonMapObjectReaderWriter.MAX_RECURSION_DEPTH + 2;
+ StringBuilder sb = new StringBuilder(levels * 8);
+ for (int i = 0; i < levels; i++) {
+ sb.append("{\"a\":");
+ }
+ sb.append("\"v\"");
+ for (int i = 0; i < levels; i++) {
+ sb.append('}');
+ }
+ new JsonMapObjectReaderWriter().fromJson(sb.toString());
+ }
+
+ /**
+ * A payload with exactly {@code MAX_RECURSION_DEPTH + 1} brace levels
reaches a
+ * maximum internal depth of {@code MAX_RECURSION_DEPTH} — right at the
boundary —
+ * and must parse successfully.
+ */
+ @Test
+ public void testDepthLimitNotExceededParsesSuccessfully() {
+ int levels = JsonMapObjectReaderWriter.MAX_RECURSION_DEPTH + 1;
+ StringBuilder sb = new StringBuilder(levels * 8);
+ for (int i = 0; i < levels; i++) {
+ sb.append("{\"a\":");
+ }
+ sb.append("\"v\"");
+ for (int i = 0; i < levels; i++) {
+ sb.append('}');
+ }
+ // Should not throw
+ new JsonMapObjectReaderWriter().fromJson(sb.toString());
+ }
+
}