// 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 "components/origin_matcher/origin_matcher.h"

#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/strings/string_util.h"
#include "components/origin_matcher/origin_matcher_internal.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/parse_number.h"
#include "net/base/url_util.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
#include "url/url_util.h"

namespace origin_matcher {

namespace {

inline int GetDefaultPortForSchemeIfNoPortInfo(const std::string& scheme,
                                               int port) {
  // The input has explicit port information, so don't modify it.
  if (port != -1) {
    return port;
  }

  // Hard code the port for http and https.
  if (scheme == url::kHttpScheme) {
    return 80;
  }
  if (scheme == url::kHttpsScheme) {
    return 443;
  }

  return port;
}

}  // namespace

OriginMatcher::OriginMatcher() = default;
// Allow copy and assign.
OriginMatcher::OriginMatcher(OriginMatcher&&) = default;
OriginMatcher& OriginMatcher::operator=(OriginMatcher&&) = default;

OriginMatcher::~OriginMatcher() = default;

OriginMatcher::OriginMatcher(const OriginMatcher& rhs) {
  *this = rhs;
}

OriginMatcher& OriginMatcher::operator=(const OriginMatcher& rhs) {
  rules_.clear();

  rules_.reserve(rhs.rules_.size());
  for (const auto& rule : rhs.rules_) {
    rules_.push_back(rule->Clone());
  }

  return *this;
}

void OriginMatcher::SetRules(RuleList rules) {
  rules_.swap(rules);
}

void OriginMatcher::MoveRules(OriginMatcher& matcher) {
  rules_ = std::move(matcher.rules_);
}

bool OriginMatcher::AddRuleFromString(const std::string& raw_untrimmed) {
  std::string raw;
  base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw);

  if (raw == "*") {
    rules_.push_back(std::make_unique<MatchAllOriginsRule>());
    return true;
  }

  // Extract scheme-restriction.
  std::string::size_type scheme_pos = raw.find("://");
  if (scheme_pos == std::string::npos) {
    return false;
  }

  const std::string scheme = raw.substr(0, scheme_pos);
  if (!SubdomainMatchingRule::IsValidScheme(scheme)) {
    return false;
  }

  std::string host_and_port = raw.substr(scheme_pos + 3);
  if (host_and_port.empty()) {
    if (!SubdomainMatchingRule::IsValidSchemeAndHost(scheme, std::string())) {
      return false;
    }
    rules_.push_back(
        std::make_unique<SubdomainMatchingRule>(scheme, std::string(), -1));
    return true;
  }

  std::string host;
  int port;
  if (!net::ParseHostAndPort(host_and_port, &host, &port) ||
      !SubdomainMatchingRule::IsValidSchemeAndHost(scheme, host)) {
    return false;
  }

  // Check if we have an <ip-address>[:port] input and try to canonicalize the
  // IP literal.
  net::IPAddress ip_address;
  if (ip_address.AssignFromIPLiteral(host)) {
    port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
    host = ip_address.ToString();
    if (ip_address.IsIPv6()) {
      host = '[' + host + ']';
    }
    rules_.push_back(
        std::make_unique<SubdomainMatchingRule>(scheme, host, port));
    return true;
  }

  port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
  rules_.push_back(std::make_unique<SubdomainMatchingRule>(scheme, host, port));
  return true;
}

bool OriginMatcher::Matches(const url::Origin& origin) const {
  GURL origin_url = origin.GetURL();
  // Since we only do kInclude vs kNoMatch, the order doesn't actually matter.
  for (const std::unique_ptr<OriginMatcherRule>& rule :
       base::Reversed(rules_)) {
    net::SchemeHostPortMatcherResult result = rule->Evaluate(origin_url);
    if (result == net::SchemeHostPortMatcherResult::kInclude) {
      return true;
    }
  }
  return false;
}

std::vector<std::string> OriginMatcher::Serialize() const {
  std::vector<std::string> result;
  result.reserve(rules_.size());
  for (const auto& rule : rules_) {
    result.push_back(rule->ToString());
  }
  return result;
}

}  // namespace origin_matcher
