// 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.

// Portions of this code based on Mozilla:
//   (netwerk/cookie/src/nsCookieService.cpp)
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Daniel Witte (dwitte@stanford.edu)
 *   Michiel van Leeuwen (mvl@exedo.nl)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "net/cookies/canonical_cookie.h"

#include <limits>
#include <utility>

#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/base/features.h"
#include "net/base/url_util.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/parsed_cookie.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/url_canon.h"
#include "url/url_util.h"

using base::Time;

namespace net {

static constexpr int kMinutesInTwelveHours = 12 * 60;
static constexpr int kMinutesInTwentyFourHours = 24 * 60;

namespace {

// Determine the cookie domain to use for setting the specified cookie.
bool GetCookieDomain(const GURL& url,
                     const ParsedCookie& pc,
                     CookieInclusionStatus& status,
                     std::string* result) {
  std::string domain_string;
  if (pc.HasDomain())
    domain_string = pc.Domain();
  return cookie_util::GetCookieDomainWithString(url, domain_string, status,
                                                result);
}

// Compares cookies using name, domain and path, so that "equivalent" cookies
// (per RFC 2965) are equal to each other.
int PartialCookieOrdering(const CanonicalCookie& a, const CanonicalCookie& b) {
  int diff = a.Name().compare(b.Name());
  if (diff != 0)
    return diff;

  diff = a.Domain().compare(b.Domain());
  if (diff != 0)
    return diff;

  return a.Path().compare(b.Path());
}

void AppendCookieLineEntry(const CanonicalCookie& cookie,
                           std::string* cookie_line) {
  if (!cookie_line->empty())
    *cookie_line += "; ";
  // In Mozilla, if you set a cookie like "AAA", it will have an empty token
  // and a value of "AAA". When it sends the cookie back, it will send "AAA",
  // so we need to avoid sending "=AAA" for a blank token value.
  if (!cookie.Name().empty())
    *cookie_line += cookie.Name() + "=";
  *cookie_line += cookie.Value();
}

// Captures Strict -> Lax context downgrade with Strict cookie
bool IsBreakingStrictToLaxDowngrade(
    CookieOptions::SameSiteCookieContext::ContextType context,
    CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
    CookieEffectiveSameSite effective_same_site,
    bool is_cookie_being_set) {
  if (context ==
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT &&
      schemeful_context ==
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX &&
      effective_same_site == CookieEffectiveSameSite::STRICT_MODE) {
    // This downgrade only applies when a SameSite=Strict cookie is being sent.
    // A Strict -> Lax downgrade will not affect a Strict cookie which is being
    // set because it will be set in either context.
    return !is_cookie_being_set;
  }

  return false;
}

// Captures Strict -> Cross-site context downgrade with {Strict, Lax} cookie
// Captures Strict -> Lax Unsafe context downgrade with {Strict, Lax} cookie.
// This is treated as a cross-site downgrade due to the Lax Unsafe context
// behaving like cross-site.
bool IsBreakingStrictToCrossDowngrade(
    CookieOptions::SameSiteCookieContext::ContextType context,
    CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
    CookieEffectiveSameSite effective_same_site) {
  bool breaking_schemeful_context =
      schemeful_context ==
          CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE ||
      schemeful_context == CookieOptions::SameSiteCookieContext::ContextType::
                               SAME_SITE_LAX_METHOD_UNSAFE;

  bool strict_lax_enforcement =
      effective_same_site == CookieEffectiveSameSite::STRICT_MODE ||
      effective_same_site == CookieEffectiveSameSite::LAX_MODE ||
      // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of
      // our SameSite enforcement check.
      effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE;

  if (context ==
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT &&
      breaking_schemeful_context && strict_lax_enforcement) {
    return true;
  }

  return false;
}

// Captures Lax -> Cross context downgrade with {Strict, Lax} cookies.
// Ignores Lax Unsafe context.
bool IsBreakingLaxToCrossDowngrade(
    CookieOptions::SameSiteCookieContext::ContextType context,
    CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
    CookieEffectiveSameSite effective_same_site,
    bool is_cookie_being_set) {
  bool lax_enforcement =
      effective_same_site == CookieEffectiveSameSite::LAX_MODE ||
      // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of
      // our SameSite enforcement check.
      effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE;

  if (context ==
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX &&
      schemeful_context ==
          CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE) {
    // For SameSite=Strict cookies this downgrade only applies when it is being
    // set. A Lax -> Cross downgrade will not affect a Strict cookie which is
    // being sent because it wouldn't be sent in either context.
    return effective_same_site == CookieEffectiveSameSite::STRICT_MODE
               ? is_cookie_being_set
               : lax_enforcement;
  }

  return false;
}

void ApplySameSiteCookieWarningToStatus(
    CookieSameSite samesite,
    CookieEffectiveSameSite effective_samesite,
    bool is_secure,
    const CookieOptions::SameSiteCookieContext& same_site_context,
    CookieInclusionStatus* status,
    bool is_cookie_being_set) {
  if (samesite == CookieSameSite::UNSPECIFIED &&
      same_site_context.GetContextForCookieInclusion() <
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
    status->AddWarningReason(
        CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
  }
  if (effective_samesite == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE &&
      same_site_context.GetContextForCookieInclusion() ==
          CookieOptions::SameSiteCookieContext::ContextType::
              SAME_SITE_LAX_METHOD_UNSAFE) {
    // This warning is more specific so remove the previous, more general,
    // warning.
    status->RemoveWarningReason(
        CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
    status->AddWarningReason(
        CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
  }
  if (samesite == CookieSameSite::NO_RESTRICTION && !is_secure) {
    status->AddWarningReason(
        CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE);
  }

  // Add a warning if the cookie would be accessible in
  // |same_site_context|::context but not in
  // |same_site_context|::schemeful_context.
  if (IsBreakingStrictToLaxDowngrade(same_site_context.context(),
                                     same_site_context.schemeful_context(),
                                     effective_samesite, is_cookie_being_set)) {
    status->AddWarningReason(
        CookieInclusionStatus::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
  } else if (IsBreakingStrictToCrossDowngrade(
                 same_site_context.context(),
                 same_site_context.schemeful_context(), effective_samesite)) {
    // Which warning to apply depends on the SameSite value.
    if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) {
      status->AddWarningReason(
          CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
    } else {
      // LAX_MODE or LAX_MODE_ALLOW_UNSAFE.
      status->AddWarningReason(
          CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
    }

  } else if (IsBreakingLaxToCrossDowngrade(
                 same_site_context.context(),
                 same_site_context.schemeful_context(), effective_samesite,
                 is_cookie_being_set)) {
    // Which warning to apply depends on the SameSite value.
    if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) {
      status->AddWarningReason(
          CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
    } else {
      // LAX_MODE or LAX_MODE_ALLOW_UNSAFE.
      // This warning applies to both set/send.
      status->AddWarningReason(
          CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
    }
  }

  // Apply warning for whether inclusion was changed by considering redirects
  // for the SameSite context calculation. This does not look at the actual
  // inclusion or exclusion, but only at whether the inclusion differs between
  // considering redirects and not.
  using ContextDowngradeType = CookieOptions::SameSiteCookieContext::
      ContextMetadata::ContextDowngradeType;
  const auto& metadata = same_site_context.GetMetadataForCurrentSchemefulMode();
  bool apply_cross_site_redirect_downgrade_warning = false;
  switch (effective_samesite) {
    case CookieEffectiveSameSite::STRICT_MODE:
      // Strict contexts are all normalized to lax for cookie writes, so a
      // strict-to-{lax,cross} downgrade cannot occur for response cookies.
      apply_cross_site_redirect_downgrade_warning =
          is_cookie_being_set ? metadata.cross_site_redirect_downgrade ==
                                    ContextDowngradeType::kLaxToCross
                              : (metadata.cross_site_redirect_downgrade ==
                                     ContextDowngradeType::kStrictToLax ||
                                 metadata.cross_site_redirect_downgrade ==
                                     ContextDowngradeType::kStrictToCross);
      break;
    case CookieEffectiveSameSite::LAX_MODE:
    case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
      // Note that a lax-to-cross downgrade can only happen for response
      // cookies, because a laxly same-site context only happens for a safe
      // top-level cross-site request, which cannot be downgraded due to a
      // cross-site redirect to a non-top-level or unsafe cross-site request.
      apply_cross_site_redirect_downgrade_warning =
          metadata.cross_site_redirect_downgrade ==
          (is_cookie_being_set ? ContextDowngradeType::kLaxToCross
                               : ContextDowngradeType::kStrictToCross);
      break;
    default:
      break;
  }
  if (apply_cross_site_redirect_downgrade_warning) {
    status->AddWarningReason(
        CookieInclusionStatus::
            WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
  }

  // If there are reasons to exclude the cookie other than SameSite, don't warn
  // about the cookie at all.
  status->MaybeClearSameSiteWarning();
}

// Converts CookieSameSite to CookieSameSiteForMetrics by adding 1 to it.
CookieSameSiteForMetrics CookieSameSiteToCookieSameSiteForMetrics(
    CookieSameSite enum_in) {
  return static_cast<CookieSameSiteForMetrics>((static_cast<int>(enum_in) + 1));
}

// Checks if `port` is within [0,65535] or url::PORT_UNSPECIFIED. Returns `port`
// if so and url::PORT_INVALID otherwise.
int ValidateAndAdjustSourcePort(int port) {
  if ((port >= 0 && port <= 65535) || port == url::PORT_UNSPECIFIED) {
    // 0 would be really weird as it has a special meaning, but it's still
    // technically a valid tcp/ip port so we're going to accept it here.
    return port;
  }

  return url::PORT_INVALID;
}

// Tests that a cookie has the attributes for a valid __Host- prefix without
// testing that the prefix is in the cookie name.
bool HasValidHostPrefixAttributes(const GURL& url,
                                  bool secure,
                                  const std::string& domain,
                                  const std::string& path) {
  if (!secure || !url.SchemeIsCryptographic() || path != "/")
    return false;
  return domain.empty() || (url.HostIsIPAddress() && url.host() == domain);
}

}  // namespace

CookieAccessParams::CookieAccessParams(CookieAccessSemantics access_semantics,
                                       bool delegate_treats_url_as_trustworthy,
                                       CookieSamePartyStatus same_party_status)
    : access_semantics(access_semantics),
      delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy),
      same_party_status(same_party_status) {}

CanonicalCookie::CanonicalCookie() = default;

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

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

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

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

CanonicalCookie::CanonicalCookie(
    base::PassKey<CanonicalCookie> pass_key,
    std::string name,
    std::string value,
    std::string domain,
    std::string path,
    base::Time creation,
    base::Time expiration,
    base::Time last_access,
    base::Time last_update,
    bool secure,
    bool httponly,
    CookieSameSite same_site,
    CookiePriority priority,
    bool same_party,
    absl::optional<CookiePartitionKey> partition_key,
    CookieSourceScheme source_scheme,
    int source_port)
    : name_(std::move(name)),
      value_(std::move(value)),
      domain_(std::move(domain)),
      path_(std::move(path)),
      creation_date_(creation),
      expiry_date_(expiration),
      last_access_date_(last_access),
      last_update_date_(last_update),
      secure_(secure),
      httponly_(httponly),
      same_site_(same_site),
      priority_(priority),
      same_party_(same_party),
      partition_key_(std::move(partition_key)),
      source_scheme_(source_scheme),
      source_port_(source_port) {}

CanonicalCookie::~CanonicalCookie() = default;

// static
std::string CanonicalCookie::CanonPathWithString(
    const GURL& url,
    const std::string& path_string) {
  // The path was supplied in the cookie, we'll take it.
  if (!path_string.empty() && path_string[0] == '/')
    return path_string;

  // The path was not supplied in the cookie or invalid, we will default
  // to the current URL path.
  // """Defaults to the path of the request URL that generated the
  //    Set-Cookie response, up to, but not including, the
  //    right-most /."""
  // How would this work for a cookie on /?  We will include it then.
  const std::string& url_path = url.path();

  size_t idx = url_path.find_last_of('/');

  // The cookie path was invalid or a single '/'.
  if (idx == 0 || idx == std::string::npos)
    return std::string("/");

  // Return up to the rightmost '/'.
  return url_path.substr(0, idx);
}

// static
Time CanonicalCookie::ParseExpiration(const ParsedCookie& pc,
                                      const Time& current,
                                      const Time& server_time) {
  // First, try the Max-Age attribute.
  if (pc.HasMaxAge()) {
    int64_t max_age = 0;
    // Use the output if StringToInt64 returns true ("perfect" conversion). This
    // case excludes overflow/underflow, leading/trailing whitespace, non-number
    // strings, and empty string. (ParsedCookie trims whitespace.)
    if (base::StringToInt64(pc.MaxAge(), &max_age)) {
      // RFC 6265bis algorithm for parsing Max-Age:
      // "If delta-seconds is less than or equal to zero (0), let expiry-
      // time be the earliest representable date and time. ... "
      if (max_age <= 0)
        return Time::Min();
      // "... Otherwise, let the expiry-time be the current date and time plus
      // delta-seconds seconds."
      return current + base::Seconds(max_age);
    } else {
      // If the conversion wasn't perfect, but the best-effort conversion
      // resulted in an overflow/underflow, use the min/max representable time.
      // (This is alluded to in the spec, which says the user agent MAY clip an
      // Expires attribute to a saturated time. We'll do the same for Max-Age.)
      if (max_age == std::numeric_limits<int64_t>::min())
        return Time::Min();
      if (max_age == std::numeric_limits<int64_t>::max())
        return Time::Max();
    }
  }

  // Try the Expires attribute.
  if (pc.HasExpires() && !pc.Expires().empty()) {
    // Adjust for clock skew between server and host.
    Time parsed_expiry = cookie_util::ParseCookieExpirationTime(pc.Expires());
    if (!parsed_expiry.is_null()) {
      // Record metrics related to prevalence of clock skew.
      base::TimeDelta clock_skew = (current - server_time);
      // Record the magnitude (absolute value) of the skew in minutes.
      int clock_skew_magnitude = clock_skew.magnitude().InMinutes();
      // Determine the new expiry with clock skew factored in.
      Time adjusted_expiry = parsed_expiry + (current - server_time);
      if (clock_skew.is_positive() || clock_skew.is_zero()) {
        UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes",
                                    clock_skew_magnitude, 1,
                                    kMinutesInTwelveHours, 100);
        UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes12To24Hours",
                                    clock_skew_magnitude, kMinutesInTwelveHours,
                                    kMinutesInTwentyFourHours, 100);
        // Also record the range of minutes added that allowed the cookie to
        // avoid expiring immediately.
        if (parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now()) {
          UMA_HISTOGRAM_CUSTOM_COUNTS(
              "Cookie.ClockSkew.WithoutAddMinutesExpires", clock_skew_magnitude,
              1, kMinutesInTwentyFourHours, 100);
        }
      } else if (clock_skew.is_negative()) {
        // These histograms only support positive numbers, so negative skews
        // will be converted to positive (via magnitude) before recording.
        UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.SubtractMinutes",
                                    clock_skew_magnitude, 1,
                                    kMinutesInTwelveHours, 100);
        UMA_HISTOGRAM_CUSTOM_COUNTS(
            "Cookie.ClockSkew.SubtractMinutes12To24Hours", clock_skew_magnitude,
            kMinutesInTwelveHours, kMinutesInTwentyFourHours, 100);
      }
      // Record if we were going to expire the cookie before we added the clock
      // skew.
      UMA_HISTOGRAM_BOOLEAN(
          "Cookie.ClockSkew.ExpiredWithoutSkew",
          parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now());
      return adjusted_expiry;
    }
  }

  // Invalid or no expiration, session cookie.
  return Time();
}

// static
base::Time CanonicalCookie::ValidateAndAdjustExpiryDate(
    const base::Time& expiry_date,
    const base::Time& creation_date) {
  if (expiry_date.is_null())
    return expiry_date;
  base::Time fixed_creation_date = creation_date;
  if (fixed_creation_date.is_null()) {
    // TODO(crbug.com/1264458): Push this logic into
    // CanonicalCookie::CreateSanitizedCookie. The four sites that call it
    // with a null `creation_date` (CanonicalCookie::Create cannot be called
    // this way) are:
    // * GaiaCookieManagerService::ForceOnCookieChangeProcessing
    // * CookiesSetFunction::Run
    // * cookie_store.cc::ToCanonicalCookie
    // * network_handler.cc::MakeCookieFromProtocolValues
    fixed_creation_date = base::Time::Now();
  }
  if (base::FeatureList::IsEnabled(features::kClampCookieExpiryTo400Days)) {
    base::Time maximum_expiry_date = fixed_creation_date + base::Days(400);
    if (expiry_date > maximum_expiry_date)
      return maximum_expiry_date;
  }
  return expiry_date;
}

// static
std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
    const GURL& url,
    const std::string& cookie_line,
    const base::Time& creation_time,
    absl::optional<base::Time> server_time,
    absl::optional<CookiePartitionKey> cookie_partition_key,
    CookieInclusionStatus* status) {
  // Put a pointer on the stack so the rest of the function can assign to it if
  // the default nullptr is passed in.
  CookieInclusionStatus blank_status;
  if (status == nullptr) {
    status = &blank_status;
  }
  *status = CookieInclusionStatus();

  // Check the URL; it may be nonsense since some platform APIs may permit
  // it to be specified directly.
  if (!url.is_valid()) {
    status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
    return nullptr;
  }

  ParsedCookie parsed_cookie(cookie_line, status);

  // We record this metric before checking validity because the presence of an
  // HTAB will invalidate the ParsedCookie.
  UMA_HISTOGRAM_BOOLEAN("Cookie.NameOrValueHtab",
                        parsed_cookie.HasInternalHtab());

  if (!parsed_cookie.IsValid()) {
    DVLOG(net::cookie_util::kVlogSetCookies)
        << "WARNING: Couldn't parse cookie";
    DCHECK(!status->IsInclude());
    // Don't continue, because an invalid ParsedCookie doesn't have any
    // attributes.
    // TODO(chlily): Log metrics.
    return nullptr;
  }

  // Record warning for non-ASCII octecs in the Domain attribute.
  // This should lead to rejection of the cookie in the future.
  UMA_HISTOGRAM_BOOLEAN("Cookie.DomainHasNonASCII",
                        parsed_cookie.HasDomain() &&
                            !base::IsStringASCII(parsed_cookie.Domain()));

  std::string cookie_domain;
  if (!GetCookieDomain(url, parsed_cookie, *status, &cookie_domain)) {
    DVLOG(net::cookie_util::kVlogSetCookies)
        << "Create() failed to get a valid cookie domain";
    status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
  }

  std::string cookie_path = CanonPathWithString(
      url, parsed_cookie.HasPath() ? parsed_cookie.Path() : std::string());

  Time cookie_server_time(creation_time);
  if (server_time.has_value() && !server_time->is_null())
    cookie_server_time = server_time.value();

  DCHECK(!creation_time.is_null());
  Time cookie_expires = CanonicalCookie::ParseExpiration(
      parsed_cookie, creation_time, cookie_server_time);
  cookie_expires = ValidateAndAdjustExpiryDate(cookie_expires, creation_time);

  CookiePrefix prefix_case_sensitive =
      GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/false);
  CookiePrefix prefix_case_insensitive =
      GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/true);

