Safari2/3 RPC quoting fixes.

Patch by: jat
Review by: jgw (desk review)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.5@3767 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index ed1adea..2b695c4 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -57,13 +57,24 @@
   private static native JavaScriptObject getQuotingRegex() /*-{
     // "|" = AbstractSerializationStream.RPC_SEPARATOR_CHAR
     var ua = navigator.userAgent.toLowerCase();
+    var webkitregex = /webkit\/([\d]+)/;
+    var webkit = 0;
+    var result = webkitregex.exec(ua);
+    if (result) {
+      webkit = parseInt(result[1]);
+    }
     if (ua.indexOf("android") != -1) {
       // initial version of Android WebKit has a double-encoding bug for UTF8,
-      // so we have to encode every non-ASCII character.  Later builds can
-      // use \u0300 instead of \u0080, and hopefully by the time Android
-      // supports non-Latin input it will be fully fixed.
+      // so we have to encode every non-ASCII character.
       // TODO(jat): revisit when this bug is fixed in Android
       return /[\u0000\|\\\u0080-\uFFFF]/g;
+    } else if (webkit < 522) {
+      // Safari 2 doesn't handle \\uXXXX in regexes
+      // TODO(jat): should iPhone be treated specially?
+      return /[\x00\|\\]/g;
+    } else if (webkit > 0) {
+      // other WebKit-based browsers need some additional quoting
+      return /[\u0000\|\\\u0300-\u036F\u0590-\u05FF\uD800-\uFFFF]/g;
     } else {
       return /[\u0000\|\\\uD800-\uFFFF]/g;
     }
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 492dbb7..92e1cd3 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -77,43 +77,50 @@
   private enum ValueReader {
     BOOLEAN {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readBoolean();
       }
     },
     BYTE {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readByte();
       }
     },
     CHAR {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readChar();
       }
     },
     DOUBLE {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readDouble();
       }
     },
     FLOAT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readFloat();
       }
     },
     INT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readInt();
       }
     },
     LONG {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readLong();
       }
     },
@@ -126,13 +133,15 @@
     },
     SHORT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readShort();
       }
     },
     STRING {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readString();
       }
     };
@@ -323,6 +332,7 @@
   private final ArrayList<String> tokenList = new ArrayList<String>();
 
   private int tokenListIndex;
+
   {
     CLASS_TO_VECTOR_READER.put(boolean[].class, VectorReader.BOOLEAN_VECTOR);
     CLASS_TO_VECTOR_READER.put(byte[].class, VectorReader.BYTE_VECTOR);
@@ -426,42 +436,42 @@
     }
   }
 
-  public boolean readBoolean() {
+  public boolean readBoolean() throws SerializationException {
     return !extract().equals("0");
   }
 
-  public byte readByte() {
+  public byte readByte() throws SerializationException {
     return Byte.parseByte(extract());
   }
 
-  public char readChar() {
+  public char readChar() throws SerializationException {
     // just use an int, it's more foolproof
     return (char) Integer.parseInt(extract());
   }
 
-  public double readDouble() {
+  public double readDouble() throws SerializationException {
     return Double.parseDouble(extract());
   }
 
-  public float readFloat() {
+  public float readFloat() throws SerializationException {
     return (float) Double.parseDouble(extract());
   }
 
-  public int readInt() {
+  public int readInt() throws SerializationException {
     return Integer.parseInt(extract());
   }
 
-  public long readLong() {
+  public long readLong() throws SerializationException {
     // Keep synchronized with LongLib. The wire format are the two component
     // parts of the double in the client code.
     return (long) readDouble() + (long) readDouble();
   }
 
-  public short readShort() {
+  public short readShort() throws SerializationException {
     return Short.parseShort(extract());
   }
 
-  public String readString() {
+  public String readString() throws SerializationException {
     return getString(readInt());
   }
 
@@ -675,14 +685,18 @@
     throw new NoSuchMethodException("deserialize");
   }
 
-  private String extract() {
-    return tokenList.get(tokenListIndex++);
+  private String extract() throws SerializationException {
+    try {
+      return tokenList.get(tokenListIndex++);
+    } catch (IndexOutOfBoundsException e) {
+      throw new SerializationException("Too few tokens in RPC request", e);
+    }
   }
 
   private Object instantiate(Class<?> customSerializer, Class<?> instanceClass)
       throws InstantiationException, IllegalAccessException,
       IllegalArgumentException, InvocationTargetException,
-      NoSuchMethodException {
+      NoSuchMethodException, SerializationException {
     if (customSerializer != null) {
       for (Method method : customSerializer.getMethods()) {
         if ("instantiate".equals(method.getName())) {
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 e000493..f45777e 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
@@ -60,6 +60,28 @@
     return buf.toString();
   }
 
+  /*
+   * Copied from HistoryTest.
+   */
+  private static native boolean isSafari2() /*-{
+    var exp = / AppleWebKit\/([\d]+)/;
+    var result = exp.exec(navigator.userAgent);
+    if (result) {
+      // The standard history implementation works fine on WebKit >= 522
+      // (Safari 3 beta).
+      if (parseInt(result[1]) >= 522) {
+        return false;
+      }
+    }
+  
+    // The standard history implementation works just fine on the iPhone, which
+    // unfortunately reports itself as WebKit/420+.
+    if (navigator.userAgent.indexOf('iPhone') != -1) {
+      return false;
+    }
+  
+    return true;
+  }-*/;
   /**
    * Verifies that the supplied string includes the requested code points.
    * 
@@ -113,6 +135,10 @@
    * server for verification. This ensures that client->server string escaping
    * properly handles all BMP characters.
    * 
+   * Unpaired or improperly paired surrogates are not tested here, as some
+   * browsers refuse to accept them.  Properly paired surrogates are tested
+   * in the non-BMP test.
+   *  
    * Note that this does not test all possible combinations, which might be an
    * issue, particularly with combining marks, though they should be logically
    * equivalent in that case.
@@ -121,9 +147,19 @@
    */
   public void testClientToServerBMP() throws InvalidCharacterException {
     delayTestFinish(TEST_FINISH_DELAY_MS);
-    clientToServerVerifyRange(Character.MIN_CODE_POINT,
-        Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
-        CHARACTER_BLOCK_SIZE);
+    if (isSafari2()) {
+      // Safari2 can't be fixed for many characters, including null
+      // We only guarantee that basic ISO-Latin characters are unmolested.
+      clientToServerVerifyRange(0x0001, 0x0300, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+    } else {
+      clientToServerVerifyRange(Character.MIN_CODE_POINT,
+          Character.MIN_SURROGATE, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+      clientToServerVerifyRange(Character.MAX_SURROGATE + 1,
+          Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+    }
   }
 
   /**