summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/android/BUILD.gn1
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc2
-rw-r--r--chrome/browser/supervised_user/supervised_user_whitelist_service_unittest.cc7
-rw-r--r--chrome/chrome.gyp3
-rw-r--r--chrome/test/base/chrome_test_suite.cc10
-rw-r--r--components/BUILD.gn5
-rw-r--r--components/components_tests.gyp7
-rw-r--r--components/safe_json.gypi52
-rw-r--r--components/safe_json/BUILD.gn38
-rw-r--r--components/safe_json/DEPS6
-rw-r--r--components/safe_json/android/BUILD.gn22
-rw-r--r--components/safe_json/android/component_jni_registrar.cc25
-rw-r--r--components/safe_json/android/component_jni_registrar.h19
-rw-r--r--components/safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java178
-rw-r--r--components/safe_json/json_sanitizer.cc89
-rw-r--r--components/safe_json/json_sanitizer.h52
-rw-r--r--components/safe_json/json_sanitizer_android.cc115
-rw-r--r--components/safe_json/json_sanitizer_unittest.cc184
-rw-r--r--components/safe_json/safe_json_parser.cc22
-rw-r--r--components/safe_json/safe_json_parser_android.cc56
-rw-r--r--components/safe_json/safe_json_parser_android.h41
-rw-r--r--components/test/DEPS1
-rw-r--r--components/test/run_all_unittests.cc8
-rw-r--r--tools/android/eclipse/.classpath1
24 files changed, 931 insertions, 13 deletions
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index b5265a6..8e29a4b 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -126,6 +126,7 @@ android_library("chrome_java") {
"//components/gcm_driver/android:gcm_driver_java",
"//components/invalidation/impl:java",
"//components/navigation_interception/android:navigation_interception_java",
+ "//components/safe_json/android:safe_json_java",
"//components/service_tab_launcher:service_tab_launcher_java",
"//components/policy/android:policy_java",
"//components/precache/android:precache_java",
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 416214c..3c4b02b 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -132,6 +132,7 @@
#include "components/gcm_driver/android/component_jni_registrar.h"
#include "components/invalidation/impl/android/component_jni_registrar.h"
#include "components/navigation_interception/component_jni_registrar.h"
+#include "components/safe_json/android/component_jni_registrar.h"
#include "components/service_tab_launcher/component_jni_registrar.h"
#include "components/variations/android/component_jni_registrar.h"
#include "components/web_contents_delegate_android/component_jni_registrar.h"
@@ -153,6 +154,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"Invalidation", invalidation::android::RegisterInvalidationJni},
{"NavigationInterception",
navigation_interception::RegisterNavigationInterceptionJni},
+ {"SafeJson", safe_json::android::RegisterSafeJsonJni},
{"WebContentsDelegateAndroid",
web_contents_delegate_android::RegisterWebContentsDelegateAndroidJni},
// Register JNI for chrome classes.
diff --git a/chrome/browser/supervised_user/supervised_user_whitelist_service_unittest.cc b/chrome/browser/supervised_user/supervised_user_whitelist_service_unittest.cc
index 5a7ef97..1ae9984 100644
--- a/chrome/browser/supervised_user/supervised_user_whitelist_service_unittest.cc
+++ b/chrome/browser/supervised_user/supervised_user_whitelist_service_unittest.cc
@@ -21,13 +21,16 @@
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
-#include "components/safe_json/testing_json_parser.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "sync/api/sync_change.h"
#include "sync/api/sync_error_factory.h"
#include "sync/protocol/sync.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if !defined(OS_ANDROID)
+#include "components/safe_json/testing_json_parser.h"
+#endif
+
namespace {
const char kClientId[] = "client-id";
@@ -152,7 +155,9 @@ class SupervisedUserWhitelistServiceTest : public testing::Test {
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
+#if !defined(OS_ANDROID)
safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_;
+#endif
scoped_ptr<MockSupervisedUserWhitelistInstaller> installer_;
scoped_ptr<SupervisedUserWhitelistService> service_;
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 6d10f0d..f0bcc0a 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -598,9 +598,10 @@
'../components/components.gyp:gcm_driver_java',
'../components/components.gyp:invalidation_java',
'../components/components.gyp:navigation_interception_java',
- '../components/components.gyp:service_tab_launcher_java',
'../components/components.gyp:policy_java',
'../components/components.gyp:precache_java',
+ '../components/components.gyp:safe_json_java',
+ '../components/components.gyp:service_tab_launcher_java',
'../components/components.gyp:variations_java',
'../components/components.gyp:web_contents_delegate_android_java',
'../content/content.gyp:content_java',
diff --git a/chrome/test/base/chrome_test_suite.cc b/chrome/test/base/chrome_test_suite.cc
index 933d6a0..6581d13 100644
--- a/chrome/test/base/chrome_test_suite.cc
+++ b/chrome/test/base/chrome_test_suite.cc
@@ -22,6 +22,11 @@
#include "extensions/common/constants.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "chrome/browser/android/chrome_jni_registrar.h"
+#endif
+
#if defined(OS_CHROMEOS)
#include "base/process/process_metrics.h"
#include "chromeos/chromeos_paths.h"
@@ -77,6 +82,11 @@ void ChromeTestSuite::Initialize() {
PathService::Override(base::DIR_MODULE, browser_dir_);
}
+#if defined(OS_ANDROID)
+ ASSERT_TRUE(chrome::android::RegisterBrowserJNI(
+ base::android::AttachCurrentThread()));
+#endif
+
#if !defined(OS_IOS)
// Disable external libraries load if we are under python process in
// ChromeOS. That means we are autotest and, if ASAN is used,
diff --git a/components/BUILD.gn b/components/BUILD.gn
index ae681a1..591d2fd 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -349,7 +349,10 @@ test("components_unittests") {
}
if (!is_ios) {
- deps += [ "//components/scheduler:unit_tests" ]
+ deps += [
+ "//components/safe_json:unit_tests",
+ "//components/scheduler:unit_tests",
+ ]
}
}
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index d06e4ce..dddb001 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -489,6 +489,9 @@
'rappor/rappor_utils_unittest.cc',
'rappor/sampler_unittest.cc',
],
+ 'safe_json_unittest_sources': [
+ 'safe_json/json_sanitizer_unittest.cc',
+ ],
'scheduler_unittest_sources': [
'scheduler/child/idle_helper_unittest.cc',
'scheduler/child/nestable_task_runner_for_test.cc',
@@ -900,6 +903,7 @@
'<@(navigation_interception_unittest_sources)',
'<@(network_hints_unittest_sources)',
'<@(power_unittest_sources)',
+ '<@(safe_json_unittest_sources)',
'<@(scheduler_unittest_sources)',
'<@(storage_monitor_unittest_sources)',
'<@(ui_unittest_sources)',
@@ -929,6 +933,8 @@
'components.gyp:password_manager_content_common',
'components.gyp:power',
'components.gyp:precache_content',
+ 'components.gyp:safe_json',
+ 'components.gyp:safe_json_test_support',
'components.gyp:sessions_content',
'components.gyp:storage_monitor',
'components.gyp:storage_monitor_test_support',
@@ -1061,6 +1067,7 @@
'dependencies': [
'components.gyp:cronet_static',
'components.gyp:data_reduction_proxy_content',
+ 'components.gyp:safe_json_java',
'../content/content.gyp:content_java',
'../testing/android/native_test.gyp:native_test_native_code',
],
diff --git a/components/safe_json.gypi b/components/safe_json.gypi
index dcf2824..bda8494 100644
--- a/components/safe_json.gypi
+++ b/components/safe_json.gypi
@@ -13,17 +13,37 @@
'../base/base.gyp:base',
'../components/components_strings.gyp:components_strings',
'../content/content.gyp:content_browser',
+ '../content/content.gyp:content_common',
'../ui/base/ui_base.gyp:ui_base',
],
'include_dirs': [
'..',
],
'sources': [
+ 'safe_json/android/component_jni_registrar.cc',
+ 'safe_json/android/component_jni_registrar.h',
+ 'safe_json/json_sanitizer.cc',
+ 'safe_json/json_sanitizer.h',
+ 'safe_json/json_sanitizer_android.cc',
'safe_json/safe_json_parser.cc',
'safe_json/safe_json_parser.h',
+ 'safe_json/safe_json_parser_android.cc',
+ 'safe_json/safe_json_parser_android.h',
'safe_json/safe_json_parser_impl.cc',
'safe_json/safe_json_parser_impl.h',
],
+ 'conditions': [
+ ['OS == "android"', {
+ 'dependencies': [
+ 'safe_json_jni_headers',
+ ],
+ 'sources!': [
+ 'safe_json/json_sanitizer.cc',
+ 'safe_json/safe_json_parser_impl.cc',
+ 'safe_json/safe_json_parser_impl.h',
+ ],
+ }],
+ ],
},
{
'target_name': 'safe_json_test_support',
@@ -45,7 +65,7 @@
'type': 'static_library',
'dependencies': [
'../base/base.gyp:base',
- '../content/content.gyp:content_browser',
+ '../content/content.gyp:content_utility',
'../ipc/ipc.gyp:ipc',
],
'include_dirs': [
@@ -59,4 +79,34 @@
],
},
],
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ # GN version: //components/safe_json/android:safe_json_java
+ 'target_name': 'safe_json_java',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ ],
+ 'variables': {
+ 'java_in_dir': 'safe_json/android/java',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ # GN version: //components/safe_json:jni
+ 'target_name': 'safe_json_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'safe_json/android/java/src/org/chromium/components/safejson/JsonSanitizer.java',
+ ],
+ 'variables': {
+ 'jni_gen_package': 'safe_json',
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
+ ],
+ }],
+ ]
}
diff --git a/components/safe_json/BUILD.gn b/components/safe_json/BUILD.gn
index a57efcc..2748f60 100644
--- a/components/safe_json/BUILD.gn
+++ b/components/safe_json/BUILD.gn
@@ -2,11 +2,22 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+if (is_android) {
+ import("//build/config/android/rules.gni")
+}
+
# GYP version: components/safe_json.gypi:safe_json
source_set("safe_json") {
sources = [
+ "android/component_jni_registrar.cc",
+ "android/component_jni_registrar.h",
+ "json_sanitizer.cc",
+ "json_sanitizer.h",
+ "json_sanitizer_android.cc",
"safe_json_parser.cc",
"safe_json_parser.h",
+ "safe_json_parser_android.cc",
+ "safe_json_parser_android.h",
"safe_json_parser_impl.cc",
"safe_json_parser_impl.h",
]
@@ -18,6 +29,30 @@ source_set("safe_json") {
"//content/public/browser",
"//ui/base",
]
+
+ if (is_android) {
+ sources -= [
+ "json_sanitizer.cc",
+ "safe_json_parser_impl.cc",
+ "safe_json_parser_impl.h",
+ ]
+ deps += [ "android:safe_json_jni_headers" ]
+ }
+}
+
+# GYP version: components/safe_json.gypi:safe_json_unittest_sources
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "json_sanitizer_unittest.cc",
+ ]
+
+ deps = [
+ ":safe_json",
+ ":test_support",
+ "//base",
+ "//testing/gtest",
+ ]
}
# GYP version: components/safe_json.gypi:safe_json_test_support
@@ -45,7 +80,8 @@ source_set("safe_json_parser_message_filter") {
deps = [
"//base",
- "//content/public/browser",
+ "//content/public/common",
+ "//content/public/utility",
"//ipc",
]
}
diff --git a/components/safe_json/DEPS b/components/safe_json/DEPS
index 4e771fc..478fc1a 100644
--- a/components/safe_json/DEPS
+++ b/components/safe_json/DEPS
@@ -1,8 +1,10 @@
include_rules = [
"+base",
- "+content/public",
+ "+content/public/browser",
+ "+content/public/common",
+ "+content/public/utility",
"+grit", # For generated headers
- "+grit/components_strings.h", # For generated headers
"+ipc",
+ "+jni",
"+ui/base",
]
diff --git a/components/safe_json/android/BUILD.gn b/components/safe_json/android/BUILD.gn
new file mode 100644
index 0000000..bfb462d
--- /dev/null
+++ b/components/safe_json/android/BUILD.gn
@@ -0,0 +1,22 @@
+# 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.
+
+import("//build/config/android/rules.gni")
+
+_jni_sources =
+ [ "java/src/org/chromium/components/safejson/JsonSanitizer.java" ]
+
+# GYP: //components/safe_json.gypi:safe_json_jni_headers
+generate_jni("safe_json_jni_headers") {
+ sources = _jni_sources
+ jni_package = "safe_json"
+}
+
+# GYP: //components/safe_json.gypi:safe_json_java
+android_library("safe_json_java") {
+ deps = [
+ "//base:base_java",
+ ]
+ java_files = [] + _jni_sources
+}
diff --git a/components/safe_json/android/component_jni_registrar.cc b/components/safe_json/android/component_jni_registrar.cc
new file mode 100644
index 0000000..f1f937b
--- /dev/null
+++ b/components/safe_json/android/component_jni_registrar.cc
@@ -0,0 +1,25 @@
+// 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.
+
+#include "components/safe_json/android/component_jni_registrar.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "base/basictypes.h"
+#include "components/safe_json/json_sanitizer.h"
+
+namespace safe_json {
+namespace android {
+
+static base::android::RegistrationMethod kSafeJsonRegisteredMethods[] = {
+ {"JsonSanitizer", JsonSanitizer::Register},
+};
+
+bool RegisterSafeJsonJni(JNIEnv* env) {
+ return base::android::RegisterNativeMethods(
+ env, kSafeJsonRegisteredMethods, arraysize(kSafeJsonRegisteredMethods));
+}
+
+} // namespace android
+} // namespace safe_json
diff --git a/components/safe_json/android/component_jni_registrar.h b/components/safe_json/android/component_jni_registrar.h
new file mode 100644
index 0000000..8417783
--- /dev/null
+++ b/components/safe_json/android/component_jni_registrar.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef COMPONENTS_SAFE_JSON_ANDROID_COMPONENT_JNI_REGISTRAR_H_
+#define COMPONENTS_SAFE_JSON_ANDROID_COMPONENT_JNI_REGISTRAR_H_
+
+#include <jni.h>
+
+namespace safe_json {
+namespace android {
+
+// Register all JNI bindings necessary for the safe_json component.
+bool RegisterSafeJsonJni(JNIEnv* env);
+
+} // namespace android
+} // namespace safe_json
+
+#endif // COMPONENTS_SAFE_JSON_ANDROID_COMPONENT_JNI_REGISTRAR_H_
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 0000000..1fae2fd
--- /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);
+}
diff --git a/components/safe_json/json_sanitizer.cc b/components/safe_json/json_sanitizer.cc
new file mode 100644
index 0000000..e00043e
--- /dev/null
+++ b/components/safe_json/json_sanitizer.cc
@@ -0,0 +1,89 @@
+// 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.
+
+#include "components/safe_json/json_sanitizer.h"
+
+#if defined(OS_ANDROID)
+#error Build json_sanitizer_android.cc instead of this file on Android.
+#endif
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/safe_json/safe_json_parser.h"
+
+namespace safe_json {
+
+namespace {
+
+class OopJsonSanitizer : public JsonSanitizer {
+ public:
+ OopJsonSanitizer(const std::string& unsafe_json,
+ const StringCallback& success_callback,
+ const StringCallback& error_callback);
+
+ private:
+ friend struct base::DefaultDeleter<OopJsonSanitizer>;
+ ~OopJsonSanitizer() {}
+
+ void OnParseSuccess(scoped_ptr<base::Value> value);
+ void OnParseError(const std::string& error);
+
+ StringCallback success_callback_;
+ StringCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(OopJsonSanitizer);
+};
+
+OopJsonSanitizer::OopJsonSanitizer(const std::string& unsafe_json,
+ const StringCallback& success_callback,
+ const StringCallback& error_callback)
+ : success_callback_(success_callback), error_callback_(error_callback) {
+ SafeJsonParser::Parse(unsafe_json,
+ base::Bind(&OopJsonSanitizer::OnParseSuccess,
+ base::Unretained(this)),
+ base::Bind(&OopJsonSanitizer::OnParseError,
+ base::Unretained(this)));
+}
+
+void OopJsonSanitizer::OnParseSuccess(scoped_ptr<base::Value> value) {
+ // Self-destruct at the end of this method.
+ scoped_ptr<OopJsonSanitizer> deleter(this);
+
+ // A valid JSON document may only have a dictionary or list as its top-level
+ // type, but the JSON parser also accepts other types, so we filter them out.
+ base::Value::Type type = value->GetType();
+ if (type != base::Value::TYPE_DICTIONARY && type != base::Value::TYPE_LIST) {
+ error_callback_.Run("Invalid top-level type");
+ return;
+ }
+
+ std::string json;
+ if (!base::JSONWriter::Write(*value, &json)) {
+ error_callback_.Run("Encoding error");
+ return;
+ }
+
+ success_callback_.Run(json);
+}
+
+void OopJsonSanitizer::OnParseError(const std::string& error) {
+ error_callback_.Run("Parse error: " + error);
+ delete this;
+}
+
+} // namespace
+
+// static
+void JsonSanitizer::Sanitize(const std::string& unsafe_json,
+ const StringCallback& success_callback,
+ const StringCallback& error_callback) {
+ // OopJsonSanitizer destroys itself when it is finished.
+ new OopJsonSanitizer(unsafe_json, success_callback, error_callback);
+}
+
+} // namespace safe_json
diff --git a/components/safe_json/json_sanitizer.h b/components/safe_json/json_sanitizer.h
new file mode 100644
index 0000000..3f393d5
--- /dev/null
+++ b/components/safe_json/json_sanitizer.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef COMPONENTS_SAFE_JSON_JSON_SANITIZER_H_
+#define COMPONENTS_SAFE_JSON_JSON_SANITIZER_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+
+#if defined(OS_ANDROID)
+#include <jni.h>
+#endif
+
+namespace safe_json {
+
+// Sanitizes and normalizes JSON by parsing it in a safe environment and
+// re-serializing it. Parsing the sanitized JSON should result in a value
+// identical to parsing the original JSON.
+// This allows parsing the sanitized JSON with the regular JSONParser while
+// reducing the risk versus parsing completely untrusted JSON. It also minifies
+// the resulting JSON, which might save some space.
+class JsonSanitizer {
+ public:
+ using StringCallback = base::Callback<void(const std::string&)>;
+
+ // Starts sanitizing the passed in unsafe JSON string. Either the passed
+ // |success_callback| or the |error_callback| will be called with the result
+ // of the sanitization or an error message, respectively, but not before the
+ // method returns.
+ static void Sanitize(const std::string& unsafe_json,
+ const StringCallback& success_callback,
+ const StringCallback& error_callback);
+
+#if defined(OS_ANDROID)
+ static bool Register(JNIEnv* env);
+#endif
+
+ protected:
+ JsonSanitizer() {}
+ ~JsonSanitizer() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(JsonSanitizer);
+};
+
+} // namespace safe_json
+
+#endif // COMPONENTS_SAFE_JSON_JSON_SANITIZER_H_
diff --git a/components/safe_json/json_sanitizer_android.cc b/components/safe_json/json_sanitizer_android.cc
new file mode 100644
index 0000000..9ccad08
--- /dev/null
+++ b/components/safe_json/json_sanitizer_android.cc
@@ -0,0 +1,115 @@
+// 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.
+
+#include "components/safe_json/json_sanitizer.h"
+
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "jni/JsonSanitizer_jni.h"
+
+namespace safe_json {
+
+namespace {
+
+// An implementation of JsonSanitizer that calls into Java. It deals with
+// malformed input (in particular malformed Unicode encodings) in the following
+// steps:
+// 1. The input string is checked for whether it is well-formed UTF-8. Malformed
+// UTF-8 is rejected.
+// 2. The UTF-8 string is converted in native code to a Java String, which is
+// encoded as UTF-16.
+// 2. The Java String is parsed as JSON in the memory-safe environment of the
+// Java VM and any string literals are unescaped.
+// 3. The string literals themselves are now untrusted, so they are checked in
+// Java for whether they are valid UTF-16.
+// 4. The parsed JSON with sanitized literals is encoded back into a Java
+// String and passed back to native code.
+// 5. The Java String is converted back to UTF-8 in native code.
+// This ensures that both invalid UTF-8 and invalid escaped UTF-16 will be
+// rejected.
+class JsonSanitizerAndroid : public JsonSanitizer {
+ public:
+ JsonSanitizerAndroid(const StringCallback& success_callback,
+ const StringCallback& error_callback);
+ ~JsonSanitizerAndroid() {}
+
+ void Sanitize(const std::string& unsafe_json);
+
+ void OnSuccess(const std::string& json);
+ void OnError(const std::string& error);
+
+ private:
+ StringCallback success_callback_;
+ StringCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsonSanitizerAndroid);
+};
+
+JsonSanitizerAndroid::JsonSanitizerAndroid(
+ const StringCallback& success_callback,
+ const StringCallback& error_callback)
+ : success_callback_(success_callback),
+ error_callback_(error_callback) {}
+
+void JsonSanitizerAndroid::Sanitize(const std::string& unsafe_json) {
+ // The JSON parser only accepts wellformed UTF-8.
+ if (!base::IsStringUTF8(unsafe_json)) {
+ OnError("Unsupported encoding");
+ return;
+ }
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jstring> unsafe_json_java =
+ base::android::ConvertUTF8ToJavaString(env, unsafe_json);
+
+ // This will synchronously call either OnSuccess() or OnError().
+ Java_JsonSanitizer_sanitize(env, reinterpret_cast<jlong>(this),
+ unsafe_json_java.obj());
+}
+
+void JsonSanitizerAndroid::OnSuccess(const std::string& json) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(success_callback_, json));
+}
+
+void JsonSanitizerAndroid::OnError(const std::string& error) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(error_callback_, error));
+}
+
+} // namespace
+
+void OnSuccess(JNIEnv* env, jclass clazz, jlong jsanitizer, jstring json) {
+ JsonSanitizerAndroid* sanitizer =
+ reinterpret_cast<JsonSanitizerAndroid*>(jsanitizer);
+ sanitizer->OnSuccess(base::android::ConvertJavaStringToUTF8(env, json));
+}
+
+void OnError(JNIEnv* env, jclass clazz, jlong jsanitizer, jstring error) {
+ JsonSanitizerAndroid* sanitizer =
+ reinterpret_cast<JsonSanitizerAndroid*>(jsanitizer);
+ sanitizer->OnError(base::android::ConvertJavaStringToUTF8(env, error));
+}
+
+// static
+void JsonSanitizer::Sanitize(const std::string& unsafe_json,
+ const StringCallback& success_callback,
+ const StringCallback& error_callback) {
+ // JsonSanitizerAndroid does all its work synchronously, but posts any
+ // callbacks to the current message loop. This means it can be destroyed at
+ // the end of this method.
+ JsonSanitizerAndroid sanitizer(success_callback, error_callback);
+ sanitizer.Sanitize(unsafe_json);
+}
+
+// static
+bool JsonSanitizer::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace safe_json
diff --git a/components/safe_json/json_sanitizer_unittest.cc b/components/safe_json/json_sanitizer_unittest.cc
new file mode 100644
index 0000000..0a75ad3
--- /dev/null
+++ b/components/safe_json/json_sanitizer_unittest.cc
@@ -0,0 +1,184 @@
+// 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.
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/safe_json/json_sanitizer.h"
+#include "components/safe_json/safe_json_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+#include "components/safe_json/testing_json_parser.h"
+#endif
+
+namespace safe_json {
+
+class JsonSanitizerTest : public ::testing::Test {
+ public:
+ void TearDown() override {
+ // Flush any tasks from the message loop to avoid leaks.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ void CheckSuccess(const std::string& json);
+ void CheckError(const std::string& json);
+
+ private:
+ enum class State {
+ // ERROR is a #define on Windows, so we prefix the values with STATE_.
+ STATE_IDLE,
+ STATE_SUCCESS,
+ STATE_ERROR,
+ };
+
+ void Sanitize(const std::string& json);
+
+ void OnSuccess(const std::string& json);
+ void OnError(const std::string& error);
+
+ base::MessageLoop message_loop_;
+
+#if !defined(OS_ANDROID)
+ safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_;
+#endif
+
+ std::string result_;
+ std::string error_;
+ State state_;
+
+ scoped_ptr<base::RunLoop> run_loop_;
+};
+
+void JsonSanitizerTest::CheckSuccess(const std::string& json) {
+ SCOPED_TRACE(json);
+ Sanitize(json);
+ scoped_ptr<base::Value> parsed = base::JSONReader::Read(json);
+ ASSERT_TRUE(parsed);
+ EXPECT_EQ(State::STATE_SUCCESS, state_) << "Error: " << error_;
+
+ // The JSON parser should accept the result.
+ int error_code;
+ std::string error;
+ scoped_ptr<base::Value> reparsed = base::JSONReader::ReadAndReturnError(
+ result_, base::JSON_PARSE_RFC, &error_code, &error);
+ EXPECT_TRUE(reparsed)
+ << "Invalid result: " << error;
+
+ // The parsed values should be equal.
+ EXPECT_TRUE(reparsed->Equals(parsed.get()));
+}
+
+void JsonSanitizerTest::CheckError(const std::string& json) {
+ SCOPED_TRACE(json);
+ Sanitize(json);
+ EXPECT_EQ(State::STATE_ERROR, state_) << "Result: " << result_;
+}
+
+void JsonSanitizerTest::Sanitize(const std::string& json) {
+ state_ = State::STATE_IDLE;
+ result_.clear();
+ error_.clear();
+ run_loop_.reset(new base::RunLoop);
+ JsonSanitizer::Sanitize(
+ json,
+ base::Bind(&JsonSanitizerTest::OnSuccess, base::Unretained(this)),
+ base::Bind(&JsonSanitizerTest::OnError, base::Unretained(this)));
+
+ // We should never get a result immediately.
+ EXPECT_EQ(State::STATE_IDLE, state_);
+ run_loop_->Run();
+}
+
+void JsonSanitizerTest::OnSuccess(const std::string& json) {
+ ASSERT_EQ(State::STATE_IDLE, state_);
+ state_ = State::STATE_SUCCESS;
+ result_ = json;
+ run_loop_->Quit();
+}
+
+void JsonSanitizerTest::OnError(const std::string& error) {
+ ASSERT_EQ(State::STATE_IDLE, state_);
+ state_ = State::STATE_ERROR;
+ error_ = error;
+ run_loop_->Quit();
+}
+
+TEST_F(JsonSanitizerTest, Json) {
+ // Valid JSON:
+ CheckSuccess("{\n \"foo\": \"bar\"\n}");
+ CheckSuccess("[true]");
+ CheckSuccess("[42]");
+ CheckSuccess("[3.14]");
+ CheckSuccess("[4.0]");
+ CheckSuccess("[null]");
+ CheckSuccess("[\"foo\", \"bar\"]");
+
+ // JSON syntax errors:
+ CheckError("");
+ CheckError("[");
+ CheckError("null");
+
+ // Unterminated array.
+ CheckError("[1,2,3,]");
+}
+
+TEST_F(JsonSanitizerTest, Nesting) {
+ // 99 nested arrays are fine.
+ std::string nested(99u, '[');
+ nested.append(99u, ']');
+ CheckSuccess(nested);
+
+ // 100 nested arrays is too much.
+ CheckError(std::string(100u, '[') + std::string(100u, ']'));
+}
+
+TEST_F(JsonSanitizerTest, Unicode) {
+ // Non-ASCII characters encoded either directly as UTF-8 or escaped as UTF-16:
+ CheckSuccess("[\"☃\"]");
+ CheckSuccess("[\"\\u2603\"]");
+ CheckSuccess("[\"😃\"]");
+ CheckSuccess("[\"\\ud83d\\ude03\"]");
+
+ // Malformed UTF-8:
+ // A continuation byte outside of a sequence.
+ CheckError("[\"\x80\"]");
+
+ // A start byte that is missing a continuation byte.
+ CheckError("[\"\xc0\"]");
+
+ // An invalid byte in UTF-8.
+ CheckError("[\"\xfe\"]");
+
+ // An overlong encoding (of the letter 'A').
+ CheckError("[\"\xc1\x81\"]");
+
+ // U+D83D, a code point reserved for (high) surrogates.
+ CheckError("[\"\xed\xa0\xbd\"]");
+
+ // U+4567890, a code point outside of the valid range for Unicode.
+ CheckError("[\"\xfc\x84\x95\xa7\xa2\x90\"]");
+
+ // Malformed escaped UTF-16:
+ // An unmatched high surrogate.
+ CheckError("[\"\\ud83d\"]");
+
+ // An unmatched low surrogate.
+ CheckError("[\"\\ude03\"]");
+
+ // A low surrogate followed by a high surrogate.
+ CheckError("[\"\\ude03\\ud83d\"]");
+
+ // Valid escaped UTF-16 that encodes non-characters:
+ CheckError("[\"\\ufdd0\"]");
+ CheckError("[\"\\ufffe\"]");
+ CheckError("[\"\\ud83f\\udffe\"]");
+}
+
+} // namespace safe_json
diff --git a/components/safe_json/safe_json_parser.cc b/components/safe_json/safe_json_parser.cc
index 8193d03..653800e 100644
--- a/components/safe_json/safe_json_parser.cc
+++ b/components/safe_json/safe_json_parser.cc
@@ -4,7 +4,11 @@
#include "components/safe_json/safe_json_parser.h"
+#if defined(OS_ANDROID)
+#include "components/safe_json/safe_json_parser_android.h"
+#else
#include "components/safe_json/safe_json_parser_impl.h"
+#endif
namespace safe_json {
@@ -12,6 +16,20 @@ namespace {
SafeJsonParser::Factory g_factory = nullptr;
+SafeJsonParser* Create(const std::string& unsafe_json,
+ const SafeJsonParser::SuccessCallback& success_callback,
+ const SafeJsonParser::ErrorCallback& error_callback) {
+ if (g_factory)
+ return g_factory(unsafe_json, success_callback, error_callback);
+
+#if defined(OS_ANDROID)
+ return new SafeJsonParserAndroid(unsafe_json, success_callback,
+ error_callback);
+#else
+ return new SafeJsonParserImpl(unsafe_json, success_callback, error_callback);
+#endif
+}
+
} // namespace
// static
@@ -24,9 +42,7 @@ void SafeJsonParser::Parse(const std::string& unsafe_json,
const SuccessCallback& success_callback,
const ErrorCallback& error_callback) {
SafeJsonParser* parser =
- g_factory ? g_factory(unsafe_json, success_callback, error_callback)
- : new SafeJsonParserImpl(unsafe_json, success_callback,
- error_callback);
+ Create(unsafe_json, success_callback, error_callback);
parser->Start();
}
diff --git a/components/safe_json/safe_json_parser_android.cc b/components/safe_json/safe_json_parser_android.cc
new file mode 100644
index 0000000..9266ec7
--- /dev/null
+++ b/components/safe_json/safe_json_parser_android.cc
@@ -0,0 +1,56 @@
+// 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.
+
+#include "components/safe_json/safe_json_parser_android.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "components/safe_json/json_sanitizer.h"
+
+namespace safe_json {
+
+SafeJsonParserAndroid::SafeJsonParserAndroid(
+ const std::string& unsafe_json,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback)
+ : unsafe_json_(unsafe_json),
+ success_callback_(success_callback),
+ error_callback_(error_callback) {}
+
+SafeJsonParserAndroid::~SafeJsonParserAndroid() {}
+
+void SafeJsonParserAndroid::Start() {
+ JsonSanitizer::Sanitize(
+ unsafe_json_,
+ base::Bind(&SafeJsonParserAndroid::OnSanitizationSuccess,
+ base::Unretained(this)),
+ base::Bind(&SafeJsonParserAndroid::OnSanitizationError,
+ base::Unretained(this)));
+}
+
+void SafeJsonParserAndroid::OnSanitizationSuccess(
+ const std::string& sanitized_json) {
+ // Self-destruct at the end of this method.
+ scoped_ptr<SafeJsonParserAndroid> deleter(this);
+
+ int error_code;
+ std::string error;
+ scoped_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
+ sanitized_json, base::JSON_PARSE_RFC, &error_code, &error);
+
+ if (!value) {
+ error_callback_.Run(error);
+ return;
+ }
+
+ success_callback_.Run(value.Pass());
+}
+
+void SafeJsonParserAndroid::OnSanitizationError(const std::string& error) {
+ error_callback_.Run(error);
+ delete this;
+}
+
+} // namespace safe_json
diff --git a/components/safe_json/safe_json_parser_android.h b/components/safe_json/safe_json_parser_android.h
new file mode 100644
index 0000000..2058731
--- /dev/null
+++ b/components/safe_json/safe_json_parser_android.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef COMPONENTS_SAFE_JSON_SAFE_JSON_PARSER_ANDROID_H_
+#define COMPONENTS_SAFE_JSON_SAFE_JSON_PARSER_ANDROID_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "components/safe_json/safe_json_parser.h"
+
+namespace safe_json {
+
+class JsonSanitizer;
+
+class SafeJsonParserAndroid : public SafeJsonParser {
+ public:
+ SafeJsonParserAndroid(const std::string& unsafe_json,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+ private:
+ friend struct base::DefaultDeleter<SafeJsonParserAndroid>;
+
+ ~SafeJsonParserAndroid() override;
+
+ void OnSanitizationSuccess(const std::string& sanitized_json);
+ void OnSanitizationError(const std::string& error);
+
+ // SafeJsonParser implementation.
+ void Start() override;
+
+ const std::string unsafe_json_;
+ SuccessCallback success_callback_;
+ ErrorCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SafeJsonParserAndroid);
+};
+
+} // namespace safe_json
+
+#endif // COMPONENTS_SAFE_JSON_SAFE_JSON_PARSER_ANDROID_H_
diff --git a/components/test/DEPS b/components/test/DEPS
index b734027..ad24217 100644
--- a/components/test/DEPS
+++ b/components/test/DEPS
@@ -2,6 +2,7 @@ include_rules = [
# To initialize the global data of content_settings.
"+components/content_settings/core/common",
"+components/invalidation/impl/android/component_jni_registrar.h",
+ "+components/safe_json/android/component_jni_registrar.h",
"+content/public/android/java/src/org/chromium/content/browser",
"+content/public/app/content_jni_onload.h",
"+content/public/app/content_main.h",
diff --git a/components/test/run_all_unittests.cc b/components/test/run_all_unittests.cc
index 0c2dd10..a24c284 100644
--- a/components/test/run_all_unittests.cc
+++ b/components/test/run_all_unittests.cc
@@ -23,6 +23,7 @@
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#include "components/invalidation/impl/android/component_jni_registrar.h"
+#include "components/safe_json/android/component_jni_registrar.h"
#include "ui/base/android/ui_base_jni_registrar.h"
#include "ui/gfx/android/gfx_jni_registrar.h"
#endif
@@ -48,9 +49,10 @@ class ComponentsTestSuite : public base::TestSuite {
#if defined(OS_ANDROID)
// Register JNI bindings for android.
JNIEnv* env = base::android::AttachCurrentThread();
- gfx::android::RegisterJni(env);
- ui::android::RegisterJni(env);
- invalidation::android::RegisterInvalidationJni(env);
+ ASSERT_TRUE(gfx::android::RegisterJni(env));
+ ASSERT_TRUE(ui::android::RegisterJni(env));
+ ASSERT_TRUE(invalidation::android::RegisterInvalidationJni(env));
+ ASSERT_TRUE(safe_json::android::RegisterSafeJsonJni(env));
#endif
ui::RegisterPathProvider();
diff --git a/tools/android/eclipse/.classpath b/tools/android/eclipse/.classpath
index 3c2770a..4400f24 100644
--- a/tools/android/eclipse/.classpath
+++ b/tools/android/eclipse/.classpath
@@ -46,6 +46,7 @@ to the classpath for downstream development. See "additional_entries" below.
<classpathentry kind="src" path="components/policy/android/java/src"/>
<classpathentry kind="src" path="components/precache/android/java/src"/>
<classpathentry kind="src" path="components/precache/android/javatests/src"/>
+ <classpathentry kind="src" path="components/safe_json/android/java/src"/>
<classpathentry kind="src" path="components/service_tab_launcher/android/java/src"/>
<classpathentry kind="src" path="components/variations/android/java/src"/>
<classpathentry kind="src" path="components/web_contents_delegate_android/android/java/src"/>