  bool is_sensitive_prefix_valid =
      IsCookiePrefixValid(prefix_case_sensitive, url, parsed_cookie);
  bool is_insensitive_prefix_valid =
      IsCookiePrefixValid(prefix_case_insensitive, url, parsed_cookie);
  bool is_cookie_prefix_valid =
      base::FeatureList::IsEnabled(net::features::kCaseInsensitiveCookiePrefix)
          ? is_insensitive_prefix_valid
          : is_sensitive_prefix_valid;

  RecordCookiePrefixMetrics(prefix_case_sensitive, prefix_case_insensitive,
                            is_insensitive_prefix_valid);

  if (parsed_cookie.Name() == "") {
    is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value());
  }

  if (!is_cookie_prefix_valid) {
    DVLOG(net::cookie_util::kVlogSetCookies)
        << "Create() failed because the cookie violated prefix rules.";
    status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
  }

  bool is_same_party_valid = IsCookieSamePartyValid(parsed_cookie);
  if (!is_same_party_valid) {
    status->AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY);
  }

  bool partition_has_nonce = CookiePartitionKey::HasNonce(cookie_partition_key);
  bool is_partitioned_valid =
      IsCookiePartitionedValid(url, parsed_cookie, partition_has_nonce);
  if (!is_partitioned_valid) {
    status->AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
  }

  // Collect metrics on whether usage of the Partitioned attribute is correct.
  // Do not include implicit nonce-based partitioned cookies in these metrics.
  if (parsed_cookie.IsPartitioned()) {
    if (!partition_has_nonce)
      UMA_HISTOGRAM_BOOLEAN("Cookie.IsPartitionedValid", is_partitioned_valid);
  } else if (!partition_has_nonce) {
    cookie_partition_key = absl::nullopt;
  }

  if (!status->IsInclude())
    return nullptr;

  CookieSameSiteString samesite_string = CookieSameSiteString::kUnspecified;
  CookieSameSite samesite = parsed_cookie.SameSite(&samesite_string);

  // The next two sections set the source_scheme_ and source_port_. Normally
  // these are taken directly from the url's scheme and port but if the url
  // setting this cookie is considered a trustworthy origin then we may make
  // some modifications. Note that here we assume that a trustworthy url must
  // have a non-secure scheme (http). Since we can't know at this point if a url
  // is trustworthy or not, we'll assume it is if the cookie is set with the
  // `Secure` attribute.
  //
  // For both convenience and to try to match expectations, cookies that have
  // the `Secure` attribute are modified to look like they were created by a
  // secure url. This is helpful because this cookie can be treated like any
  // other secure cookie when we're retrieving them and helps to prevent the
  // cookie from getting "trapped" if the url loses trustworthiness.

  CookieSourceScheme source_scheme;
  if (parsed_cookie.IsSecure() || url.SchemeIsCryptographic()) {
    // It's possible that a trustworthy origin is setting this cookie with the
    // `Secure` attribute even if the url's scheme isn't secure. In that case
    // we'll act like it was a secure scheme. This cookie will be rejected later
    // if the url isn't allowed to access secure cookies so this isn't a
    // problem.
    source_scheme = CookieSourceScheme::kSecure;

    if (!url.SchemeIsCryptographic()) {
      status->AddWarningReason(
          CookieInclusionStatus::
              WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
    }
  } else {
    source_scheme = CookieSourceScheme::kNonSecure;
  }

  // Get the port, this will get a default value if a port isn't explicitly
  // provided. Similar to the source scheme, it's possible that a trustworthy
  // origin is setting this cookie with the `Secure` attribute even if the url's
  // scheme isn't secure. This function will return 443 to pretend like this
  // cookie was set by a secure scheme.
  int source_port = CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
      url, parsed_cookie.IsSecure());

  auto cc = std::make_unique<CanonicalCookie>(
      base::PassKey<CanonicalCookie>(), parsed_cookie.Name(),
      parsed_cookie.Value(), cookie_domain, cookie_path, creation_time,
      cookie_expires, creation_time,
      /*last_update=*/base::Time::Now(), parsed_cookie.IsSecure(),
      parsed_cookie.IsHttpOnly(), samesite, parsed_cookie.Priority(),
      parsed_cookie.IsSameParty(), cookie_partition_key, source_scheme,
      source_port);

  // TODO(chlily): Log metrics.
  if (!cc->IsCanonical()) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
    return nullptr;
  }

  RecordCookieSameSiteAttributeValueHistogram(samesite_string);

  // These metrics capture whether or not a cookie has a Non-ASCII character in
  // it.
  UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Name",
                        !base::IsStringASCII(cc->Name()));
  UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Value",
                        !base::IsStringASCII(cc->Value()));

  // Check for "__" prefixed names, excluding the cookie prefixes.
  bool name_prefixed_with_underscores =
      (prefix_case_insensitive == CanonicalCookie::COOKIE_PREFIX_NONE) &&
      base::StartsWith(parsed_cookie.Name(), "__");

  UMA_HISTOGRAM_BOOLEAN("Cookie.DoubleUnderscorePrefixedName",
                        name_prefixed_with_underscores);

  UMA_HISTOGRAM_ENUMERATION(
      "Cookie.TruncatingCharacterInCookieString",
      parsed_cookie.GetTruncatingCharacterInCookieStringType());

  return cc;
}

