chromium/services/data_decoder/public/cpp/json_sanitizer_android.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/data_decoder/public/cpp/json_sanitizer.h"

#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "services/data_decoder/public/cpp/android/safe_json_jni_headers/JsonSanitizer_jni.h"

using base::android::JavaParamRef;

// This file contains 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.

namespace data_decoder {

// static
void JsonSanitizer::Sanitize(const std::string& json, Callback callback) {
  // The JSON parser only accepts wellformed UTF-8.
  if (!base::IsStringUTF8AllowingNoncharacters(json)) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback),
                                  base::unexpected("Unsupported encoding")));
    return;
  }

  JNIEnv* env = jni_zero::AttachCurrentThread();
  base::android::ScopedJavaLocalRef<jstring> json_java =
      base::android::ConvertUTF8ToJavaString(env, json);

  // NOTE: This will *synchronously* invoke either the
  // |JNI_JsonSanitizer_OnSuccess| or |JNI_JsonSanitizer_OnError| function
  // below, so passing the address of |callback| is safe.
  Java_JsonSanitizer_sanitize(env, reinterpret_cast<jlong>(&callback),
                              json_java);
}

void JNI_JsonSanitizer_OnSuccess(JNIEnv* env,
                                 jlong jcallback,
                                 const JavaParamRef<jstring>& json) {
  auto* callback = reinterpret_cast<JsonSanitizer::Callback*>(jcallback);
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(*callback),
                                base::ok(base::android::ConvertJavaStringToUTF8(
                                    env, json))));
}

void JNI_JsonSanitizer_OnError(JNIEnv* env,
                               jlong jcallback,
                               const JavaParamRef<jstring>& error) {
  auto* callback = reinterpret_cast<JsonSanitizer::Callback*>(jcallback);
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(*callback),
                     base::unexpected(
                         base::android::ConvertJavaStringToUTF8(env, error))));
}

}  // namespace data_decoder