Use Streams instead of Escaper.
Fixes issue 345.
This commit is contained in:
parent
041d499a7c
commit
9cf579ef01
@ -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]);
|
||||
}
|
||||
}
|
@ -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(']');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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('}');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user