Skip to content

Commit

Permalink
Fix for Bug#34529014, JSonParser not accepting valid JSON string when…
Browse files Browse the repository at this point in the history
… converting to DbDoc.

Change-Id: I643277d83cda4449c0af93310ddbe417ed3f057c
  • Loading branch information
fjssilva committed Sep 3, 2022
1 parent 83bbcac commit cb57466
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 62 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Version 8.0.31

- Fix for Bug#34529014, JSonParser not accepting valid JSON string when converting to DbDoc.

- Fix for Bug#67828 (Bug#15967406), Crashing applets due to reading file.encoding system property.

- WL#15259, Review maven publishing process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ JsonParser.8=Wrong ''{0}'' position after ''{1}''.
JsonParser.10=Wrong ''{0}'' occurrence after ''{1}'', it is allowed only once per number.
JsonParser.11=''.'' is not allowed in the exponent.
JsonParser.12=Wrong literal ''{0}''.
JsonParser.13=Invalid Unicode code point ''{0}''.

LoadBalanceConnectionGroupManager.0=Unable to register load-balance management bean with JMX

Expand Down
63 changes: 39 additions & 24 deletions src/main/user-impl/java/com/mysql/cj/xdevapi/JsonParser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
Expand Down Expand Up @@ -41,7 +41,6 @@
import com.mysql.cj.exceptions.WrongArgumentException;

