Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package jackson-core for openSUSE:Factory checked in at 2026-09-24 22:56:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/jackson-core (Old) and /work/SRC/openSUSE:Factory/.jackson-core.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "jackson-core" Thu Sep 24 22:56:28 2026 rev:22 rq:1380224 version:2.18.11 Changes: -------- --- /work/SRC/openSUSE:Factory/jackson-core/jackson-core.changes 2026-08-29 17:42:56.169263809 +0200 +++ /work/SRC/openSUSE:Factory/.jackson-core.new.383539/jackson-core.changes 2026-09-24 22:58:39.531237446 +0200 @@ -1,0 +2,22 @@ +Wed Sep 23 07:32:28 UTC 2026 - Fridrich Strba <[email protected]> + +- Update to 2.18.11 + * #1649: Optimize NumberInput.looksLikeValidNumber (bsc#1282645, + CVE-2026-89407) + * #1698: UTF8DataInputJsonParser does not honor + maxErrorTokenLength when reporting an unrecognized token + (bsc#1282505, CVE-2026-89425) +- Modified patch: + * 0001-Remove-ch.randelshofer.fastdoubleparser.patch + + rebase + +------------------------------------------------------------------- +Thu Sep 17 08:19:44 UTC 2026 - Fridrich Strba <[email protected]> + +- Update to 2.18.10 + * #1642: Fix maxDocumentLength bypass in async parser + single-feedInput() case [GHSA-2c4j-63jj-9fqr] + * #1643: Enforce maxNameLength incrementally in + ReaderBasedJsonParser (bsc#1282641, CVE-2026-68498) + +------------------------------------------------------------------- Old: ---- jackson-core-2.18.9.tar.gz New: ---- jackson-core-2.18.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ jackson-core.spec ++++++ --- /var/tmp/diff_new_pack.48NyK2/_old 2026-09-24 22:58:40.103261367 +0200 +++ /var/tmp/diff_new_pack.48NyK2/_new 2026-09-24 22:58:40.105261450 +0200 @@ -20,7 +20,7 @@ # binaries are java 8 compatible %define __requires_exclude java-headless Name: jackson-core -Version: 2.18.9 +Version: 2.18.11 Release: 0 Summary: Core part of Jackson License: Apache-2.0 ++++++ 0001-Remove-ch.randelshofer.fastdoubleparser.patch ++++++ --- /var/tmp/diff_new_pack.48NyK2/_old 2026-09-24 22:58:40.122262161 +0200 +++ /var/tmp/diff_new_pack.48NyK2/_new 2026-09-24 22:58:40.126262328 +0200 @@ -1,4 +1,4 @@ -From 9f0be7eee70d55cf229c900cc5dd17ad8a680f08 Mon Sep 17 00:00:00 2001 +From 14674bcfe365cd31e5182c893b997ffce68cf378 Mon Sep 17 00:00:00 2001 From: Chris Kelley <[email protected]> Date: Mon, 19 Jun 2023 21:07:53 +0100 Subject: [PATCH] Remove ch.randelshofer.fastdoubleparser @@ -143,7 +143,7 @@ - } -} diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java -index 5cef8dbb3..4db891eac 100644 +index 461897039..13f496919 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java @@ -1,8 +1,5 @@ @@ -154,8 +154,8 @@ - import java.math.BigDecimal; import java.math.BigInteger; - import java.util.regex.Pattern; -@@ -407,7 +404,7 @@ public final class NumberInput + +@@ -387,7 +384,7 @@ public final class NumberInput * @since v2.14 */ public static double parseDouble(final String s, final boolean useFastParser) throws NumberFormatException { @@ -164,7 +164,7 @@ } /** -@@ -432,8 +429,7 @@ public final class NumberInput +@@ -412,8 +409,7 @@ public final class NumberInput */ public static double parseDouble(final char[] array, final int offset, final int len, final boolean useFastParser) throws NumberFormatException { @@ -174,7 +174,7 @@ } /** -@@ -458,9 +454,6 @@ public final class NumberInput +@@ -438,9 +434,6 @@ public final class NumberInput * @since v2.14 */ public static float parseFloat(final String s, final boolean useFastParser) throws NumberFormatException { @@ -184,7 +184,7 @@ return Float.parseFloat(s); } -@@ -486,8 +479,7 @@ public final class NumberInput +@@ -466,8 +459,7 @@ public final class NumberInput */ public static float parseFloat(final char[] array, final int offset, final int len, final boolean useFastParser) throws NumberFormatException { @@ -194,7 +194,7 @@ } /** -@@ -510,9 +502,6 @@ public final class NumberInput +@@ -490,9 +482,6 @@ public final class NumberInput * @since v2.15 */ public static BigDecimal parseBigDecimal(final String s, final boolean useFastParser) throws NumberFormatException { @@ -204,7 +204,7 @@ return BigDecimalParser.parse(s); } -@@ -543,9 +532,6 @@ public final class NumberInput +@@ -523,9 +512,6 @@ public final class NumberInput final boolean useFastParser) throws NumberFormatException { @@ -214,7 +214,7 @@ return BigDecimalParser.parse(ch, off, len); } -@@ -569,9 +555,6 @@ public final class NumberInput +@@ -549,9 +535,6 @@ public final class NumberInput * @since v2.15 */ public static BigDecimal parseBigDecimal(final char[] ch, final boolean useFastParser) throws NumberFormatException { @@ -224,7 +224,7 @@ return BigDecimalParser.parse(ch); } -@@ -596,9 +579,6 @@ public final class NumberInput +@@ -576,9 +559,6 @@ public final class NumberInput * @since v2.15 */ public static BigInteger parseBigInteger(final String s, final boolean useFastParser) throws NumberFormatException { @@ -234,7 +234,7 @@ return new BigInteger(s); } -@@ -612,9 +592,6 @@ public final class NumberInput +@@ -592,9 +572,6 @@ public final class NumberInput */ public static BigInteger parseBigIntegerWithRadix(final String s, final int radix, final boolean useFastParser) throws NumberFormatException { @@ -343,6 +343,6 @@ - } -} -- -2.54.0 +2.55.0 ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.48NyK2/_old 2026-09-24 22:58:40.148263249 +0200 +++ /var/tmp/diff_new_pack.48NyK2/_new 2026-09-24 22:58:40.151263374 +0200 @@ -1,6 +1,6 @@ -mtime: 1785945613 -commit: f6286b25f7c53da52d8efa32ed1b21439eca69a5b5790569ab28bdde6a9db19f +mtime: 1790265721 +commit: f508219b9a9a6a522d42d85441fbf55b28dd6bc8531e693390ba1feb897e265a url: https://src.opensuse.org/java-packages/jackson-core -revision: f6286b25f7c53da52d8efa32ed1b21439eca69a5b5790569ab28bdde6a9db19f +revision: f508219b9a9a6a522d42d85441fbf55b28dd6bc8531e693390ba1feb897e265a projectscmsync: https://src.opensuse.org/java-packages/_ObsPrj ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-24 18:02:01.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ jackson-core-2.18.9.tar.gz -> jackson-core-2.18.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/.github/workflows/trigger_dep_builds_v2.yml new/jackson-core-jackson-core-2.18.11/.github/workflows/trigger_dep_builds_v2.yml --- old/jackson-core-jackson-core-2.18.9/.github/workflows/trigger_dep_builds_v2.yml 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/.github/workflows/trigger_dep_builds_v2.yml 2026-09-21 01:25:19.000000000 +0200 @@ -21,7 +21,7 @@ steps: - name: Repository dispatch - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3 with: token: ${{ secrets.token }} repository: ${{ matrix.repo }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/pom.xml new/jackson-core-jackson-core-2.18.11/pom.xml --- old/jackson-core-jackson-core-2.18.9/pom.xml 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/pom.xml 2026-09-21 01:25:19.000000000 +0200 @@ -8,12 +8,12 @@ <parent> <groupId>com.fasterxml.jackson</groupId> <artifactId>jackson-base</artifactId> - <version>2.18.9</version> + <version>2.18.11</version> </parent> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <name>Jackson-core</name> - <version>2.18.9</version> + <version>2.18.11</version> <packaging>jar</packaging> <description>Core Jackson processing abstractions (aka Streaming API), implementation for JSON</description> <licenses> @@ -29,7 +29,7 @@ <connection>scm:git:[email protected]:FasterXML/jackson-core.git</connection> <developerConnection>scm:git:[email protected]:FasterXML/jackson-core.git</developerConnection> <url>https://github.com/FasterXML/jackson-core</url> - <tag>jackson-core-2.18.9</tag> + <tag>jackson-core-2.18.11</tag> </scm> <properties> @@ -57,7 +57,7 @@ <packageVersion.package>${project.groupId}.json</packageVersion.package> <!-- for Reproducible Builds --> - <project.build.outputTimestamp>2026-07-07T23:48:22Z</project.build.outputTimestamp> + <project.build.outputTimestamp>2026-09-20T23:24:28Z</project.build.outputTimestamp> </properties> <!-- Alas, need to include snapshot reference since otherwise can not find diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/release-notes/CREDITS-2.x new/jackson-core-jackson-core-2.18.11/release-notes/CREDITS-2.x --- old/jackson-core-jackson-core-2.18.9/release-notes/CREDITS-2.x 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/release-notes/CREDITS-2.x 2026-09-21 01:25:19.000000000 +0200 @@ -479,3 +479,13 @@ Rohan Nagendra (@rohan-repos) * Reported #1555: Enforce StreamReadConstraints.maxNumberLength for non-blocking (async) parser (2.18.6) + +Revanth Meesala (@revanthmeesala) + * Contributed #1642: Fix maxDocumentLength bypass in async parser single-feedInput() + case [GHSA-2c4j-63jj-9fqr] + (2.18.10) + +@tinyb0y + * Contributed #1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser + [GHSA-649p-m576-vr99] + (2.18.10) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/release-notes/VERSION-2.x new/jackson-core-jackson-core-2.18.11/release-notes/VERSION-2.x --- old/jackson-core-jackson-core-2.18.9/release-notes/VERSION-2.x 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/release-notes/VERSION-2.x 2026-09-21 01:25:19.000000000 +0200 @@ -14,6 +14,22 @@ === Releases === ------------------------------------------------------------------------ +2.18.11 (20-Sep-2026) + +#1649: Optimize `NumberInput.looksLikeValidNumber` [CVE-2026-89407] + (fix by @cowtowncoder, w/ Claude code) +#1698: `UTF8DataInputJsonParser` does not honor `maxErrorTokenLength` + when reporting an unrecognized token [CVE-2026-89425] + (fix by @pjfanning) + +2.18.10 (15-Aug-2026) + +#1642: Fix maxDocumentLength bypass in async parser single-feedInput() case + [GHSA-2c4j-63jj-9fqr] + (fix by Revanth M) +#1643: Enforce maxNameLength incrementally in ReaderBasedJsonParser [CVE-2026-68498] + (fix by @tinyb0y) + 2.18.9 (07-Jul-2026) No changes since 2.18.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java 2026-09-21 01:25:19.000000000 +0200 @@ -5,7 +5,6 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.regex.Pattern; /** * Helper class for efficient parsing of various JSON numbers. @@ -33,25 +32,6 @@ final static String MAX_LONG_STR = String.valueOf(Long.MAX_VALUE); /** - * Regexp used to pre-validate "Stringified Numbers": slightly looser than - * JSON Number definition (allows leading zeroes, positive sign). - * - * @since 2.17 - */ - private final static Pattern PATTERN_FLOAT = Pattern.compile( - "[+-]?[0-9]*[\\.]?[0-9]+([eE][+-]?[0-9]+)?"); - - - /** - * Secondary regexp used along with {@code PATTERN_FLOAT} to cover - * case where number ends with dot, like {@code "+12."} - * - * @since 2.17.2 - */ - private final static Pattern PATTERN_FLOAT_TRAILING_DOT = Pattern.compile( - "[+-]?[0-9]+[\\.]"); - - /** * Fast method for parsing unsigned integers that are known to fit into * regular 32-bit signed int type. This means that length is * between 1 and 9 digits (inclusive) and there is no sign character. @@ -636,23 +616,86 @@ *<p> * Note: this method returning {@code true} DOES NOT GUARANTEE String is valid * number but just that it looks close enough. + *<p> + * Note: method rewritten in 2.18.11 to avoid use of JDK regexp functionality. * * @param s String to validate * * @return True if String looks like valid Java number; false otherwise. * - * @since 2.17 + * @since 2.17 (rewritten in 2.18.11) */ public static boolean looksLikeValidNumber(final String s) { - // While PATTERN_FLOAT handles most cases we can optimize some simple ones: - if (s == null || s.isEmpty()) { + // 08-Aug-2026, tatu: [core#1649] Hand-rolled scan; matches (union of) + // "[+-]?[0-9]*[.]?[0-9]+([eE][+-]?[0-9]+)?" and "[+-]?[0-9]+[.]" + if (s == null) { return false; } - if (s.length() == 1) { - char c = s.charAt(0); - return (c <= '9') && (c >= '0'); + final int len = s.length(); + if (len == 0) { + return false; + } + int i = 0; + char c = s.charAt(i); + + // Optional sign + if (c == '+' || c == '-') { + if (++i == len) { // sign alone + return false; + } + c = s.charAt(i); + } + + // Integer part, if any + final int intStart = i; + while (c >= '0' && c <= '9') { + if (++i == len) { // "[+-]?[0-9]+" + return true; + } + c = s.charAt(i); + } + final int intDigits = i - intStart; + + if (c == '.') { + if (++i == len) { // trailing dot only allowed after digit(s) + return intDigits > 0; + } + c = s.charAt(i); + final int fractStart = i; + while (c >= '0' && c <= '9') { + if (++i == len) { + return true; + } + c = s.charAt(i); + } + if (i == fractStart) { // "1.x": no fraction digits, and not trailing dot + return false; + } + } else if (intDigits == 0) { // no mantissa digits at all + return false; + } + + // Only an exponent may follow + if (c != 'e' && c != 'E') { + return false; + } + if (++i == len) { + return false; + } + c = s.charAt(i); + if (c == '+' || c == '-') { + if (++i == len) { + return false; + } + c = s.charAt(i); + } + while (c >= '0' && c <= '9') { + if (++i == len) { + return true; + } + c = s.charAt(i); } - return PATTERN_FLOAT.matcher(s).matches() - || PATTERN_FLOAT_TRAILING_DOT.matcher(s).matches(); + // Either no exponent digits, or trailing garbage + return false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java 2026-09-21 01:25:19.000000000 +0200 @@ -1841,6 +1841,15 @@ */ char[] outBuf = _textBuffer.getCurrentSegment(); int outPtr = _textBuffer.getCurrentSegmentSize(); + // 28-Jul-2026, tinyb0y: [core#1643] Track total length accumulated so far so we + // can validate against `maxNameLength` incrementally, same as byte-based + // parsers already do via `ParserBase._growNameDecodeBuffer()`. Without this, + // only the much larger `maxStringLength` bound (enforced inside + // `TextBuffer.finishCurrentSegment()`) applies until the whole name has + // already been buffered. + // Note: only updated when segment gets full (at which point `outPtr` is + // always exactly `outBuf.length`), to keep the per-character loop tight. + int totalLen = 0; while (true) { if (_inputPtr >= _inputEnd) { @@ -1872,6 +1881,8 @@ // Need more room? if (outPtr >= outBuf.length) { + totalLen += outBuf.length; + _streamReadConstraints.validateNameLength(totalLen); outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; } @@ -2102,6 +2113,9 @@ char[] outBuf = _textBuffer.getCurrentSegment(); int outPtr = _textBuffer.getCurrentSegmentSize(); final int maxCode = codes.length; + // 28-Jul-2026, tinyb0y: [core#1643] Same incremental `maxNameLength` check as + // `_parseName2()` needs to apply to unquoted ("odd") names as well + int totalLen = 0; while (true) { if (_inputPtr >= _inputEnd) { @@ -2125,6 +2139,8 @@ // Need more room? if (outPtr >= outBuf.length) { + totalLen += outBuf.length; + _streamReadConstraints.validateNameLength(totalLen); outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; } @@ -3002,6 +3018,7 @@ * nothing fancy here. */ StringBuilder sb = new StringBuilder(matchedPart); + final int maxTokenLength = _ioContext.errorReportConfiguration().getMaxErrorTokenLength(); while ((_inputPtr < _inputEnd) || _loadMore()) { char c = _inputBuffer[_inputPtr]; if (!Character.isJavaIdentifierPart(c)) { @@ -3009,7 +3026,7 @@ } ++_inputPtr; sb.append(c); - if (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) { + if (sb.length() >= maxTokenLength) { sb.append("..."); break; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java 2026-09-21 01:25:19.000000000 +0200 @@ -2132,8 +2132,14 @@ return _handleInvalidNumberStart(_inputData.readUnsignedByte(), false, true); } // [core#77] Try to decode most likely token - if (Character.isJavaIdentifierStart(c)) { - _reportInvalidToken(c, ""+((char) c), _validJsonTokenList()); + if (c > 0x7F) { // multi-byte UTF-8 char: decode first (consumes rest of its bytes) + c = _decodeCharForError(c); + if (Character.isJavaIdentifierStart(c)) { + _reportInvalidToken(_inputData.readUnsignedByte(), ""+((char) c), _validJsonTokenList()); + } + } else if (Character.isJavaIdentifierStart(c)) { + // NOTE: 'c' is decoded (and appended) by _reportInvalidToken(); do not pre-append + _reportInvalidToken(c, "", _validJsonTokenList()); } // but if it doesn't look like a token: _reportUnexpectedChar(c, "expected a valid value "+_validJsonValueList()); @@ -2274,7 +2280,9 @@ // but actually only alphanums are problematic char c = (char) _decodeCharForError(ch); if (Character.isJavaIdentifierPart(c)) { - _reportInvalidToken(c, matchStr.substring(0, i)); + // 'c' already decoded (all of its bytes consumed): include it as matched, + // continue from the following byte + _reportInvalidToken(_inputData.readUnsignedByte(), matchStr.substring(0, i) + c); } } @@ -2765,6 +2773,7 @@ throws IOException { StringBuilder sb = new StringBuilder(matchedPart); + final int maxTokenLength = _ioContext.errorReportConfiguration().getMaxErrorTokenLength(); /* Let's just try to find what appears to be the token, using * regular Java identifier character rules. It's just a heuristic, @@ -2776,6 +2785,10 @@ break; } sb.append(c); + if (sb.length() >= maxTokenLength) { + sb.append("..."); + break; + } ch = _inputData.readUnsignedByte(); } _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java 2026-09-21 01:25:19.000000000 +0200 @@ -2745,6 +2745,9 @@ return _handleInvalidNumberStart(_inputBuffer[_inputPtr++] & 0xFF, false, true); } // [core#77] Try to decode most likely token + if (c > 0x7F) { // multi-byte UTF-8 char: decode first (consumes rest of its bytes) + c = _decodeCharForError(c); + } if (Character.isJavaIdentifierStart(c)) { _reportInvalidToken(""+((char) c), _validJsonTokenList()); } @@ -2990,10 +2993,25 @@ private final void _checkMatchEnd(String matchStr, int i, int ch) throws IOException { // but actually only alphanums are problematic + if (ch < 0x80) { // single-byte char: can check without consuming it + if (Character.isJavaIdentifierPart((char) ch)) { + _reportInvalidToken(matchStr.substring(0, i)); + } + return; + } + // Multi-byte char: must consume lead byte (decoding consumes the rest) + final int ptr = _inputPtr++; + final long processed = _currInputProcessed; char c = (char) _decodeCharForError(ch); if (Character.isJavaIdentifierPart(c)) { - _reportInvalidToken(matchStr.substring(0, i)); + _reportInvalidToken(matchStr.substring(0, i) + c); + } + // Not part of token: rewind so regular handling reports it -- unless + // buffer was reloaded during decoding, in which case must report here + if (_currInputProcessed != processed) { + _reportUnexpectedChar(c, "expected white space, comma or end marker after token '"+matchStr+"'"); } + _inputPtr = ptr; } /* @@ -3642,6 +3660,7 @@ * nothing fancy here (nor fast). */ StringBuilder sb = new StringBuilder(matchedPart); + final int maxTokenLength = _ioContext.errorReportConfiguration().getMaxErrorTokenLength(); while ((_inputPtr < _inputEnd) || _loadMore()) { int i = _inputBuffer[_inputPtr++]; char c = (char) _decodeCharForError(i); @@ -3654,7 +3673,7 @@ break; } sb.append(c); - if (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) { + if (sb.length() >= maxTokenLength) { sb.append("..."); break; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingByteBufferJsonParser.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingByteBufferJsonParser.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingByteBufferJsonParser.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingByteBufferJsonParser.java 2026-09-21 01:25:19.000000000 +0200 @@ -37,12 +37,8 @@ } @Override - public void feedInput(final ByteBuffer byteBuffer) throws IOException { - // Must not have remaining input - if (_inputPtr < _inputEnd) { - _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr); - } - + public void feedInput(final ByteBuffer byteBuffer) throws IOException + { final int start = byteBuffer.position(); final int end = byteBuffer.limit(); @@ -53,12 +49,21 @@ if (_endOfInput) { _reportError("Already closed, can not feed more input"); } + // Must not have remaining input + if (_inputPtr < _inputEnd) { + _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr); + } + // 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit + // 17-Jul-2026, revanthm: [core#1642] Must include buffer being fed, not just + // previously fed ones, so that a single feedInput() call carrying the + // whole document is checked against its real length. Also: validate + // before updating any state, to leave parser untouched if this throws + _streamReadConstraints.validateDocumentLength( + _currInputProcessed + _origBufferLen + (end - start)); + // Time to update pointers first _currInputProcessed += _origBufferLen; - // 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit - _streamReadConstraints.validateDocumentLength(_currInputProcessed); - // Also need to adjust row start, to work as if it extended into the past wrt new buffer _currInputRowStart = start - (_inputEnd - _currInputRowStart); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java 2026-09-21 01:25:19.000000000 +0200 @@ -33,11 +33,8 @@ } @Override - public void feedInput(final byte[] buf, final int start, final int end) throws IOException { - // Must not have remaining input - if (_inputPtr < _inputEnd) { - _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr); - } + public void feedInput(final byte[] buf, final int start, final int end) throws IOException + { if (end < start) { _reportError("Input end (%d) may not be before start (%d)", end, start); } @@ -45,12 +42,21 @@ if (_endOfInput) { _reportError("Already closed, can not feed more input"); } + // Must not have remaining input + if (_inputPtr < _inputEnd) { + _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr); + } + // 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit + // 17-Jul-2026, revanthm: [core#1642] Must include buffer being fed, not just + // previously fed ones, so that a single feedInput() call carrying the + // whole document is checked against its real length. Also: validate + // before updating any state, to leave parser untouched if this throws + _streamReadConstraints.validateDocumentLength( + _currInputProcessed + _origBufferLen + (end - start)); + // Time to update pointers first _currInputProcessed += _origBufferLen; - // 06-Sep-2023, tatu: [core#1046] Enforce max doc length limit - _streamReadConstraints.validateDocumentLength(_currInputProcessed); - // Also need to adjust row start, to work as if it extended into the past wrt new buffer _currInputRowStart = start - (_inputEnd - _currInputRowStart); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java --- old/jackson-core-jackson-core-2.18.9/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java 2026-09-21 01:25:19.000000000 +0200 @@ -1235,6 +1235,7 @@ protected JsonToken _finishErrorToken() throws IOException { + final int maxTokenLength = _ioContext.errorReportConfiguration().getMaxErrorTokenLength(); while (_inputPtr < _inputEnd) { int i = getNextSignedByteFromBuffer(); @@ -1246,7 +1247,7 @@ // 11-Jan-2016, tatu: note: we will fully consume the character, // included or not, so if recovery was possible, it'd be off-by-one... _textBuffer.append(ch); - if (_textBuffer.size() < _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) { + if (_textBuffer.size() < maxTokenLength) { continue; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java --- old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/ErrorReportConfigurationTest.java 2026-09-21 01:25:19.000000000 +0200 @@ -216,6 +216,57 @@ } } + // [core#1698]: every parser backend must honor `maxErrorTokenLength`; + // `UTF8DataInputJsonParser` used to accumulate the whole token instead + @Test + void errorTokenLengthBoundedInAllModes() + throws Exception + { + final int maxLen = 256; + final JsonFactory f = streamFactoryBuilder() + .errorReportConfiguration(ErrorReportConfiguration.builder() + .maxErrorTokenLength(maxLen).build()) + .build(); + // Broken token far longer than the limit: must be truncated, not accumulated in full + final String doc = _buildBrokenJsonOfLength(50 * maxLen); + // limit, plus appended "..." + final String expToken = _brokenToken(maxLen) + "..."; + + for (int mode : ALL_MODES) { + try (JsonParser p = createParser(f, mode, doc)) { + p.nextToken(); + p.nextToken(); + fail("Should not pass, mode: "+mode); + } catch (JsonProcessingException e) { + assertThat(_unrecognizedToken(e.getMessage())) + .as("mode: %d", mode) + .isEqualTo(expToken); + } + } + } + + // Short broken token (below limit) must be reported verbatim, without + // duplicated leading char, in all modes + @Test + void shortErrorTokenReportedExactlyInAllModes() + throws Exception + { + final JsonFactory f = newStreamFactory(); + final String doc = "{\"key\":abc!}"; + + for (int mode : ALL_MODES) { + try (JsonParser p = createParser(f, mode, doc)) { + p.nextToken(); + p.nextToken(); + fail("Should not pass, mode: "+mode); + } catch (JsonProcessingException e) { + assertThat(_unrecognizedToken(e.getMessage())) + .as("mode: %d", mode) + .isEqualTo("abc"); + } + } + } + @Test void nonPositiveErrorTokenConfig() { @@ -318,13 +369,29 @@ } } + // Extracts X from "Unrecognized token 'X': was expecting ..." + private String _unrecognizedToken(String msg) + { + final String prefix = "Unrecognized token '"; + final int start = msg.indexOf(prefix); + assertThat(start).as("message: %s", msg).isGreaterThanOrEqualTo(0); + final int end = msg.indexOf("': was expecting", start); + assertThat(end).as("message: %s", msg).isGreaterThan(start); + return msg.substring(start + prefix.length(), end); + } + private String _buildBrokenJsonOfLength(int len) { - StringBuilder sb = new StringBuilder("{\"key\":"); + return "{\"key\":" + _brokenToken(len) + "!}"; + } + + // Varied (not repeating single) chars so that duplicated/dropped chars are detectable + private String _brokenToken(int len) + { + StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { - sb.append("a"); + sb.append((char) ('a' + (i % 26))); } - sb.append("!}"); return sb.toString(); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/constraints/LargeDocReadTest.java new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/constraints/LargeDocReadTest.java --- old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/constraints/LargeDocReadTest.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/constraints/LargeDocReadTest.java 2026-09-21 01:25:19.000000000 +0200 @@ -1,11 +1,15 @@ package com.fasterxml.jackson.core.constraints; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; import com.fasterxml.jackson.core.*; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.core.async.AsyncTestBase; +import com.fasterxml.jackson.core.async.ByteArrayFeeder; +import com.fasterxml.jackson.core.async.ByteBufferFeeder; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.testsupport.AsyncReaderWrapper; import com.fasterxml.jackson.core.testsupport.MockDataInput; @@ -103,6 +107,140 @@ } } + // [core#1642] maxDocumentLength must also be enforced when the caller feeds + // the whole document via a single feedInput() call (e.g. pre-buffered input), + // not just when input arrives split across multiple feedInput() calls. + @Test + void largeNameWithSmallLimitAsyncSingleFeed() throws Exception + { + final byte[] doc = utf8Bytes(generateJSON(12_000)); + + // first with byte[] backend: bytesPerRead >= doc.length so the whole + // document goes through in exactly one feedInput() call + try (AsyncReaderWrapper p = asyncForBytes(JSON_F_DOC_10K, doc.length, doc, 1)) { + consumeAsync(p); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyMaxDocLen(JSON_F_DOC_10K, e); + } + + // then with byte buffer backend, same single-call condition + try (AsyncReaderWrapper p = asyncForByteBuffer(JSON_F_DOC_10K, doc.length, doc, 1)) { + consumeAsync(p); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyMaxDocLen(JSON_F_DOC_10K, e); + } + } + + // [core#1642] Boundary check: a single feedInput() call carrying EXACTLY + // maxDocumentLength bytes must still parse successfully -- validateDocumentLength() + // rejects only len > maxDocumentLength, so the limit itself is inclusive. + // This pins down "bytes fed, not consumed" semantics and guards against a + // future off-by-one in the single-feed fix. + @Test + void largeNameWithSmallLimitAsyncSingleFeedAtBoundary() throws Exception + { + final long limit = JSON_F_DOC_10K.streamReadConstraints().getMaxDocumentLength(); + final byte[] doc = utf8Bytes(generateExactLengthJSON((int) limit)); + assertEquals(limit, doc.length); + + // first with byte[] backend: bytesPerRead >= doc.length so the whole + // document goes through in exactly one feedInput() call + try (AsyncReaderWrapper p = asyncForBytes(JSON_F_DOC_10K, doc.length, doc, 1)) { + consumeAsync(p); + } + + // then with byte buffer backend, same single-call condition + try (AsyncReaderWrapper p = asyncForByteBuffer(JSON_F_DOC_10K, doc.length, doc, 1)) { + consumeAsync(p); + } + } + + // [core#1642] Same boundary, but reached across MANY feedInput() calls: bytes + // fed must accumulate to exactly maxDocumentLength and still parse, verifying + // the single-feed fix did not start double-counting incrementally fed buffers. + @Test + void largeNameWithSmallLimitAsyncMultiFeedAtBoundary() throws Exception + { + final long limit = JSON_F_DOC_10K.streamReadConstraints().getMaxDocumentLength(); + final byte[] doc = utf8Bytes(generateExactLengthJSON((int) limit)); + assertEquals(limit, doc.length); + + // 1000 bytes per call, so exactly 10 feedInput() calls totalling the limit + try (AsyncReaderWrapper p = asyncForBytes(JSON_F_DOC_10K, 1000, doc, 1)) { + consumeAsync(p); + } + try (AsyncReaderWrapper p = asyncForByteBuffer(JSON_F_DOC_10K, 1000, doc, 1)) { + consumeAsync(p); + } + } + + // [core#1642] A rejected feedInput() must not corrupt the running byte count: + // validation happens BEFORE any state is updated, so a caller that catches the + // StreamConstraintsException and keeps feeding still gets an accurate total + // (the rejected call's predecessor must not be counted twice). + @Test + void docLengthCountIntactAfterRejectedFeedBytes() throws Exception + { + try (JsonParser p = JSON_F_DOC_10K.createNonBlockingByteArrayParser()) { + final ByteArrayFeeder feeder = (ByteArrayFeeder) p.getNonBlockingInputFeeder(); + + // 5000 fed, well under the 10000 limit + feeder.feedInput(whitespace(5000), 0, 5000); + assertToken(JsonToken.NOT_AVAILABLE, p.nextToken()); + + // would reach 11000: rejected, and must leave the count at 5000 + try { + feeder.feedInput(whitespace(6000), 0, 6000); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyMaxDocLen(JSON_F_DOC_10K, e); + } + + // 5000 more == 10000 total: at the limit, so must still be accepted + feeder.feedInput(whitespace(5000), 0, 5000); + assertToken(JsonToken.NOT_AVAILABLE, p.nextToken()); + + // and one byte past it must report the true total, not an inflated one + try { + feeder.feedInput(whitespace(1), 0, 1); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyException(e, "Document length (10001)"); + } + } + } + + // [core#1642] as above, for the ByteBuffer-backed parser + @Test + void docLengthCountIntactAfterRejectedFeedByteBuffer() throws Exception + { + try (JsonParser p = JSON_F_DOC_10K.createNonBlockingByteBufferParser()) { + final ByteBufferFeeder feeder = (ByteBufferFeeder) p.getNonBlockingInputFeeder(); + + feeder.feedInput(ByteBuffer.wrap(whitespace(5000))); + assertToken(JsonToken.NOT_AVAILABLE, p.nextToken()); + + try { + feeder.feedInput(ByteBuffer.wrap(whitespace(6000))); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyMaxDocLen(JSON_F_DOC_10K, e); + } + + feeder.feedInput(ByteBuffer.wrap(whitespace(5000))); + assertToken(JsonToken.NOT_AVAILABLE, p.nextToken()); + + try { + feeder.feedInput(ByteBuffer.wrap(whitespace(1))); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyException(e, "Document length (10001)"); + } + } + } + // [core#1570] Should fail fast when DataInput used with maxDocumentLength set @Test void dataInputWithDocLengthLimitFails() throws Exception @@ -150,6 +288,31 @@ } } + // Builds a valid JSON array whose UTF-8 byte length is exactly {@code exactLen}, + // using trailing whitespace padding before the closing bracket (all-ASCII content, + // so char length == byte length). + private String generateExactLengthJSON(final int exactLen) { + final StringBuilder sb = new StringBuilder(); + sb.append('['); + while (sb.length() < exactLen - 10) { + sb.append("1,"); + } + sb.append('1'); + while (sb.length() < exactLen - 1) { + sb.append(' '); + } + sb.append(']'); + return sb.toString(); + } + + // Content that is valid-but-tokenless, so buffers can be fed and fully consumed + // without producing tokens: lets tests exercise feedInput() accounting directly. + private byte[] whitespace(final int len) { + final byte[] b = new byte[len]; + Arrays.fill(b, (byte) ' '); + return b; + } + private String generateJSON(final int docLen) { final StringBuilder sb = new StringBuilder(); sb.append("["); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/constraints/LargeNameReadTest.java new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/constraints/LargeNameReadTest.java --- old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/constraints/LargeNameReadTest.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/constraints/LargeNameReadTest.java 2026-09-21 01:25:19.000000000 +0200 @@ -1,13 +1,17 @@ package com.fasterxml.jackson.core.constraints; import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.fasterxml.jackson.core.*; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import com.fasterxml.jackson.core.json.JsonReadFeature; import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser; +import com.fasterxml.jackson.core.util.JsonRecyclerPools; import static org.junit.jupiter.api.Assertions.fail; @@ -26,6 +30,13 @@ .maxNameLength(100).build()); } + // Factory that also allows non-standard name flavors ("apostrophe" and unquoted) + private final JsonFactory JSON_F_NAME_100_ODD = JsonFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNameLength(100).build()) + .configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true) + .configure(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .build(); + // Test name that is below default max name @Test void largeNameBytes() throws Exception { @@ -77,6 +88,64 @@ } } + // [core#1643]: Reader-backed parser must reject an over-limit name promptly, the + // same way byte-based input already does -- not only once the entire (possibly + // huge) name has already been buffered. + // (note: `String` / `char[]` input is not affected the same way, since the whole + // document is already in memory and gets scanned in-place, without buffering) + @Test + void largeNameWithSmallLimitCharsFailsFast() throws Exception { + // Name much larger than the configured limit: without incremental checking + // the whole name gets buffered (bounded only by much bigger `maxStringLength`) + // before failing, whereas the fix must reject within a segment fill or two. + _testLargeNameFailsFast(JSON_F_NAME_100, "\""); + _testLargeNameFailsFast(JSON_F_NAME_100_B, "\""); + } + + // [core#1643]: ... and same goes for the non-standard name flavors, which are + // decoded by different code paths ("apostrophe" and unquoted names) + @Test + void largeOddNameWithSmallLimitCharsFailsFast() throws Exception { + _testLargeNameFailsFast(JSON_F_NAME_100_ODD, "'"); + _testLargeNameFailsFast(JSON_F_NAME_100_ODD, ""); + } + + private void _testLargeNameFailsFast(JsonFactory jf, String nameQuote) throws Exception + { + // 09-Sep-2026, tatu: [core#1643] Must NOT use recycled buffers here: check is + // only made when `TextBuffer` segment gets full, and a buffer left behind by + // an earlier test in same thread may be up to 64kB (`BufferRecycler` retains + // the biggest one released, see [core#1186]) -- which would make the first + // check occur much later than with a fresh (small) buffer. + jf = jf.rebuild() + .recyclerPool(JsonRecyclerPools.nonRecyclingPool()) + .build(); + final int nameLen = 1_000_000; + final String doc = generateJSON(nameLen, nameQuote); + try (JsonParser p = createParserUsingReader(jf, doc)) { + consumeTokens(p); + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + verifyException(e, "Name length"); + // Length the exception reports tells us how much had been accumulated + // before the check fired: needs to be small fraction of the whole name + final int reportedLen = _reportedNameLength(e); + final int maxExpected = nameLen >> 4; + if (reportedLen > maxExpected) { + fail("Should have failed before buffering "+maxExpected + +" chars (limit is 100), but reported length was: "+reportedLen); + } + } + } + + private int _reportedNameLength(StreamConstraintsException e) { + Matcher m = Pattern.compile("Name length \\((\\d+)\\)").matcher(e.getMessage()); + if (!m.find()) { + fail("Could not find reported name length from message: "+e.getMessage()); + } + return Integer.parseInt(m.group(1)); + } + @Test void largeNameWithSmallLimitAsync() throws Exception { @@ -116,12 +185,17 @@ } private String generateJSON(final int nameLen) { + return generateJSON(nameLen, "\""); + } + + // @param nameQuote Quote character to use around name; empty String for unquoted name + private String generateJSON(final int nameLen, final String nameQuote) { final StringBuilder sb = new StringBuilder(); - sb.append("{\""); + sb.append("{").append(nameQuote); for (int i = 0; i < nameLen; i++) { sb.append("a"); } - sb.append("\":\"value\"}"); + sb.append(nameQuote).append(":\"value\"}"); return sb.toString(); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/io/NumberInputTest.java new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/io/NumberInputTest.java --- old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/io/NumberInputTest.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/io/NumberInputTest.java 2026-09-21 01:25:19.000000000 +0200 @@ -1,6 +1,7 @@ package com.fasterxml.jackson.core.io; import java.math.BigInteger; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; @@ -105,6 +106,10 @@ assertTrue(NumberInput.looksLikeValidNumber("1.4E-45")); assertTrue(NumberInput.looksLikeValidNumber("1.4e+45")); + // Fully populated form: sign, integer part, fraction, signed exponent + assertTrue(NumberInput.looksLikeValidNumber("+1.2e+3")); + assertTrue(NumberInput.looksLikeValidNumber("-12.34E-56")); + // https://github.com/FasterXML/jackson-core/issues/1308 assertTrue(NumberInput.looksLikeValidNumber("0.")); assertTrue(NumberInput.looksLikeValidNumber("6.")); @@ -139,5 +144,49 @@ assertFalse(NumberInput.looksLikeValidNumber("+.")); assertFalse(NumberInput.looksLikeValidNumber("-E")); assertFalse(NumberInput.looksLikeValidNumber("+E")); + + assertFalse(NumberInput.looksLikeValidNumber("1.2.3")); + assertFalse(NumberInput.looksLikeValidNumber("1.e5")); + assertFalse(NumberInput.looksLikeValidNumber("1e")); + assertFalse(NumberInput.looksLikeValidNumber("1e+")); + assertFalse(NumberInput.looksLikeValidNumber("1e5x")); + assertFalse(NumberInput.looksLikeValidNumber("1e5.0")); + assertFalse(NumberInput.looksLikeValidNumber("--1")); + assertFalse(NumberInput.looksLikeValidNumber("1 ")); + assertFalse(NumberInput.looksLikeValidNumber(" 1")); + assertFalse(NumberInput.looksLikeValidNumber("0x1F")); + } + + // [core#1649]: hand-rolled implementation must accept exactly what the + // original Regexp-based one did + @Test + void looksLikeValidNumberMatchesLegacyRegexps() + { + final Pattern patternFloat = Pattern.compile("[+-]?[0-9]*[\\.]?[0-9]+([eE][+-]?[0-9]+)?"); + final Pattern patternTrailingDot = Pattern.compile("[+-]?[0-9]+[\\.]"); + // Digit-class bounds ('0', '9') plus the chars just outside it ('/', ':') + // to catch off-by-one in digit checks; rest are the structural characters + final char[] alphabet = new char[] { '0', '9', '/', ':', '+', '-', '.', 'e', 'E' }; + + // Length 5 needed to reach forms combining both signs ("+1e+1") or + // integer + fraction + exponent ("1.2e3"); ~66k inputs, runs in ~50 msec + _verifyAgainstRegexps(patternFloat, patternTrailingDot, alphabet, "", 5); + } + + private void _verifyAgainstRegexps(Pattern patternFloat, Pattern patternTrailingDot, + char[] alphabet, String prefix, int remainingLength) + { + if (!prefix.isEmpty()) { + boolean exp = patternFloat.matcher(prefix).matches() + || patternTrailingDot.matcher(prefix).matches(); + assertEquals(exp, NumberInput.looksLikeValidNumber(prefix), + "Mismatch for input '"+prefix+"'"); + } + if (remainingLength > 0) { + for (char c : alphabet) { + _verifyAgainstRegexps(patternFloat, patternTrailingDot, alphabet, + prefix + c, remainingLength - 1); + } + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/read/ParserErrorHandlingTest.java new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/read/ParserErrorHandlingTest.java --- old/jackson-core-jackson-core-2.18.9/src/test/java/com/fasterxml/jackson/core/read/ParserErrorHandlingTest.java 2026-07-08 01:49:20.000000000 +0200 +++ new/jackson-core-jackson-core-2.18.11/src/test/java/com/fasterxml/jackson/core/read/ParserErrorHandlingTest.java 2026-09-21 01:25:19.000000000 +0200 @@ -68,6 +68,21 @@ doTestInvalidKeyword1(mode, "treu"); doTestInvalidKeyword1(mode, "trueenough"); doTestInvalidKeyword1(mode, "C"); + + // Multi-byte (non-ASCII) identifier chars right after keyword + doTestInvalidKeyword1(mode, "trueé"); + doTestInvalidKeyword1(mode, "nullé"); + doTestInvalidKeyword1(mode, "false中x"); + + // Multi-byte (non-ASCII) identifier char at start of token + doTestInvalidKeyword1(mode, "éabc"); + doTestInvalidKeyword1(mode, "中x"); + + // Multi-byte char that is NOT part of token (NBSP): after keyword, or at value start + _testNonTokenChar(mode, "[true\u00A0]"); + _testNonTokenChar(mode, "{\"a\":null\u00A0}"); + _testNonTokenChar(mode, "[\u00A0]"); + _testNonTokenChar(mode, "{\"a\":\u00A0}"); } private void doTestInvalidKeyword1(int mode, String value) @@ -103,6 +118,17 @@ } } + // Must be reported as an error: not skipped, nor mis-decoded + private void _testNonTokenChar(int mode, String doc) throws IOException + { + try (JsonParser p = createParser(JSON_F, mode, doc)) { + while (p.nextToken() != null) { } + fail("Expected an exception for invalid non-token char; doc: "+doc); + } catch (JsonParseException jex) { + verifyException(jex, "Unexpected character"); + } + } + private void _testMangledNumbersInt(int mode) throws Exception { JsonParser p = createParser(JSON_F, mode, "123true"); ++++++ jackson-core-build.xml ++++++ --- /var/tmp/diff_new_pack.48NyK2/_old 2026-09-24 22:58:40.536279474 +0200 +++ /var/tmp/diff_new_pack.48NyK2/_new 2026-09-24 22:58:40.539279599 +0200 @@ -11,7 +11,7 @@ <property name="project.groupId" value="com.fasterxml.jackson.core"/> <property name="project.artifactId" value="jackson-core"/> <property name="project.name" value="Jackson-core"/> - <property name="project.version" value="2.18.9"/> + <property name="project.version" value="2.18.11"/> <property name="project.vendor" value="FasterXML"/> <property name="project.description" value="Core Jackson processing abstractions (aka Streaming API), implementation for JSON"/> <property name="bundle.version" value="${project.version}"/>
