// 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 <memory>

#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "build/build_config.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_;

  std::unique_ptr<base::RunLoop> run_loop_;
};

void JsonSanitizerTest::CheckSuccess(const std::string& json) {
  SCOPED_TRACE(json);
  Sanitize(json);
  std::unique_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;
  std::unique_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
