// Copyright 2018 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 "extensions/browser/json_file_sanitizer.h"

#include "base/files/file_util.h"
#include "base/json/json_string_value_serializer.h"
#include "base/task_runner_util.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "services/service_manager/public/cpp/connector.h"

namespace extensions {

namespace {

// Reads the file in |path| and then deletes it.
// Returns a tuple containing: the file content, whether the read was
// successful, whether the delete was successful.
std::tuple<std::string, bool, bool> ReadAndDeleteTextFile(
    const base::FilePath& path) {
  std::string contents;
  bool read_success = base::ReadFileToString(path, &contents);
  bool delete_success = base::DeleteFile(path, /*recursive=*/false);
  return std::make_tuple(contents, read_success, delete_success);
}

int WriteStringToFile(const std::string& contents,
                      const base::FilePath& file_path) {
  int size = static_cast<int>(contents.length());
  return base::WriteFile(file_path, contents.data(), size);
}

}  // namespace

// static
std::unique_ptr<JsonFileSanitizer> JsonFileSanitizer::CreateAndStart(
    service_manager::Connector* connector,
    const service_manager::Identity& identity,
    const std::set<base::FilePath>& file_paths,
    Callback callback) {
  // Note we can't use std::make_unique as we want to keep the constructor
  // private.
  std::unique_ptr<JsonFileSanitizer> sanitizer(
      new JsonFileSanitizer(file_paths, std::move(callback)));
  sanitizer->Start(connector, identity);
  return sanitizer;
}

JsonFileSanitizer::JsonFileSanitizer(const std::set<base::FilePath>& file_paths,
                                     Callback callback)
    : file_paths_(file_paths),
      callback_(std::move(callback)),
      weak_factory_(this) {}

JsonFileSanitizer::~JsonFileSanitizer() = default;

void JsonFileSanitizer::Start(service_manager::Connector* connector,
                              const service_manager::Identity& identity) {
  if (file_paths_.empty()) {
    base::SequencedTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::BindOnce(&JsonFileSanitizer::ReportSuccess,
                                  weak_factory_.GetWeakPtr()));
    return;
  }

  connector->BindInterface(identity, &json_parser_ptr_);

  for (const base::FilePath& path : file_paths_) {
    base::PostTaskAndReplyWithResult(
        extensions::GetExtensionFileTaskRunner().get(), FROM_HERE,
        base::BindOnce(&ReadAndDeleteTextFile, path),
        base::BindOnce(&JsonFileSanitizer::JsonFileRead,
                       weak_factory_.GetWeakPtr(), path));
  }
}

void JsonFileSanitizer::JsonFileRead(
    const base::FilePath& file_path,
    std::tuple<std::string, bool, bool> read_and_delete_result) {
  if (!std::get<1>(read_and_delete_result)) {
    ReportError(Status::kFileReadError, std::string());
    return;
  }
  if (!std::get<2>(read_and_delete_result)) {
    ReportError(Status::kFileDeleteError, std::string());
    return;
  }
  json_parser_ptr_->Parse(
      std::get<0>(read_and_delete_result),
      base::BindOnce(&JsonFileSanitizer::JsonParsingDone,
                     weak_factory_.GetWeakPtr(), file_path));
}

void JsonFileSanitizer::JsonParsingDone(
    const base::FilePath& file_path,
    base::Optional<base::Value> json_value,
    const base::Optional<std::string>& error) {
  if (!json_value || !json_value->is_dict()) {
    ReportError(Status::kDecodingError, error ? *error : std::string());
    return;
  }

  // Reserialize the JSON and write it back to the original file.
  std::string json_string;
  JSONStringValueSerializer serializer(&json_string);
  serializer.set_pretty_print(true);
  if (!serializer.Serialize(*json_value)) {
    ReportError(Status::kSerializingError, std::string());
    return;
  }

  int size = static_cast<int>(json_string.length());
  base::PostTaskAndReplyWithResult(
      extensions::GetExtensionFileTaskRunner().get(), FROM_HERE,
      base::BindOnce(&WriteStringToFile, std::move(json_string), file_path),
      base::BindOnce(&JsonFileSanitizer::JsonFileWritten,
                     weak_factory_.GetWeakPtr(), file_path, size));
}

void JsonFileSanitizer::JsonFileWritten(const base::FilePath& file_path,
                                        int expected_size,
                                        int actual_size) {
  if (expected_size != actual_size) {
    ReportError(Status::kFileWriteError, std::string());
    return;
  }
  // We have finished with this JSON file.
  size_t removed_count = file_paths_.erase(file_path);
  DCHECK_EQ(1U, removed_count);

  if (file_paths_.empty()) {
    // This was the last path, we are done.
    ReportSuccess();
  }
}

void JsonFileSanitizer::ReportSuccess() {
  std::move(callback_).Run(Status::kSuccess, std::string());
}

void JsonFileSanitizer::ReportError(Status status, const std::string& error) {
  // Prevent any other task from reporting, we want to notify only once.
  weak_factory_.InvalidateWeakPtrs();
  std::move(callback_).Run(status, error);
}

}  // namespace extensions