// static
std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie(
    const GURL& url,
    const std::string& name,
    const std::string& value,
    const std::string& domain,
    const std::string& path,
    base::Time creation_time,
    base::Time expiration_time,
    base::Time last_access_time,
    bool secure,
    bool http_only,
    CookieSameSite same_site,
    CookiePriority priority,
    bool same_party,
    absl::optional<CookiePartitionKey> partition_key,
    CookieInclusionStatus* status) {
  // Put a pointer on the stack so the rest of the function can assign to it if
  // the default nullptr is passed in.
  CookieInclusionStatus blank_status;
  if (status == nullptr) {
    status = &blank_status;
  }
  *status = CookieInclusionStatus();

  // Validate consistency of passed arguments.
  if (ParsedCookie::ParseTokenString(name) != name) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  } else if (ParsedCookie::ParseValueString(value) != value) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  } else if (ParsedCookie::ParseValueString(path) != path) {
    // NOTE: If `path` contains  "terminating characters" ('\r', '\n', and
    // '\0'), ';', or leading / trailing whitespace, path will be rejected,
    // but any other control characters will just get URL-encoded below.
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  }

  // Validate name and value against character set and size limit constraints.
  // If IsValidCookieNameValuePair identifies that `name` and/or `value` are
  // invalid, it will add an ExclusionReason to `status`.
  ParsedCookie::IsValidCookieNameValuePair(name, value, status);

  // Validate domain against character set and size limit constraints.
  bool domain_is_valid = true;

  if ((ParsedCookie::ParseValueString(domain) != domain)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
    domain_is_valid = false;
  }

  if (!ParsedCookie::CookieAttributeValueHasValidCharSet(domain)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
    domain_is_valid = false;
  }
  if (!ParsedCookie::CookieAttributeValueHasValidSize(domain)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
    domain_is_valid = false;
  }
  const std::string& domain_attribute =
      domain_is_valid ? domain : std::string();

  std::string cookie_domain;
  // This validation step must happen before GetCookieDomainWithString, so it
  // doesn't fail DCHECKs.
  if (!cookie_util::DomainIsHostOnly(url.host())) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
  } else if (!cookie_util::GetCookieDomainWithString(url, domain_attribute,
                                                     *status, &cookie_domain)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
  }

  // The next two sections set the source_scheme_ and source_port_. Normally
  // these are taken directly from the url's scheme and port but if the url
  // setting this cookie is considered a trustworthy origin then we may make
  // some modifications. Note that here we assume that a trustworthy url must
  // have a non-secure scheme (http). Since we can't know at this point if a url
  // is trustworthy or not, we'll assume it is if the cookie is set with the
  // `Secure` attribute.
  //
  // For both convenience and to try to match expectations, cookies that have
  // the `Secure` attribute are modified to look like they were created by a
  // secure url. This is helpful because this cookie can be treated like any
  // other secure cookie when we're retrieving them and helps to prevent the
  // cookie from getting "trapped" if the url loses trustworthiness.

  CookieSourceScheme source_scheme = CookieSourceScheme::kNonSecure;
  // This validation step must happen before SchemeIsCryptographic, so it
  // doesn't fail DCHECKs.
  if (!url.is_valid()) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
  } else {
    // It's possible that a trustworthy origin is setting this cookie with the
    // `Secure` attribute even if the url's scheme isn't secure. In that case
    // we'll act like it was a secure scheme. This cookie will be rejected later
    // if the url isn't allowed to access secure cookies so this isn't a
    // problem.
    source_scheme = (secure || url.SchemeIsCryptographic())
                        ? CookieSourceScheme::kSecure
                        : CookieSourceScheme::kNonSecure;

    if (source_scheme == CookieSourceScheme::kSecure &&
        !url.SchemeIsCryptographic()) {
      status->AddWarningReason(
          CookieInclusionStatus::
              WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
    }
  }

  // Get the port, this will get a default value if a port isn't explicitly
  // provided. Similar to the source scheme, it's possible that a trustworthy
  // origin is setting this cookie with the `Secure` attribute even if the url's
  // scheme isn't secure. This function will return 443 to pretend like this
  // cookie was set by a secure scheme.
  int source_port =
      CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(url, secure);

  std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
  // Canonicalize path again to make sure it escapes characters as needed.
  url::Component path_component(0, cookie_path.length());
  url::RawCanonOutputT<char> canon_path;
  url::Component canon_path_component;
  url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
                        &canon_path_component);
  std::string encoded_cookie_path = std::string(
      canon_path.data() + canon_path_component.begin, canon_path_component.len);

  if (!path.empty()) {
    if (cookie_path != path) {
      // The path attribute was specified and found to be invalid, so record an
      // error.
      status->AddExclusionReason(
          net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
    } else if (!ParsedCookie::CookieAttributeValueHasValidSize(
                   encoded_cookie_path)) {
      // The path attribute was specified and encodes into a value that's longer
      // than the length limit, so record an error.
      status->AddExclusionReason(
          net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
    }
  }

  CookiePrefix prefix = GetCookiePrefix(name);
  if (!IsCookiePrefixValid(prefix, url, secure, domain_attribute,
                           cookie_path)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
  }

  if (name == "" && HasHiddenPrefixName(value)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
  }

  if (!IsCookieSamePartyValid(same_party, secure, same_site)) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY);
  }
  if (!IsCookiePartitionedValid(url, secure,
                                /*is_partitioned=*/partition_key.has_value(),
                                /*partition_has_nonce=*/
                                CookiePartitionKey::HasNonce(partition_key))) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
  }

  if (!last_access_time.is_null() && creation_time.is_null()) {
    status->AddExclusionReason(
        net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
  }
  expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time);

  if (!status->IsInclude())
    return nullptr;

  auto cc = std::make_unique<CanonicalCookie>(
      base::PassKey<CanonicalCookie>(), name, value, cookie_domain,
      encoded_cookie_path, creation_time, expiration_time, last_access_time,
      /*last_update=*/base::Time::Now(), secure, http_only, same_site, priority,
      same_party, partition_key, source_scheme, source_port);
  DCHECK(cc->IsCanonical());

  return cc;
}

