blob: 5bd61dffd76e70156c088c956283c0b90d3c9f67 [file] [log] [blame]
/*
* Copyright 2009 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.core.client;
/**
* Provides JSON-related utility methods.
*/
public class JsonUtils {
/**
* Converts a value to JSON.
*/
public static native String stringify(JavaScriptObject obj) /*-{
return JSON.stringify(obj);
}-*/;
/**
* Converts a value to JSON.
*
* @param space controls the spacing in the final string. Successive levels in the stringification
* will each be indented by this string (or the first ten characters of it).
*/
public static native String stringify(JavaScriptObject obj, String space) /*-{
return JSON.stringify(obj, null, space);
}-*/;
/**
* Escapes characters within a JSON string than cannot be passed directly to
* eval(). Control characters, quotes and backslashes are not affected.
*/
public static native String escapeJsonForEval(String toEscape) /*-{
var escapeTable = @JsonUtils::getEscapeTable()();
var s = toEscape.replace(/[\xad\u0600-\u0603\u06dd\u070f\u17b4\u17b5\u200b-\u200f\u2028-\u202e\u2060-\u2064\u206a-\u206f\ufeff\ufff9-\ufffb]/g, function(x) {
return @JsonUtils::escapeChar(*)(x, escapeTable);
});
return s;
}-*/;
/**
* Returns a quoted, escaped JSON String.
*/
public static native String escapeValue(String toEscape) /*-{
var escapeTable = @JsonUtils::getEscapeTable()();
var s = toEscape.replace(/[\x00-\x1f\xad\u0600-\u0603\u06dd\u070f\u17b4\u17b5\u200b-\u200f\u2028-\u202e\u2060-\u2064\u206a-\u206f\ufeff\ufff9-\ufffb"\\]/g, function(x) {
return @JsonUtils::escapeChar(*)(x, escapeTable);
});
return "\"" + s + "\"";
}-*/;
/**
* Evaluates a JSON expression safely. The payload must evaluate to an Object
* or an Array (not a primitive or a String).
*
* @param <T> The type of JavaScriptObject that should be returned
* @param json The source JSON text
* @return The evaluated object
*
* @throws IllegalArgumentException if the input is not valid JSON
*/
public static native <T extends JavaScriptObject> T safeEval(String json) /*-{
try {
return JSON.parse(json);
} catch (e) {
return @JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}-*/;
/**
* Returns true if the given JSON string may be safely evaluated by {@code
* eval()} without undersired side effects or security risks. Note that a true
* result from this method does not guarantee that the input string is valid
* JSON. This method does not consider the contents of quoted strings; it
* may still be necessary to perform escaping prior to evaluation for correct
* results.
*
* <p> The technique used is taken from <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>.
*/
public static native boolean safeToEval(String text) /*-{
// Remove quoted strings and disallow anything except:
//
// 1) symbols and brackets ,:{}[]
// 2) numbers: digits 0-9, ., -, +, e, and E
// 3) literal values: 'null', 'true' and 'false' = [aeflnr-u]
// 4) whitespace: ' ', '\n', '\r', and '\t'
return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(text.replace(/"(\\.|[^"\\])*"/g, '')));
}-*/;
/**
* Evaluates a JSON expression using {@code eval()}. This method does not
* validate the JSON text and should only be used on JSON from trusted
* sources. The payload must evaluate to an Object or an Array (not a
* primitive or a String).
*
* @param <T> The type of JavaScriptObject that should be returned
* @param json The source JSON text
* @return The evaluated object
*/
public static native <T extends JavaScriptObject> T unsafeEval(String json) /*-{
var escaped = @JsonUtils::escapeJsonForEval(Ljava/lang/String;)(json);
try {
return eval('(' + escaped + ')');
} catch (e) {
return @JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}-*/;
static void throwIllegalArgumentException(String message, String data) {
throw new IllegalArgumentException(message + "\n" + data);
}
private static native String escapeChar(String c, JavaScriptObject escapeTable) /*-{
var lookedUp = @JsonUtils::escapeTable[c.charCodeAt(0)];
return (lookedUp == null) ? c : lookedUp;
}-*/;
private static JavaScriptObject escapeTable; // Lazily initialized.
private static JavaScriptObject getEscapeTable() {
if (escapeTable == null) {
escapeTable = initEscapeTable();
}
return escapeTable;
}
private static native JavaScriptObject initEscapeTable() /*-{
var out = [
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005",
"\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B",
"\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011",
"\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
"\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D",
"\\u001E", "\\u001F"];
out[34] = '\\"';
out[92] = '\\\\';
out[0xad] = '\\u00ad'; // Soft hyphen
out[0x600] = '\\u0600'; // Arabic number sign
out[0x601] = '\\u0601'; // Arabic sign sanah
out[0x602] = '\\u0602'; // Arabic footnote marker
out[0x603] = '\\u0603'; // Arabic sign safha
out[0x6dd] = '\\u06dd'; // Arabic and of ayah
out[0x70f] = '\\u070f'; // Syriac abbreviation mark
out[0x17b4] = '\\u17b4'; // Khmer vowel inherent aq
out[0x17b5] = '\\u17b5'; // Khmer vowel inherent aa
out[0x200b] = '\\u200b'; // Zero width space
out[0x200c] = '\\u200c'; // Zero width non-joiner
out[0x200d] = '\\u200d'; // Zero width joiner
out[0x200e] = '\\u200e'; // Left-to-right mark
out[0x200f] = '\\u200f'; // Right-to-left mark
out[0x2028] = '\\u2028'; // Line separator
out[0x2029] = '\\u2029'; // Paragraph separator
out[0x202a] = '\\u202a'; // Left-to-right embedding
out[0x202b] = '\\u202b'; // Right-to-left embedding
out[0x202c] = '\\u202c'; // Pop directional formatting
out[0x202d] = '\\u202d'; // Left-to-right override
out[0x202e] = '\\u202e'; // Right-to-left override
out[0x2060] = '\\u2060'; // Word joiner
out[0x2061] = '\\u2061'; // Function application
out[0x2062] = '\\u2062'; // Invisible times
out[0x2063] = '\\u2063'; // Invisible separator
out[0x2064] = '\\u2064'; // Invisible plus
out[0x206a] = '\\u206a'; // Inhibit symmetric swapping
out[0x206b] = '\\u206b'; // Activate symmetric swapping
out[0x206c] = '\\u206c'; // Inherent Arabic form shaping
out[0x206d] = '\\u206d'; // Activate Arabic form shaping
out[0x206e] = '\\u206e'; // National digit shapes
out[0x206f] = '\\u206f'; // Nominal digit shapes
out[0xfeff] = '\\ufeff'; // Zero width no-break space
out[0xfff9] = '\\ufff9'; // Intralinear annotation anchor
out[0xfffa] = '\\ufffa'; // Intralinear annotation separator
out[0xfffb] = '\\ufffb'; // Intralinear annotation terminator
return out;
}-*/;
private JsonUtils() {
}
}