blob: 571bdad598ddfd24ca02631e0514a43a3176b80d [file] [log] [blame]
/*
* 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;
}
}