// static
std::unique_ptr<CanonicalCookie> CanonicalCookie::FromStorage(
    std::string name,
    std::string value,
    std::string domain,
    std::string path,
    base::Time creation,
    base::Time expiration,
    base::Time last_access,
    base::Time last_update,
    bool secure,
    bool httponly,
    CookieSameSite same_site,
    CookiePriority priority,
    bool same_party,
    absl::optional<CookiePartitionKey> partition_key,
    CookieSourceScheme source_scheme,
    int source_port) {
  // We check source_port here because it could have concievably been
  // corrupted and changed to out of range. Eventually this would be caught by
  // IsCanonical*() but since the source_port is only used by metrics so far
  // nothing else checks it. So let's normalize it here and then update this
  // method when origin-bound cookies is implemented.
  // TODO(crbug.com/1170548)
  int validated_port = ValidateAndAdjustSourcePort(source_port);

  auto cc = std::make_unique<CanonicalCookie>(
      base::PassKey<CanonicalCookie>(), std::move(name), std::move(value),
      std::move(domain), std::move(path), creation, expiration, last_access,
      last_update, secure, httponly, same_site, priority, same_party,
      partition_key, source_scheme, validated_port);

  if (cc->IsCanonicalForFromStorage()) {
    // This will help capture the number of times a cookie is canonical but does
    // not have a valid name+value size length
    bool valid_cookie_name_value_pair =
        ParsedCookie::IsValidCookieNameValuePair(cc->Name(), cc->Value());
    UMA_HISTOGRAM_BOOLEAN("Cookie.FromStorageWithValidLength",
                          valid_cookie_name_value_pair);
  } else {
    return nullptr;
  }
  return cc;
}

// static
std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateUnsafeCookieForTesting(
    const std::string& name,
    const std::string& value,
    const std::string& domain,
    const std::string& path,
    const base::Time& creation,
    const base::Time& expiration,
    const base::Time& last_access,
    const base::Time& last_update,
    bool secure,
    bool httponly,
    CookieSameSite same_site,
    CookiePriority priority,
    bool same_party,
    absl::optional<CookiePartitionKey> partition_key,
    CookieSourceScheme source_scheme,
    int source_port) {
  return std::make_unique<CanonicalCookie>(
      base::PassKey<CanonicalCookie>(), name, value, domain, path, creation,
      expiration, last_access, last_update, secure, httponly, same_site,
      priority, same_party, partition_key, source_scheme, source_port);
}

