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

#include "components/cookie_config/cookie_store_util.h"

#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "components/os_crypt/async/browser/os_crypt_async.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "net/extras/sqlite/cookie_crypto_delegate.h"

namespace cookie_config {

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ARKWEB)
namespace {

void OnOsCryptReadyOnUi(
    base::OnceCallback<void(os_crypt_async::Encryptor)> callback,
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    os_crypt_async::Encryptor encryptor) {
  task_runner->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(encryptor)));
}

void InitOnUi(base::OnceCallback<void(os_crypt_async::Encryptor)> callback,
              os_crypt_async::OSCryptAsync* os_crypt_async,
              scoped_refptr<base::SequencedTaskRunner> task_runner) {
  os_crypt_async->GetInstance(
      base::BindOnce(&OnOsCryptReadyOnUi, std::move(callback),
                     std::move(task_runner)),
      os_crypt_async::Encryptor::Option::kEncryptSyncCompat);
}

// Use the operating system's mechanisms to encrypt cookies before writing
// them to persistent store.  Currently this only is done with desktop OS's
// because ChromeOS and Android already protect the entire profile contents.
class CookieOSCryptoDelegate : public net::CookieCryptoDelegate {
 public:
  CookieOSCryptoDelegate(
      os_crypt_async::OSCryptAsync* os_crypt_async,
      scoped_refptr<base::SequencedTaskRunner> ui_task_runner);

  CookieOSCryptoDelegate(const CookieOSCryptoDelegate&) = delete;
  CookieOSCryptoDelegate& operator=(const CookieOSCryptoDelegate&) = delete;

  ~CookieOSCryptoDelegate() override;

  // net::CookieCryptoDelegate implementation:
  void Init(base::OnceClosure callback) override;
  bool EncryptString(const std::string& plaintext,
                     std::string* ciphertext) override;
  bool DecryptString(const std::string& ciphertext,
                     std::string* plaintext) override;

 private:
  void OnOsCryptReady(os_crypt_async::Encryptor encryptor);

  raw_ptr<os_crypt_async::OSCryptAsync> os_crypt_async_;
  scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
  std::optional<os_crypt_async::Encryptor> encryptor_;

  bool initializing_ = false;
  std::vector<base::OnceClosure> init_callbacks_;

  base::WeakPtrFactory<CookieOSCryptoDelegate> weak_ptr_factory_{this};
};

CookieOSCryptoDelegate::CookieOSCryptoDelegate(
    os_crypt_async::OSCryptAsync* os_crypt_async,
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
    : os_crypt_async_(os_crypt_async), ui_task_runner_(ui_task_runner) {}

CookieOSCryptoDelegate::~CookieOSCryptoDelegate() = default;

void CookieOSCryptoDelegate::Init(base::OnceClosure callback) {
  if (encryptor_.has_value()) {
    std::move(callback).Run();
    return;
  }

  init_callbacks_.emplace_back(std::move(callback));
  if (initializing_) {
    return;
  }
  initializing_ = true;

  // PostTaskAndReplyWithResult can't be used here because
  // OSCryptAsync::GetInstance() is async.
  ui_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&InitOnUi,
                     base::BindOnce(&CookieOSCryptoDelegate::OnOsCryptReady,
                                    weak_ptr_factory_.GetWeakPtr()),
                     os_crypt_async_,
                     base::SequencedTaskRunner::GetCurrentDefault()));
  os_crypt_async_ = nullptr;
}

bool CookieOSCryptoDelegate::EncryptString(const std::string& plaintext,
                                           std::string* ciphertext) {
  CHECK(encryptor_) << "EncryptString called before Init completed";
  return encryptor_->EncryptString(plaintext, ciphertext);
}

bool CookieOSCryptoDelegate::DecryptString(const std::string& ciphertext,
                                           std::string* plaintext) {
  CHECK(encryptor_) << "DecryptString called before Init completed";
  return encryptor_->DecryptString(ciphertext, plaintext);
}

void CookieOSCryptoDelegate::OnOsCryptReady(
    os_crypt_async::Encryptor encryptor) {
  encryptor_ = std::move(encryptor);
  initializing_ = false;
  for (auto& callback : init_callbacks_) {
    std::move(callback).Run();
  }
  init_callbacks_.clear();
}

}  // namespace

std::unique_ptr<net::CookieCryptoDelegate> GetCookieCryptoDelegate(
    os_crypt_async::OSCryptAsync* os_crypt_async,
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
  return std::make_unique<CookieOSCryptoDelegate>(os_crypt_async,
                                                  ui_task_runner);
}
#else   // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ARKWEB)
std::unique_ptr<net::CookieCryptoDelegate> GetCookieCryptoDelegate(
    os_crypt_async::OSCryptAsync* os_crypt_async,
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
  return nullptr;
}
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ARKWEB)

}  // namespace cookie_config