Skip to content

Commit 7699eef

Browse files
committed
fix: Fix bug stringifying number-like keys and values
A string like "1e 6" will currently be stringified as 1e+6, which is ambiguous because it looks like a number literal and it will be parsed as one. This patch fixes the stringify code so that these strings are quoted. AQF strings have a similar issue and this patch addresses them as well.
1 parent 32a89ad commit 7699eef

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

module/jsonurl-core/src/main/java/org/jsonurl/text/JsonUrlTextAppender.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,23 @@ private static <T extends Appendable> boolean appendLiteral(
520520
}
521521
return true;
522522
}
523+
524+
if (NumberBuilder.isNumber(text, start, end, false, true, options)) {
525+
//
526+
// Special handling if this would look like a number literal once
527+
// the space is replaced with '+'
528+
//
529+
if (optionAQF(options)) {
530+
dest.append('!');
531+
encodeAqf(dest, text, start, end);
532+
533+
} else {
534+
dest.append('\'');
535+
encode(dest, text, start, end, true, false);
536+
dest.append('\'');
537+
}
538+
return true;
539+
}
523540

524541
if (optionAQF(options)) {
525542
encodeAqf(dest, text, start, end);

module/jsonurl-core/src/main/java/org/jsonurl/text/NumberBuilder.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public class NumberBuilder implements NumberText { // NOPMD
124124
* minus/dash.
125125
*/
126126
private static final char PLUS = '+';
127+
128+
/**
129+
* ASCII space.
130+
*/
131+
private static final char SPACE = ' ';
127132

128133
/**
129134
* Lookup table for exponent values.
@@ -382,7 +387,8 @@ public Exponent getExponentType() {
382387
private static NumberText.Exponent getExponentType(//NOPMD
383388
CharSequence text,
384389
int start,
385-
int stop) {
390+
int stop,
391+
boolean spaceIsPlus) {
386392

387393
if (stop <= start) {
388394
return NumberText.Exponent.NONE;
@@ -406,6 +412,11 @@ private static NumberText.Exponent getExponentType(//NOPMD
406412
char c = text.charAt(i); //NOPMD
407413

408414
switch (c) {
415+
case SPACE:
416+
if (!spaceIsPlus) {
417+
return NumberText.Exponent.NONE;
418+
}
419+
// fall through
409420
case PLUS:
410421
i++;
411422
if (i == stop) {
@@ -521,7 +532,7 @@ public boolean parse(
521532
fractIndexStop = pos = digits(text, pos + 1, stop);
522533
}
523534

524-
exponentType = getExponentType(text, pos, stop);
535+
exponentType = getExponentType(text, pos, stop, false);
525536

526537
switch (exponentType) { // NOPMD - SwitchStmtsShouldHaveDefault
527538
case JUST_VALUE:
@@ -609,6 +620,27 @@ public static boolean isNumber(
609620
return isNumber(text, start, stop, false, options);
610621
}
611622

623+
/**
624+
* Determine if the given CharSequence is a valid JSON&#x2192;URL number literal.
625+
*
626+
* <p>Convenience for {@link
627+
* #isNumber(CharSequence, int, int, boolean, boolean, Set)
628+
* isNumber(s, 0, stop, isNonFractional, false, options)}.
629+
* @param text a valid CharSequence
630+
* @param start an index
631+
* @param stop an index
632+
* @param options a valid set of options or {@code null}
633+
* @return true if the CharSequence is a JSON&#x2192;URL number
634+
*/
635+
public static boolean isNumber(
636+
CharSequence text,
637+
int start,
638+
int stop,
639+
boolean isNonFractional,
640+
Set<JsonUrlOption> options) {
641+
return isNumber(text, start, stop, isNonFractional, false, options);
642+
}
643+
612644
/**
613645
* Determine if the given CharSequence is a valid JSON&#x2192;URL number literal.
614646
*
@@ -633,6 +665,7 @@ public static boolean isNumber(
633665
int start,
634666
int stop,
635667
boolean isNonFractional,
668+
boolean spaceIsPlus,
636669
Set<JsonUrlOption> options) {
637670

638671
int pos = start; //NOPMD - DataflowAnomalyAnalysis
@@ -677,7 +710,7 @@ public static boolean isNumber(
677710

678711
final int expDigitSkip;
679712

680-
switch (getExponentType(text, pos, stop)) {
713+
switch (getExponentType(text, pos, stop, spaceIsPlus)) {
681714
case JUST_VALUE:
682715
expDigitSkip = 1;
683716
pos = digits(text, pos + 1, stop);

module/jsonurl-core/src/test/java/org/jsonurl/stream/JsonUrlIteratorTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,17 @@ static Stream<EventTest> testAQF() {
414414
"(hello)",
415415
JsonUrlEvent.END_STREAM}),
416416

417+
new EventTest(
418+
"(!1e+1:!1e+1)",
419+
new Object[] {
420+
JsonUrlEvent.START_OBJECT,
421+
JsonUrlEvent.KEY_NAME,
422+
"1e 1",
423+
JsonUrlEvent.VALUE_STRING,
424+
"1e 1",
425+
JsonUrlEvent.END_OBJECT,
426+
JsonUrlEvent.END_STREAM}),
427+
417428
}));
418429
}
419430

@@ -607,6 +618,17 @@ static Stream<EventTest> testNotAQF() {
607618
"!(hello!)",
608619
JsonUrlEvent.END_STREAM}),
609620

621+
new EventTest(
622+
"('1e+1':'1e+1')",
623+
new Object[] {
624+
JsonUrlEvent.START_OBJECT,
625+
JsonUrlEvent.KEY_NAME,
626+
"1e 1",
627+
JsonUrlEvent.VALUE_STRING,
628+
"1e 1",
629+
JsonUrlEvent.END_OBJECT,
630+
JsonUrlEvent.END_STREAM}),
631+
610632
}));
611633
}
612634

@@ -1295,6 +1317,17 @@ static Stream<EventTest> testNotAQF() {
12951317
JsonUrlEvent.END_OBJECT,
12961318
JsonUrlEvent.END_STREAM}),
12971319

1320+
new EventTest(
1321+
"(1e+1:1e+1)",
1322+
new Object[] {
1323+
JsonUrlEvent.START_OBJECT,
1324+
JsonUrlEvent.KEY_NAME,
1325+
"1e+1",
1326+
JsonUrlEvent.VALUE_NUMBER,
1327+
"1e+1",
1328+
JsonUrlEvent.END_OBJECT,
1329+
JsonUrlEvent.END_STREAM}),
1330+
12981331

12991332
// Exception
13001333
new EventTest(

module/jsonurl-core/src/test/java/org/jsonurl/text/JsonUrlStringBuilderTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,33 @@ void testText6() throws IOException {
527527
expected);
528528
}
529529

530+
@Test
531+
void testText7() throws IOException {
532+
String expectedAqf = "(!1e+3:1)";
533+
assertEquals(
534+
expectedAqf,
535+
new JsonUrlStringBuilder(JsonUrlOption.AQF)
536+
.beginObject()
537+
.addKey("1e 3")
538+
.nameSeparator()
539+
.add(1)
540+
.endObject()
541+
.build(),
542+
expectedAqf);
543+
544+
String expected = "('1e+3':1)";
545+
assertEquals(
546+
expected,
547+
new JsonUrlStringBuilder()
548+
.beginObject()
549+
.addKey("1e 3")
550+
.nameSeparator()
551+
.add(1)
552+
.endObject()
553+
.build(),
554+
expected);
555+
}
556+
530557
@ParameterizedTest
531558
@Tag("exception")
532559
@ValueSource(strings = {

0 commit comments

Comments
 (0)