Skip to content

Commit

Permalink
Remove serialization workarounds for ie6/7 and rhino (gwtproject#9578)
Browse files Browse the repository at this point in the history
  • Loading branch information
codemasterover9000 committed Dec 13, 2023
1 parent f439c5e commit ec8b1b9
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 177 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,78 +42,6 @@
public final class ServerSerializationStreamWriter extends
AbstractSerializationStreamWriter {

/**
* Builds a string that evaluates into an array containing the given elements.
* This class exists to work around a bug in IE6/7 that limits the size of
* array literals.
*/
public static class LengthConstrainedArray {
public static final int MAXIMUM_ARRAY_LENGTH = 1 << 15;
private static final String POSTLUDE = "])";
private static final String PRELUDE = "].concat([";

private final StringBuffer buffer;
private int count = 0;
private boolean needsComma = false;
private int total = 0;
private boolean javascript = false;

public LengthConstrainedArray() {
buffer = new StringBuffer();
}

public LengthConstrainedArray(int capacityGuess) {
buffer = new StringBuffer(capacityGuess);
}

public void addToken(CharSequence token) {
total++;
if (count++ == MAXIMUM_ARRAY_LENGTH) {
if (total == MAXIMUM_ARRAY_LENGTH + 1) {
buffer.append(PRELUDE);
javascript = true;
} else {
buffer.append("],[");
}
count = 0;
needsComma = false;
}

if (needsComma) {
buffer.append(",");
} else {
needsComma = true;
}

buffer.append(token);
}

public void addEscapedToken(String token) {
addToken(escapeString(token, true, this));
}

public void addToken(int i) {
addToken(String.valueOf(i));
}

public boolean isJavaScript() {
return javascript;
}

public void setJavaScript(boolean javascript) {
this.javascript = javascript;
}

@Override
public String toString() {
if (total > MAXIMUM_ARRAY_LENGTH) {
return "[" + buffer.toString() + POSTLUDE;
} else {
return "[" + buffer.toString() + "]";
}
}
}

/**
* Enumeration used to provided typed instance writers.
*/
Expand Down Expand Up @@ -337,14 +265,6 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance)

private static final char NON_BREAKING_HYPHEN = '\u2011';

/**
* Maximum length of a string node in RPC responses, not including surrounding
* quote characters (2 ^ 16 - 1) = 65535.
* This exists to work around a Rhino parser bug in the hosted mode client
* that limits string node lengths to 64KB.
*/
private static final int MAX_STRING_NODE_LENGTH = 0xFFFF;

static {
/*
* NOTE: The JS VM in IE6 & IE7 do not interpret \v correctly. They convert
Expand Down Expand Up @@ -382,36 +302,15 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance)
CLASS_TO_VALUE_WRITER.put(String.class, ValueWriter.STRING);
}

/**
* This method takes a string and outputs a JavaScript string literal. The
* data is surrounded with quotes, and any contained characters that need to
* be escaped are mapped onto their escape sequence.
*
* Assumptions: We are targeting a version of JavaScript that that is later
* than 1.3 that supports unicode strings.
*/
public static String escapeString(String toEscape) {
return escapeString(toEscape, false, null);
}

/**
* This method takes a string and outputs a JavaScript string literal. The
* data is surrounded with quotes, and any contained characters that need to
* be escaped are mapped onto their escape sequence.
*
* This splits strings into 64KB chunks to workaround an issue with the hosted mode client where
* the Rhino parser can't handle string nodes larger than 64KB, e.g. {@code "longstring"} is
* converted to {@code "long" + "string"}.
*
* Assumptions: We are targeting a version of JavaScript that that is later
* than 1.3 that supports unicode strings.
*/
public static String escapeStringSplitNodes(String toEscape) {
return escapeString(toEscape, true, null);
}

private static String escapeString(String toEscape, boolean splitNodes,
LengthConstrainedArray array) {
public static String escapeString(String toEscape) {
// Since escaped characters will increase the output size, allocate extra room to start.
int length = toEscape.length();
int capacityIncrement = Math.max(length, 16);
Expand All @@ -422,30 +321,14 @@ private static String escapeString(String toEscape, boolean splitNodes,
int i = 0;
while (i < length) {

// Add one segment at a time, up to maxNodeLength characters. Note this always leave room
// for at least 6 characters at the end (maximum unicode escaped character size).
int maxSegmentVectorSize = splitNodes
? (charVector.getSize() + MAX_STRING_NODE_LENGTH - 5)
: Integer.MAX_VALUE;

while (i < length && charVector.getSize() < maxSegmentVectorSize) {
while (i < length) {
char c = toEscape.charAt(i++);
if (needsUnicodeEscape(c)) {
unicodeEscape(c, charVector);
} else {
charVector.add(c);
}
}

// If there's another segment left, insert a '+' operator.
if (splitNodes && i < length) {
charVector.add(JS_QUOTE_CHAR);
charVector.add('+');
charVector.add(JS_QUOTE_CHAR);
if (array != null) {
array.setJavaScript(true);
}
}
}

charVector.add(JS_QUOTE_CHAR);
Expand Down Expand Up @@ -502,7 +385,7 @@ private static Class<?> getClassForSerialization(Object instance) {
* <li>Total Characters Escaped: 2082</li></li>
* </ul> </li>
* </ol>
*
*
* @param ch character to check
* @return <code>true</code> if the character requires the \\uXXXX unicode
* character escape
Expand Down Expand Up @@ -557,7 +440,7 @@ private static boolean needsUnicodeEscape(char ch) {
* Writes a safe escape sequence for a character. Some characters have a short
* form, such as \n for U+000D, while others are represented as \\xNN or
* \\uNNNN.
*
*
* @param ch character to unicode escape
* @param charVector char vector to receive the unicode escaped representation
*/
Expand Down Expand Up @@ -610,7 +493,7 @@ public void serializeValue(Object value, Class<?> type)
/**
* Build an array of JavaScript string literals that can be decoded by the
* client via the eval function.
*
*
* NOTE: We build the array in reverse so the client can simply use the pop
* function to remove the next item from the list.
*/
Expand All @@ -620,14 +503,14 @@ public String toString() {
// We take a guess at how big to make to buffer to avoid numerous resizes.
//
int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.size();
LengthConstrainedArray stream = new LengthConstrainedArray(capacityGuess);
writePayload(stream);
writeStringTable(stream);
writeHeader(stream);
StringBuffer buffer = new StringBuffer(capacityGuess);
writePayload(buffer);
writeStringTable(buffer);
writeHeader(buffer);

return stream.toString();
return "[" + buffer.toString() + "]";
}

@Override
public void writeLong(long value) {
if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) {
Expand Down Expand Up @@ -702,7 +585,7 @@ protected void serialize(Object instance, String typeSignature)
* Serialize an instance that is an array. Will default to serializing the
* instance as an Object vector if the instance is not a vector of primitives,
* Strings or Object.
*
*
* @param instanceClass
* @param instance
* @throws SerializationException
Expand Down Expand Up @@ -735,14 +618,14 @@ private void serializeClass(Object instance, Class<?> instanceClass)
List<Field> serverFields = new ArrayList<Field>();
for (Field declField : serializableFields) {
assert (declField != null);

// Identify server-only fields
if (!clientFieldNames.contains(declField.getName())) {
serverFields.add(declField);
continue;
}
}

// Serialize the server-only fields into a byte array and encode as a String
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Expand All @@ -765,7 +648,7 @@ private void serializeClass(Object instance, Class<?> instanceClass)
throw new SerializationException(e);
}
}

// Write the client-visible field data
for (Field declField : serializableFields) {
if ((clientFieldNames != null) && !clientFieldNames.contains(declField.getName())) {
Expand Down Expand Up @@ -861,29 +744,39 @@ private void serializeWithCustomSerializer(Class<?> customSerializer,
* Notice that the field are written in reverse order that the client can just
* pop items out of the stream.
*/
private void writeHeader(LengthConstrainedArray stream) {
stream.addToken(getFlags());
if (stream.isJavaScript() && getVersion() >= SERIALIZATION_STREAM_JSON_VERSION) {
// Ensure we are not using the JSON supported version if stream is Javascript instead of JSON
stream.addToken(SERIALIZATION_STREAM_JSON_VERSION - 1);
} else {
stream.addToken(getVersion());
}
private void writeHeader(StringBuffer buffer) {
addToken(buffer, getFlags());
addToken(buffer, getVersion());
}

private void writePayload(LengthConstrainedArray stream) {
private void writePayload(StringBuffer buffer) {
ListIterator<String> tokenIterator = tokenList.listIterator(tokenList.size());
while (tokenIterator.hasPrevious()) {
stream.addToken(tokenIterator.previous());
addToken(buffer, tokenIterator.previous());
}
}

private void writeStringTable(LengthConstrainedArray stream) {
LengthConstrainedArray tableStream = new LengthConstrainedArray();
private void writeStringTable(StringBuffer buffer) {
StringBuffer tableBuffer = new StringBuffer();
for (String s : getStringTable()) {
tableStream.addEscapedToken(s);
addEscapedToken(tableBuffer, s);
}
stream.addToken(tableStream.toString());
stream.setJavaScript(stream.isJavaScript() || tableStream.isJavaScript());
addToken(buffer, "[" + tableBuffer + "]");
}

public void addToken(StringBuffer buffer, CharSequence token) {
if (buffer.length() > 0) {
buffer.append(",");
}

buffer.append(token);
}

public void addEscapedToken(StringBuffer buffer, String token) {
addToken(buffer, escapeString(token));
}

public void addToken(StringBuffer buffer, int i) {
addToken(buffer, String.valueOf(i));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public void testEscapeString() {
}

public void testEscapeStringSplitNodes() {
String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes("test");
String escaped = ServerSerializationStreamWriter.escapeString("test");
assertEquals("\"test\"", escaped);
}

public void testEscapeStringSplitNodes_unicodeEscape() {
String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes(
String escaped = ServerSerializationStreamWriter.escapeString(
"测试" // Unicode characters
+ "\"" // JS quote char
+ "\\" // JS escape char
Expand Down Expand Up @@ -75,11 +75,11 @@ public void testEscapeStringSplitNodes_over64KB() {
secondNodeBuilder.append('2');
}

String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes(
String escaped = ServerSerializationStreamWriter.escapeString(
firstNodeBuilder.toString() + secondNodeBuilder.toString());

assertEquals(
"\"" + firstNodeBuilder.toString() + "\"+\"" + secondNodeBuilder.toString() + "\"",
"\"" + firstNodeBuilder.toString() + secondNodeBuilder.toString() + "\"",
escaped);
}

Expand All @@ -104,12 +104,11 @@ public void testEscapeStringSplitNodes_over64KBEscaped() {
}
String secondNode = secondNodeBuilder.toString();

String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes(
String escaped = ServerSerializationStreamWriter.escapeString(
firstNodeBuilder.toString() + secondNode);

assertEquals(
"\"" + firstNodeNoUnicode + "\\u2011" // first node (including escaped unicode character)
+ "\"+\""
+ secondNode + "\"", // second node
escaped);
}
Expand All @@ -122,28 +121,4 @@ public void testWritingRpcVersion8Message() {
assertEquals("[\"NaN\",\"Infinity\",\"-Infinity\",[],0,8]", writer.toString());
}

public void testVersion8Fallbacks() {
StringBuilder longString = new StringBuilder(66000);
for (int i = 0; i < 660000; i++) {
longString.append("a");
}

// Fallbacks to 7 if string gets concatenated
ServerSerializationStreamWriter writer = new ServerSerializationStreamWriter(null, 8);
writer.writeString(longString.toString());
String encoded = writer.toString();
assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]")));

// Fallbacks to 7 if array size reached maximum
int maxArrayLength =
ServerSerializationStreamWriter.LengthConstrainedArray.MAXIMUM_ARRAY_LENGTH + 100;
writer = new ServerSerializationStreamWriter(null, 8);
for (int i = 0; i < maxArrayLength; i++) {
writer.writeInt(i);
}

encoded = writer.toString();
assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]")));
}

}

0 comments on commit ec8b1b9

Please sign in to comment.