You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2026/05/27 09:35:02 UTC
(cxf) branch coheigea/json-depth-limit created (now 19b9a6c86e4)
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a change to branch coheigea/json-depth-limit
in repository https://gitbox.apache.org/repos/asf/cxf.git
at 19b9a6c86e4 Add a depth limit for JSON parsing mirroring what Jettison does
This branch includes the following new commits:
new 19b9a6c86e4 Add a depth limit for JSON parsing mirroring what Jettison does
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(cxf) 01/01: Add a depth limit for JSON parsing mirroring what Jettison does
Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch coheigea/json-depth-limit
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 19b9a6c86e4aaf0f941e62e2244bddab3b21a7e7
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Wed May 27 10:34:35 2026 +0100
Add a depth limit for JSON parsing mirroring what Jettison does
---
.../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());
+ }
+
}