blob: 2b695c4b0199d6eb412ab37f65e94cf289af49d8 [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.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
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();
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();
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.
// 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;
}
}-*/;
@UnsafeNativeLong
// Keep synchronized with LongLib
private static native double[] makeLongComponents0(long value) /*-{
return value;
}-*/;
/**
* Quote characters in a user-supplied string to make sure they are safe to
* send to the server.
*
* See {@link ServerSerializationStreamReader#deserializeStringTable}
* for the corresponding dequoting.
*
* @param str string to quote
* @return quoted string
*/
private 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 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 fieldValue) {
/*
* Client code represents longs internally as an array of two Numbers. In
* order to make serialization of longs faster, we'll send the component
* parts so that the value can be directly reconstituted on the server.
*/
double[] parts;
if (GWT.isScript()) {
parts = makeLongComponents0(fieldValue);
} else {
parts = makeLongComponents((int) (fieldValue >> 32), (int) fieldValue);
}
assert parts.length == 2;
writeDouble(parts[0]);
writeDouble(parts[1]);
}
/**
* 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();
}
String typeName = clazz.getName();
String serializationSignature = serializer.getSerializationSignature(typeName);
if (serializationSignature != null) {
typeName += "/" + serializationSignature;
}
return typeName;
}
@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;
}
}