Complete the proper handling of all special characters in JSON strings for TJSONProtocol

Reviewed By: dreiss

Test Plan: test/java/TestClient


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665373 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/java/src/TByteArrayOutputStream.java b/lib/java/src/TByteArrayOutputStream.java
new file mode 100644
index 0000000..94ce003
--- /dev/null
+++ b/lib/java/src/TByteArrayOutputStream.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+package com.facebook.thrift;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Class that allows access to the underlying buf without doing deep
+ * copies on it.
+ *
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+public class TByteArrayOutputStream extends ByteArrayOutputStream {
+  public TByteArrayOutputStream(int size) {
+    super(size);
+  }
+
+  public byte[] get() {
+    return buf;
+  }
+
+  public int len() {
+    return count;
+  }
+}
diff --git a/lib/java/src/protocol/TJSONProtocol.java b/lib/java/src/protocol/TJSONProtocol.java
index 07dee63..fefa65c 100644
--- a/lib/java/src/protocol/TJSONProtocol.java
+++ b/lib/java/src/protocol/TJSONProtocol.java
@@ -6,8 +6,10 @@
 
 package com.facebook.thrift.protocol;
 
+import com.facebook.thrift.TByteArrayOutputStream;
 import com.facebook.thrift.TException;
 import com.facebook.thrift.transport.TTransport;
+import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.Stack;
 
@@ -30,11 +32,11 @@
 
   public static final byte[] COMMA = new byte[] {','};
   public static final byte[] COLON = new byte[] {':'};
-  public static final byte[] QUOTE = new byte[] {'"'};
   public static final byte[] LBRACE = new byte[] {'{'};
   public static final byte[] RBRACE = new byte[] {'}'};
   public static final byte[] LBRACKET = new byte[] {'['};
   public static final byte[] RBRACKET = new byte[] {']'};
+  public static final char QUOTE = '"';
 
   protected class Context {
     protected void write() throws TException {}
@@ -208,26 +210,55 @@
 
   public void writeString(String str) throws TException {
     writeContext_.write();
-    trans_.write(QUOTE);
-
     int length = str.length();
-    StringBuffer escape = new StringBuffer(length);
-    char c;
+    StringBuffer escape = new StringBuffer(length + 16);
+    escape.append(QUOTE);
     for (int i = 0; i < length; ++i) {
-      c = str.charAt(i);
+      char c = str.charAt(i);
       switch (c) {
       case '"':
       case '\\':
         escape.append('\\');
         escape.append(c);
         break;
+      case '\b':
+        escape.append('\\');
+        escape.append('b');
+        break;
+      case '\f':
+        escape.append('\\');
+        escape.append('f');
+        break;
+      case '\n':
+        escape.append('\\');
+        escape.append('n');
+        break;
+      case '\r':
+        escape.append('\\');
+        escape.append('r');
+        break;
+      case '\t':
+        escape.append('\\');
+        escape.append('t');
+        break;
       default:
-        escape.append(c);
+        // Control characeters! According to JSON RFC u0020 (space)
+        if (c < ' ') {
+          String hex = Integer.toHexString(c);
+          escape.append('\\');
+          escape.append('u');
+          for (int j = 4; j > hex.length(); --j) {
+            escape.append('0');
+          }
+          escape.append(hex);
+        } else {
+          escape.append(c);
+        }
         break;
       }
     }
+    escape.append(QUOTE);
     _writeStringData(escape.toString());
-    trans_.write(QUOTE);
   }
 
   public void writeBinary(byte[] bin) throws TException {
diff --git a/lib/java/src/transport/TFramedTransport.java b/lib/java/src/transport/TFramedTransport.java
index 9eb068d..7121c09 100644
--- a/lib/java/src/transport/TFramedTransport.java
+++ b/lib/java/src/transport/TFramedTransport.java
@@ -9,6 +9,8 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 
+import com.facebook.thrift.TByteArrayOutputStream;
+
 /**
  * Socket implementation of the TTransport interface. To be commented soon!
  *
@@ -22,29 +24,11 @@
   private TTransport transport_ = null;
 
   /**
-   * Class that allows access to the underlying buf without doing deep
-   * copies on it.
-   */
-  private static class MyByteArrayOutputStream extends ByteArrayOutputStream {
-    public MyByteArrayOutputStream(int size) {
-      super(size);
-    }
-
-    public byte[] get() {
-      return buf;
-    }
-
-    public int len() {
-      return count;
-    }
-  }
-
-  /**
    * Buffer for output
    */
-  private final MyByteArrayOutputStream writeBuffer_ =
-    new MyByteArrayOutputStream(1024);
-  
+  private final TByteArrayOutputStream writeBuffer_ =
+    new TByteArrayOutputStream(1024);
+
   /**
    * Buffer for input
    */
@@ -59,7 +43,7 @@
    * Whether to frame output
    */
   private boolean frameWrite_ = true;
-  
+
   /**
    * Constructor wraps around another tranpsort
    */
@@ -135,7 +119,7 @@
     }
     writeBuffer_.write(buf, off, len);
   }
-  
+
   public void flush() throws TTransportException {
     if (!frameWrite_) {
       transport_.flush();