Correctly encode special characters in JSON

Change-Id: Idc6ad9eb93b44ba5ba1ce4b830b51e404d53e1a7
diff --git a/elemental/src/elemental/json/impl/JsonUtil.java b/elemental/src/elemental/json/impl/JsonUtil.java
index c84fa75..e853e5e 100644
--- a/elemental/src/elemental/json/impl/JsonUtil.java
+++ b/elemental/src/elemental/json/impl/JsonUtil.java
@@ -188,7 +188,7 @@
     for (int i = 0; i < text.length(); i++) {
       char c = text.charAt(i);
       if (isControlChar(c)) {
-        toReturn.append(escapeStringAsUnicode(String.valueOf(c)));
+        toReturn.append(escapeCharAsUnicode(c));
       } else {
         toReturn.append(c);
       }
@@ -233,7 +233,7 @@
           break;
         default:
           if (isControlChar(c)) {
-            toAppend = escapeStringAsUnicode(String.valueOf(c));
+            toAppend = escapeCharAsUnicode(c);
           }
       }
       toReturn.append(toAppend);
@@ -285,11 +285,10 @@
   /**
    * Turn a single unicode character into a 32-bit unicode hex literal.
    */
-  private static String escapeStringAsUnicode(String match) {
-    String hexValue = Integer.toString(match.charAt(0), 16);
-    hexValue = hexValue.length() > 4 ? hexValue.substring(hexValue.length() - 4)
-        : hexValue;
-    return "\\u0000" + hexValue;
+  private static String escapeCharAsUnicode(char toEscape) {
+    String hexValue = Integer.toString(toEscape, 16);
+    int padding = 4 - hexValue.length();
+    return "\\u" + ("0000".substring(0, padding)) + hexValue;
   }
 
   private static boolean isControlChar(char c) {
diff --git a/elemental/tests/elemental/json/JsonUtilTest.java b/elemental/tests/elemental/json/JsonUtilTest.java
index d9e2265..d331c2e 100755
--- a/elemental/tests/elemental/json/JsonUtilTest.java
+++ b/elemental/tests/elemental/json/JsonUtilTest.java
@@ -111,7 +111,7 @@
   
   public void testEscapeControlChars() {
     String unicodeString = "\u2060Test\ufeffis a test\u17b5";
-    assertEquals("\\u00002060Test\\u0000feffis a test\\u000017b5",
+    assertEquals("\\u2060Test\\ufeffis a test\\u17b5",
         JsonUtil.escapeControlChars(unicodeString));
   }
 
@@ -142,8 +142,8 @@
 
   public void testQuote() {
     String badString = "\bThis\"is\ufeff\ta\\bad\nstring\u2029\u2029";
-    assertEquals("\"\\bThis\\\"is\\u0000feff\\ta\\\\bad\\nstring"
-        + "\\u00002029\\u00002029\"", JsonUtil.quote(badString));
+    assertEquals("\"\\bThis\\\"is\\ufeff\\ta\\\\bad\\nstring"
+        + "\\u2029\\u2029\"", JsonUtil.quote(badString));
   }
 
   public void testStringify() {
@@ -214,4 +214,48 @@
     o.y = o.x + 1;
     return o;
   }-*/;
+
+  public void testQuoteCharacters() {
+    // See spec at https://tools.ietf.org/html/rfc7159
+    for (int i = 0; i < 0xffff; i++) {
+      String unencodedString = String.valueOf((char) i);
+      String res = JsonUtil.quote(unencodedString);
+      if (res.equals("\"" + unencodedString + "\"")) {
+        // passed through unescaped
+        if (i == 0x20 || i == 0x21 || (i >= 0x23 && i <= 0x5b)
+          || i >= 0x5d) {
+            // ok for %x20-21 / %x23-5B / %x5D-10FFFF
+        } else {
+          fail("Character " + i + " must be escaped in JSON");
+        }
+      } else {
+        // Was escaped, should be in format \\X or \\uXXXX
+        if (res.length() == 4) {
+          // "\\X"
+          char escapedChar = res.charAt(2);
+          // btnfr\"
+          if (escapedChar == 'b') {
+            assertEquals('\b',i);
+          } else if (escapedChar == 't') {
+            assertEquals('\t',i);
+          } else if (escapedChar == 'n') {
+            assertEquals('\n',i);
+          } else if (escapedChar == 'f') {
+            assertEquals('\f',i);
+          } else if (escapedChar == 'r') {
+            assertEquals('\r',i);
+          } else if (escapedChar == '"') {
+            assertEquals('"',i);
+          } else if (escapedChar == '\\') {
+            assertEquals('\\',i);
+          } else {
+            fail("Character" + i + " was unexpectedly escaped as "+escapedChar);
+          }
+        } else {
+          assertTrue("Character " + i + " was incorrectly encoded as " +
+            res,res.matches("\"\\\\u....\""));
+        }
+      }
+    }
+  }
 }