diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index ced787dd..c80bd5e6 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -424,9 +424,11 @@ protected T _fromString(JsonParser p, DeserializationContext ctxt, if (dots >= 0) { // negative if not simple number try { if (dots == 0) { + _validateTimestampLength(p, string, false); return _fromLong(ctxt, NumberInput.parseLong(string)); } if (dots == 1) { + _validateTimestampLength(p, string, true); return _fromDecimal(ctxt, NumberInput.parseBigDecimal(string, false)); } } catch (NumberFormatException e) { diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java index 27b2f009..b48b9225 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -238,4 +240,27 @@ protected DateTimeException _peelDTE(DateTimeException e) { } return e; } + + /** + * Helper method to validate length of a stringified numeric Date/Time value + * against {@link StreamReadConstraints} limits. + * + * @param p Parser to get constraints from + * @param value Stringified numeric value to validate + * @param isFP Whether {@code value} is a floating-point (has decimal point) + * or integer number; caller knows this already so we avoid re-scanning + * + * @since 2.18.10 + */ + protected void _validateTimestampLength(JsonParser p, String value, boolean isFP) + throws StreamConstraintsException + { + final int len = value.length(); + StreamReadConstraints constraints = p.streamReadConstraints(); + if (isFP) { + constraints.validateFPLength(len); + } else { + constraints.validateIntegerLength(len); + } + } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java index cc6725a8..1ed01eb9 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java @@ -66,6 +66,14 @@ protected void verifyException(Throwable e, String... matches) throw new Error("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\""); } + protected static String repeat(String s, int count) { + StringBuilder sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } + protected static Map asMap(T key, String value) { return Collections.singletonMap(key, value); } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index cc0f7d12..6222ac4f 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -9,6 +9,8 @@ import org.junit.Test; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.exc.MismatchedInputException; @@ -640,4 +642,40 @@ public void testISO8601ColonlessRegexDoesNotAffectNegativeYearsWithColonless() { assertTrue("Matcher finds +0100 as an colonless offset", matcher.find()); assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100"); } + + // [modules-java8#387]: StreamReadConstraints should limit numeric string lengths + // parsed via _fromString to prevent excessive BigDecimal construction. + // NOTE: values MUST be quoted -- unquoted ones are Number tokens, limits for + // which are enforced by the streaming parser and not by this deserializer. + @Test + public void testNumericStringRespectsStreamReadConstraints() throws Exception + { + final int MAX_ALLOWED_LEN = StreamReadConstraints.DEFAULT_MAX_NUM_LEN; + + // Normal epoch seconds as integer should work + assertNotNull(MAPPER.readValue(q("1234567890"), Instant.class)); + + // Normal epoch seconds with decimal should work + assertNotNull(MAPPER.readValue(q("1234567890.123456789"), Instant.class)); + + // A very long integer string (exceeding default 1000-digit limit) should fail + String longInt = repeat("1", MAX_ALLOWED_LEN + 1); + try { + MAPPER.readValue(q(longInt), Instant.class); + fail("Should not pass with excessively long integer string"); + } catch (StreamConstraintsException e) { + verifyException(e, "Number value length"); + verifyException(e, "exceeds the maximum allowed"); + } + + // A very long decimal string (exceeding default 1000-char limit) should fail + String longDecimal = "1234." + repeat("9", MAX_ALLOWED_LEN); + try { + MAPPER.readValue(q(longDecimal), Instant.class); + fail("Should not pass with excessively long decimal string"); + } catch (StreamConstraintsException e) { + verifyException(e, "Number value length"); + verifyException(e, "exceeds the maximum allowed"); + } + } } diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index ac77f20b..1a71b206 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,11 @@ Modules: === Releases === ------------------------------------------------------------------------ +2.18.10 (not yet released) + +#387: Validate length of input in `InstantDeserializer` + (fix by @pjfanning) + 2.18.9 (07-Jul-2026) 2.18.8 (28-May-2026) 2.18.7 (24-Apr-2026)