diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 6f5d21a88..8e8f4b199 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -76,3 +76,6 @@ PJ Fanning (@pjfanning) * Contributed #763: (protobuf) Use `VarHandle` for multi-byte primitive reads and writes in `ProtobufParser` / `ProtobufGenerator` (3.3.0) +* Contributed #781: (smile) `SmileGenerator.writeNumber(String)` should validate + number length against configured `StreamReadConstraints`, not global defaults + (3.3.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index 34c5dfd62..d498ab9cc 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -38,6 +38,9 @@ implementations) #767: (smile) Use more efficient `String` construction wrt "Compact Strings" for "short" ASCII text values of async parser (fix by @cowtowncoder, w/ Claude code) +#781: (smile) `SmileGenerator.writeNumber(String)` should validate number length + against configured `StreamReadConstraints`, not global defaults + (contributed by @pjfanning) 3.2.3 (not yet released) diff --git a/smile/src/main/java/tools/jackson/dataformat/smile/SmileGenerator.java b/smile/src/main/java/tools/jackson/dataformat/smile/SmileGenerator.java index fce5d2b4e..ca5361f09 100644 --- a/smile/src/main/java/tools/jackson/dataformat/smile/SmileGenerator.java +++ b/smile/src/main/java/tools/jackson/dataformat/smile/SmileGenerator.java @@ -2761,12 +2761,10 @@ protected UnsupportedOperationException _notSupported() { /** * We need access to some reader-side constraints for safety-check within - * number decoding for {@linl #writeNumber(String)}: for now we need to - * rely on global defaults; should be ok for basic safeguarding. - * - * @since 2.17 + * number decoding for {@link #writeNumber(String)}: these are the ones + * configured for the underlying factory, accessed via {@link IOContext}. */ protected StreamReadConstraints _streamReadConstraints() { - return StreamReadConstraints.defaults(); + return _ioContext.streamReadConstraints(); } } diff --git a/smile/src/test/java/tools/jackson/dataformat/smile/gen/SmileGeneratorNumbersTest.java b/smile/src/test/java/tools/jackson/dataformat/smile/gen/SmileGeneratorNumbersTest.java index 2b27bd1ee..e0b28b171 100644 --- a/smile/src/test/java/tools/jackson/dataformat/smile/gen/SmileGeneratorNumbersTest.java +++ b/smile/src/test/java/tools/jackson/dataformat/smile/gen/SmileGeneratorNumbersTest.java @@ -1,14 +1,21 @@ package tools.jackson.dataformat.smile.gen; import java.io.ByteArrayOutputStream; +import java.math.BigInteger; import java.util.Arrays; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.core.StreamReadConstraints; +import tools.jackson.core.exc.StreamConstraintsException; + import tools.jackson.dataformat.smile.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; public class SmileGeneratorNumbersTest extends BaseTestForSmile @@ -203,6 +210,42 @@ public void testNumbersAsString() throws Exception assertEquals(10, out.toByteArray().length); } + // [dataformats-binary#781]: `writeNumber(String)` should use the factory's + // `StreamReadConstraints` for its number-length guard, not global defaults + @Test + public void testNumbersAsStringLengthLimit() throws Exception + { + final int maxLen = 20; + SmileFactory f = smileFactoryBuilder(false, false, false) + .streamReadConstraints(StreamReadConstraints.builder() + .maxNumberLength(maxLen).build()) + .build(); + final String tooLongInt = "1".repeat(maxLen + 1); + final String tooLongDec = "1." + "1".repeat(maxLen - 1); + + for (String num : new String[] { tooLongInt, tooLongDec }) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (SmileGenerator gen = (SmileGenerator) f.createGenerator(out)) { + gen.writeNumber(num); + fail("Should not pass for: "+num); + } catch (StreamConstraintsException e) { + verifyException(e, "Number value length (" + (maxLen + 1) + + ") exceeds the maximum allowed (" + maxLen + ","); + } + } + + // And conversely, one at the limit is fine + final String atLimit = "1".repeat(maxLen); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (SmileGenerator gen = (SmileGenerator) f.createGenerator(out)) { + gen.writeNumber(atLimit); + } + try (JsonParser p = f.createParser(out.toByteArray())) { + assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(new BigInteger(atLimit), p.getBigIntegerValue()); + } + } + // [dataformats-binary#608] @Test public void testFloat32FromSpecEncoding() throws Exception