diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 0ebde3226a..3d76ef7da7 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -57,6 +57,8 @@ Project: jackson-databind #3559: Support `null`-valued `Map` fields with "any setter" #3568: Change `JsonNode.with(String)` and `withArray(String)` to consider argument as `JsonPointer` if valid expression +#3590: Add check in primitive value deserializers to avoid deep wrapper array + nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS` 2.13.4 (03-Sep-2022) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index 50284cd125..7e8b111ef8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid // either supporting nested arrays, or to cause infinite looping. if (p.hasToken(JsonToken.START_ARRAY)) { - String msg = String.format( -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s", - ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY, - "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS"); @SuppressWarnings("unchecked") - T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg); + T result = (T) handleNestedArrayForSingle(p, ctxt); return result; } return (T) deserialize(p, ctxt); @@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont case JsonTokenId.ID_START_ARRAY: // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (boolean) handleNestedArrayForSingle(p, ctxt); + } final boolean parsed = _parseBooleanPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion? // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (byte) handleNestedArrayForSingle(p, ctxt); + } final byte parsed = _parseBytePrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -652,7 +652,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext case JsonTokenId.ID_START_ARRAY: // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (short) handleNestedArrayForSingle(p, ctxt); + } final short parsed = _parseShortPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -719,7 +721,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt break; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (int) handleNestedArrayForSingle(p, ctxt); + } final int parsed = _parseIntPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -870,7 +874,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct break; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (long) handleNestedArrayForSingle(p, ctxt); + } final long parsed = _parseLongPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -1003,7 +1009,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext break; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (float) handleNestedArrayForSingle(p, ctxt); + } final float parsed = _parseFloatPrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -1132,7 +1140,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex break; case JsonTokenId.ID_START_ARRAY: if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - p.nextToken(); + if (p.nextToken() == JsonToken.START_ARRAY) { + return (double) handleNestedArrayForSingle(p, ctxt); + } final double parsed = _parseDoublePrimitive(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -1313,6 +1323,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex default: } } else if (unwrap) { + if (t == JsonToken.START_ARRAY) { + return (java.util.Date) handleNestedArrayForSingle(p, ctxt); + } final Date parsed = _parseDate(p, ctxt); _verifyEndArrayForSingle(p, ctxt); return parsed; @@ -2109,6 +2122,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte // but for now just fall through } + /** + * Helper method called when detecting a deep(er) nesting of Arrays when trying + * to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}. + * + * @since 2.14 + */ + protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException + { + String msg = String.format( +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s", + ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY, + "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS"); + return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg); + } + protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken t = p.nextToken(); diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java new file mode 100644 index 0000000000..e5b0f1eaf3 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java @@ -0,0 +1,95 @@ +package com.fasterxml.jackson.databind.deser.dos; + +import java.util.Date; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; + +public class DeepArrayWrappingForDeser3590Test extends BaseMapTest +{ + // 05-Sep-2022, tatu: Before fix, failed with 5000 + private final static int TOO_DEEP_NESTING = 9999; + + private final ObjectMapper MAPPER = jsonMapperBuilder() + .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) + .build(); + + private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123"); + + public void testArrayWrappingForBoolean() throws Exception + { + _testArrayWrappingFor(Boolean.class); + _testArrayWrappingFor(Boolean.TYPE); + } + + public void testArrayWrappingForByte() throws Exception + { + _testArrayWrappingFor(Byte.class); + _testArrayWrappingFor(Byte.TYPE); + } + + public void testArrayWrappingForShort() throws Exception + { + _testArrayWrappingFor(Short.class); + _testArrayWrappingFor(Short.TYPE); + } + + public void testArrayWrappingForInt() throws Exception + { + _testArrayWrappingFor(Integer.class); + _testArrayWrappingFor(Integer.TYPE); + } + + public void testArrayWrappingForLong() throws Exception + { + _testArrayWrappingFor(Long.class); + _testArrayWrappingFor(Long.TYPE); + } + + public void testArrayWrappingForFloat() throws Exception + { + _testArrayWrappingFor(Float.class); + _testArrayWrappingFor(Float.TYPE); + } + + public void testArrayWrappingForDouble() throws Exception + { + _testArrayWrappingFor(Double.class); + _testArrayWrappingFor(Double.TYPE); + } + + public void testArrayWrappingForDate() throws Exception + { + _testArrayWrappingFor(Date.class); + } + + private void _testArrayWrappingFor(Class cls) throws Exception + { + try { + MAPPER.readValue(TOO_DEEP_DOC, cls); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot deserialize"); + verifyException(e, "nested Arrays not allowed"); + } + } + + private static String _nestedDoc(int nesting, String open, String close, String content) { + StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length())); + for (int i = 0; i < nesting; ++i) { + sb.append(open); + if ((i & 31) == 0) { + sb.append("\n"); + } + } + sb.append("\n").append(content).append("\n"); + for (int i = 0; i < nesting; ++i) { + sb.append(close); + if ((i & 31) == 0) { + sb.append("\n"); + } + } + return sb.toString(); + } + +}