Skip to content

Commit 02851fc

Browse files
authored
Check the scale before converting xcontent long values, rather than the absolute value (#111538) (#111584)
Large numbers are rejected, small numbers rounded to zero (if rounding enabled)
1 parent 3301105 commit 02851fc

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

libs/x-content/src/main/java/org/elasticsearch/xcontent/support/AbstractXContentParser.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,8 @@ public int intValue(boolean coerce) throws IOException {
142142

143143
protected abstract int doIntValue() throws IOException;
144144

145-
private static BigInteger LONG_MAX_VALUE_AS_BIGINTEGER = BigInteger.valueOf(Long.MAX_VALUE);
146-
private static BigInteger LONG_MIN_VALUE_AS_BIGINTEGER = BigInteger.valueOf(Long.MIN_VALUE);
147-
// weak bounds on the BigDecimal representation to allow for coercion
148-
private static BigDecimal BIGDECIMAL_GREATER_THAN_LONG_MAX_VALUE = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE);
149-
private static BigDecimal BIGDECIMAL_LESS_THAN_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE).subtract(BigDecimal.ONE);
145+
private static final BigInteger LONG_MAX_VALUE_AS_BIGINTEGER = BigInteger.valueOf(Long.MAX_VALUE);
146+
private static final BigInteger LONG_MIN_VALUE_AS_BIGINTEGER = BigInteger.valueOf(Long.MIN_VALUE);
150147

151148
/** Return the long that {@code stringValue} stores or throws an exception if the
152149
* stored value cannot be converted to a long that stores the exact same
@@ -161,11 +158,21 @@ private static long toLong(String stringValue, boolean coerce) {
161158
final BigInteger bigIntegerValue;
162159
try {
163160
final BigDecimal bigDecimalValue = new BigDecimal(stringValue);
164-
if (bigDecimalValue.compareTo(BIGDECIMAL_GREATER_THAN_LONG_MAX_VALUE) >= 0
165-
|| bigDecimalValue.compareTo(BIGDECIMAL_LESS_THAN_LONG_MIN_VALUE) <= 0) {
161+
// long can have a maximum of 19 digits - any more than that cannot be a long
162+
// the scale is stored as the negation, so negative scale -> big number
163+
if (bigDecimalValue.scale() < -19) {
166164
throw new IllegalArgumentException("Value [" + stringValue + "] is out of range for a long");
167165
}
168-
bigIntegerValue = coerce ? bigDecimalValue.toBigInteger() : bigDecimalValue.toBigIntegerExact();
166+
// large scale -> very small number
167+
if (bigDecimalValue.scale() > 19) {
168+
if (coerce) {
169+
bigIntegerValue = BigInteger.ZERO;
170+
} else {
171+
throw new ArithmeticException("Number has a decimal part");
172+
}
173+
} else {
174+
bigIntegerValue = coerce ? bigDecimalValue.toBigInteger() : bigDecimalValue.toBigIntegerExact();
175+
}
169176
} catch (ArithmeticException e) {
170177
throw new IllegalArgumentException("Value [" + stringValue + "] has a decimal part");
171178
} catch (NumberFormatException e) {

libs/x-content/src/test/java/org/elasticsearch/xcontent/XContentParserTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static org.hamcrest.Matchers.hasSize;
3434
import static org.hamcrest.Matchers.in;
3535
import static org.hamcrest.Matchers.instanceOf;
36+
import static org.hamcrest.Matchers.is;
3637
import static org.hamcrest.Matchers.nullValue;
3738
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
3839

@@ -83,6 +84,44 @@ public void testFloat() throws IOException {
8384
}
8485
}
8586

87+
public void testLongCoercion() throws IOException {
88+
XContentType xContentType = randomFrom(XContentType.values());
89+
90+
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
91+
builder.startObject();
92+
builder.field("decimal", "5.5");
93+
builder.field("expInRange", "5e18");
94+
builder.field("expTooBig", "2e100");
95+
builder.field("expTooSmall", "2e-100");
96+
builder.endObject();
97+
98+
try (XContentParser parser = createParser(xContentType.xContent(), BytesReference.bytes(builder))) {
99+
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
100+
101+
assertThat(parser.nextToken(), is(XContentParser.Token.FIELD_NAME));
102+
assertThat(parser.currentName(), is("decimal"));
103+
assertThat(parser.nextToken(), is(XContentParser.Token.VALUE_STRING));
104+
assertThat(parser.longValue(), equalTo(5L));
105+
106+
assertThat(parser.nextToken(), is(XContentParser.Token.FIELD_NAME));
107+
assertThat(parser.currentName(), is("expInRange"));
108+
assertThat(parser.nextToken(), is(XContentParser.Token.VALUE_STRING));
109+
assertThat(parser.longValue(), equalTo((long) 5e18));
110+
111+
assertThat(parser.nextToken(), is(XContentParser.Token.FIELD_NAME));
112+
assertThat(parser.currentName(), is("expTooBig"));
113+
assertThat(parser.nextToken(), is(XContentParser.Token.VALUE_STRING));
114+
expectThrows(IllegalArgumentException.class, parser::longValue);
115+
116+
// too small goes to zero
117+
assertThat(parser.nextToken(), is(XContentParser.Token.FIELD_NAME));
118+
assertThat(parser.currentName(), is("expTooSmall"));
119+
assertThat(parser.nextToken(), is(XContentParser.Token.VALUE_STRING));
120+
assertThat(parser.longValue(), equalTo(0L));
121+
}
122+
}
123+
}
124+
86125
public void testReadList() throws IOException {
87126
assertThat(readList("{\"foo\": [\"bar\"]}"), contains("bar"));
88127
assertThat(readList("{\"foo\": [\"bar\",\"baz\"]}"), contains("bar", "baz"));

0 commit comments

Comments
 (0)