// 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 "extensions/common/manifest_handlers/webview_info.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"

namespace extensions {

namespace keys = extensions::manifest_keys;
namespace errors = extensions::manifest_errors;

// A PartitionItem represents a set of accessible resources given a partition
// ID pattern.
class PartitionItem {
 public:
  explicit PartitionItem(const std::string& partition_pattern)
      : partition_pattern_(partition_pattern) {
  }

  virtual ~PartitionItem() {
  }

  bool Matches(const std::string& partition_id) const {
    return base::MatchPattern(partition_id, partition_pattern_);
  }

  // Adds a pattern to the set. Returns true if a new pattern was inserted,
  // false if the pattern was already in the set.
  bool AddPattern(const URLPattern& pattern) {
    return accessible_resources_.AddPattern(pattern);
  }

  const URLPatternSet& accessible_resources() const {
    return accessible_resources_;
  }
 private:
  // A pattern string that matches partition IDs.
  const std::string partition_pattern_;
  // A URL pattern set of resources accessible to the given
  // |partition_pattern_|.
  URLPatternSet accessible_resources_;
};

// static
const char* WebviewInfo::kManifestDataKey = keys::kWebviewAccessibleResources;

WebviewInfo::WebviewInfo(const ExtensionId& extension_id)
    : extension_id_(extension_id) {}

WebviewInfo::~WebviewInfo() = default;

// static
bool WebviewInfo::IsResourceWebviewAccessible(
    const Extension* extension,
    const std::string& partition_id,
    const std::string& relative_path) {
  if (!extension) {
    return false;
  }

  const WebviewInfo* webview_info = extension->GetManifestData<WebviewInfo>();
  if (!webview_info) {
    return false;
  }

  for (const auto& item : webview_info->partition_items_) {
    if (item->Matches(partition_id) &&
        extension->ResourceMatches(item->accessible_resources(), relative_path,
                                   /*case_sensitive=*/true)) {
      return true;
    }
  }

  return false;
}

// static
bool WebviewInfo::HasWebviewAccessibleResources(
    const Extension& extension,
    const std::string& partition_id) {
  const WebviewInfo* webview_info = extension.GetManifestData<WebviewInfo>();
  if (!webview_info) {
    return false;
  }

  for (const auto& item : webview_info->partition_items_) {
    if (item->Matches(partition_id)) {
      return true;
    }
  }
  return false;
}

void WebviewInfo::AddPartitionItem(std::unique_ptr<PartitionItem> item) {
  partition_items_.push_back(std::move(item));
}

WebviewHandler::WebviewHandler() = default;

WebviewHandler::~WebviewHandler() = default;

bool WebviewHandler::Parse(Extension* extension, std::u16string* error) {
  std::unique_ptr<WebviewInfo> info(new WebviewInfo(extension->id()));

  const base::DictValue* dict =
      extension->manifest()->available_values().FindDict(keys::kWebview);
  if (!dict) {
    *error = errors::kInvalidWebview;
    return false;
  }

  const base::ListValue* partition_list =
      dict->FindList(keys::kWebviewPartitions);
  if (partition_list == nullptr) {
    *error = errors::kInvalidWebviewPartitionsList;
    return false;
  }

  // The partition list must have at least one entry.
  if (partition_list->empty()) {
    *error = errors::kInvalidWebviewPartitionsList;
    return false;
  }

  for (size_t i = 0; i < partition_list->size(); ++i) {
    if (!(*partition_list)[i].is_dict()) {
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidWebviewPartition, base::NumberToString(i));
      return false;
    }

    const base::DictValue& item_dict = (*partition_list)[i].GetDict();

    const std::string* partition_pattern =
        item_dict.FindString(keys::kWebviewName);
    if (partition_pattern == nullptr) {
      *error = ErrorUtils::FormatErrorMessageUTF16(
          errors::kInvalidWebviewPartitionName, base::NumberToString(i));
      return false;
    }

    const base::ListValue* url_list =
        item_dict.FindList(keys::kWebviewAccessibleResources);
    // The URL list should have at least one entry.
    if (url_list == nullptr || url_list->empty()) {
      *error = errors::kInvalidWebviewAccessibleResourcesList;
      return false;
    }

    auto partition_item = std::make_unique<PartitionItem>(*partition_pattern);

    for (const base::Value& item : *url_list) {
      if (!item.is_string()) {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kInvalidWebviewAccessibleResource, base::NumberToString(i));
        return false;
      }

      GURL pattern_url = extension->ResolveExtensionURL(item.GetString());
      // If passed a non-relative URL (like http://example.com),
      // Extension::ResolveExtensionURL() will return an invalid URL.
      if (!pattern_url.is_valid()) {
        // NOTE: Warning instead of error because there are existing apps that
        // have this bug, and we don't want to hard-error on them.
        // https://crbug.com/40582582.
        std::string warning = ErrorUtils::FormatErrorMessage(
            errors::kInvalidWebviewAccessibleResource, base::NumberToString(i));
        extension->AddInstallWarning(
            InstallWarning(std::move(warning), keys::kWebview));
        continue;
      }
      URLPattern pattern(URLPattern::SCHEME_EXTENSION);
      if (pattern.Parse(pattern_url.spec()) !=
          URLPattern::ParseResult::kSuccess) {
        // NOTE: Warning instead of error because there are existing apps that
        // have this bug, and we don't want to hard-error on them.
        // https://crbug.com/40582582.
        std::string warning = ErrorUtils::FormatErrorMessage(
            errors::kInvalidWebviewAccessibleResource, base::NumberToString(i));
        extension->AddInstallWarning(
            InstallWarning(std::move(warning), keys::kWebview));
        continue;
      }

      partition_item->AddPattern(std::move(pattern));
    }
    info->AddPartitionItem(std::move(partition_item));
  }

  extension->SetManifestData(std::move(info));
  return true;
}

base::span<const char* const> WebviewHandler::Keys() const {
  static constexpr const char* kKeys[] = {keys::kWebview};
  return kKeys;
}

}  // namespace extensions
