Use Streams instead of Escaper.

Fixes issue 345.
This commit is contained in:
Jesse Wilson 2011-07-12 23:50:00 +00:00
parent 041d499a7c
commit 9cf579ef01
7 changed files with 8 additions and 415 deletions

View File

@ -1,160 +0,0 @@
/*
* Copyright (C) 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.gson;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* A utility class that is used to perform JSON escaping so that ", <, >, etc. characters are
* properly encoded in the JSON string representation before returning to the client code.
*
* <p>This class contains a single method to escape a passed in string value:
* <pre>
* String jsonStringValue = "beforeQuote\"afterQuote";
* String escapedValue = Escaper.escapeJsonString(jsonStringValue);
* </pre></p>
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
final class Escaper {
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
private static final Set<Character> JS_ESCAPE_CHARS;
private static final Set<Character> HTML_ESCAPE_CHARS;
static {
Set<Character> mandatoryEscapeSet = new HashSet<Character>();
mandatoryEscapeSet.add('"');
mandatoryEscapeSet.add('\\');
JS_ESCAPE_CHARS = Collections.unmodifiableSet(mandatoryEscapeSet);
Set<Character> htmlEscapeSet = new HashSet<Character>();
htmlEscapeSet.add('<');
htmlEscapeSet.add('>');
htmlEscapeSet.add('&');
htmlEscapeSet.add('=');
htmlEscapeSet.add('\'');
// htmlEscapeSet.add('/'); -- Removing slash for now since it causes some incompatibilities
HTML_ESCAPE_CHARS = Collections.unmodifiableSet(htmlEscapeSet);
}
private final boolean escapeHtmlCharacters;
Escaper(boolean escapeHtmlCharacters) {
this.escapeHtmlCharacters = escapeHtmlCharacters;
}
public String escapeJsonString(CharSequence plainText) {
StringBuilder escapedString = new StringBuilder(plainText.length() + 20);
try {
escapeJsonString(plainText, escapedString);
} catch (IOException e) {
throw new RuntimeException(e);
}
return escapedString.toString();
}
private void escapeJsonString(CharSequence plainText, StringBuilder out) throws IOException {
int pos = 0; // Index just past the last char in plainText written to out.
int len = plainText.length();
for (int charCount, i = 0; i < len; i += charCount) {
int codePoint = Character.codePointAt(plainText, i);
charCount = Character.charCount(codePoint);
if (!isControlCharacter(codePoint) && !mustEscapeCharInJsString(codePoint)) {
continue;
}
out.append(plainText, pos, i);
pos = i + charCount;
switch (codePoint) {
case '\b':
out.append("\\b");
break;
case '\t':
out.append("\\t");
break;
case '\n':
out.append("\\n");
break;
case '\f':
out.append("\\f");
break;
case '\r':
out.append("\\r");
break;
case '\\':
out.append("\\\\");
break;
case '/':
out.append("\\/");
break;
case '"':
out.append("\\\"");
break;
default:
appendHexJavaScriptRepresentation(codePoint, out);
break;
}
}
out.append(plainText, pos, len);
}
private boolean mustEscapeCharInJsString(int codepoint) {
if (!Character.isSupplementaryCodePoint(codepoint)) {
char c = (char) codepoint;
return JS_ESCAPE_CHARS.contains(c)
|| (escapeHtmlCharacters && HTML_ESCAPE_CHARS.contains(c));
}
return false;
}
private static boolean isControlCharacter(int codePoint) {
// JSON spec defines these code points as control characters, so they must be escaped
return codePoint < 0x20
|| codePoint == 0x2028 // Line separator
|| codePoint == 0x2029 // Paragraph separator
|| (codePoint >= 0x7f && codePoint <= 0x9f);
}
private static void appendHexJavaScriptRepresentation(int codePoint, Appendable out)
throws IOException {
if (Character.isSupplementaryCodePoint(codePoint)) {
// Handle supplementary unicode values which are not representable in
// javascript. We deal with these by escaping them as two 4B sequences
// so that they will round-trip properly when sent from java to javascript
// and back.
char[] surrogates = Character.toChars(codePoint);
appendHexJavaScriptRepresentation(surrogates[0], out);
appendHexJavaScriptRepresentation(surrogates[1], out);
return;
}
out.append("\\u")
.append(HEX_CHARS[(codePoint >>> 12) & 0xf])
.append(HEX_CHARS[(codePoint >>> 8) & 0xf])
.append(HEX_CHARS[(codePoint >>> 4) & 0xf])
.append(HEX_CHARS[codePoint & 0xf]);
}
}

View File

@ -16,7 +16,6 @@
package com.google.gson;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
@ -316,19 +315,4 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
public int hashCode() {
return elements.hashCode();
}
@Override
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append('[');
boolean first = true;
for (JsonElement element : elements) {
if (first) {
first = false;
} else {
sb.append(',');
}
element.toString(sb, escaper);
}
sb.append(']');
}
}

View File

@ -16,7 +16,9 @@
package com.google.gson;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -28,8 +30,6 @@ import java.math.BigInteger;
* @author Joel Leitch
*/
public abstract class JsonElement {
private static final Escaper BASIC_ESCAPER = new Escaper(false);
/**
* provides check for verifying if this element is an array or not.
*
@ -325,19 +325,17 @@ public abstract class JsonElement {
/**
* Returns a String representation of this element.
*
* @return String the string representation of this element.
*/
@Override
public String toString() {
try {
StringBuilder sb = new StringBuilder();
toString(sb, BASIC_ESCAPER);
return sb.toString();
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
Streams.write(this, true, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {
throw new RuntimeException(e);
throw new AssertionError(e);
}
}
protected abstract void toString(Appendable sb, Escaper escaper) throws IOException;
}

View File

@ -16,8 +16,6 @@
package com.google.gson;
import java.io.IOException;
/**
* A class representing a Json {@code null} value.
*
@ -46,11 +44,6 @@ public final class JsonNull extends JsonElement {
return this; // immutable!
}
@Override
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append("null");
}
/**
* All instances of JsonNull have the same hash code since they are indistinguishable
*/

View File

@ -17,8 +17,6 @@
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@ -210,22 +208,4 @@ public final class JsonObject extends JsonElement {
public int hashCode() {
return members.hashCode();
}
@Override
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append('{');
boolean first = true;
for (Map.Entry<String, JsonElement> entry : members.entrySet()) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append('\"');
sb.append(escaper.escapeJsonString(entry.getKey()));
sb.append("\":");
entry.getValue().toString(sb, escaper);
}
sb.append('}');
}
}