std::string CanonicalCookie::DomainWithoutDot() const {
  return cookie_util::CookieDomainAsHost(domain_);
}

void CanonicalCookie::SetSourcePort(int port) {
  source_port_ = ValidateAndAdjustSourcePort(port);
}

bool CanonicalCookie::IsEquivalentForSecureCookieMatching(
    const CanonicalCookie& secure_cookie) const {
  // Partition keys must both be equivalent.
  bool same_partition_key = PartitionKey() == secure_cookie.PartitionKey();

  // Names must be the same
  bool same_name = name_ == secure_cookie.Name();

  // They should domain-match in one direction or the other. (See RFC 6265bis
  // section 5.1.3.)
  // TODO(chlily): This does not check for the IP address case. This is bad due
  // to https://crbug.com/1069935.
  bool domain_match =
      IsSubdomainOf(DomainWithoutDot(), secure_cookie.DomainWithoutDot()) ||
      IsSubdomainOf(secure_cookie.DomainWithoutDot(), DomainWithoutDot());

  bool path_match = secure_cookie.IsOnPath(Path());

  bool equivalent_for_secure_cookie_matching =
      same_partition_key && same_name && domain_match && path_match;

  // IsEquivalent() is a stricter check than this.
  DCHECK(!IsEquivalent(secure_cookie) || equivalent_for_secure_cookie_matching);

  return equivalent_for_secure_cookie_matching;
}

bool CanonicalCookie::IsOnPath(const std::string& url_path) const {
  return cookie_util::IsOnPath(path_, url_path);
}

bool CanonicalCookie::IsDomainMatch(const std::string& host) const {
  return cookie_util::IsDomainMatch(domain_, host);
}

CookieAccessResult CanonicalCookie::IncludeForRequestURL(
    const GURL& url,
    const CookieOptions& options,
    const CookieAccessParams& params) const {
  CookieInclusionStatus status;
  // Filter out HttpOnly cookies, per options.
  if (options.exclude_httponly() && IsHttpOnly())
    status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_HTTP_ONLY);
  // Secure cookies should not be included in requests for URLs with an
  // insecure scheme, unless it is a localhost url, or the CookieAccessDelegate
  // otherwise denotes them as trustworthy
  // (`delegate_treats_url_as_trustworthy`).
  bool is_allowed_to_access_secure_cookies = false;
  CookieAccessScheme cookie_access_scheme =
      cookie_util::ProvisionalAccessScheme(url);
  if (cookie_access_scheme == CookieAccessScheme::kNonCryptographic &&
      params.delegate_treats_url_as_trustworthy) {
    cookie_access_scheme = CookieAccessScheme::kTrustworthy;
  }
  switch (cookie_access_scheme) {
    case CookieAccessScheme::kNonCryptographic:
      if (IsSecure())
        status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SECURE_ONLY);
      break;
    case CookieAccessScheme::kTrustworthy:
      is_allowed_to_access_secure_cookies = true;
      if (IsSecure() || (cookie_util::IsSchemeBoundCookiesEnabled() &&
                         source_scheme_ == CookieSourceScheme::kSecure)) {
        status.AddWarningReason(
            CookieInclusionStatus::
                WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC);
      }
      break;
    case CookieAccessScheme::kCryptographic:
      is_allowed_to_access_secure_cookies = true;
      break;
  }

  // For the following two sections we're checking to see if a cookie's
  // `source_scheme_` and `source_port_` match that of the url's. In most cases
  // this is a direct comparison but it does get a bit more complicated when
  // trustworthy origins are taken into accounts. Note that here, a kTrustworthy
  // url must have a non-secure scheme (http) because otherwise it'd be a
  // kCryptographic url.
  //
  // Trustworthy origins are allowed to both secure and non-secure cookies. This
  // means that we'll match source_scheme_ for both their usual kNonSecure as
  // well as KSecure. For source_port_ we'll match per usual as well as any 443
  // ports, since those are the default values for secure cookies and we still
  // want to be able to access them.

  // A cookie with a source scheme of kSecure shouldn't be accessible by
  // kNonCryptographic urls. But we can skip adding a status if the cookie is
  // already blocked due to the `Secure` attribute.
  if (source_scheme_ == CookieSourceScheme::kSecure &&
      cookie_access_scheme == CookieAccessScheme::kNonCryptographic &&
      !status.HasExclusionReason(CookieInclusionStatus::EXCLUDE_SECURE_ONLY)) {
    if (cookie_util::IsSchemeBoundCookiesEnabled()) {
      status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SCHEME_MISMATCH);
    } else {
      status.AddWarningReason(CookieInclusionStatus::WARN_SCHEME_MISMATCH);
    }
  }
  // A cookie with a source scheme of kNonSecure shouldn't be accessible by
  // kCryptographic urls.
  else if (source_scheme_ == CookieSourceScheme::kNonSecure &&
           cookie_access_scheme == CookieAccessScheme::kCryptographic) {
    if (cookie_util::IsSchemeBoundCookiesEnabled()) {
      status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SCHEME_MISMATCH);
    } else {
      status.AddWarningReason(CookieInclusionStatus::WARN_SCHEME_MISMATCH);
    }
  }
  // Else, the cookie has a source scheme of kUnset or the access scheme is
  // kTrustworthy. Neither of which will block the cookie.

  int url_port = url.EffectiveIntPort();
  CHECK(url_port != url::PORT_INVALID);
  // The cookie's source port either must match the url's port, be
  // PORT_UNSPECIFIED, or the cookie must be a domain cookie.
  bool port_matches = url_port == source_port_ ||
                      source_port_ == url::PORT_UNSPECIFIED || IsDomainCookie();

  // Or if the url is trustworthy, we'll also match 443 (in order to get secure
  // cookies).
  bool trustworthy_and_443 =
      cookie_access_scheme == CookieAccessScheme::kTrustworthy &&
      source_port_ == 443;
  if (!port_matches && !trustworthy_and_443) {
    if (cookie_util::IsPortBoundCookiesEnabled()) {
      status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_PORT_MISMATCH);
    } else {
      status.AddWarningReason(CookieInclusionStatus::WARN_PORT_MISMATCH);
    }
  }

  // Don't include cookies for requests that don't apply to the cookie domain.
  if (!IsDomainMatch(url.host()))
    status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_DOMAIN_MISMATCH);
  // Don't include cookies for requests with a url path that does not path
  // match the cookie-path.
  if (!IsOnPath(url.path()))
    status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_NOT_ON_PATH);

  // For LEGACY cookies we should always return the schemeless context,
  // otherwise let GetContextForCookieInclusion() decide.
  CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context =
      params.access_semantics == CookieAccessSemantics::LEGACY
          ? options.same_site_cookie_context().context()
          : options.same_site_cookie_context().GetContextForCookieInclusion();

  // Don't include same-site cookies for cross-site requests.
  CookieEffectiveSameSite effective_same_site =
      GetEffectiveSameSite(params.access_semantics);
  DCHECK(effective_same_site != CookieEffectiveSameSite::UNDEFINED);
  UMA_HISTOGRAM_ENUMERATION(
      "Cookie.RequestSameSiteContext", cookie_inclusion_context,
      CookieOptions::SameSiteCookieContext::ContextType::COUNT);

  switch (effective_same_site) {
    case CookieEffectiveSameSite::STRICT_MODE:
      if (cookie_inclusion_context <
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT) {
        status.AddExclusionReason(
            CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT);
      }
      break;
    case CookieEffectiveSameSite::LAX_MODE:
      if (cookie_inclusion_context <
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
        status.AddExclusionReason(
            (SameSite() == CookieSameSite::UNSPECIFIED)
                ? CookieInclusionStatus::
                      EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX
                : CookieInclusionStatus::EXCLUDE_SAMESITE_LAX);
      }
      break;
    // TODO(crbug.com/990439): Add a browsertest for this behavior.
    case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
      DCHECK(SameSite() == CookieSameSite::UNSPECIFIED);
      if (cookie_inclusion_context <
          CookieOptions::SameSiteCookieContext::ContextType::
              SAME_SITE_LAX_METHOD_UNSAFE) {
        // TODO(chlily): Do we need a separate CookieInclusionStatus for this?
        status.AddExclusionReason(
            CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
      }
      break;
    default:
      break;
  }

  // Unless legacy access semantics are in effect, SameSite=None cookies without
  // the Secure attribute should be ignored. This can apply to cookies which
  // were created before "SameSite=None requires Secure" was enabled (as
  // SameSite=None insecure cookies cannot be set while the options are on).
  if (params.access_semantics != CookieAccessSemantics::LEGACY &&
      SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) {
    status.AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
  }

  switch (params.same_party_status) {
    case CookieSamePartyStatus::kEnforceSamePartyExclude:
      DCHECK(IsSameParty());
      status.AddExclusionReason(
          CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT);
      [[fallthrough]];
    case CookieSamePartyStatus::kEnforceSamePartyInclude: {
      status.AddWarningReason(CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY);
      // Remove any SameSite exclusion reasons, since SameParty overrides
      // SameSite.
      DCHECK(!status.HasExclusionReason(
          CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT));
      DCHECK_NE(effective_same_site, CookieEffectiveSameSite::STRICT_MODE);
      bool included_by_samesite =
          !status.HasExclusionReason(
              CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) &&
          !status.HasExclusionReason(
              CookieInclusionStatus::
                  EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
      if (!included_by_samesite) {
        status.RemoveExclusionReasons({
            CookieInclusionStatus::EXCLUDE_SAMESITE_LAX,
            CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
        });
      }

      // Update metrics.
      if (status.HasOnlyExclusionReason(
              CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) &&
          included_by_samesite) {
        status.AddWarningReason(
            CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE);
      }
      if (status.IsInclude()) {
        if (!included_by_samesite) {
          status.AddWarningReason(
              CookieInclusionStatus::
                  WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE);
        }
      }
      break;
    }
    case CookieSamePartyStatus::kNoSamePartyEnforcement:
      // Only apply SameSite-related warnings if SameParty is not in effect.
      ApplySameSiteCookieWarningToStatus(
          SameSite(), effective_same_site, IsSecure(),
          options.same_site_cookie_context(), &status,
          false /* is_cookie_being_set */);
      break;
  }

  if (status.IsInclude()) {
    UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedRequestEffectiveSameSite",
                              effective_same_site,
                              CookieEffectiveSameSite::COUNT);
  }

  using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
      ContextMetadata::ContextRedirectTypeBug1221316;

  ContextRedirectTypeBug1221316 redirect_type_for_metrics =
      options.same_site_cookie_context()
          .GetMetadataForCurrentSchemefulMode()
          .redirect_type_bug_1221316;
  if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
    UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Read",
                              redirect_type_for_metrics);
  }

  if (status.HasWarningReason(
          CookieInclusionStatus::
              WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
    UMA_HISTOGRAM_ENUMERATION(
        "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Read",
        CookieSameSiteToCookieSameSiteForMetrics(SameSite()));

    using HttpMethod =
        CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod;

    HttpMethod http_method_enum = options.same_site_cookie_context()
                                      .GetMetadataForCurrentSchemefulMode()
                                      .http_method_bug_1221316;

    DCHECK(http_method_enum != HttpMethod::kUnset);

    UMA_HISTOGRAM_ENUMERATION(
        "Cookie.CrossSiteRedirectDowngradeChangesInclusionHttpMethod",
        http_method_enum);

    base::TimeDelta cookie_age = base::Time::Now() - creation_date_;
    UMA_HISTOGRAM_EXACT_LINEAR(
        "Cookie.CrossSiteRedirectDowngradeChangesInclusionAge",
        cookie_age.InMinutes(), 30);
  }

  return CookieAccessResult(effective_same_site, status,
                            params.access_semantics,
                            is_allowed_to_access_secure_cookies);
}

