// 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 "services/network/public/cpp/ip_address_space_util.h"

#include <utility>

#include "base/command_line.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/transport_info.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/parsed_headers.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_loader_network_service_observer.mojom-shared.h"
#include "url/gurl.h"

namespace network {
namespace {

using mojom::IPAddressSpace;
using net::IPAddress;
using net::IPEndPoint;
using CIDR = IPAddressSpaceOverrides::CIDR;
using AddressSpaceOverride = IPAddressSpaceOverrides::AddressSpaceOverride;

// Parses a string of the form "<URL-safe IP address>:<port>".
std::optional<IPEndPoint> ParseEndpoint(std::string_view str) {
  // Find the last colon character in `str`. We do not use
  // `base::SplitStringPiece()` because IPv6 address literals may contain colon
  // characters too.
  const auto pos = str.rfind(':');
  if (pos == str.npos) {
    return std::nullopt;
  }

  std::string_view address_str = str.substr(0, pos);

  // Skip the colon. Note that this is safe because if `pos` is not `npos`, it
  // is guaranteed to be < `str.size()`, and `substr()` accepts arguments that
  // are <= `str.size()`. In other words, if the colon character is the last in
  // `str`, then `port_str` is assigned "".
  std::string_view port_str = str.substr(pos + 1);

  IPAddress address;
  if (!net::ParseURLHostnameToAddress(address_str, &address)) {
    return std::nullopt;
  }

  // Parse to a `unsigned int`, which is guaranteed to be at least 16 bits wide.
  // See https://en.cppreference.com/w/cpp/language/types.
  unsigned port_unsigned = 0;
  if (!base::StringToUint(port_str, &port_unsigned)) {
    return std::nullopt;
  }

  if (!base::IsValueInRangeForNumericType<uint16_t>(port_unsigned)) {
    return std::nullopt;
  }

  // Use `checked_cast()` for extra safety, though this should never `CHECK()`
  // thanks to the previous call to `IsValueInRangeForNumericType()`.
  uint16_t port = base::checked_cast<uint16_t>(port_unsigned);
  return IPEndPoint(address, port);
}

// Parses an IP block specifier from CIDR notation, with the IP prefix in a
// URL-safe format (e.g. `[2001::]/16`).  Unlike ParseEndpoint, no port is
// specified.
std::optional<CIDR> ParseCIDR(std::string_view str) {
  size_t mask_bits;
  std::optional<IPAddress> ip_address =
      net::ParseCIDRBlockNonStandardURLFormat(str, &mask_bits);
  if (!ip_address) {
    return std::nullopt;
  }

  CIDR cidr(std::move(*ip_address), mask_bits);
  return cidr;
  // cidr.ip_address = std::move(ip_address);
  // return std::make_optional({std::move(*ip_address), mask_bits});
}

std::optional<IPAddressSpace> ParseIPAddressSpace(std::string_view str) {
  if (str == "public") {
    return IPAddressSpace::kPublic;
  }

  // Keep 'private' as an alias for 'local' until usages of 'private' are
  // removed from Web Platform Test code base.
  //
  // TODO(crbug.com/418737577): remove private alias after Web Platform Test
  // code base moves to using "local"
  if (str == "private") {
    return IPAddressSpace::kLocal;
  }

  if (str == "local") {
    return IPAddressSpace::kLocal;
  }

  if (str == "loopback") {
    return IPAddressSpace::kLoopback;
  }

  return std::nullopt;
}

// Parses an override from `str`, of the form "<endpoint|range>=<space>".
std::optional<AddressSpaceOverride> ParseAddressSpaceOverride(
    std::string_view str) {
  std::vector<std::string_view> tokens = base::SplitStringPiece(
      str, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  // There should be 2 parts: the CIDR/endpoint and the address space.
  if (tokens.size() != 2) {
    return std::nullopt;
  }

  std::string_view endpoint_or_cidr = tokens[0];
  std::string_view address_space = tokens[1];

  std::optional<IPAddressSpace> parsed_address_space =
      ParseIPAddressSpace(address_space);
  if (!parsed_address_space.has_value()) {
    return std::nullopt;
  }

  std::optional<IPEndPoint> parsed_endpoint = ParseEndpoint(endpoint_or_cidr);
  if (parsed_endpoint.has_value()) {
    AddressSpaceOverride result;
    result.endpoint = std::move(*parsed_endpoint);
    result.space = *parsed_address_space;
    return result;
  }

  std::optional<CIDR> parsed_cidr = ParseCIDR(endpoint_or_cidr);
  if (parsed_cidr.has_value()) {
    AddressSpaceOverride result;
    result.cidr = std::move(*parsed_cidr);
    result.space = *parsed_address_space;
    return result;
  }

  return std::nullopt;
}

std::optional<IPAddressSpace> ApplyOverrides(
    const IPEndPoint& endpoint,
    std::vector<AddressSpaceOverride>& address_space_overrides) {
  for (const auto& address_space_override : address_space_overrides) {
    if (address_space_override.endpoint) {
      if (address_space_override.endpoint == endpoint) {
        return address_space_override.space;
      }
      if ((address_space_override.endpoint->port() == 0) &&
          (address_space_override.endpoint->address() == endpoint.address())) {
        return address_space_override.space;
      }
    } else if (address_space_override.cidr) {
      // net::IPAddressMatchesPrefix will automatically convert IPv4 address to
      // IPv4-mapped IPv6 addresses if only one of the address is IPv4. To avoid
      // overrides in this case, a check is added to ensure that overrides only
      // occur when both addresses are IPv4 or both are IPv6.
      if (endpoint.address().IsIPv4() ==
              address_space_override.cidr->ip_address.IsIPv4() &&

          net::IPAddressMatchesPrefix(endpoint.address(),
                                      address_space_override.cidr->ip_address,
                                      address_space_override.cidr->mask_bits)) {
        return address_space_override.space;
      }
    }
  }

  return std::nullopt;
}

// Represents a single entry of the form: subnet -> address space.
class AddressSpaceMapEntry {
 public:
  AddressSpaceMapEntry(IPAddress prefix,
                       size_t prefix_length,
                       IPAddressSpace space)
      : prefix_(std::move(prefix)),
        prefix_length_(prefix_length),
        space_(space) {}

  // Returns the assigned address space if `address` belongs to this instance's
  // subnet. Returns nullopt otherwise.
  std::optional<IPAddressSpace> Apply(const IPAddress& address) const;

 private:
  IPAddress prefix_;
  size_t prefix_length_ = 0;
  IPAddressSpace space_ = IPAddressSpace::kUnknown;
};

std::optional<IPAddressSpace> AddressSpaceMapEntry::Apply(
    const IPAddress& address) const {
  if (net::IPAddressMatchesPrefix(address, prefix_, prefix_length_)) {
    return space_;
  }

  return std::nullopt;
}

// Maps IP addresses to IP address spaces.
class AddressSpaceMap {
 public:
  explicit AddressSpaceMap(std::vector<AddressSpaceMapEntry> entries)
      : entries_(std::move(entries)) {}

  // Applies entries in this map to `address`, in sequential order.
  // Returns the address space of the first matching entry.
  // Returns nullopt if no match is found.
  std::optional<IPAddressSpace> Apply(const IPAddress& address) const;

 private:
  std::vector<AddressSpaceMapEntry> entries_;
};

std::optional<IPAddressSpace> AddressSpaceMap::Apply(
    const IPAddress& address) const {
  std::optional<IPAddressSpace> space;

  for (const AddressSpaceMapEntry& entry : entries_) {
    space = entry.Apply(address);
    if (space.has_value()) {
      break;
    }
  }

  // If we never found a match, `space` is still `nullopt`.
  return space;
}

// Returns a map containing all default-non-public subnets.
const AddressSpaceMap& NonPublicAddressSpaceMap() {
  // For brevity below, otherwise entries do not fit on single lines.
  using Entry = AddressSpaceMapEntry;

  // Have to repeat `AddressSpaceMap` because perfect forwarding does not deal
  // well with initializer lists.
  static const base::NoDestructor<AddressSpaceMap> kMap(AddressSpaceMap({
      // IPv6 Loopback (RFC 4291): ::1/128
      Entry(IPAddress::IPv6Localhost(), 128, IPAddressSpace::kLoopback),
      // IPv6 Unique-local (RFC 4193, RFC 8190): fc00::/7
      Entry(IPAddress(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 7,
            IPAddressSpace::kLocal),
      // IPv6 Link-local unicast (RFC 4291): fe80::/10
      Entry(IPAddress(0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 10,
            IPAddressSpace::kLocal),
      // IPv4 Loopback (RFC 1122): 127.0.0.0/8
      Entry(IPAddress(127, 0, 0, 0), 8, IPAddressSpace::kLoopback),
      // IPv4 Private use (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
      Entry(IPAddress(10, 0, 0, 0), 8, IPAddressSpace::kLocal),
      Entry(IPAddress(172, 16, 0, 0), 12, IPAddressSpace::kLocal),
      Entry(IPAddress(192, 168, 0, 0), 16, IPAddressSpace::kLocal),
      // IPv4 Link-local (RFC 3927): 169.254.0.0/16
      Entry(IPAddress(169, 254, 0, 0), 16, IPAddressSpace::kLocal),
      // IPv4 Null IP (RFC 5735): 0.0.0.0/32 is "this host on this network".
      // Other addresses in 0.0.0.0/8 may refer to "specified hosts on this
      // network". This is somewhat under-defined for the purposes of assigning
      // local vs loopback address space but we assign 0.0.0.0/32 to "loopback"
      // and the rest of the block to "local". Note that this mapping can be
      // overridden by a killswitch feature flag in IPAddressToIPAddressSpace()
      // since these addresses were previously treated as public. See
      // https://crbug.com/40058874.
      Entry(IPAddress(0, 0, 0, 0), 32, IPAddressSpace::kLoopback),
      Entry(IPAddress(0, 0, 0, 0), 8, IPAddressSpace::kLocal),
      // IPv6 Null IP (RFC 1884): ::/128 is the unspecified address, but many
      // documentation sources consider it to be treated the same as 0.0.0.0/32,
      // so we map it to "loopback" out of an abundance of caution. RFC 1884
      // specifies that the unspecified address "must never be assigned to any
      // node" and "must not be used as the destination address of IPv6
      // datagrams".
      Entry(IPAddress(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 128,
            IPAddressSpace::kLoopback),
      // Carrier Grade NAT (RFC 6598): 100.64.0.0/10
      Entry(IPAddress(100, 64, 0, 0), 10, IPAddressSpace::kLocal),
      // IPv6 Documentation Address Prefixes (RFC 3849, RFC 9637): 2001:db8::/32
      // and 3fff::/20 are reserved for documrentation purposes, and they *must
      // not* be routed to the public (and in general should not be used for
      // production traffic). We include them for completeness. See
      // https://github.com/WICG/local-network-access/issues/15.
      Entry(
          IPAddress(0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
          32, IPAddressSpace::kLocal),
      Entry(IPAddress(0x3f, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 20,
            IPAddressSpace::kLocal),
      // IPv6 Site Local Unicast (RFC 3513): fec0::/10
      // These are deprecated by RFC3879 but still allowed to be used.
      // See https://github.com/WICG/local-network-access/issues/15
      Entry(IPAddress(0xfe, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 10,
            IPAddressSpace::kLocal),
  }));
  return *kMap;
}

}  // namespace

bool IsAddressSpaceOverrideValid(std::string_view str) {
  return ParseAddressSpaceOverride(str) != std::nullopt;
}

IPAddressSpace IPAddressToIPAddressSpace(const IPAddress& address) {
  return NonPublicAddressSpaceMap().Apply(address).value_or(
      IPAddressSpace::kPublic);
}

IPAddressSpace IPEndPointToIPAddressSpace(const IPEndPoint& endpoint) {
  if (!endpoint.address().IsValid()) {
    return IPAddressSpace::kUnknown;
  }

  std::optional<IPAddressSpace> space =
      IPAddressSpaceOverrides::GetInstance().HasOverride(endpoint);
  if (space.has_value()) {
    return *space;
  }

  return IPAddressToIPAddressSpace(endpoint.address());
}

std::string_view LocalNetworkAccessResultToStringPiece(
    mojom::LocalNetworkAccessResult result) {
  switch (result) {
    case mojom::LocalNetworkAccessResult::kGranted:
      return "granted";
    case mojom::LocalNetworkAccessResult::kDenied:
      return "denied";
    case mojom::LocalNetworkAccessResult::kRetryDueToCache:
      return "retryDueToCache";
  }
  // In case enum value gets corrupted.
  return "unknown";
}

std::string_view TransportTypeToStringPiece(
    mojom::TransportType transport_type) {
  switch (transport_type) {
    case mojom::TransportType::kDirect:
      return "direct";
    case mojom::TransportType::kProxied:
      return "proxied";
    case mojom::TransportType::kCached:
      return "cached";
    case mojom::TransportType::kCachedFromProxy:
      return "cachedFromProxy";
  }
  // In case enum value gets corrupted.
  return "unknown";
}

std::string_view IPAddressSpaceToStringPiece(IPAddressSpace space) {
  switch (space) {
    case IPAddressSpace::kUnknown:
      return "unknown";
    case IPAddressSpace::kPublic:
      return "public";
    case IPAddressSpace::kLocal:
      return "local";
    case IPAddressSpace::kLoopback:
      return "loopback";
  }
  // In case enum value gets corrupted.
  return "unknown";
}

IPAddressSpace TransportInfoToIPAddressSpace(const net::TransportInfo& info) {
  switch (info.type) {
    case net::TransportType::kDirect:
    case net::TransportType::kCached:
      return IPEndPointToIPAddressSpace(info.endpoint);
    case net::TransportType::kProxied:
    case net::TransportType::kCachedFromProxy:
      return mojom::IPAddressSpace::kUnknown;
  }
}

namespace {

// For comparison purposes, we treat kUnknown the same as kPublic.
IPAddressSpace CollapseUnknown(IPAddressSpace space) {
  if (space == IPAddressSpace::kUnknown) {
    return IPAddressSpace::kPublic;
  }
  return space;
}

// For comparison purposes, we treat kLocal and kLoopback as equivalent
// (kLocal arbitrarily chosen over kLoopback).
IPAddressSpace CollapseLocalAndLoopback(IPAddressSpace space) {
  if (space == IPAddressSpace::kLoopback) {
    return IPAddressSpace::kLocal;
  }
  return space;
}

}  // namespace

bool IsLessPublicAddressSpace(IPAddressSpace lhs, IPAddressSpace rhs) {
  // Apart from the special case for kUnknown, the built-in comparison operator
  // works just fine. The comment on IPAddressSpace's definition notes that the
  // enum values' ordering matters.
  return CollapseUnknown(lhs) < CollapseUnknown(rhs);
}

bool IsLessPublicAddressSpaceLNA(IPAddressSpace lhs, IPAddressSpace rhs) {
  // Similar to IsLessPublicAddressSpace but with additional collapsing of
  // kLocal and kLoopback.
  return CollapseLocalAndLoopback(CollapseUnknown(lhs)) <
         CollapseLocalAndLoopback(CollapseUnknown(rhs));
}

CalculateClientAddressSpaceParams::~CalculateClientAddressSpaceParams() =
    default;

mojom::IPAddressSpace CalculateClientAddressSpace(
    const GURL& url,
    std::optional<CalculateClientAddressSpaceParams> params) {
  if (params.has_value() &&
      params->client_address_space_inherited_from_service_worker.has_value()) {
    return *params->client_address_space_inherited_from_service_worker;
  }

  if (url.SchemeIsFile()) {
    // See: https://wicg.github.io/local-network-access/#file-urls
    return mojom::IPAddressSpace::kLoopback;
  }

  if (!params.has_value()) {
    return mojom::IPAddressSpace::kUnknown;
  }

  // First, check whether the response forces itself into a public address
  // space as per
  // https://wicg.github.io/local-network-access/#treat-as-public-address.
  DCHECK(*params->parsed_headers) << "CalculateIPAddressSpace() called for URL "
                                  << url << " with null parsed_headers.";
  if (ShouldTreatAsPublicAddress(
          (*params->parsed_headers)->content_security_policy)) {
    return mojom::IPAddressSpace::kPublic;
  }

  // Otherwise, calculate the address space via the provided IP address.
  return IPEndPointToIPAddressSpace(*params->remote_endpoint);
}

mojom::IPAddressSpace CalculateResourceAddressSpace(
    const GURL& url,
    const net::IPEndPoint& endpoint) {
  if (url.SchemeIsFile()) {
    // See: https://wicg.github.io/local-network-access/#file-urls
    return mojom::IPAddressSpace::kLoopback;
  }

  return IPEndPointToIPAddressSpace(endpoint);
}

std::optional<mojom::IPAddressSpace> GetAddressSpaceFromUrl(const GURL& url) {
  if (url.DomainIs("local")) {
    return mojom::IPAddressSpace::kLocal;
  }

  if (url.DomainIs("localhost")) {
    // Check IP address space mapping for 127.0.0.1, on the off chance that
    // there is an override remapping this to something else.
    net::IPEndPoint endpoint(net::IPAddress::IPv4Localhost(),
                             url.EffectiveIntPort());
    return IPEndPointToIPAddressSpace(endpoint);
  }

  net::IPAddress address;
  if (!address.AssignFromIPLiteral(url.HostNoBracketsPiece())) {
    return std::nullopt;
  }
  net::IPEndPoint endpoint(address, url.EffectiveIntPort());
  return IPEndPointToIPAddressSpace(endpoint);
}

IPAddressSpaceOverrides& IPAddressSpaceOverrides::GetInstance() {
  static base::NoDestructor<IPAddressSpaceOverrides> s_instance;
  return *s_instance;
}

IPAddressSpaceOverrides::IPAddressSpaceOverrides() = default;

IPAddressSpaceOverrides::AddressSpaceOverride::AddressSpaceOverride() = default;
IPAddressSpaceOverrides::AddressSpaceOverride::~AddressSpaceOverride() =
    default;

IPAddressSpaceOverrides::AddressSpaceOverride::AddressSpaceOverride(
    const AddressSpaceOverride&) = default;
IPAddressSpaceOverrides::AddressSpaceOverride&
IPAddressSpaceOverrides::AddressSpaceOverride::operator=(
    const AddressSpaceOverride& other) = default;
IPAddressSpaceOverrides::AddressSpaceOverride::AddressSpaceOverride(
    AddressSpaceOverride&&) = default;
IPAddressSpaceOverrides::AddressSpaceOverride&
IPAddressSpaceOverrides::AddressSpaceOverride::operator=(
    AddressSpaceOverride&& other) = default;

void IPAddressSpaceOverrides::SetAuxiliaryOverrides(
    const std::string& auxiliary_overrides,
    std::vector<std::string>* rejected_patterns) {
  // Ignore empty entries
  std::vector<std::string_view> tokens =
      base::SplitStringPiece(auxiliary_overrides, ",", base::TRIM_WHITESPACE,
                             base::SPLIT_WANT_NONEMPTY);

  std::vector<AddressSpaceOverride> address_space_overrides;
  std::vector<std::string> address_space_overrides_str;
  for (std::string_view token : tokens) {
    std::optional<AddressSpaceOverride> parsed =
        ParseAddressSpaceOverride(token);
    if (parsed.has_value()) {
      address_space_overrides.push_back(*std::move(parsed));
      address_space_overrides_str.emplace_back(token);
    } else {
      rejected_patterns->emplace_back(token);
    }
  }

  base::AutoLock lock(lock_);

  auxiliary_overrides_ = std::move(address_space_overrides_str);
  parsed_auxiliary_overrides_ = std::move(address_space_overrides);
}

void IPAddressSpaceOverrides::ParseCmdlineIfNeeded() {
  lock_.AssertAcquired();
  if (has_cmdline_been_parsed_) {
    return;
  }

  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  std::string cmdline_overrides_str = "";
  if (command_line.HasSwitch(switches::kIpAddressSpaceOverrides)) {
    cmdline_overrides_str =
        command_line.GetSwitchValueASCII(switches::kIpAddressSpaceOverrides);
  }

  std::vector<std::string_view> tokens =
      base::SplitStringPiece(cmdline_overrides_str, ",", base::TRIM_WHITESPACE,
                             base::SPLIT_WANT_NONEMPTY);

  for (std::string_view token : tokens) {
    std::optional<AddressSpaceOverride> parsed =
        ParseAddressSpaceOverride(token);
    if (parsed.has_value()) {
      parsed_cmdline_overrides_.push_back(*std::move(parsed));
      cmdline_overrides_.emplace_back(token);
    }
  }

  has_cmdline_been_parsed_ = true;
}

void IPAddressSpaceOverrides::ResetForTesting() {
  base::AutoLock lock(lock_);

  cmdline_overrides_.clear();
  parsed_cmdline_overrides_.clear();
  has_cmdline_been_parsed_ = false;

  auxiliary_overrides_.clear();
  parsed_auxiliary_overrides_.clear();
}

std::optional<IPAddressSpace> IPAddressSpaceOverrides::HasOverride(
    const IPEndPoint& endpoint) {
  base::AutoLock lock(lock_);
  ParseCmdlineIfNeeded();
  auto cmdline_address_space_override =
      ApplyOverrides(endpoint, parsed_cmdline_overrides_);
  if (cmdline_address_space_override) {
    return cmdline_address_space_override;
  }

  auto auxiliary_address_space_override =
      ApplyOverrides(endpoint, parsed_auxiliary_overrides_);
  if (auxiliary_address_space_override) {
    return auxiliary_address_space_override;
  }
  return std::nullopt;
}

std::vector<std::string> IPAddressSpaceOverrides::GetCurrentOverrides() {
  base::AutoLock lock(lock_);
  ParseCmdlineIfNeeded();

  std::vector<std::string> result;
  result.reserve(cmdline_overrides_.size() + auxiliary_overrides_.size());
  std::ranges::copy(cmdline_overrides_, std::back_inserter(result));
  std::ranges::copy(auxiliary_overrides_, std::back_inserter(result));
  return result;
}

}  // namespace network
