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

#include "chrome/updater/external_constants_override.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/external_constants.h"
#include "chrome/updater/external_constants_default.h"
#include "chrome/updater/get_updater_scope.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/crx_file/crx_verifier.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_MAC)
#include "base/apple/foundation_util.h"
#elif BUILDFLAG(IS_WIN)
#include "base/path_service.h"
#endif

namespace {

// Developer override file name, relative to app data directory.
constexpr char kDevOverrideFileName[] = "overrides.json";

std::vector<GURL> GURLVectorFromStringList(
    const base::ListValue& update_url_list) {
  std::vector<GURL> ret;
  ret.reserve(update_url_list.size());
  for (const base::Value& url : update_url_list) {
    CHECK(url.is_string()) << "Non-string Value in update URL list";
    ret.push_back(GURL(url.GetString()));
  }
  return ret;
}

// The test binary only ever needs to contact localhost during integration
// tests. To reduce the program's utility as a mule, crash if there is a
// non-localhost override.
GURL CheckURL(const GURL& url) {
  CHECK(url.is_empty() || url.GetHost() == "localhost" ||
        url.GetHost() == "127.0.0.1" || url.GetHost() == "not_exist")
      << "Illegal URL override: " << url;
  return url;
}

std::vector<GURL> CheckURLs(const std::vector<GURL>& urls) {
  for (const auto& url : urls) {
    CheckURL(url);
  }
  return urls;
}

}  // anonymous namespace