public class JsonParser {

enum Whitespace {
TAB('\u0009'), LF('\n'), CR('\r'), SPACE('\u0020');

Expand Down Expand Up @@ -88,53 +87,55 @@ private StructuralToken(char character) {

enum EscapeChar {
/**
* \" represents the quotation mark character (U+0022)
* \\" represents the quotation mark character (U+0022)
*/
QUOTE('\u0022', "\\\""),
QUOTE('\u0022', "\\\"", true),
/**
* \\ represents the reverse solidus character (U+005C)
* \\\\ represents the reverse solidus character (U+005C)
*/
RSOLIDUS('\\', "\\\\"),
RSOLIDUS('\\', "\\\\", true),
/**
* \/ represents the solidus character (U+002F)
* \\/ represents the solidus character (U+002F)
*/
SOLIDUS('\u002F', "\\\u002F"),
SOLIDUS('\u002F', "\\\u002F", false),
/**
* \b represents the backspace character (U+0008)
* \\b represents the backspace character (U+0008)
*/
BACKSPACE('\u0008', "\\b"),
BACKSPACE('\u0008', "\\b", true),
/**
* \f represents the form feed character (U+000C)
* \\f represents the form feed character (U+000C)
*/
FF('\u000C', "\\f"),
FF('\u000C', "\\f", true),
/**
* \n represents the line feed character (U+000A)
* \\n represents the line feed character (U+000A)
*/
LF('\n', "\\n"),
LF('\n', "\\n", true),
/**
* \r represents the carriage return character (U+000D)
* \\r represents the carriage return character (U+000D)
*/
CR('\r', "\\r"),
CR('\r', "\\r", true),
/**
* \t represents the character tabulation character (U+0009)
* \\t represents the character tabulation character (U+0009)
*/
TAB('\t', "\\t");
TAB('\t', "\\t", true);

public final char CHAR;
public final String ESCAPED;
public final boolean NEEDS_ESCAPING;

private EscapeChar(char character, String escaped) {
private EscapeChar(char character, String escaped, boolean needsEscaping) {
this.CHAR = character;
this.ESCAPED = escaped;
this.NEEDS_ESCAPING = needsEscaping;
}
};

static Set<Character> whitespaceChars = new HashSet<>();
static HashMap<Character, Character> unescapeChars = new HashMap<>();
static HashMap<Character, Character> escapeChars = new HashMap<>();

static {
for (EscapeChar ec : EscapeChar.values()) {
unescapeChars.put(ec.ESCAPED.charAt(1), ec.CHAR);
escapeChars.put(ec.ESCAPED.charAt(1), ec.CHAR);
}
for (Whitespace ws : Whitespace.values()) {
whitespaceChars.add(ws.CHAR);
Expand Down Expand Up @@ -379,8 +380,23 @@ static JsonString parseString(StringReader reader) throws IOException {
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (escapeNextChar) {
if (unescapeChars.containsKey(ch)) {
appendChar(sb, unescapeChars.get(ch));
if (escapeChars.containsKey(ch)) {
appendChar(sb, escapeChars.get(ch));
} else if (ch == 'u') {
// \\u[4 hex digits] represents a unicode code point (ISO/IEC 10646)
char[] buf = new char[4];
int countRead = reader.read(buf);
String hexCodePoint = countRead == -1 ? "" : String.valueOf(buf, 0, countRead);
if (countRead != 4) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.13", new String[] { hexCodePoint }));
}
try {
appendChar(sb, (char) Integer.parseInt(hexCodePoint, 16));
} catch (NumberFormatException e) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.13", new String[] { hexCodePoint }));
}
} else {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.7", new Character[] { ch }));
}
Expand Down Expand Up @@ -589,5 +605,4 @@ static JsonLiteral parseLiteral(StringReader reader) throws IOException {

throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.12", new String[] { sb.toString() }));
}

}
6 changes: 4 additions & 2 deletions src/main/user-impl/java/com/mysql/cj/xdevapi/JsonString.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
Expand Down Expand Up @@ -42,7 +42,9 @@ public class JsonString implements JsonValue {

static {
for (EscapeChar ec : EscapeChar.values()) {
escapeChars.put(ec.CHAR, ec.ESCAPED);
if (ec.NEEDS_ESCAPING) {
escapeChars.put(ec.CHAR, ec.ESCAPED);
}
}
}

Expand Down
91 changes: 55 additions & 36 deletions src/test/java/com/mysql/cj/xdevapi/JsonDocTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
Expand Down Expand Up @@ -46,11 +46,38 @@
* DbDoc tests.
*/
public class JsonDocTest {
protected static <EX extends Throwable> EX assertThrows(Class<EX> throwable, Callable<?> testRoutine) {
try {
testRoutine.call();
} catch (Throwable t) {
assertTrue(throwable.isAssignableFrom(t.getClass()),
"Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown.");
return throwable.cast(t);
}
fail("Expected exception of type '" + throwable.getName() + "'.");

// never reaches here
return null;
}

protected static <EX extends Throwable> EX assertThrows(Class<EX> throwable, String msgMatchesRegex, Callable<?> testRoutine) {
try {
testRoutine.call();
} catch (Throwable t) {
assertTrue(throwable.isAssignableFrom(t.getClass()),
"Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown.");
assertTrue(t.getMessage().matches(msgMatchesRegex), "The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "].");
return throwable.cast(t);
}
fail("Expected exception of type '" + throwable.getName() + "'.");

// never reaches here
return null;
}

@Test
public void testEscaping() throws Exception {

String testStr = "\"\\\"\\\\\\\u002F\\b\\f\\n\\r\\t\"";
String testStr = "\"\\\"\\\\\u002F\\b\\f\\n\\r\\t\"";
JsonString val = JsonParser.parseString(new StringReader(testStr));
assertEquals(8, val.getString().length());
assertEquals('\"', val.getString().charAt(0));
Expand Down Expand Up @@ -81,11 +108,20 @@ public void bracketAsValue() throws Exception {

@Test
public void testParseString() throws Exception {

// ignore whitespaces
JsonString val = JsonParser.parseString(new StringReader(" \\n\\r \" qq \" "));
assertEquals(" qq ", val.getString());

val = JsonParser.parseString(new StringReader("\"\\u005C// \\\"foo\\u003Dbar\\\" & \\uD83D\\uDC2C\tbaz\\n\""));
assertEquals("\\// \"foo=bar\" & \uD83D\uDC2C\tbaz\n", val.getString());
val = JsonParser.parseString(new StringReader("\"\\// \\\"foo\u003Dbar\\\" & \uD83D\uDC2C\tbaz\n\""));
assertEquals("// \"foo=bar\" & \uD83D\uDC2C\tbaz\n", val.getString());

JsonString jsonStr = new JsonString().setValue("\\u005C// \"foo\\u003Dbar\" & \\uD83D\\uDC2C\\tbaz\\n");
assertEquals("\\u005C// \"foo\\u003Dbar\" & \\uD83D\\uDC2C\\tbaz\\n", jsonStr.getString());
jsonStr = new JsonString().setValue("\\// \"foo\u003Dbar\" & \uD83D\uDC2C\tbaz\n");
assertEquals("\\// \"foo=bar\" & \uD83D\uDC2C\tbaz\n", jsonStr.getString());

// don't ignore other symbols before opening quotation mark
assertThrows(WrongArgumentException.class, "Attempt to add character '\\\\' to unopened string.", new Callable<Void>() {
public Void call() throws Exception {
Expand Down Expand Up @@ -121,7 +157,6 @@ public Void call() throws Exception {

@Test
public void testParseNumber() throws Exception {

JsonNumber val;

// ignore whitespaces
Expand Down Expand Up @@ -534,7 +569,6 @@ public Void call() throws Exception {

@Test
public void testParseDoc() throws Exception {

DbDoc doc;

// empty doc
Expand Down Expand Up @@ -685,11 +719,25 @@ public Void call() throws Exception {
assertTrue(DbDoc.class.isAssignableFrom(doc.get("x").getClass()));
assertEquals("{\"y\":true}", doc.get("x").toString());
assertEquals("{\n\"y\" : true\n}", doc.get("x").toFormattedString());

// Doc with escape sequences
doc = new DbDocImpl();
doc.add("foo", new JsonString().setValue("\\u005C// \"foo\\u003Dbar\" & \\uD83D\\uDC2C\tbaz\\n"));
assertEquals("{\"foo\":\"\\\\u005C// \\\"foo\\\\u003Dbar\\\" & \\\\uD83D\\\\uDC2C\\tbaz\\\\n\"}", doc.toString());
assertEquals("\\u005C// \"foo\\u003Dbar\" & \\uD83D\\uDC2C\tbaz\\n", ((JsonString) doc.get("foo")).getString());
// - Escape sequences are processed differently when sent in Java Strings and JSON string to be parsed. The next are identical:
DbDoc doc1 = new DbDocImpl();
doc1.add("foo", new JsonString().setValue("\\// \"foo\u003Dbar\" & \uD83D\uDC2C\tbaz\n"));
assertEquals("{\"foo\":\"\\\\// \\\"foo=bar\\\" & \uD83D\uDC2C\\tbaz\\n\"}", doc1.toString());
assertEquals("\\// \"foo=bar\" & \uD83D\uDC2C\tbaz\n", ((JsonString) doc1.get("foo")).getString());
DbDoc doc2 = JsonParser.parseDoc("{ \"foo\": \"\\u005C// \\\"foo\\u003Dbar\\\" & \\uD83D\\uDC2C\tbaz\\n\" }");
assertEquals("{\"foo\":\"\\\\// \\\"foo=bar\\\" & \uD83D\uDC2C\\tbaz\\n\"}", doc2.toString());
assertEquals("\\// \"foo=bar\" & \uD83D\uDC2C\tbaz\n", ((JsonString) doc2.get("foo")).getString());
assertEquals(doc1.toFormattedString(), doc2.toFormattedString());
}

@Test
public void testToJsonString() {

DbDoc doc = new DbDocImpl().add("field1", new JsonString().setValue("value 1")).add("field2", new JsonNumber().setValue("12345.44E22"))
.add("field3", JsonLiteral.TRUE).add("field4", JsonLiteral.FALSE).add("field5", JsonLiteral.NULL)
.add("field6",
Expand All @@ -714,33 +762,4 @@ public void testToJsonString() {
public void testJsonNumberAtEnd() throws Exception {
JsonParser.parseDoc(new StringReader("{\"x\":2}"));
}

protected static <EX extends Throwable> EX assertThrows(Class<EX> throwable, Callable<?> testRoutine) {
try {
testRoutine.call();
} catch (Throwable t) {
assertTrue(throwable.isAssignableFrom(t.getClass()),
"Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown.");
return throwable.cast(t);
}
fail("Expected exception of type '" + throwable.getName() + "'.");

// never reaches here
return null;
}

protected static <EX extends Throwable> EX assertThrows(Class<EX> throwable, String msgMatchesRegex, Callable<?> testRoutine) {
try {
testRoutine.call();
} catch (Throwable t) {
assertTrue(throwable.isAssignableFrom(t.getClass()),
"Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown.");
assertTrue(t.getMessage().matches(msgMatchesRegex), "The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "].");
return throwable.cast(t);
}
fail("Expected exception of type '" + throwable.getName() + "'.");

// never reaches here
return null;
}
}

0 comments on commit cb57466

Please sign in to comment.