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

#include "extensions/common/manifest_handlers/chrome_url_overrides_handler.h"

#include <memory>

#include "base/files/file_util.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/api/chrome_url_overrides.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/manifest_url_handlers.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/permissions/api_permission.h"

namespace extensions {

namespace keys = manifest_keys;
namespace errors = manifest_errors;

namespace {

const char kOverrideExtentUrlPatternFormat[] = "chrome://%s/*";
using ChromeUrlOverridesKeys = api::chrome_url_overrides::ManifestKeys;

}  // namespace

// static
const char* URLOverrides::kManifestDataKey =
    ChromeUrlOverridesKeys::kChromeUrlOverrides;

URLOverrides::URLOverrides() = default;
URLOverrides::~URLOverrides() = default;

// static
const URLOverrides::URLOverrideMap& URLOverrides::GetChromeURLOverrides(
    const Extension* extension) {
  static const base::NoDestructor<URLOverrides::URLOverrideMap>
      empty_url_overrides;
  const URLOverrides* url_overrides =
      extension->GetManifestData<URLOverrides>();
  return url_overrides ? url_overrides->chrome_url_overrides_
                       : *empty_url_overrides;
}

URLOverridesHandler::URLOverridesHandler() = default;
URLOverridesHandler::~URLOverridesHandler() = default;

bool URLOverridesHandler::Parse(Extension* extension, std::u16string* error) {
  ChromeUrlOverridesKeys manifest_keys;
  if (!ChromeUrlOverridesKeys::ParseFromDictionary(
          extension->manifest()->available_values(), manifest_keys, *error)) {
    return false;
  }

  CHECK(manifest_keys.chrome_url_overrides.has_value());
  using UrlOverrideInfo = api::chrome_url_overrides::UrlOverrideInfo;
  auto url_overrides = std::make_unique<URLOverrides>();
  auto property_map =
      std::map<const char*,
               std::reference_wrapper<const std::optional<std::string>>>{
          {UrlOverrideInfo::kNewtab,
           std::ref(manifest_keys.chrome_url_overrides->newtab)},
          {UrlOverrideInfo::kBookmarks,
           std::ref(manifest_keys.chrome_url_overrides->bookmarks)},
          {UrlOverrideInfo::kHistory,
           std::ref(manifest_keys.chrome_url_overrides->history)},
          {UrlOverrideInfo::kActivationmessage,
           std::ref(manifest_keys.chrome_url_overrides->activationmessage)},
          {UrlOverrideInfo::kKeyboard,
           std::ref(manifest_keys.chrome_url_overrides->keyboard)}};

  for (const auto& property : property_map) {
    if (!property.second.get()) {
      continue;
    }

    // Replace the entry with a fully qualified chrome-extension:// URL.
    url_overrides->chrome_url_overrides_[property.first] =
        extension->ResolveExtensionURL(*property.second.get());

    // For component extensions, add override URL to extent patterns.
    if (extension->is_legacy_packaged_app() &&
        extension->location() == mojom::ManifestLocation::kComponent) {
      URLPattern pattern(URLPattern::SCHEME_CHROMEUI);
      std::string url =
          base::StringPrintf(kOverrideExtentUrlPatternFormat, property.first);
      if (pattern.Parse(url) != URLPattern::ParseResult::kSuccess) {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kInvalidURLPatternError, url);
        return false;
      }
      extension->AddWebExtentPattern(pattern);
    }
  }

  // An extension may override at most one page.
  if (url_overrides->chrome_url_overrides_.size() > 1u) {
    *error = errors::kMultipleOverrides;
    return false;
  }

  // If this is an NTP override extension, add the NTP override permission.
  if (manifest_keys.chrome_url_overrides->newtab) {
    PermissionsParser::AddAPIPermission(
        extension, mojom::APIPermissionID::kNewTabPageOverride);
  }

  extension->SetManifestData(std::move(url_overrides));

  return true;
}

bool URLOverridesHandler::Validate(
    const Extension& extension,
    std::string* error,
    std::vector<InstallWarning>* warnings) const {
  const URLOverrides::URLOverrideMap& overrides =
      URLOverrides::GetChromeURLOverrides(&extension);
  if (overrides.empty()) {
    return true;
  }

  for (const auto& entry : overrides) {
    base::FilePath relative_path =
        file_util::ExtensionURLToRelativeFilePath(entry.second);
    base::FilePath resource_path =
        extension.GetResource(relative_path).GetFilePath();
    if (resource_path.empty() || !base::PathExists(resource_path)) {
      *error = ErrorUtils::FormatErrorMessage(errors::kFileNotFound,
                                              relative_path.AsUTF8Unsafe());
      return false;
    }
  }
  return true;
}

base::span<const char* const> URLOverridesHandler::Keys() const {
  static constexpr const char* kKeys[] = {
      ChromeUrlOverridesKeys::kChromeUrlOverrides};
  return kKeys;
}

}  // namespace extensions
