Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ public enum Feature {
@Deprecated
ALLOW_NUMERIC_LEADING_ZEROS(false),

/**
* @deprecated Use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS} instead
*/
ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS(false),

/**
* @deprecated Use {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS} instead
*/
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/json/JsonReadFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ public enum JsonReadFeature
@SuppressWarnings("deprecation")
ALLOW_LEADING_ZEROS_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS),

/**
* Feature that determines whether parser will allow
* JSON decimal numbers to start with a plus sign
* (like: +123). If enabled, no exception is thrown, and the number
* is parsed as though a leading sign had not been present.
*<p>
* Since JSON specification does not allow leading plus signs,
* this is a non-standard feature, and as such disabled by default.
*
* @since 2.14
*/
@SuppressWarnings("deprecation")
ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS),


/**
* Feature that determines whether parser will allow
* JSON decimal numbers to start with a decimal point
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,10 @@ public final JsonToken nextToken() throws IOException

// Ok: we must have a value... what is it?

if (i == '+' && isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
return nextToken();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is problematic as it allows something like ++++4 I think. Or, potentially, DoS with 10,000 plus signs. :)

But I think it should be possible to handle this as a new entry in switch below, maybe adding case right above 0 ... 9, reading next character, falling through?
Same goes for all parsers.

}

JsonToken t;

switch (i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,10 @@ public JsonToken nextToken() throws IOException
return _currToken;
}

if (i == INT_PLUS && isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
return nextToken();
}

// Nope: do we then expect a comma?
if (_parsingContext.expectComma()) {
if (i != INT_COMMA) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,10 @@ public JsonToken nextToken() throws IOException
return (_currToken = JsonToken.END_OBJECT);
}

if (i == INT_PLUS && isEnabled(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS.mappedFeature())) {
return nextToken();
}

// Nope: do we then expect a comma?
if (_parsingContext.expectComma()) {
if (i != INT_COMMA) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class FastParserNonStandardNumberParsingTest
{
private final JsonFactory fastFactory =
JsonFactory.builder()
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class NonStandardNumberParsingTest
extends com.fasterxml.jackson.core.BaseTest
{
private final JsonFactory JSON_F = JsonFactory.builder()
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
.build();
Expand All @@ -31,6 +32,22 @@ public void testLeadingDotInDecimal() throws Exception {
}
}

/*
* The format "+NNN" (as opposed to "NNN") is not valid JSON, so this should fail
*/
public void testLeadingPlusSignInDecimal() throws Exception {
for (int mode : ALL_MODES) {
JsonParser p = createParser(mode, " +123 ");
try {
p.nextToken();
fail("Should not pass");
} catch (JsonParseException e) {
verifyException(e, "expected digit (0-9) to follow minus sign, for valid numeric value");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cowtowncoder I've left this in draft because the existing code throws an exception with a confusing message if a plus sign precedes a number - is it ok if I use this PR to change the message to say that plus signs are not allowed in front of JSON numbers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutely, improvements to error messages are good.

}
p.close();
}
}

/**
* The format "NNN." (as opposed to "NNN") is not valid JSON, so this should fail
*/
Expand Down Expand Up @@ -73,23 +90,58 @@ public void testTrailingDotInDecimalAllowedReader() throws Exception {
_testTrailingDotInDecimalAllowed(jsonFactory(), MODE_READER);
}

public void testLeadingPlusSignInDecimalAllowedAsync() throws Exception {
_testLeadingPlusSignInDecimalAllowed(jsonFactory(), MODE_DATA_INPUT);
}

public void testLeadingPlusSignInDecimalAllowedBytes() throws Exception {
_testLeadingPlusSignInDecimalAllowed(jsonFactory(), MODE_INPUT_STREAM);
_testLeadingPlusSignInDecimalAllowed(jsonFactory(), MODE_INPUT_STREAM_THROTTLED);
}

public void testLeadingPlusSignInDecimalAllowedReader() throws Exception {
_testLeadingPlusSignInDecimalAllowed(jsonFactory(), MODE_READER);
}

private void _testLeadingDotInDecimalAllowed(JsonFactory f, int mode) throws Exception
{
JsonParser p = createParser(f, mode, " .125 ");
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(0.125, p.getValueAsDouble());
assertEquals("0.125", p.getDecimalValue().toString());
assertEquals(".125", p.getText());
p.close();
try (JsonParser p = createParser(f, mode, " .125 ")) {
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(0.125, p.getValueAsDouble());
assertEquals("0.125", p.getDecimalValue().toString());
assertEquals(".125", p.getText());
}
}

private void _testLeadingPlusSignInDecimalAllowed(JsonFactory f, int mode) throws Exception
{
try (JsonParser p = createParser(f, mode, " +125 ")) {
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(125.0, p.getValueAsDouble());
assertEquals("125", p.getDecimalValue().toString());
assertEquals("125", p.getText());
}
try (JsonParser p = createParser(f, mode, " +.125 ")) {
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(0.125, p.getValueAsDouble());
assertEquals("0.125", p.getDecimalValue().toString());
assertEquals(".125", p.getText());
}
}

private void _testTrailingDotInDecimalAllowed(JsonFactory f, int mode) throws Exception
{
JsonParser p = createParser(f, mode, " 125. ");
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(125.0, p.getValueAsDouble());
assertEquals("125", p.getDecimalValue().toString());
assertEquals("125.", p.getText());
p.close();
try (JsonParser p = createParser(f, mode, " 125. ")) {
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(125.0, p.getValueAsDouble());
assertEquals("125", p.getDecimalValue().toString());
assertEquals("125.", p.getText());
}
try (JsonParser p = createParser(f, mode, " 125. ")) {
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(125.0, p.getValueAsDouble());
assertEquals("125", p.getDecimalValue().toString());
assertEquals("125.", p.getText());
}
}
}