CookieAccessResult CanonicalCookie::IsSetPermittedInContext(
    const GURL& source_url,
    const CookieOptions& options,
    const CookieAccessParams& params,
    const std::vector<std::string>& cookieable_schemes,
    const absl::optional<CookieAccessResult>& cookie_access_result) const {
  CookieAccessResult access_result;
  if (cookie_access_result) {
    access_result = *cookie_access_result;
  }

  if (!base::Contains(cookieable_schemes, source_url.scheme())) {
    access_result.status.AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_NONCOOKIEABLE_SCHEME);
  }

  CookieAccessScheme access_scheme =
      cookie_util::ProvisionalAccessScheme(source_url);
  if (access_scheme == CookieAccessScheme::kNonCryptographic &&
      params.delegate_treats_url_as_trustworthy) {
    access_scheme = CookieAccessScheme::kTrustworthy;
  }

  switch (access_scheme) {
    case CookieAccessScheme::kNonCryptographic:
      access_result.is_allowed_to_access_secure_cookies = false;
      if (IsSecure()) {
        access_result.status.AddExclusionReason(
            CookieInclusionStatus::EXCLUDE_SECURE_ONLY);
      }
      break;

    case CookieAccessScheme::kCryptographic:
      // All cool!
      access_result.is_allowed_to_access_secure_cookies = true;
      break;

    case CookieAccessScheme::kTrustworthy:
      access_result.is_allowed_to_access_secure_cookies = true;
      if (IsSecure()) {
        // OK, but want people aware of this.
        // Note, we also want to apply this warning to cookies whose source
        // scheme is kSecure but are set by non-cryptographic (but trustworthy)
        // urls. Helpfully, since those cookies only get a kSecure source scheme
        // when they also specify "Secure" this if statement will already apply
        // to them.
        access_result.status.AddWarningReason(
            CookieInclusionStatus::
                WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC);
      }
      break;
  }

  access_result.access_semantics = params.access_semantics;
  if (options.exclude_httponly() && IsHttpOnly()) {
    DVLOG(net::cookie_util::kVlogSetCookies)
        << "HttpOnly cookie not permitted in script context.";
    access_result.status.AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_HTTP_ONLY);
  }

  // Unless legacy access semantics are in effect, SameSite=None cookies without
  // the Secure attribute will be rejected.
  if (params.access_semantics != CookieAccessSemantics::LEGACY &&
      SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) {
    DVLOG(net::cookie_util::kVlogSetCookies)
        << "SetCookie() rejecting insecure cookie with SameSite=None.";
    access_result.status.AddExclusionReason(
        CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
  }

  // For LEGACY cookies we should always return the schemeless context,
  // otherwise let GetContextForCookieInclusion() decide.
  CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context =
      params.access_semantics == CookieAccessSemantics::LEGACY
          ? options.same_site_cookie_context().context()
          : options.same_site_cookie_context().GetContextForCookieInclusion();

  access_result.effective_same_site =
      GetEffectiveSameSite(params.access_semantics);
  DCHECK(access_result.effective_same_site !=
         CookieEffectiveSameSite::UNDEFINED);
  switch (access_result.effective_same_site) {
    case CookieEffectiveSameSite::STRICT_MODE:
      // This intentionally checks for `< SAME_SITE_LAX`, as we allow
      // `SameSite=Strict` cookies to be set for top-level navigations that
      // qualify for receipt of `SameSite=Lax` cookies.
      if (cookie_inclusion_context <
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
        DVLOG(net::cookie_util::kVlogSetCookies)
            << "Trying to set a `SameSite=Strict` cookie from a "
               "cross-site URL.";
        access_result.status.AddExclusionReason(
            CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT);
      }
      break;
    case CookieEffectiveSameSite::LAX_MODE:
    case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
      if (cookie_inclusion_context <
          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
        if (SameSite() == CookieSameSite::UNSPECIFIED) {
          DVLOG(net::cookie_util::kVlogSetCookies)
              << "Cookies with no known SameSite attribute being treated as "
                 "lax; attempt to set from a cross-site URL denied.";
          access_result.status.AddExclusionReason(
              CookieInclusionStatus::
                  EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
        } else {
          DVLOG(net::cookie_util::kVlogSetCookies)
              << "Trying to set a `SameSite=Lax` cookie from a cross-site URL.";
          access_result.status.AddExclusionReason(
              CookieInclusionStatus::EXCLUDE_SAMESITE_LAX);
        }
      }
      break;
    default:
      break;
  }

  switch (params.same_party_status) {
    case CookieSamePartyStatus::kEnforceSamePartyExclude:
      DCHECK(IsSameParty());
      access_result.status.AddExclusionReason(
          CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT);
      [[fallthrough]];
    case CookieSamePartyStatus::kEnforceSamePartyInclude: {
      DCHECK(IsSameParty());
      access_result.status.AddWarningReason(
          CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY);
      // Remove any SameSite exclusion reasons, since SameParty overrides
      // SameSite.
      DCHECK(!access_result.status.HasExclusionReason(
          CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT));
      DCHECK_NE(access_result.effective_same_site,
                CookieEffectiveSameSite::STRICT_MODE);
      bool included_by_samesite =
          !access_result.status.HasExclusionReason(
              CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) &&
          !access_result.status.HasExclusionReason(
              CookieInclusionStatus::
                  EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
      if (!included_by_samesite) {
        access_result.status.RemoveExclusionReasons({
            CookieInclusionStatus::EXCLUDE_SAMESITE_LAX,
            CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
        });
      }

      // Update metrics.
      if (access_result.status.HasOnlyExclusionReason(
              CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) &&
          included_by_samesite) {
        access_result.status.AddWarningReason(
            CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE);
      }
      if (access_result.status.IsInclude()) {
        if (!included_by_samesite) {
          access_result.status.AddWarningReason(
              CookieInclusionStatus::
                  WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE);
        }
      }
      break;
    }
    case CookieSamePartyStatus::kNoSamePartyEnforcement:
      // Only apply SameSite-related warnings if SameParty is not in effect.
      ApplySameSiteCookieWarningToStatus(
          SameSite(), access_result.effective_same_site, IsSecure(),
          options.same_site_cookie_context(), &access_result.status,
          true /* is_cookie_being_set */);
      break;
  }

  if (access_result.status.IsInclude()) {
    UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedResponseEffectiveSameSite",
                              access_result.effective_same_site,
                              CookieEffectiveSameSite::COUNT);
  }

  using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
      ContextMetadata::ContextRedirectTypeBug1221316;

  ContextRedirectTypeBug1221316 redirect_type_for_metrics =
      options.same_site_cookie_context()
          .GetMetadataForCurrentSchemefulMode()
          .redirect_type_bug_1221316;
  if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
    UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Write",
                              redirect_type_for_metrics);
  }

  if (access_result.status.HasWarningReason(
          CookieInclusionStatus::
              WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
    UMA_HISTOGRAM_ENUMERATION(
        "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Write",
        CookieSameSiteToCookieSameSiteForMetrics(SameSite()));
  }

  return access_result;
}

