diff --git a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java index 1f64c9c8d3..193a08f1e1 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/ReaderBasedJsonParser.java @@ -777,11 +777,11 @@ public final JsonToken nextToken() throws IOException break; case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleOddValue(i); } @@ -980,11 +980,11 @@ public String nextFieldName() throws IOException switch (i) { case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleOddValue(i); } @@ -1059,11 +1059,11 @@ private final void _isNextTokenNameYes(int i) throws IOException _nextToken = JsonToken.VALUE_NULL; return; case '-': - _nextToken = _parsePossibleNumber(true); + _nextToken = _parseSignedNumber(true); return; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - _nextToken = _parsePossibleNumber(false); + _nextToken = _parseSignedNumber(false); } else { _nextToken = _handleOddValue(i); } @@ -1104,11 +1104,11 @@ protected boolean _isNextTokenNameMaybe(int i, String nameToMatch) throws IOExce JsonToken t; switch (i) { case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleOddValue(i); } @@ -1177,7 +1177,7 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException _matchToken("null", 1); return (_currToken = JsonToken.VALUE_NULL); case '-': - return (_currToken = _parsePossibleNumber(true)); + return (_currToken = _parseSignedNumber(true)); /* Should we have separate handling for plus? Although * it is not allowed per se, it may be erroneously used, * and could be indicated by a more specific error message. @@ -1477,7 +1477,7 @@ private final JsonToken _parseFloat(int ch, int startPtr, int ptr, boolean neg, return resetFloat(neg, intLen, fractLen, expLen); } - private final JsonToken _parsePossibleNumber(final boolean negative) throws IOException + private final JsonToken _parseSignedNumber(final boolean negative) throws IOException { int ptr = _inputPtr; int startPtr = negative ? ptr-1 : ptr; // to include sign/digit already read @@ -1493,7 +1493,7 @@ private final JsonToken _parsePossibleNumber(final boolean negative) throws IOEx if (ch == INT_PERIOD) { return _parseFloatThatStartsWithPeriod(); } - return _handleInvalidNumberStart(ch, negative); + return _handleInvalidNumberStart(ch, negative, true); } // One special case, leading zero(es): if (ch == INT_0) { @@ -1722,6 +1722,11 @@ private char _verifyNLZ2() throws IOException // Method called if expected numeric value (due to leading sign) does not // look like a number protected JsonToken _handleInvalidNumberStart(int ch, boolean negative) throws IOException + { + return _handleInvalidNumberStart(ch, negative, false); + } + + protected JsonToken _handleInvalidNumberStart(int ch, final boolean negative, final boolean hasSign) throws IOException { if (ch == 'I') { if (_inputPtr >= _inputEnd) { @@ -1746,6 +1751,9 @@ protected JsonToken _handleInvalidNumberStart(int ch, boolean negative) throws I _reportError("Non-standard token '"+match+"': enable `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow"); } } + if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature()) && hasSign && !negative) { + reportUnexpectedNumberChar('+', "JSON spec does not allow numbers to have plus signs: enable `JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS` to allow"); + } reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value"); return null; } @@ -2026,7 +2034,7 @@ protected JsonToken _handleOddValue(int i) throws IOException _reportInvalidEOFInValue(JsonToken.VALUE_NUMBER_INT); } } - return _handleInvalidNumberStart(_inputBuffer[_inputPtr++], false); + return _handleInvalidNumberStart(_inputBuffer[_inputPtr++], false, true); } // [core#77] Try to decode most likely token if (Character.isJavaIdentifierStart(i)) { diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java index b690b798fd..377b5e8ed8 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java @@ -1082,15 +1082,15 @@ protected JsonToken _parsePosNumber(int c) throws IOException protected final JsonToken _parsePosNumber() throws IOException { - return _parsePossibleNumber(false); + return _parseSignedNumber(false); } protected final JsonToken _parseNegNumber() throws IOException { - return _parsePossibleNumber(true); + return _parseSignedNumber(true); } - private final JsonToken _parsePossibleNumber(boolean negative) throws IOException + private final JsonToken _parseSignedNumber(boolean negative) throws IOException { char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); int outPtr = 0; @@ -1109,11 +1109,11 @@ private final JsonToken _parsePossibleNumber(boolean negative) throws IOExceptio } else if (c == INT_PERIOD) { return _parseFloatThatStartsWithPeriod(); } else { - return _handleInvalidNumberStart(c, negative); + return _handleInvalidNumberStart(c, negative, true); } } else { if (c > INT_9) { - return _handleInvalidNumberStart(c, negative); + return _handleInvalidNumberStart(c, negative, true); } c = _inputData.readUnsignedByte(); } @@ -2143,7 +2143,7 @@ protected JsonToken _handleUnexpectedValue(int c) _reportError("Non-standard token 'Infinity': enable `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow"); break; case '+': // note: '-' is taken as number - return _handleInvalidNumberStart(_inputData.readUnsignedByte(), false); + return _handleInvalidNumberStart(_inputData.readUnsignedByte(), false, true); } // [core#77] Try to decode most likely token if (Character.isJavaIdentifierStart(c)) { @@ -2234,8 +2234,12 @@ protected JsonToken _handleApos() throws IOException * Method called if expected numeric value (due to leading sign) does not * look like a number */ - protected JsonToken _handleInvalidNumberStart(int ch, boolean neg) - throws IOException + protected JsonToken _handleInvalidNumberStart(int ch, final boolean neg) throws IOException + { + return _handleInvalidNumberStart(ch, neg, false); + } + + protected JsonToken _handleInvalidNumberStart(int ch, final boolean neg, final boolean hasSign) throws IOException { while (ch == 'I') { ch = _inputData.readUnsignedByte(); @@ -2253,6 +2257,9 @@ protected JsonToken _handleInvalidNumberStart(int ch, boolean neg) } _reportError("Non-standard token '"+match+"': enable `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow"); } + if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature()) && hasSign && !neg) { + reportUnexpectedNumberChar('+', "JSON spec does not allow numbers to have plus signs: enable `JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS` to allow"); + } reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value"); return null; } diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java index 61040ccb61..b749693898 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -812,11 +812,11 @@ public JsonToken nextToken() throws IOException switch (i) { case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleUnexpectedValue(i); } @@ -885,12 +885,12 @@ private final JsonToken _nextTokenNotInObject(int i) throws IOException _matchNull(); return (_currToken = JsonToken.VALUE_NULL); case '-': - return (_currToken = _parsePossibleNumber(true)); + return (_currToken = _parseSignedNumber(true)); case '+': if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { return (_currToken = _handleUnexpectedValue(i)); } - return (_currToken = _parsePossibleNumber(false)); + return (_currToken = _parseSignedNumber(false)); case '.': // [core#611]: return (_currToken = _parseFloatThatStartsWithPeriod()); case '0': @@ -1092,11 +1092,11 @@ public String nextFieldName() throws IOException JsonToken t; switch (i) { case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleUnexpectedValue(i); } @@ -1220,11 +1220,11 @@ private final void _isNextTokenNameYes(int i) throws IOException _nextToken = JsonToken.VALUE_NULL; return; case '-': - _nextToken = _parsePossibleNumber(true); + _nextToken = _parseSignedNumber(true); return; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - _nextToken = _parsePossibleNumber(false); + _nextToken = _parseSignedNumber(false); } else { _nextToken = _handleUnexpectedValue(i); } @@ -1287,11 +1287,11 @@ private final boolean _isNextTokenNameMaybe(int i, SerializableString str) throw t = JsonToken.VALUE_NULL; break; case '-': - t = _parsePossibleNumber(true); + t = _parseSignedNumber(true); break; case '+': if (isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) { - t = _parsePossibleNumber(false); + t = _parseSignedNumber(false); } else { t = _handleUnexpectedValue(i); } @@ -1503,7 +1503,7 @@ protected JsonToken _parsePosNumber(int c) throws IOException return resetInt(false, intLen); } - private final JsonToken _parsePossibleNumber(boolean negative) throws IOException + private final JsonToken _parseSignedNumber(boolean negative) throws IOException { char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); int outPtr = 0; @@ -1524,11 +1524,11 @@ private final JsonToken _parsePossibleNumber(boolean negative) throws IOExceptio if (c == INT_PERIOD) { return _parseFloatThatStartsWithPeriod(); } - return _handleInvalidNumberStart(c, negative); + return _handleInvalidNumberStart(c, negative, true); } c = _verifyNoLeadingZeroes(); } else if (c > INT_9) { - return _handleInvalidNumberStart(c, negative); + return _handleInvalidNumberStart(c, negative, true); } // Ok: we can first just add digit we saw first: @@ -2761,7 +2761,7 @@ protected JsonToken _handleUnexpectedValue(int c) throws IOException _reportInvalidEOFInValue(JsonToken.VALUE_NUMBER_INT); } } - return _handleInvalidNumberStart(_inputBuffer[_inputPtr++] & 0xFF, false); + return _handleInvalidNumberStart(_inputBuffer[_inputPtr++] & 0xFF, false, true); } // [core#77] Try to decode most likely token if (Character.isJavaIdentifierStart(c)) { @@ -2870,7 +2870,12 @@ protected JsonToken _handleApos() throws IOException // Method called if expected numeric value (due to leading sign) does not // look like a number - protected JsonToken _handleInvalidNumberStart(int ch, boolean neg) throws IOException + protected JsonToken _handleInvalidNumberStart(int ch, final boolean neg) throws IOException + { + return _handleInvalidNumberStart(ch, neg, false); + } + + protected JsonToken _handleInvalidNumberStart(int ch, final boolean neg, final boolean hasSign) throws IOException { while (ch == 'I') { if (_inputPtr >= _inputEnd) { @@ -2894,6 +2899,9 @@ protected JsonToken _handleInvalidNumberStart(int ch, boolean neg) throws IOExce _reportError("Non-standard token '%s': enable `JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS` to allow", match); } + if (!isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature()) && hasSign && !neg) { + reportUnexpectedNumberChar('+', "JSON spec does not allow numbers to have plus signs: enable `JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS` to allow"); + } reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value"); return null; } diff --git a/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java index bcf6f662d3..1bd6aea0f8 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NonStandardNumberParsingTest.java @@ -37,14 +37,18 @@ public void testLeadingDotInDecimal() throws Exception { */ public void testLeadingPlusSignInDecimal() throws Exception { for (int mode : ALL_MODES) { - JsonParser p = createParser(mode, " +123 "); - try { + try (JsonParser p = createParser(mode, " +123 ")) { p.nextToken(); fail("Should not pass"); } catch (JsonParseException e) { - verifyException(e, "expected digit (0-9) to follow minus sign, for valid numeric value"); + verifyException(e, "Unexpected character ('+' (code 43)) in numeric value: JSON spec does not allow numbers to have plus signs: enable `JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS` to allow"); + } + try (JsonParser p = createParser(mode, " +0.123 ")) { + p.nextToken(); + fail("Should not pass"); + } catch (JsonParseException e) { + verifyException(e, "Unexpected character ('+' (code 43)) in numeric value: JSON spec does not allow numbers to have plus signs: enable `JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS` to allow"); } - p.close(); } }