| Index: components/safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java
|
| diff --git a/components/safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java b/components/safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1fae2fd565a13abc13c6050bfd43f8bc3c6e9eae
|
| --- /dev/null
|
| +++ b/components/safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java
|
| @@ -0,0 +1,178 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.components.safejson;
|
| +
|
| +import android.util.JsonReader;
|
| +import android.util.JsonToken;
|
| +import android.util.JsonWriter;
|
| +import android.util.MalformedJsonException;
|
| +
|
| +import org.chromium.base.CalledByNative;
|
| +import org.chromium.base.JNINamespace;
|
| +import org.chromium.base.StreamUtil;
|
| +
|
| +import java.io.IOException;
|
| +import java.io.StringReader;
|
| +import java.io.StringWriter;
|
| +
|
| +/**
|
| + * Sanitizes and normalizes a JSON string by parsing it, checking for wellformedness, and
|
| + * serializing it again. This class is meant to be used from native code.
|
| + */
|
| +@JNINamespace("safe_json")
|
| +public class JsonSanitizer {
|
| +
|
| + // Disallow instantiating the class.
|
| + private JsonSanitizer() {
|
| + }
|
| +
|
| + /**
|
| + * The maximum nesting depth to which the native JSON parser restricts input in order to avoid
|
| + * stack overflows.
|
| + */
|
| + private static final int MAX_NESTING_DEPTH = 100;
|
| +
|
| + @CalledByNative
|
| + public static void sanitize(long nativePtr, String unsafeJson) {
|
| + JsonReader reader = new JsonReader(new StringReader(unsafeJson));
|
| + StringWriter stringWriter = new StringWriter(unsafeJson.length());
|
| + JsonWriter writer = new JsonWriter(stringWriter);
|
| + StackChecker stackChecker = new StackChecker();
|
| + try {
|
| + boolean end = false;
|
| + while (!end) {
|
| + JsonToken token = reader.peek();
|
| + switch (token) {
|
| + case BEGIN_ARRAY:
|
| + stackChecker.increaseAndCheck();
|
| + reader.beginArray();
|
| + writer.beginArray();
|
| + break;
|
| + case END_ARRAY:
|
| + stackChecker.decrease();
|
| + reader.endArray();
|
| + writer.endArray();
|
| + break;
|
| + case BEGIN_OBJECT:
|
| + stackChecker.increaseAndCheck();
|
| + reader.beginObject();
|
| + writer.beginObject();
|
| + break;
|
| + case END_OBJECT:
|
| + stackChecker.decrease();
|
| + reader.endObject();
|
| + writer.endObject();
|
| + break;
|
| + case NAME:
|
| + writer.name(sanitizeString(reader.nextName()));
|
| + break;
|
| + case STRING:
|
| + writer.value(sanitizeString(reader.nextString()));
|
| + break;
|
| + case NUMBER: {
|
| + // Read the value as a string, then try to parse it first as a long, then as
|
| + // a double.
|
| + String value = reader.nextString();
|
| + try {
|
| + writer.value(Long.parseLong(value));
|
| + } catch (NumberFormatException e) {
|
| + writer.value(Double.parseDouble(value));
|
| + }
|
| + break;
|
| + }
|
| + case BOOLEAN:
|
| + writer.value(reader.nextBoolean());
|
| + break;
|
| + case NULL:
|
| + reader.nextNull();
|
| + writer.nullValue();
|
| + break;
|
| + case END_DOCUMENT:
|
| + end = true;
|
| + break;
|
| + }
|
| + }
|
| + } catch (IOException | IllegalStateException e) {
|
| + nativeOnError(nativePtr, e.getMessage());
|
| + return;
|
| + } finally {
|
| + StreamUtil.closeQuietly(reader);
|
| + StreamUtil.closeQuietly(writer);
|
| + }
|
| + nativeOnSuccess(nativePtr, stringWriter.toString());
|
| + }
|
| +
|
| + /**
|
| + * Helper class to check nesting depth of JSON expressions.
|
| + */
|
| + private static class StackChecker {
|
| + private int mStackDepth = 0;
|
| +
|
| + public void increaseAndCheck() {
|
| + if (++mStackDepth >= MAX_NESTING_DEPTH) {
|
| + throw new IllegalStateException("Too much nesting");
|
| + }
|
| + }
|
| +
|
| + public void decrease() {
|
| + mStackDepth--;
|
| + }
|
| + }
|
| +
|
| + private static String sanitizeString(String string) throws MalformedJsonException {
|
| + if (!checkString(string)) {
|
| + throw new MalformedJsonException("Invalid escape sequence");
|
| + }
|
| + return string;
|
| + }
|
| +
|
| + /**
|
| + * Checks whether a given String is well-formed UTF-16, i.e. all surrogates appear in high-low
|
| + * pairs and each code point is a valid character.
|
| + *
|
| + * @param string The string to check.
|
| + * @return Whether the given string is well-formed UTF-16.
|
| + */
|
| + private static boolean checkString(String string) {
|
| + int length = string.length();
|
| + for (int i = 0; i < length; i++) {
|
| + char c = string.charAt(i);
|
| + // Check that surrogates only appear in pairs of a high surrogate followed by a low
|
| + // surrogate.
|
| + // A lone low surrogate is not allowed.
|
| + if (Character.isLowSurrogate(c)) return false;
|
| +
|
| + int codePoint;
|
| + if (Character.isHighSurrogate(c)) {
|
| + // A high surrogate has to be followed by a low surrogate.
|
| + char high = c;
|
| + if (++i >= length) return false;
|
| +
|
| + char low = string.charAt(i);
|
| + if (!Character.isLowSurrogate(low)) return false;
|
| +
|
| + // Decode the high-low pair into a code point.
|
| + codePoint = Character.toCodePoint(high, low);
|
| + } else {
|
| + // The code point is neither a low surrogate nor a high surrogate, so we just need
|
| + // to check that it's a valid character.
|
| + codePoint = c;
|
| + }
|
| +
|
| + if (!isUnicodeCharacter(codePoint)) return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + private static boolean isUnicodeCharacter(int codePoint) {
|
| + // See the native method base::IsValidCharacter().
|
| + return codePoint < 0xD800 || (codePoint >= 0xE000 && codePoint < 0xFDD0)
|
| + || (codePoint > 0xFDEF && codePoint <= 0x10FFFF && (codePoint & 0xFFFE) != 0xFFFE);
|
| + }
|
| +
|
| + private static native void nativeOnSuccess(long id, String json);
|
| +
|
| + private static native void nativeOnError(long id, String error);
|
| +}
|
|
|