std::string CanonicalCookie::DebugString() const {
  return base::StringPrintf(
      "name: %s value: %s domain: %s path: %s creation: %" PRId64,
      name_.c_str(), value_.c_str(), domain_.c_str(), path_.c_str(),
      static_cast<int64_t>(creation_date_.ToTimeT()));
}

bool CanonicalCookie::PartialCompare(const CanonicalCookie& other) const {
  return PartialCookieOrdering(*this, other) < 0;
}

bool CanonicalCookie::IsCanonical() const {
  // TODO(crbug.com/1244172) Eventually we should check the size of name+value,
  // assuming we collect metrics and determine that a low percentage of cookies
  // would fail this check. Note that we still don't want to enforce length
  // checks on domain or path for the reason stated above.

  // TODO(crbug.com/1264458): Eventually we should push this logic into
  // IsCanonicalForFromStorage, but for now we allow cookies already stored with
  // high expiration dates to be retrieved.
  if (ValidateAndAdjustExpiryDate(expiry_date_, creation_date_) != expiry_date_)
    return false;

  return IsCanonicalForFromStorage();
}

bool CanonicalCookie::IsCanonicalForFromStorage() const {
  // Not checking domain or path against ParsedCookie as it may have
  // come purely from the URL. Also, don't call IsValidCookieNameValuePair()
  // here because we don't want to enforce the size checks on names or values
  // that may have been reconstituted from the cookie store.
  if (ParsedCookie::ParseTokenString(name_) != name_ ||
      !ParsedCookie::ValueMatchesParsedValue(value_)) {
    return false;
  }

  if (!ParsedCookie::IsValidCookieName(name_) ||
      !ParsedCookie::IsValidCookieValue(value_)) {
    return false;
  }

  if (!last_access_date_.is_null() && creation_date_.is_null())
    return false;

  url::CanonHostInfo canon_host_info;
  std::string canonical_domain(CanonicalizeHost(domain_, &canon_host_info));

  // TODO(rdsmith): This specifically allows for empty domains.  The spec
  // suggests this is invalid (if a domain attribute is empty, the cookie's
  // domain is set to the canonicalized request host; see
  // https://tools.ietf.org/html/rfc6265#section-5.3).  However, it is
  // needed for Chrome extension cookies.
  // See http://crbug.com/730633 for more information.
  if (canonical_domain != domain_)
    return false;

  if (path_.empty() || path_[0] != '/')
    return false;

  CookiePrefix prefix = GetCookiePrefix(name_);
  switch (prefix) {
    case COOKIE_PREFIX_HOST:
      if (!secure_ || path_ != "/" || domain_.empty() || domain_[0] == '.')
        return false;
      break;
    case COOKIE_PREFIX_SECURE:
      if (!secure_)
        return false;
      break;
    default:
      break;
  }

  if (name_ == "" && HasHiddenPrefixName(value_))
    return false;

  if (!IsCookieSamePartyValid(same_party_, secure_, same_site_))
    return false;

  if (IsPartitioned()) {
    if (CookiePartitionKey::HasNonce(partition_key_))
      return true;
    if (!secure_)
      return false;
  }

  return true;
}

bool CanonicalCookie::IsEffectivelySameSiteNone(
    CookieAccessSemantics access_semantics) const {
  return GetEffectiveSameSite(access_semantics) ==
         CookieEffectiveSameSite::NO_RESTRICTION;
}

CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSiteForTesting(
    CookieAccessSemantics access_semantics) const {
  return GetEffectiveSameSite(access_semantics);
}

// static
std::string CanonicalCookie::BuildCookieLine(const CookieList& cookies) {
  std::string cookie_line;
  for (const auto& cookie : cookies) {
    AppendCookieLineEntry(cookie, &cookie_line);
  }
  return cookie_line;
}

// static
std::string CanonicalCookie::BuildCookieLine(
    const CookieAccessResultList& cookie_access_result_list) {
  std::string cookie_line;
  for (const auto& cookie_with_access_result : cookie_access_result_list) {
    const CanonicalCookie& cookie = cookie_with_access_result.cookie;
    AppendCookieLineEntry(cookie, &cookie_line);
  }
  return cookie_line;
}

// static
std::string CanonicalCookie::BuildCookieAttributesLine(
    const CanonicalCookie& cookie) {
  std::string cookie_line;
  // In Mozilla, if you set a cookie like "AAA", it will have an empty token
  // and a value of "AAA". When it sends the cookie back, it will send "AAA",
  // so we need to avoid sending "=AAA" for a blank token value.
  if (!cookie.Name().empty())
    cookie_line += cookie.Name() + "=";
  cookie_line += cookie.Value();
  if (!cookie.Domain().empty())
    cookie_line += "; domain=" + cookie.Domain();
  if (!cookie.Path().empty())
    cookie_line += "; path=" + cookie.Path();
  if (cookie.ExpiryDate() != base::Time())
    cookie_line += "; expires=" + TimeFormatHTTP(cookie.ExpiryDate());
  if (cookie.IsSecure())
    cookie_line += "; secure";
  if (cookie.IsHttpOnly())
    cookie_line += "; httponly";
  if (cookie.IsPartitioned() &&
      !CookiePartitionKey::HasNonce(cookie.PartitionKey())) {
    cookie_line += "; partitioned";
  }
  switch (cookie.SameSite()) {
    case CookieSameSite::NO_RESTRICTION:
      cookie_line += "; samesite=none";
      break;
    case CookieSameSite::LAX_MODE:
      cookie_line += "; samesite=lax";
      break;
    case CookieSameSite::STRICT_MODE:
      cookie_line += "; samesite=strict";
      break;
    case CookieSameSite::UNSPECIFIED:
      // Don't append any text if the samesite attribute wasn't explicitly set.
      break;
  }
  return cookie_line;
}

