@@ -1875,21 +1875,26 @@ public class Conversation extends AbstractEntity
if (!isNumericDatatype(datatype)) return null;
final Element range = validate.findChild("range", "http://jabber.org/protocol/xdata-validate");
- final Float min = rangeFloat(range, "min");
- final Float max = rangeFloat(range, "max");
+ 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;
+ 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;
final Float parsedValue = parseFloat(value);
if (parsedValue == null || parsedValue < min || parsedValue > max) return null;
- final List<Float> options = optionValues(field);
+ final List<Float> options = optionValues(field, integerDatatype);
if (options == null) return null;
final Float step;
if (options.size() < 2) {
- step = isIntegerDatatype(datatype) ? 1f : 0f;
+ step = integerDatatype ? 1f : 0f;
} else {
step = uniformStep(options);
if (step == null) return null;
@@ -1923,10 +1928,12 @@ public class Conversation extends AbstractEntity
return step;
}
- private static List<Float> optionValues(final Element field) {
+ private static List<Float> optionValues(final Element field, final boolean integerDatatype) {
final List<Float> values = new ArrayList<>();
for (final Option option : Option.forField(field)) {
- final Float value = parseFloat(option.getValue());
+ final String optionValue = option.getValue();
+ if (integerDatatype && !isIntegerLexicalValue(optionValue)) return null;
+ final Float value = parseFloat(optionValue);
if (value == null) return null;
values.add(value);
}
@@ -1961,6 +1968,16 @@ public class Conversation extends AbstractEntity
return Math.abs(Math.rint(multiple) - multiple) < 0.0001f;
}
+ 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;
+ if (start == value.length()) return false;
+ for (int i = start; i < value.length(); i++) {
+ if (!Character.isDigit(value.charAt(i))) return false;
+ }
+ return true;
+ }
+
private static boolean isIntegerDatatype(final String datatype) {
return "xs:integer".equals(datatype)
|| "xs:int".equals(datatype)
@@ -220,6 +220,27 @@ public class ConversationTest {
Assert.assertNull(Conversation.steppedSliderStep(malformedMax));
}
+ @Test
+ public void steppedSliderStepRejectsIntegerValueThatCannotLandOnStep() {
+ final var field = sliderField("xs:integer", "0", "10", "0.5");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
+ @Test
+ public void steppedSliderStepRejectsFractionalIntegerBounds() {
+ final var field = sliderField("xs:integer", "0.5", "10.5", "1.5");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
+ @Test
+ public void steppedSliderStepRejectsFractionalIntegerOptions() {
+ final var field = sliderField("xs:integer", "0", "1", "0", "0", "0.5", "1");
+
+ Assert.assertNull(Conversation.steppedSliderStep(field));
+ }
+
@Test
public void formatSliderValueDoesNotClampLargeIntegerDatatypesToInt() {
Assert.assertEquals("3000000000", Conversation.formatSliderValue(3_000_000_000f, "xs:long"));
@@ -310,6 +331,23 @@ public class ConversationTest {
}
}
+ @Test
+ public void rangedIntegerFieldWithFractionalValuesFallsBackToTextInput() {
+ final var session = withOccupantId.pagerAdapter.new CommandSession(
+ "test", "node", mock(XmppConnectionService.class));
+ try {
+ session.responseElement = new Element("x", Namespace.DATA);
+ session.responseElement.setAttribute("type", "form");
+ final var fractionalBounds = sliderField("xs:integer", "0.5", "10.5", "1.5");
+ final var fractionalOptions = sliderField("xs:integer", "0", "1", "0", "0", "0.5", "1");
+
+ Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(fractionalBounds).viewType);
+ Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(fractionalOptions).viewType);
+ } finally {
+ session.loadingTimer.cancel();
+ }
+ }
+
@Test
public void sliderFieldViewHolderRebindsDifferentStepSizesWithoutCrashing() {
final var context = new ContextThemeWrapper(