namespace updater {

std::optional<base::FilePath> GetOverrideFilePath(UpdaterScope scope) {
  std::optional<base::FilePath> base = GetInstallDirectory(scope);
  if (!base) {
    return std::nullopt;
  }
  return base->DirName().AppendUTF8(kDevOverrideFileName);
}

ExternalConstantsOverrider::ExternalConstantsOverrider(
    base::DictValue override_values,
    scoped_refptr<ExternalConstants> next_provider)
    : ExternalConstants(std::move(next_provider)),
      override_values_(std::move(override_values)) {}

ExternalConstantsOverrider::~ExternalConstantsOverrider() = default;

std::vector<GURL> ExternalConstantsOverrider::UpdateURL() const {
  if (!override_values_.contains(kDevOverrideKeyUrl)) {
    return next_provider_->UpdateURL();
  }
  const base::Value* update_url_value =
      override_values_.Find(kDevOverrideKeyUrl);
  switch (update_url_value->type()) {
    case base::Value::Type::STRING:
      return CheckURLs({GURL(update_url_value->GetString())});
    case base::Value::Type::LIST:
      return CheckURLs(GURLVectorFromStringList(update_url_value->GetList()));
    default:
      LOG(FATAL) << "Unexpected type of override[" << kDevOverrideKeyUrl
                 << "]: " << base::Value::GetTypeName(update_url_value->type());
  }
}

GURL ExternalConstantsOverrider::CrashUploadURL() const {
  if (!override_values_.contains(kDevOverrideKeyCrashUploadUrl)) {
    return next_provider_->CrashUploadURL();
  }
  const base::Value* crash_upload_url_value =
      override_values_.Find(kDevOverrideKeyCrashUploadUrl);
  CHECK(crash_upload_url_value->is_string())
      << "Unexpected type of override[" << kDevOverrideKeyCrashUploadUrl
      << "]: " << base::Value::GetTypeName(crash_upload_url_value->type());
  return CheckURL({GURL(crash_upload_url_value->GetString())});
}

GURL ExternalConstantsOverrider::AppLogoURL() const {
  if (!override_values_.contains(kDevOverrideKeyAppLogoUrl)) {
    return next_provider_->AppLogoURL();
  }
  const base::Value* app_logo_url_value =
      override_values_.Find(kDevOverrideKeyAppLogoUrl);
  CHECK(app_logo_url_value->is_string())
      << "Unexpected type of override[" << kDevOverrideKeyAppLogoUrl
      << "]: " << base::Value::GetTypeName(app_logo_url_value->type());
  return CheckURL({GURL(app_logo_url_value->GetString())});
}

GURL ExternalConstantsOverrider::EventLoggingURL() const {
  if (!override_values_.contains(kDevOverrideKeyEventLoggingUrl)) {
    return next_provider_->EventLoggingURL();
  }
  const base::Value* event_logging_url_value =
      override_values_.Find(kDevOverrideKeyEventLoggingUrl);
  CHECK(event_logging_url_value->is_string())
      << "Unexpected type of override[" << kDevOverrideKeyEventLoggingUrl
      << "]: " << base::Value::GetTypeName(event_logging_url_value->type());
  return CheckURL({GURL(event_logging_url_value->GetString())});
}

bool ExternalConstantsOverrider::UseCUP() const {
  if (!override_values_.contains(kDevOverrideKeyUseCUP)) {
    return next_provider_->UseCUP();
  }
  const base::Value* use_cup_value =
      override_values_.Find(kDevOverrideKeyUseCUP);
  CHECK(use_cup_value->is_bool())
      << "Unexpected type of override[" << kDevOverrideKeyUseCUP
      << "]: " << base::Value::GetTypeName(use_cup_value->type());

  return use_cup_value->GetBool();
}

base::TimeDelta ExternalConstantsOverrider::InitialDelay() const {
  if (!override_values_.contains(kDevOverrideKeyInitialDelay)) {
    return next_provider_->InitialDelay();
  }

  const base::Value* initial_delay_value =
      override_values_.Find(kDevOverrideKeyInitialDelay);
  CHECK(initial_delay_value->is_double())
      << "Unexpected type of override[" << kDevOverrideKeyInitialDelay
      << "]: " << base::Value::GetTypeName(initial_delay_value->type());
  return base::Seconds(initial_delay_value->GetDouble());
}

base::TimeDelta ExternalConstantsOverrider::ServerKeepAliveTime() const {
  if (!override_values_.contains(kDevOverrideKeyServerKeepAliveSeconds)) {
    return next_provider_->ServerKeepAliveTime();
  }

  const base::Value* server_keep_alive_seconds_value =
      override_values_.Find(kDevOverrideKeyServerKeepAliveSeconds);
  CHECK(server_keep_alive_seconds_value->is_int())
      << "Unexpected type of override[" << kDevOverrideKeyServerKeepAliveSeconds
      << "]: "
      << base::Value::GetTypeName(server_keep_alive_seconds_value->type());
  return base::Seconds(server_keep_alive_seconds_value->GetInt());
}

crx_file::VerifierFormat ExternalConstantsOverrider::CrxVerifierFormat() const {
  if (!override_values_.contains(kDevOverrideKeyCrxVerifierFormat)) {
    return next_provider_->CrxVerifierFormat();
  }

  const base::Value* crx_format_verifier_value =
      override_values_.Find(kDevOverrideKeyCrxVerifierFormat);
  CHECK(crx_format_verifier_value->is_int())
      << "Unexpected type of override[" << kDevOverrideKeyCrxVerifierFormat
      << "]: " << base::Value::GetTypeName(crx_format_verifier_value->type());
  return static_cast<crx_file::VerifierFormat>(
      crx_format_verifier_value->GetInt());
}

std::optional<std::vector<uint8_t>>
ExternalConstantsOverrider::CrxPublicKeyHash() const {
  if (!override_values_.contains(kDevOverrideKeyCrxPublicKeyHash)) {
    return next_provider_->CrxPublicKeyHash();
  }

  const base::Value* value =
      override_values_.Find(kDevOverrideKeyCrxPublicKeyHash);
  CHECK(value->is_string())
      << "Unexpected type of override[" << kDevOverrideKeyCrxPublicKeyHash
      << "]: " << base::Value::GetTypeName(value->type());
  return value->GetString().empty() ? std::nullopt
                                    : base::Base64Decode(value->GetString());
}

base::TimeDelta ExternalConstantsOverrider::MinimumEventLoggingCooldown()
    const {
  if (!override_values_.contains(
          kDevOverrideKeyMinumumEventLoggingCooldownSeconds)) {
    return next_provider_->MinimumEventLoggingCooldown();
  }

  const base::Value* minimum_event_logging_cooldown_seconds =
      override_values_.Find(kDevOverrideKeyMinumumEventLoggingCooldownSeconds);
  CHECK(minimum_event_logging_cooldown_seconds->is_int())
      << "Unexpected type of override["
      << kDevOverrideKeyMinumumEventLoggingCooldownSeconds << "]: "
      << base::Value::GetTypeName(
             minimum_event_logging_cooldown_seconds->type());
  return base::Seconds(minimum_event_logging_cooldown_seconds->GetInt());
}

std::optional<EventLoggingPermissionProvider>
ExternalConstantsOverrider::GetEventLoggingPermissionProvider() const {
  if (!override_values_.contains(
          kDevOverrideKeyEventLoggingPermissionProviderAppId)) {
    return next_provider_->GetEventLoggingPermissionProvider();
  }

  EventLoggingPermissionProvider provider;

  const base::Value* app_id =
      override_values_.Find(kDevOverrideKeyEventLoggingPermissionProviderAppId);
  CHECK(app_id->is_string())
      << "Unexpected type of override["
      << kDevOverrideKeyEventLoggingPermissionProviderAppId
      << "]: " << base::Value::GetTypeName(app_id->type());
  provider.app_id = app_id->GetString();

#if BUILDFLAG(IS_MAC)
  const base::Value* directory_name = override_values_.Find(
      kDevOverrideKeyEventLoggingPermissionProviderDirectoryName);
  CHECK(directory_name->is_string())
      << "Unexpected type of override["
      << kDevOverrideKeyEventLoggingPermissionProviderDirectoryName
      << "]: " << base::Value::GetTypeName(directory_name->type());
  provider.directory_name = directory_name->GetString();
#endif

  return provider;
}

base::DictValue ExternalConstantsOverrider::DictPolicies() const {
  if (!override_values_.contains(kDevOverrideKeyDictPolicies)) {
    return next_provider_->DictPolicies();
  }

  const base::Value* dict_policies_value =
      override_values_.Find(kDevOverrideKeyDictPolicies);
  CHECK(dict_policies_value->is_dict())
      << "Unexpected type of override[" << kDevOverrideKeyDictPolicies
      << "]: " << base::Value::GetTypeName(dict_policies_value->type());
  return dict_policies_value->GetDict().Clone();
}

base::TimeDelta ExternalConstantsOverrider::OverinstallTimeout() const {
  if (!override_values_.contains(kDevOverrideKeyOverinstallTimeout)) {
    return next_provider_->OverinstallTimeout();
  }

  const base::Value* value =
      override_values_.Find(kDevOverrideKeyOverinstallTimeout);
  CHECK(value->is_int()) << "Unexpected type of override["
                         << kDevOverrideKeyOverinstallTimeout
                         << "]: " << base::Value::GetTypeName(value->type());
  return base::Seconds(value->GetInt());
}

base::TimeDelta ExternalConstantsOverrider::IdleCheckPeriod() const {
  if (!override_values_.contains(kDevOverrideKeyIdleCheckPeriodSeconds)) {
    return next_provider_->IdleCheckPeriod();
  }

  const base::Value* value =
      override_values_.Find(kDevOverrideKeyIdleCheckPeriodSeconds);
  CHECK(value->is_int()) << "Unexpected type of override["
                         << kDevOverrideKeyIdleCheckPeriodSeconds
                         << "]: " << base::Value::GetTypeName(value->type());
  return base::Seconds(value->GetInt());
}

std::optional<bool> ExternalConstantsOverrider::IsMachineManaged() const {
  if (!override_values_.contains(kDevOverrideKeyManagedDevice)) {
    return next_provider_->IsMachineManaged();
  }
  const base::Value* is_managed =
      override_values_.Find(kDevOverrideKeyManagedDevice);
  CHECK(is_managed->is_bool())
      << "Unexpected type of override[" << kDevOverrideKeyManagedDevice
      << "]: " << base::Value::GetTypeName(is_managed->type());

  return std::make_optional(is_managed->GetBool());
}

base::TimeDelta ExternalConstantsOverrider::CecaConnectionTimeout() const {
  if (!override_values_.contains(kDevOverrideKeyCecaConnectionTimeout)) {
    return next_provider_->CecaConnectionTimeout();
  }

  const base::Value* value =
      override_values_.Find(kDevOverrideKeyCecaConnectionTimeout);
  CHECK(value->is_int()) << "Unexpected type of override["
                         << kDevOverrideKeyCecaConnectionTimeout
                         << "]: " << base::Value::GetTypeName(value->type());
  return base::Seconds(value->GetInt());
}

// static
scoped_refptr<ExternalConstantsOverrider>
ExternalConstantsOverrider::FromDefaultJSONFile(
    scoped_refptr<ExternalConstants> next_provider) {
  const std::optional<base::FilePath> override_file_path =
      GetOverrideFilePath(GetUpdaterScope());
  if (!override_file_path) {
    LOG(ERROR) << "Cannot find override file path.";
    return nullptr;
  }

  JSONFileValueDeserializer parser(*override_file_path,
                                   base::JSON_ALLOW_TRAILING_COMMAS);
  int error_code = 0;
  std::string error_message;
  std::unique_ptr<base::Value> parsed_value(
      parser.Deserialize(&error_code, &error_message));
  if (error_code || !parsed_value) {
    VLOG(2) << "Could not parse " << override_file_path << ": error "
            << error_code << ": " << error_message;
    return nullptr;
  }

  if (!parsed_value->is_dict()) {
    LOG(ERROR) << "Invalid data in " << override_file_path << ": not a dict";
    return nullptr;
  }

  return base::MakeRefCounted<ExternalConstantsOverrider>(
      std::move(*parsed_value).TakeDict(), next_provider);
}

// Declared in external_constants.h. This implementation of the function is
// used only if external_constants_override is linked into the binary.
scoped_refptr<ExternalConstants> CreateExternalConstants() {
  scoped_refptr<ExternalConstants> overrider =
      ExternalConstantsOverrider::FromDefaultJSONFile(
          CreateDefaultExternalConstants());
  return overrider ? overrider : CreateDefaultExternalConstants();
}

}  // namespace updater