View File

@ -17,8 +17,6 @@
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -294,17 +292,6 @@ public final class JsonPrimitive extends JsonElement {
return value;
}
@Override
protected void toString(Appendable sb, Escaper escaper) throws IOException {
if (isString()) {
sb.append('"');
sb.append(escaper.escapeJsonString(value.toString()));
sb.append('"');
} else {
sb.append(value.toString());
}
}
private static boolean isPrimitiveOrString(Object target) {
if (target instanceof String) {
return true;

View File

@ -1,189 +0,0 @@
/*
* Copyright (C) 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.gson;
import junit.framework.TestCase;
/**
* Performs some unit testing for the {@link Escaper} class.
*
* @author Joel Leitch
*/
public class EscaperTest extends TestCase {
private Escaper escapeHtmlChar;
private Escaper noEscapeHtmlChar;
@Override
protected void setUp() throws Exception {
super.setUp();
escapeHtmlChar = new Escaper(true);
noEscapeHtmlChar = new Escaper(false);
}
public void testNoSpecialCharacters() {
String value = "Testing123";
String escapedString = escapeHtmlChar.escapeJsonString(value);
assertEquals(value, escapedString);
}
public void testNewlineEscaping() throws Exception {
String containsNewline = "123\n456";
String escapedString = escapeHtmlChar.escapeJsonString(containsNewline);
assertEquals("123\\n456", escapedString);
}
public void testCarrageReturnEscaping() throws Exception {
String containsCarrageReturn = "123\r456";
String escapedString = escapeHtmlChar.escapeJsonString(containsCarrageReturn);
assertEquals("123\\r456", escapedString);
}
public void testTabEscaping() throws Exception {
String containsTab = "123\t456";
String escapedString = escapeHtmlChar.escapeJsonString(containsTab);
assertEquals("123\\t456", escapedString);
}
public void testDoubleQuoteEscaping() throws Exception {
String containsQuote = "123\"456";
String escapedString = escapeHtmlChar.escapeJsonString(containsQuote);
assertEquals("123\\\"456", escapedString);
}
public void testSingleQuoteEscaping() throws Exception {
String containsQuote = "123'456";
String escapedString = escapeHtmlChar.escapeJsonString(containsQuote);
assertEquals("123\\u0027456", escapedString);
}
public void testLineSeparatorEscaping() throws Exception {
String src = "123\u2028 456";
String escapedString = escapeHtmlChar.escapeJsonString(src);
assertEquals("123\\u2028 456", escapedString);
}
public void testParagraphSeparatorEscaping() throws Exception {
String src = "123\u2029 456";
String escapedString = escapeHtmlChar.escapeJsonString(src);
assertEquals("123\\u2029 456", escapedString);
}
public void testControlCharBlockEscaping() throws Exception {
for (char c = '\u007f'; c <= '\u009f'; ++c) {
String src = "123 " + c + " 456";
String escapedString = escapeHtmlChar.escapeJsonString(src);
assertFalse(src.equals(escapedString));
}
}
public void testEqualsEscaping() throws Exception {
String containsEquals = "123=456";
int index = containsEquals.indexOf('=');
String unicodeValue = convertToUnicodeString(Character.codePointAt(containsEquals, index));
String escapedString = escapeHtmlChar.escapeJsonString(containsEquals);
assertEquals("123" + unicodeValue + "456", escapedString);
escapedString = noEscapeHtmlChar.escapeJsonString(containsEquals);
assertEquals(containsEquals, escapedString);
}
public void testGreaterThanAndLessThanEscaping() throws Exception {
String containsLtGt = "123>456<";
int gtIndex = containsLtGt.indexOf('>');
int ltIndex = containsLtGt.indexOf('<');
String gtAsUnicode = convertToUnicodeString(Character.codePointAt(containsLtGt, gtIndex));
String ltAsUnicode = convertToUnicodeString(Character.codePointAt(containsLtGt, ltIndex));
String escapedString = escapeHtmlChar.escapeJsonString(containsLtGt);
assertEquals("123" + gtAsUnicode + "456" + ltAsUnicode, escapedString);
escapedString = noEscapeHtmlChar.escapeJsonString(containsLtGt);
assertEquals(containsLtGt, escapedString);
}
public void testAmpersandEscaping() throws Exception {
String containsAmp = "123&456";
int ampIndex = containsAmp.indexOf('&');
String ampAsUnicode = convertToUnicodeString(Character.codePointAt(containsAmp, ampIndex));
String escapedString = escapeHtmlChar.escapeJsonString(containsAmp);
assertEquals("123" + ampAsUnicode + "456", escapedString);
escapedString = noEscapeHtmlChar.escapeJsonString(containsAmp);
assertEquals(containsAmp, escapedString);
char ampCharAsUnicode = '\u0026';
String containsAmpUnicode = "123" + ampCharAsUnicode + "456";
escapedString = escapeHtmlChar.escapeJsonString(containsAmpUnicode);
assertEquals("123" + ampAsUnicode + "456", escapedString);
escapedString = noEscapeHtmlChar.escapeJsonString(containsAmpUnicode);
assertEquals(containsAmp, escapedString);
}
public void testSlashEscaping() throws Exception {
String containsSlash = "123\\456";
String escapedString = escapeHtmlChar.escapeJsonString(containsSlash);
assertEquals("123\\\\456", escapedString);
}
public void testSingleQuoteNotEscaped() throws Exception {
String containsSingleQuote = "123'456";
String escapedString = noEscapeHtmlChar.escapeJsonString(containsSingleQuote);
assertEquals(containsSingleQuote, escapedString);
}
public void testRequiredEscapingUnicodeCharacter() throws Exception {
char unicodeChar = '\u2028';
String unicodeString = "Testing" + unicodeChar;
String escapedString = escapeHtmlChar.escapeJsonString(unicodeString);
assertFalse(unicodeString.equals(escapedString));
assertEquals("Testing\\u2028", escapedString);
}
public void testUnicodeCharacterStringNoEscaping() throws Exception {
String unicodeString = "\u0065\u0066";
String escapedString = escapeHtmlChar.escapeJsonString(unicodeString);
assertEquals(unicodeString, escapedString);
}
/*
public void testChineseCharacterEscaping() throws Exception {
String unicodeString = "\u597d\u597d\u597d";
String chineseString = "好好好";
assertEquals(unicodeString, chineseString);
String expectedEscapedString = "\\u597d\\u597d\\u597d";
String escapedString = Escaper.escapeJsonString(chineseString);
assertEquals(expectedEscapedString, escapedString);
}
*/
private String convertToUnicodeString(int codepoint) {
String hexValue = Integer.toHexString(codepoint);
StringBuilder sb = new StringBuilder("\\u");
for (int i = 0; i < 4 - hexValue.length(); i++) {
sb.append(0);
}
sb.append(hexValue);
return sb.toString().toLowerCase();
}
}