Escape HTML special characters in GWT-RPC responses like GSON does.

Also, don't escape NUL as "\0".  If a NUL character is followed by an
octal digit in the original string, then the resulting string will be
misparsed.  E.g., "\07" will be parsed as a single character string
instead of a two character string.

Change-Id: Ifb82cb638d2cc356925a4ba84bb5f6662ca818ac
Review-Link: https://gwt-review.googlesource.com/#/c/1360/

Review by: skybrian@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11403 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index b1df67d..0348bdf 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -337,7 +337,6 @@
      * JavaScript Vertical Tab character '\v' into 'v'. As such, we do not use
      * the short form of the unicode escape here.
      */
-    JS_CHARS_ESCAPED['\u0000'] = '0';
     JS_CHARS_ESCAPED['\b'] = 'b';
     JS_CHARS_ESCAPED['\t'] = 't';
     JS_CHARS_ESCAPED['\n'] = 'n';
@@ -503,6 +502,9 @@
       case NON_BREAKING_HYPHEN:
         // This can be expanded into a break followed by a hyphen
         return true;
+      case '\'': case '&': case '<': case '=': case '>':
+        // These can cause HTML content sniffing
+        return true;
       default:
         if (ch < ' ') {
           // Chrome 11 mangles control characters
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
index f1fe049..5aee0b3 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
@@ -59,6 +59,11 @@
   }
 
   /**
+   * Returns the input string unmodified.
+   */
+  String echo(String str);
+
+  /**
    * Returns a string containing the characters from start to end.
    * 
    * Used to verify server->client escaping.
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
index 6ae9fd4..f998680 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
@@ -21,6 +21,7 @@
  * Async version of the {@link UnicodeEscapingService} interface.
  */
 public interface UnicodeEscapingServiceAsync {
+  void echo(String str, AsyncCallback<String> callback);
   void getStringContainingCharacterRange(int start, int end,
       AsyncCallback<String> callback);
   void verifyStringContainingCharacterRange(int start, int end, String str,
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
index 0a8c7fa..defb3c1 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
@@ -315,4 +315,40 @@
           }
         });
   }
+
+  /**
+   * Verify that string encoding/decoding is lossless.
+   */
+  private void echoVerify(final String str) {
+    delayTestFinish(TEST_FINISH_DELAY_MS);
+    getService().echo(str, new AsyncCallback<String>() {
+      @Override
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      @Override
+      public void onSuccess(String result) {
+        assertEquals(str, result);
+        finishTest();
+      }
+    });
+  }
+
+  /**
+   * Test that a NUL character followed by an octal character is encoded
+   * correctly.  Encoding the NUL character simply as "\0" in this case
+   * would cause the recipient to see "\07" as a single octal escape sequence,
+   * rather than two separate characters.
+   */
+  public void testEscapeNull() {
+    echoVerify("\u0000" + "7");  // split to emphasize two characters
+  }
+
+  /**
+   * Test that HTML special characters are encoded correctly.
+   */
+  public void testEscapeHtml() {
+    echoVerify("<img src=x onerror=alert(1)>");
+  }
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
index c96276a..30ffbe1 100644
--- a/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
@@ -25,6 +25,13 @@
     UnicodeEscapingService {
 
   /**
+   * @see UnicodeEscapingService#echo(String)
+   */
+  public String echo(String str) {
+    return str;
+  }
+
+  /**
    * @see UnicodeEscapingService#getStringContainingCharacterRange(int, int)
    */
   public String getStringContainingCharacterRange(int start, int end) {