| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.user.client.rpc.impl; |
| |
| import com.google.gwt.core.client.JavaScriptObject; |
| import com.google.gwt.lang.LongLib; |
| import com.google.gwt.user.client.rpc.SerializationException; |
| |
| import java.util.List; |
| |
| /** |
| * For internal use only. Used for server call serialization. |
| */ |
| public final class ClientSerializationStreamWriter extends |
| AbstractSerializationStreamWriter { |
| |
| /** |
| * Used by JSNI, see {@link #quoteString(String)}. |
| */ |
| @SuppressWarnings("unused") |
| private static JavaScriptObject regex = getQuotingRegex(); |
| |
| /** |
| * Quote characters in a user-supplied string to make sure they are safe to |
| * send to the server. |
| * |
| * @param str string to quote |
| * @return quoted string |
| */ |
| public static native String quoteString(String str) /*-{ |
| var regex = @com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter::regex; |
| var idx = 0; |
| var out = ""; |
| var result; |
| while ((result = regex.exec(str)) != null) { |
| out += str.substring(idx, result.index); |
| idx = result.index + 1; |
| var ch = result[0].charCodeAt(0); |
| if (ch == 0) { |
| out += "\\0"; |
| } else if (ch == 92) { // backslash |
| out += "\\\\"; |
| } else if (ch == 124) { // vertical bar |
| // 124 = "|" = AbstractSerializationStream.RPC_SEPARATOR_CHAR |
| out += "\\!"; |
| } else { |
| var hex = ch.toString(16); |
| out += "\\u0000".substring(0, 6 - hex.length) + hex; |
| } |
| } |
| return out + str.substring(idx); |
| }-*/; |
| |
| private static void append(StringBuffer sb, String token) { |
| assert (token != null); |
| sb.append(token); |
| sb.append(RPC_SEPARATOR_CHAR); |
| } |
| |
| /** |
| * Create the RegExp instance used for quoting dangerous characters in user |
| * payload strings. |
| * |
| * Note that {@link AbstractSerializationStream#RPC_SEPARATOR_CHAR} is used in |
| * this expression, which must be updated if the separator character is |
| * changed. |
| * |
| * For Android WebKit, we quote many more characters to keep them from being |
| * mangled. |
| * |
| * @return RegExp object |
| */ |
| private static native JavaScriptObject getQuotingRegex() /*-{ |
| // "|" = AbstractSerializationStream.RPC_SEPARATOR_CHAR |
| var ua = navigator.userAgent.toLowerCase(); |
| 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. |
| // TODO(jat): revisit when this bug is fixed in Android |
| return /[\u0000\|\\\u0080-\uFFFF]/g; |
| } else if (ua.indexOf("webkit") != -1) { |
| // other WebKit-based browsers need some additional quoting due to combining |
| // forms and normalization (one codepoint being replaced with another). |
| // Verified with Safari 4.0.1 (5530.18) |
| return /[\u0000\|\\\u0300-\u03ff\u0590-\u05FF\u0600-\u06ff\u0730-\u074A\u07eb-\u07f3\u0940-\u0963\u0980-\u09ff\u0a00-\u0a7f\u0b00-\u0b7f\u0e00-\u0e7f\u0f00-\u0fff\u1900-\u194f\u1a00-\u1a1f\u1b00-\u1b7f\u1dc0-\u1dff\u1f00-\u1fff\u2000-\u206f\u20d0-\u20ff\u2100-\u214f\u2300-\u23ff\u2a00-\u2aff\u3000-\u303f\uD800-\uFFFF]/g; |
| } else { |
| return /[\u0000\|\\\uD800-\uFFFF]/g; |
| } |
| }-*/; |
| |
| private StringBuffer encodeBuffer; |
| |
| private final String moduleBaseURL; |
| |
| private final String serializationPolicyStrongName; |
| |
| private final Serializer serializer; |
| |
| /** |
| * Constructs a <code>ClientSerializationStreamWriter</code> using the |
| * specified module base URL and the serialization policy. |
| * |
| * @param serializer the {@link Serializer} to use |
| * @param moduleBaseURL the location of the module |
| * @param serializationPolicyStrongName the strong name of serialization |
| * policy |
| */ |
| public ClientSerializationStreamWriter(Serializer serializer, |
| String moduleBaseURL, String serializationPolicyStrongName) { |
| this.serializer = serializer; |
| this.moduleBaseURL = moduleBaseURL; |
| this.serializationPolicyStrongName = serializationPolicyStrongName; |
| } |
| |
| /** |
| * Call this method before attempting to append any tokens. This method |
| * implementation <b>must</b> be called by any overridden version. |
| */ |
| @Override |
| public void prepareToWrite() { |
| super.prepareToWrite(); |
| encodeBuffer = new StringBuffer(); |
| |
| // Write serialization policy info |
| writeString(moduleBaseURL); |
| writeString(serializationPolicyStrongName); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| writeHeader(buffer); |
| writeStringTable(buffer); |
| writePayload(buffer); |
| return buffer.toString(); |
| } |
| |
| @Override |
| public void writeLong(long value) { |
| append(LongLib.toBase64(value)); |
| } |
| |
| /** |
| * Appends a token to the end of the buffer. |
| */ |
| @Override |
| protected void append(String token) { |
| append(encodeBuffer, token); |
| } |
| |
| @Override |
| protected String getObjectTypeSignature(Object o) { |
| Class<?> clazz = o.getClass(); |
| |
| if (o instanceof Enum<?>) { |
| Enum<?> e = (Enum<?>) o; |
| clazz = e.getDeclaringClass(); |
| } |
| |
| return serializer.getSerializationSignature(clazz); |
| } |
| |
| @Override |
| protected void serialize(Object instance, String typeSignature) |
| throws SerializationException { |
| serializer.serialize(this, instance, typeSignature); |
| } |
| |
| private void writeHeader(StringBuffer buffer) { |
| append(buffer, String.valueOf(getVersion())); |
| append(buffer, String.valueOf(getFlags())); |
| } |
| |
| private void writePayload(StringBuffer buffer) { |
| buffer.append(encodeBuffer.toString()); |
| } |
| |
| private StringBuffer writeStringTable(StringBuffer buffer) { |
| List<String> stringTable = getStringTable(); |
| append(buffer, String.valueOf(stringTable.size())); |
| for (String s : stringTable) { |
| append(buffer, quoteString(s)); |
| } |
| return buffer; |
| } |
| |
| } |