Require exact integer values for sliders
Amolith
created 3 days ago
Check integer range bounds, current values, and options as XML integer
text before parsing them as floats. Also require each parsed value to
round-trip to the same integer and reject no-option unit-step ranges
outside float's consecutive exact integer window.
Change summary
src/main/java/eu/siacs/conversations/entities/Conversation.java | 22
src/test/java/eu/siacs/conversations/entities/ConversationTest.java | 21
2 files changed, 40 insertions(+), 3 deletions(-)
Detailed changes
@@ -1878,14 +1878,14 @@ public class Conversation extends AbstractEntity
final String minValue = range == null ? null : range.getAttribute("min");
final String maxValue = range == null ? null : range.getAttribute("max");
final boolean integerDatatype = isIntegerDatatype(datatype);
- if (integerDatatype && (!isIntegerLexicalValue(minValue) || !isIntegerLexicalValue(maxValue))) return null;
+ if (integerDatatype && (!isExactIntegerSliderValue(minValue) || !isExactIntegerSliderValue(maxValue))) return null;
final Float min = parseFloat(minValue);
final Float max = parseFloat(maxValue);
if (min == null || max == null || min >= max) return null;
final String value = firstValue(field);
if (value == null || value.equals("")) return null;
- if (integerDatatype && !isIntegerLexicalValue(value)) return null;
+ if (integerDatatype && !isExactIntegerSliderValue(value)) return null;
final Float parsedValue = parseFloat(value);
if (parsedValue == null || parsedValue < min || parsedValue > max) return null;
@@ -1894,6 +1894,7 @@ public class Conversation extends AbstractEntity
final Float step;
if (options.size() < 2) {
+ if (integerDatatype && !isExactIntegerUnitRange(minValue, maxValue)) return null;
step = integerDatatype ? 1f : 0f;
} else {
step = uniformStep(options);
@@ -1932,7 +1933,7 @@ public class Conversation extends AbstractEntity
final List<Float> values = new ArrayList<>();
for (final Option option : Option.forField(field)) {
final String optionValue = option.getValue();
- if (integerDatatype && !isIntegerLexicalValue(optionValue)) return null;
+ if (integerDatatype && !isExactIntegerSliderValue(optionValue)) return null;
final Float value = parseFloat(optionValue);
if (value == null) return null;
values.add(value);
@@ -1968,6 +1969,21 @@ public class Conversation extends AbstractEntity
return Math.abs(Math.rint(multiple) - multiple) < 0.0001f;
}
+ private static boolean isExactIntegerSliderValue(final String value) {
+ if (!isIntegerLexicalValue(value)) return false;
+ final Float parsed = parseFloat(value);
+ return parsed != null
+ && new BigDecimal(Float.toString(parsed)).compareTo(new BigDecimal(value)) == 0;
+ }
+
+ private static boolean isExactIntegerUnitRange(final String minValue, final String maxValue) {
+ final BigDecimal min = new BigDecimal(minValue);
+ final BigDecimal max = new BigDecimal(maxValue);
+ final BigDecimal maxConsecutiveInteger = new BigDecimal("16777216");
+ return min.compareTo(maxConsecutiveInteger.negate()) >= 0
+ && max.compareTo(maxConsecutiveInteger) <= 0;
+ }
+
private static boolean isIntegerLexicalValue(final String value) {
if (value == null || value.equals("")) return false;
final int start = value.charAt(0) == '+' || value.charAt(0) == '-' ? 1 : 0;
@@ -241,6 +241,27 @@ public class ConversationTest {
Assert.assertNull(Conversation.steppedSliderStep(field));
}
+ @Test
+ public void steppedSliderStepRejectsFractionalIntegerValueAtFloatPrecisionBoundary() {
+ final var field = sliderField("xs:integer", "16777216", "16777218", "16777216.5");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
+ @Test
+ public void steppedSliderStepRejectsIntegerValueRoundedByFloatParsing() {
+ final var field = sliderField("xs:integer", "16777216", "16777218", "16777217");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
+ @Test
+ public void steppedSliderStepRejectsIntegerRangeWithUnrepresentableIntermediateValue() {
+ final var field = sliderField("xs:integer", "16777216", "16777218", "16777216");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
@Test
public void formatSliderValueDoesNotClampLargeIntegerDatatypesToInt() {
Assert.assertEquals("3000000000", Conversation.formatSliderValue(3_000_000_000f, "xs:long"));