This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/json-write-depth in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 73df38f2db96bcb0cffaaea3ce2c2609e858e095 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Thu May 28 09:57:43 2026 +0100 Apply the JSON depth limit to writes as well --- .../json/basic/JsonMapObjectReaderWriter.java | 45 +++++++++++++++++----- .../json/basic/JsonMapObjectReaderWriterTest.java | 16 ++++++++ 2 files changed, 51 insertions(+), 10 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 7ea3ca1d926..f83e48ad3ad 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 @@ -75,13 +75,13 @@ public class JsonMapObjectReaderWriter { public String toJson(Map<String, Object> map) { StringBuilder sb = new StringBuilder(); - toJsonInternal(new StringBuilderOutput(sb), map); + toJsonInternal(new StringBuilderOutput(sb), map, 0); return sb.toString(); } public String toJson(List<Object> list) { StringBuilder sb = new StringBuilder(); - toJsonInternal(new StringBuilderOutput(sb), list); + toJsonInternal(new StringBuilderOutput(sb), list, 0); return sb.toString(); } @@ -90,29 +90,49 @@ public class JsonMapObjectReaderWriter { } public void toJson(Map<String, Object> map, OutputStream os) { - toJsonInternal(new StreamOutput(os), map); + toJsonInternal(new StreamOutput(os), map, 0); } protected void toJsonInternal(Output out, Map<String, Object> map) { + toJsonInternal(out, map, 0); + } + + private void toJsonInternal(Output out, Map<String, Object> map, int depth) { + if (depth > MAX_RECURSION_DEPTH) { + throw new UncheckedIOException(new IOException( + "JSON nesting depth exceeds maximum of " + MAX_RECURSION_DEPTH)); + } out.append(OBJECT_START); for (Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext();) { Map.Entry<String, Object> entry = it.next(); out.append(DQUOTE).append(escapeJson(entry.getKey())).append(DQUOTE); out.append(COLON); - toJsonInternal(out, entry.getValue(), it.hasNext()); + toJsonInternal(out, entry.getValue(), it.hasNext(), depth); } out.append(OBJECT_END); } protected void toJsonInternal(Output out, Object[] array) { - toJsonInternal(out, Arrays.asList(array)); + toJsonInternal(out, array, 0); + } + + private void toJsonInternal(Output out, Object[] array, int depth) { + toJsonInternal(out, Arrays.asList(array), depth); } protected void toJsonInternal(Output out, Collection<?> coll) { + toJsonInternal(out, coll, 0); + } + + private void toJsonInternal(Output out, Collection<?> coll, int depth) { + if (depth > MAX_RECURSION_DEPTH) { + throw new UncheckedIOException(new IOException( + "JSON nesting depth exceeds maximum of " + MAX_RECURSION_DEPTH)); + } out.append(ARRAY_START); formatIfNeeded(out); for (Iterator<?> iter = coll.iterator(); iter.hasNext();) { - toJsonInternal(out, iter.next(), iter.hasNext()); + toJsonInternal(out, iter.next(), iter.hasNext(), depth); } formatIfNeeded(out); out.append(ARRAY_END); @@ -120,16 +140,21 @@ public class JsonMapObjectReaderWriter { @SuppressWarnings("unchecked") protected void toJsonInternal(Output out, Object value, boolean hasNext) { + toJsonInternal(out, value, hasNext, 0); + } + + @SuppressWarnings("unchecked") + private void toJsonInternal(Output out, Object value, boolean hasNext, int depth) { if (value == null) { out.append(null); } else if (JsonMapObject.class.isAssignableFrom(value.getClass())) { - out.append(toJson((JsonMapObject)value)); + toJsonInternal(out, ((JsonMapObject)value).asMap(), depth + 1); } else if (value.getClass().isArray()) { - toJsonInternal(out, (Object[])value); + toJsonInternal(out, (Object[])value, depth + 1); } else if (Collection.class.isAssignableFrom(value.getClass())) { - toJsonInternal(out, (Collection<?>)value); + toJsonInternal(out, (Collection<?>)value, depth + 1); } else if (Map.class.isAssignableFrom(value.getClass())) { - toJsonInternal(out, (Map<String, Object>)value); + toJsonInternal(out, (Map<String, Object>)value, depth + 1); } else { boolean quotesNeeded = checkQuotesNeeded(value); if (quotesNeeded) { 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 a8583cce40e..805f56b5c2f 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 @@ -430,4 +430,20 @@ public class JsonMapObjectReaderWriterTest { new JsonMapObjectReaderWriter().fromJson(sb.toString()); } + @Test(expected = UncheckedIOException.class) + public void testWriterPathDeeplyNestedMapThrowsUncheckedIOException() { + new JsonMapObjectReaderWriter().toJson(createNestedMap(20000)); + } + + private Map<String, Object> createNestedMap(int depth) { + Map<String, Object> root = new HashMap<>(); + Map<String, Object> current = root; + for (int i = 0; i < depth; i++) { + Map<String, Object> child = new HashMap<>(); + current.put("k", child); + current = child; + } + return root; + } + }