// static
CanonicalCookie::CookiePrefix CanonicalCookie::GetCookiePrefix(
    const std::string& name,
    bool check_insensitively) {
  const char kSecurePrefix[] = "__Secure-";
  const char kHostPrefix[] = "__Host-";

  base::CompareCase case_sensitivity =
      check_insensitively ? base::CompareCase::INSENSITIVE_ASCII
                          : base::CompareCase::SENSITIVE;

  if (base::StartsWith(name, kSecurePrefix, case_sensitivity))
    return CanonicalCookie::COOKIE_PREFIX_SECURE;
  if (base::StartsWith(name, kHostPrefix, case_sensitivity))
    return CanonicalCookie::COOKIE_PREFIX_HOST;
  return CanonicalCookie::COOKIE_PREFIX_NONE;
}

// static
void CanonicalCookie::RecordCookiePrefixMetrics(
    CookiePrefix prefix_case_sensitive,
    CookiePrefix prefix_case_insensitive,
    bool is_insensitive_prefix_valid) {
  const char kCookiePrefixHistogram[] = "Cookie.CookiePrefix";
  UMA_HISTOGRAM_ENUMERATION(kCookiePrefixHistogram, prefix_case_sensitive,
                            CanonicalCookie::COOKIE_PREFIX_LAST);

  // For this to be true there must a prefix, so we know it's not
  // COOKIE_PREFIX_NONE.
  bool is_case_variant = prefix_case_insensitive != prefix_case_sensitive;

  if (is_case_variant) {
    const char kCookiePrefixVariantHistogram[] =
        "Cookie.CookiePrefix.CaseVariant";
    UMA_HISTOGRAM_ENUMERATION(kCookiePrefixVariantHistogram,
                              prefix_case_insensitive,
                              CanonicalCookie::COOKIE_PREFIX_LAST);

    const char kVariantValidHistogram[] =
        "Cookie.CookiePrefix.CaseVariantValid";
    UMA_HISTOGRAM_BOOLEAN(kVariantValidHistogram, is_insensitive_prefix_valid);
  }

  const char kVariantCountHistogram[] = "Cookie.CookiePrefix.CaseVariantCount";
  if (prefix_case_insensitive > CookiePrefix::COOKIE_PREFIX_NONE) {
    UMA_HISTOGRAM_BOOLEAN(kVariantCountHistogram, is_case_variant);
  }
}

// Returns true if the cookie does not violate any constraints imposed
// by the cookie name's prefix, as described in
// https://tools.ietf.org/html/draft-west-cookie-prefixes
//
// static
bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
                                          const GURL& url,
                                          const ParsedCookie& parsed_cookie) {
  return CanonicalCookie::IsCookiePrefixValid(
      prefix, url, parsed_cookie.IsSecure(),
      parsed_cookie.HasDomain() ? parsed_cookie.Domain() : "",
      parsed_cookie.HasPath() ? parsed_cookie.Path() : "");
}

bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
                                          const GURL& url,
                                          bool secure,
                                          const std::string& domain,
                                          const std::string& path) {
  if (prefix == CanonicalCookie::COOKIE_PREFIX_SECURE)
    return secure && url.SchemeIsCryptographic();
  if (prefix == CanonicalCookie::COOKIE_PREFIX_HOST) {
    return HasValidHostPrefixAttributes(url, secure, domain, path);
  }
  return true;
}

CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSite(
    CookieAccessSemantics access_semantics) const {
  base::TimeDelta lax_allow_unsafe_threshold_age =
      base::FeatureList::IsEnabled(
          features::kSameSiteDefaultChecksMethodRigorously)
          ? base::TimeDelta::Min()
          : (base::FeatureList::IsEnabled(
                 features::kShortLaxAllowUnsafeThreshold)
                 ? kShortLaxAllowUnsafeMaxAge
                 : kLaxAllowUnsafeMaxAge);

  switch (SameSite()) {
    // If a cookie does not have a SameSite attribute, the effective SameSite
    // mode depends on the access semantics and whether the cookie is
    // recently-created.
    case CookieSameSite::UNSPECIFIED:
      return (access_semantics == CookieAccessSemantics::LEGACY)
                 ? CookieEffectiveSameSite::NO_RESTRICTION
                 : (IsRecentlyCreated(lax_allow_unsafe_threshold_age)
                        ? CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE
                        : CookieEffectiveSameSite::LAX_MODE);
    case CookieSameSite::NO_RESTRICTION:
      return CookieEffectiveSameSite::NO_RESTRICTION;
    case CookieSameSite::LAX_MODE:
      return CookieEffectiveSameSite::LAX_MODE;
    case CookieSameSite::STRICT_MODE:
      return CookieEffectiveSameSite::STRICT_MODE;
  }
}

// static
int CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
    const GURL& source_url,
    bool url_is_trustworthy) {
  // If the url isn't trustworthy, or if `source_url` is cryptographic then
  // return the port of `source_url`.
  if (!url_is_trustworthy || source_url.SchemeIsCryptographic()) {
    return source_url.EffectiveIntPort();
  }

  // Only http and ws are cookieable schemes that have a port component. For
  // both of these schemes their default port is 80 whereas their secure
  // components have a default port of 443.
  //
  // Only in cases where we have an http/ws scheme with a default should we
  // return 443.
  if ((source_url.SchemeIs(url::kHttpScheme) ||
       source_url.SchemeIs(url::kWsScheme)) &&
      source_url.EffectiveIntPort() == 80) {
    return 443;
  }

  // Different schemes, or non-default port values should keep the same port
  // value.
  return source_url.EffectiveIntPort();
}

// static
bool CanonicalCookie::HasHiddenPrefixName(
    const base::StringPiece cookie_value) {
  // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9).
  base::StringPiece value_without_BWS =
      base::TrimString(cookie_value, " \t", base::TRIM_LEADING);

  const base::StringPiece host_prefix = "__Host-";

  // Compare the value to the host_prefix.
  if (base::StartsWith(value_without_BWS, host_prefix,
                       base::CompareCase::INSENSITIVE_ASCII)) {
    // This value contains a hidden prefix name.
    return true;
  }

  // Do a similar check for the secure prefix
  const base::StringPiece secure_prefix = "__Secure-";

  if (base::StartsWith(value_without_BWS, secure_prefix,
                       base::CompareCase::INSENSITIVE_ASCII)) {
    return true;
  }

  return false;
}

bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const {
  return (base::Time::Now() - creation_date_) <= age_threshold;
}

// static
bool CanonicalCookie::IsCookieSamePartyValid(
    const ParsedCookie& parsed_cookie) {
  return IsCookieSamePartyValid(parsed_cookie.IsSameParty(),
                                parsed_cookie.IsSecure(),
                                parsed_cookie.SameSite());
}

// static
bool CanonicalCookie::IsCookieSamePartyValid(bool is_same_party,
                                             bool is_secure,
                                             CookieSameSite same_site) {
  if (!is_same_party)
    return true;
  return is_secure && (same_site != CookieSameSite::STRICT_MODE);
}

// static
bool CanonicalCookie::IsCookiePartitionedValid(
    const GURL& url,
    const ParsedCookie& parsed_cookie,
    bool partition_has_nonce) {
  return IsCookiePartitionedValid(
      url, /*secure=*/parsed_cookie.IsSecure(),
      /*is_partitioned=*/parsed_cookie.IsPartitioned(), partition_has_nonce);
}

// static
bool CanonicalCookie::IsCookiePartitionedValid(const GURL& url,
                                               bool secure,
                                               bool is_partitioned,
                                               bool partition_has_nonce) {
  if (!is_partitioned)
    return true;
  if (partition_has_nonce)
    return true;
  CookieAccessScheme scheme = cookie_util::ProvisionalAccessScheme(url);
  bool result = (scheme != CookieAccessScheme::kNonCryptographic) && secure;
  DLOG_IF(WARNING, !result)
      << "CanonicalCookie has invalid Partitioned attribute";
  return result;
}

CookieAndLineWithAccessResult::CookieAndLineWithAccessResult() = default;

CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
    absl::optional<CanonicalCookie> cookie,
    std::string cookie_string,
    CookieAccessResult access_result)
    : cookie(std::move(cookie)),
      cookie_string(std::move(cookie_string)),
      access_result(access_result) {}

CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
    const CookieAndLineWithAccessResult&) = default;

CookieAndLineWithAccessResult& CookieAndLineWithAccessResult::operator=(
    const CookieAndLineWithAccessResult& cookie_and_line_with_access_result) =
    default;

CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
    CookieAndLineWithAccessResult&&) = default;

CookieAndLineWithAccessResult::~CookieAndLineWithAccessResult() = default;

}  // namespace net
