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

#include "net/cookies/cookie_partition_key.h"

#include <compare>
#include <optional>
#include <ostream>
#include <tuple>

#include "base/auto_reset.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/types/optional_util.h"
#include "net/base/cronet_buildflags.h"
#include "net/base/features.h"
#include "net/base/network_isolation_partition.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"

#if !BUILDFLAG(CRONET_BUILD)
#include "mojo/public/cpp/bindings/default_construct_tag.h"
#endif

namespace net {

#if BUILDFLAG(IS_ANDROID)
bool CookiePartitionKey::g_partitioning_disabled_in_webview_ = false;
bool CookiePartitionKey::g_constructor_called_ = false;
#endif  // BUILDFLAG(IS_ANDROID)

namespace {

base::unexpected<std::string> WarnAndCreateUnexpected(
    const std::string& message) {
  DLOG(WARNING) << message;
  return base::unexpected(message);
}

std::string SerializeSchemefulSite(const SchemefulSite& site) {
  return site.GetURL().SchemeIsFile() ? site.SerializeFileSiteWithHost()
                                      : site.Serialize();
}

}  // namespace

CookiePartitionKey::SerializedCookiePartitionKey::SerializedCookiePartitionKey(
    base::PassKey<CookiePartitionKey> key,
    const std::string& site,
    bool has_cross_site_ancestor)
    : top_level_site_(site),
      has_cross_site_ancestor_(has_cross_site_ancestor) {}

const std::string&
CookiePartitionKey::SerializedCookiePartitionKey::TopLevelSite() const {
  return top_level_site_;
}

std::string CookiePartitionKey::SerializedCookiePartitionKey::GetDebugString()
    const {
  std::string out = TopLevelSite();
  base::StrAppend(
      &out, {", ", has_cross_site_ancestor() ? "cross-site" : "same-site"});
  return out;
}

#if !BUILDFLAG(CRONET_BUILD)
CookiePartitionKey::CookiePartitionKey(mojo::DefaultConstruct::Tag) {
#if BUILDFLAG(IS_ANDROID)
  g_constructor_called_ = true;
#endif  // BUILDFLAG(IS_ANDROID)
}
#endif  // !BUILDFLAG(CRONET_BUILD)
bool CookiePartitionKey::SerializedCookiePartitionKey::has_cross_site_ancestor()
    const {
  return has_cross_site_ancestor_;
}

// static
CookiePartitionKey::AncestorChainBit CookiePartitionKey::BoolToAncestorChainBit(
    bool cross_site) {
  return cross_site ? AncestorChainBit::kCrossSite
                    : AncestorChainBit::kSameSite;
}

CookiePartitionKey::CookiePartitionKey(
    const SchemefulSite& site,
    std::optional<base::UnguessableToken> nonce,
    AncestorChainBit ancestor_chain_bit)
    : site_(site), nonce_(nonce), ancestor_chain_bit_(ancestor_chain_bit) {
#if BUILDFLAG(IS_ANDROID)
  g_constructor_called_ = true;
#endif  // BUILDFLAG(IS_ANDROID)
}

CookiePartitionKey::CookiePartitionKey(bool from_script)
    : from_script_(from_script) {
#if BUILDFLAG(IS_ANDROID)
  g_constructor_called_ = true;
#endif  // BUILDFLAG(IS_ANDROID)
}

CookiePartitionKey::CookiePartitionKey(const CookiePartitionKey& other) =
    default;

CookiePartitionKey::CookiePartitionKey(CookiePartitionKey&& other) = default;

CookiePartitionKey& CookiePartitionKey::operator=(
    const CookiePartitionKey& other) = default;

CookiePartitionKey& CookiePartitionKey::operator=(CookiePartitionKey&& other) =
    default;

CookiePartitionKey::~CookiePartitionKey() = default;

bool CookiePartitionKey::operator==(const CookiePartitionKey& other) const {
  return (*this <=> other) == 0;
}

std::strong_ordering CookiePartitionKey::operator<=>(
    const CookiePartitionKey& other) const {
  if (from_script_ || other.from_script_) {
    return from_script_ <=> other.from_script_;
  }
  AncestorChainBit this_bit = GetAncestorChainBit();
  AncestorChainBit other_bit = other.GetAncestorChainBit();
  return std::tie(site_, nonce_, this_bit) <=>
         std::tie(other.site_, other.nonce_, other_bit);
}

// static
base::expected<CookiePartitionKey::SerializedCookiePartitionKey, std::string>
CookiePartitionKey::Serialize(base::optional_ref<const CookiePartitionKey> in) {
  if (!in) {
    return base::ok(SerializedCookiePartitionKey(
        base::PassKey<CookiePartitionKey>(), kEmptyCookiePartitionKey, true));
  }

  if (!in->IsSerializeable()) {
    return WarnAndCreateUnexpected("CookiePartitionKey is not serializeable");
  }

  return base::ok(SerializedCookiePartitionKey(
      base::PassKey<CookiePartitionKey>(), SerializeSchemefulSite(in->site_),
      in->IsThirdParty()));
}

std::optional<CookiePartitionKey> CookiePartitionKey::FromNetworkIsolationKey(
    const NetworkIsolationKey& network_isolation_key,
    const SiteForCookies& site_for_cookies,
    const SchemefulSite& request_site,
    bool main_frame_navigation) {
  // Support for creating a CookiePartitionKey from IsolationInfos with
  // special NetworkIsolationPartition is not implemented. The original use
  // cases for special NetworkIsolationPartitions disallow cookies.
  if (network_isolation_key.GetNetworkIsolationPartition() !=
      NetworkIsolationPartition::kGeneral) {
    return std::nullopt;
  }
#if BUILDFLAG(IS_ANDROID)
  if (g_partitioning_disabled_in_webview_) {
    return std::nullopt;
  }
#endif

  const std::optional<base::UnguessableToken>& nonce =
      network_isolation_key.GetNonce();

  // Use frame site for nonced partitions. Since the nonce is unique, this
  // still creates a unique partition key. The reason we use the frame site is
  // to align CookiePartitionKey's implementation of nonced partitions with
  // StorageKey's. See https://crbug.com/1440765.
  const std::optional<SchemefulSite>& partition_key_site =
      nonce ? network_isolation_key.GetFrameSiteForCookiePartitionKey(
                  NetworkIsolationKey::CookiePartitionKeyPassKey())
            : network_isolation_key.GetTopFrameSite();
  if (!partition_key_site) {
    return std::nullopt;
  }

  // When a main_frame_navigation occurs, the ancestor chain bit value should
  // always be kSameSite, unless there is a nonce, since a main frame has no
  // ancestor, context: crbug.com/(337206302).
  AncestorChainBit ancestor_chain_bit;
  if (nonce) {
    ancestor_chain_bit = AncestorChainBit::kCrossSite;
  } else if (main_frame_navigation) {
    ancestor_chain_bit = AncestorChainBit::kSameSite;
  } else if (site_for_cookies.IsNull()) {
    ancestor_chain_bit = AncestorChainBit::kCrossSite;
  } else {
    ancestor_chain_bit = BoolToAncestorChainBit(
        !site_for_cookies.IsFirstParty(request_site.GetURL()));
  }

  return CookiePartitionKey(*partition_key_site, nonce, ancestor_chain_bit);
}

// static
std::optional<CookiePartitionKey> CookiePartitionKey::FromStorageKeyComponents(
    const SchemefulSite& site,
    AncestorChainBit ancestor_chain_bit,
    base::optional_ref<const base::UnguessableToken> nonce) {
#if BUILDFLAG(IS_ANDROID)
  if (g_partitioning_disabled_in_webview_) {
    return std::nullopt;
  }
#endif
  return CookiePartitionKey::FromWire(site, ancestor_chain_bit,
                                      nonce.CopyAsOptional());
}

// static
base::expected<std::optional<CookiePartitionKey>, std::string>
CookiePartitionKey::FromStorage(const std::string& top_level_site,
                                bool has_cross_site_ancestor) {
  if (top_level_site == kEmptyCookiePartitionKey) {
    return base::ok(std::nullopt);
  }

  base::expected<CookiePartitionKey, std::string> key = DeserializeInternal(
      top_level_site, BoolToAncestorChainBit(has_cross_site_ancestor),
      ParsingMode::kStrict);
  if (!key.has_value()) {
    DLOG(WARNING) << key.error();
  }

  return key;
}

// static
base::expected<CookiePartitionKey, std::string>
CookiePartitionKey::FromUntrustedInput(const std::string& top_level_site,
                                       bool has_cross_site_ancestor) {
  if (top_level_site.empty()) {
    return WarnAndCreateUnexpected("top_level_site is unexpectedly empty");
  }

  base::expected<CookiePartitionKey, std::string> key = DeserializeInternal(
      top_level_site, BoolToAncestorChainBit(has_cross_site_ancestor),
      ParsingMode::kLoose);
  if (!key.has_value()) {
    return WarnAndCreateUnexpected(key.error());
  }
  return key;
}

base::expected<CookiePartitionKey, std::string>
CookiePartitionKey::DeserializeInternal(
    const std::string& top_level_site,
    CookiePartitionKey::AncestorChainBit has_cross_site_ancestor,
    CookiePartitionKey::ParsingMode parsing_mode) {
#if BUILDFLAG(IS_ANDROID)
  if (g_partitioning_disabled_in_webview_) {
    return WarnAndCreateUnexpected("Partitioned cookies are disabled");
  }
#endif

  auto schemeful_site = SchemefulSite::Deserialize(top_level_site);
  if (schemeful_site.opaque()) {
    return WarnAndCreateUnexpected(
        "Cannot deserialize opaque origin to CookiePartitionKey");
  } else if (parsing_mode == ParsingMode::kStrict &&
             SerializeSchemefulSite(schemeful_site) != top_level_site) {
    return WarnAndCreateUnexpected(
        "Cannot deserialize malformed top_level_site to CookiePartitionKey");
  }
  return base::ok(CookiePartitionKey(schemeful_site, std::nullopt,
                                     has_cross_site_ancestor));
}

bool CookiePartitionKey::IsSerializeable() const {
  // We should not try to serialize a partition key created by a renderer.
  DCHECK(!from_script_);
  return !site_.opaque() && !nonce_.has_value();
}

std::ostream& operator<<(std::ostream& os, const CookiePartitionKey& cpk) {
  os << cpk.site();
  if (cpk.nonce().has_value()) {
    os << ",nonced";
  }
  os << (cpk.IsThirdParty() ? ",cross_site" : ",same_site");
  if (cpk.from_script()) {
    os << ",from_script";
  }
  return os;
}

#if BUILDFLAG(IS_ANDROID)
// static
void CookiePartitionKey::DisablePartitioningInWebView() {
  CHECK(!g_constructor_called_);
  g_partitioning_disabled_in_webview_ = true;
}

bool CookiePartitionKey::IsPartitioningDisabledInWebView() {
  return g_partitioning_disabled_in_webview_;
}

// static
base::AutoReset<bool>
CookiePartitionKey::DisablePartitioningInScopeForTesting() {
  return base::AutoReset<bool>(&g_partitioning_disabled_in_webview_, true);
}
#endif  // BUILDFLAG(IS_ANDROID)

}  // namespace net
