tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CookieCommons.cpp (36459B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "Cookie.h"
      7 #include "CookieCommons.h"
      8 #include "CookieLogging.h"
      9 #include "CookieParser.h"
     10 #include "CookieService.h"
     11 #include "mozilla/ContentBlockingNotifier.h"
     12 #include "mozilla/StaticPrefs_network.h"
     13 #include "mozilla/StorageAccess.h"
     14 #include "mozilla/dom/nsMixedContentBlocker.h"
     15 #include "mozilla/dom/CanonicalBrowsingContext.h"
     16 #include "mozilla/dom/Document.h"
     17 #include "mozilla/dom/WindowGlobalParent.h"
     18 #include "mozilla/dom/WorkerCommon.h"
     19 #include "mozilla/dom/WorkerPrivate.h"
     20 #include "mozilla/net/CookieJarSettings.h"
     21 #include "mozIThirdPartyUtil.h"
     22 #include "nsContentUtils.h"
     23 #include "nsICookiePermission.h"
     24 #include "nsICookieService.h"
     25 #include "nsIEffectiveTLDService.h"
     26 #include "nsIGlobalObject.h"
     27 #include "nsIHttpChannel.h"
     28 #include "nsIRedirectHistoryEntry.h"
     29 #include "nsIWebProgressListener.h"
     30 #include "nsNetUtil.h"
     31 #include "nsSandboxFlags.h"
     32 #include "nsScriptSecurityManager.h"
     33 #include "nsReadableUtils.h"
     34 #include "ThirdPartyUtil.h"
     35 
     36 namespace mozilla {
     37 
     38 using dom::Document;
     39 
     40 namespace net {
     41 
     42 // static
     43 bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) {
     44  // first, check for an exact host or domain cookie match, e.g. "google.com"
     45  // or ".google.com"; second a subdomain match, e.g.
     46  // host = "mail.google.com", cookie domain = ".google.com".
     47  return aCookie->RawHost() == aHost ||
     48         (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
     49 }
     50 
     51 // static
     52 bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) {
     53  return PathMatches(aCookie->Path(), aPath);
     54 }
     55 
     56 // static
     57 bool CookieCommons::PathMatches(const nsACString& aCookiePath,
     58                                const nsACString& aPath) {
     59  // if our cookie path is empty we can't really perform our prefix check, and
     60  // also we can't check the last character of the cookie path, so we would
     61  // never return a successful match.
     62  if (aCookiePath.IsEmpty()) {
     63    return false;
     64  }
     65 
     66  // if the cookie path and the request path are identical, they match.
     67  if (aCookiePath.Equals(aPath)) {
     68    return true;
     69  }
     70 
     71  // if the cookie path is a prefix of the request path, and the last character
     72  // of the cookie path is %x2F ("/"), they match.
     73  bool isPrefix = StringBeginsWith(aPath, aCookiePath);
     74  if (isPrefix && aCookiePath.Last() == '/') {
     75    return true;
     76  }
     77 
     78  // if the cookie path is a prefix of the request path, and the first character
     79  // of the request path that is not included in the cookie path is a %x2F ("/")
     80  // character, they match.
     81  uint32_t cookiePathLen = aCookiePath.Length();
     82  return isPrefix && aPath[cookiePathLen] == '/';
     83 }
     84 
     85 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
     86 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
     87 // dot may be present. If aHostURI is an IP address, an alias such as
     88 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
     89 // be the exact host, and aRequireHostMatch will be true to indicate that
     90 // substring matches should not be performed.
     91 nsresult CookieCommons::GetBaseDomain(nsIEffectiveTLDService* aTLDService,
     92                                      nsIURI* aHostURI, nsACString& aBaseDomain,
     93                                      bool& aRequireHostMatch) {
     94  // get the base domain. this will fail if the host contains a leading dot,
     95  // more than one trailing dot, or is otherwise malformed.
     96  nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
     97  aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
     98                      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
     99  if (aRequireHostMatch) {
    100    // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
    101    // such as 'co.uk', or the empty string. use the host as a key in such
    102    // cases.
    103    rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, aBaseDomain);
    104  }
    105  NS_ENSURE_SUCCESS(rv, rv);
    106 
    107  // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
    108  if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') {
    109    return NS_ERROR_INVALID_ARG;
    110  }
    111 
    112  // block any URIs without a host that aren't file:// URIs.
    113  if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) {
    114    return NS_ERROR_INVALID_ARG;
    115  }
    116 
    117  return NS_OK;
    118 }
    119 
    120 nsresult CookieCommons::GetBaseDomain(nsIPrincipal* aPrincipal,
    121                                      nsACString& aBaseDomain) {
    122  MOZ_ASSERT(aPrincipal);
    123 
    124  // for historical reasons we use ascii host for file:// URLs.
    125  if (aPrincipal->SchemeIs("file")) {
    126    return nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, aBaseDomain);
    127  }
    128 
    129  nsresult rv = aPrincipal->GetBaseDomain(aBaseDomain);
    130  if (NS_FAILED(rv)) {
    131    return rv;
    132  }
    133 
    134  nsContentUtils::MaybeFixIPv6Host(aBaseDomain);
    135  return NS_OK;
    136 }
    137 
    138 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
    139 // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it
    140 // is assumed that aHost is already normalized, and it may contain a leading dot
    141 // (indicating that it represents a domain). A trailing dot may be present.
    142 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
    143 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
    144 // leading dot will be treated as an error.
    145 nsresult CookieCommons::GetBaseDomainFromHost(
    146    nsIEffectiveTLDService* aTLDService, const nsACString& aHost,
    147    nsCString& aBaseDomain) {
    148  // aHost must not be the string '.'.
    149  if (aHost.Length() == 1 && aHost.Last() == '.') {
    150    return NS_ERROR_INVALID_ARG;
    151  }
    152 
    153  // aHost may contain a leading dot; if so, strip it now.
    154  bool domain = !aHost.IsEmpty() && aHost.First() == '.';
    155 
    156  // get the base domain. this will fail if the host contains a leading dot,
    157  // more than one trailing dot, or is otherwise malformed.
    158  nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0,
    159                                                   aBaseDomain);
    160  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
    161      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
    162    // aHost is either an IP address, an alias such as 'localhost', an eTLD
    163    // such as 'co.uk', or the empty string. use the host as a key in such
    164    // cases; however, we reject any such hosts with a leading dot, since it
    165    // doesn't make sense for them to be domain cookies.
    166    if (domain) {
    167      return NS_ERROR_INVALID_ARG;
    168    }
    169 
    170    aBaseDomain = aHost;
    171    return NS_OK;
    172  }
    173  return rv;
    174 }
    175 
    176 /* static */ bool CookieCommons::IsIPv6BaseDomain(
    177    const nsACString& aBaseDomain) {
    178  return aBaseDomain.Contains(':');
    179 }
    180 
    181 namespace {
    182 
    183 void NotifyRejectionToObservers(nsIURI* aHostURI, CookieOperation aOperation) {
    184  if (aOperation == OPERATION_WRITE) {
    185    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
    186    if (os) {
    187      os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
    188    }
    189  } else {
    190    MOZ_ASSERT(aOperation == OPERATION_READ);
    191  }
    192 }
    193 
    194 }  // namespace
    195 
    196 // Notify observers that a cookie was rejected due to the users' prefs.
    197 void CookieCommons::NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel,
    198                                   uint32_t aRejectedReason,
    199                                   CookieOperation aOperation) {
    200  NotifyRejectionToObservers(aHostURI, aOperation);
    201 
    202  ContentBlockingNotifier::OnDecision(
    203      aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
    204      aRejectedReason);
    205 }
    206 
    207 // static
    208 bool CookieCommons::CheckCookiePermission(nsIChannel* aChannel,
    209                                          CookieStruct& aCookieData) {
    210  if (!aChannel) {
    211    // No channel, let's assume this is a system-principal request.
    212    return true;
    213  }
    214 
    215  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    216  nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
    217  nsresult rv =
    218      loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
    219  if (NS_WARN_IF(NS_FAILED(rv))) {
    220    return true;
    221  }
    222 
    223  nsIScriptSecurityManager* ssm =
    224      nsScriptSecurityManager::GetScriptSecurityManager();
    225  MOZ_ASSERT(ssm);
    226 
    227  nsCOMPtr<nsIPrincipal> channelPrincipal;
    228  rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal));
    229  if (NS_WARN_IF(NS_FAILED(rv))) {
    230    return false;
    231  }
    232 
    233  return CheckCookiePermission(channelPrincipal, cookieJarSettings,
    234                               aCookieData);
    235 }
    236 
    237 // static
    238 bool CookieCommons::CheckCookiePermission(
    239    nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
    240    CookieStruct& aCookieData) {
    241  MOZ_ASSERT(aPrincipal);
    242  MOZ_ASSERT(aCookieJarSettings);
    243 
    244  if (!aPrincipal->GetIsContentPrincipal()) {
    245    return true;
    246  }
    247 
    248  uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
    249  nsresult rv =
    250      aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission);
    251  if (NS_WARN_IF(NS_FAILED(rv))) {
    252    return true;
    253  }
    254 
    255  if (cookiePermission == nsICookiePermission::ACCESS_ALLOW) {
    256    return true;
    257  }
    258 
    259  if (cookiePermission == nsICookiePermission::ACCESS_SESSION) {
    260    aCookieData.isSession() = true;
    261    return true;
    262  }
    263 
    264  if (cookiePermission == nsICookiePermission::ACCESS_DENY) {
    265    return false;
    266  }
    267 
    268  return true;
    269 }
    270 
    271 namespace {
    272 
    273 CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow,
    274                                   nsIURI* aDocumentURI) {
    275  MOZ_ASSERT(aWindow);
    276  MOZ_ASSERT(aDocumentURI);
    277 
    278  ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
    279  if (thirdPartyUtil) {
    280    bool isThirdParty = true;
    281 
    282    nsresult rv = thirdPartyUtil->IsThirdPartyWindow(
    283        aWindow->GetOuterWindow(), aDocumentURI, &isThirdParty);
    284    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Third-party window check failed.");
    285 
    286    if (NS_SUCCEEDED(rv) && !isThirdParty) {
    287      return STATUS_ACCEPTED;
    288    }
    289  }
    290 
    291  return STATUS_ACCEPTED;
    292 }
    293 
    294 bool CheckCookieStringFromDocument(const nsACString& aCookieString) {
    295  // If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F character (CTL
    296  // characters excluding HTAB): Abort these steps and ignore the
    297  // set-cookie-string entirely.
    298  const char illegalCharacters[] = {
    299      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C,
    300      0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    301      0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x7F, 0x00};
    302 
    303  const auto* start = aCookieString.BeginReading();
    304  const auto* end = aCookieString.EndReading();
    305 
    306  auto charFilter = [&](unsigned char c) {
    307    if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) {
    308      return true;
    309    }
    310    return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
    311                     c) != std::end(illegalCharacters);
    312  };
    313 
    314  return std::find_if(start, end, charFilter) == end;
    315 }
    316 
    317 }  // namespace
    318 
    319 // static
    320 already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument(
    321    CookieParser& aCookieParser, Document* aDocument,
    322    const nsACString& aCookieString, int64_t currentTimeInUsec,
    323    nsIEffectiveTLDService* aTLDService, mozIThirdPartyUtil* aThirdPartyUtil,
    324    nsACString& aBaseDomain, OriginAttributes& aAttrs) {
    325  if (!CookieCommons::IsSchemeSupported(aCookieParser.HostURI())) {
    326    return nullptr;
    327  }
    328 
    329  if (!CheckCookieStringFromDocument(aCookieString)) {
    330    return nullptr;
    331  }
    332 
    333  nsAutoCString baseDomain;
    334  bool requireHostMatch = false;
    335  nsresult rv = CookieCommons::GetBaseDomain(
    336      aTLDService, aCookieParser.HostURI(), baseDomain, requireHostMatch);
    337  if (NS_WARN_IF(NS_FAILED(rv))) {
    338    return nullptr;
    339  }
    340 
    341  nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
    342  if (NS_WARN_IF(!innerWindow)) {
    343    return nullptr;
    344  }
    345 
    346  bool isForeignAndNotAddon = false;
    347  if (!BasePrincipal::Cast(aDocument->NodePrincipal())->AddonPolicy()) {
    348    rv = aThirdPartyUtil->IsThirdPartyWindow(innerWindow->GetOuterWindow(),
    349                                             aCookieParser.HostURI(),
    350                                             &isForeignAndNotAddon);
    351    if (NS_WARN_IF(NS_FAILED(rv))) {
    352      isForeignAndNotAddon = true;
    353    }
    354  }
    355 
    356  bool mustBePartitioned =
    357      isForeignAndNotAddon &&
    358      aDocument->CookieJarSettings()->GetCookieBehavior() ==
    359          nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
    360      !aDocument->UsingStorageAccess();
    361 
    362  // If we are here, we have been already accepted by the anti-tracking.
    363  // We just need to check if we have to be in session-only mode.
    364  CookieStatus cookieStatus =
    365      CookieStatusForWindow(innerWindow, aCookieParser.HostURI());
    366  MOZ_ASSERT(cookieStatus == STATUS_ACCEPTED ||
    367             cookieStatus == STATUS_ACCEPT_SESSION);
    368 
    369  nsCString cookieString(aCookieString);
    370 
    371  nsCOMPtr<nsILoadInfo> loadInfo =
    372      aDocument->GetChannel() ? aDocument->GetChannel()->LoadInfo() : nullptr;
    373  const bool on3pcbException = loadInfo && loadInfo->GetIsOn3PCBExceptionList();
    374 
    375  aCookieParser.Parse(baseDomain, requireHostMatch, cookieStatus, cookieString,
    376                      EmptyCString(), false, isForeignAndNotAddon,
    377                      mustBePartitioned, aDocument->IsInPrivateBrowsing(),
    378                      on3pcbException, PR_Now());
    379 
    380  if (!aCookieParser.ContainsCookie()) {
    381    return nullptr;
    382  }
    383 
    384  // check permissions from site permission list.
    385  if (!CookieCommons::CheckCookiePermission(aDocument->NodePrincipal(),
    386                                            aDocument->CookieJarSettings(),
    387                                            aCookieParser.CookieData())) {
    388    NotifyRejectionToObservers(aCookieParser.HostURI(), OPERATION_WRITE);
    389    ContentBlockingNotifier::OnDecision(
    390        innerWindow, ContentBlockingNotifier::BlockingDecision::eBlock,
    391        nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION);
    392    return nullptr;
    393  }
    394 
    395  // CHIPS - If the partitioned attribute is set, store cookie in partitioned
    396  // cookie jar independent of context. If the cookies are stored in the
    397  // partitioned cookie jar anyway no special treatment of CHIPS cookies
    398  // necessary.
    399  bool needPartitioned = StaticPrefs::network_cookie_CHIPS_enabled() &&
    400                         aCookieParser.CookieData().isPartitioned();
    401  nsCOMPtr<nsIPrincipal> cookiePrincipal =
    402      needPartitioned ? aDocument->PartitionedPrincipal()
    403                      : aDocument->EffectiveCookiePrincipal();
    404  MOZ_ASSERT(cookiePrincipal);
    405 
    406  nsCOMPtr<nsICookieService> service =
    407      do_GetService(NS_COOKIESERVICE_CONTRACTID);
    408  if (!service) {
    409    return nullptr;
    410  }
    411 
    412  // Check if limit-foreign is required.
    413  uint32_t dummyRejectedReason = 0;
    414  if (aDocument->CookieJarSettings()->GetLimitForeignContexts() &&
    415      !service->HasExistingCookies(baseDomain,
    416                                   cookiePrincipal->OriginAttributesRef()) &&
    417      !ShouldAllowAccessFor(innerWindow, aCookieParser.HostURI(), true,
    418                            &dummyRejectedReason)) {
    419    return nullptr;
    420  }
    421 
    422  RefPtr<Cookie> cookie = Cookie::Create(
    423      aCookieParser.CookieData(), cookiePrincipal->OriginAttributesRef());
    424  MOZ_ASSERT(cookie);
    425 
    426  cookie->SetLastAccessedInUSec(currentTimeInUsec);
    427  cookie->SetCreationTimeInUSec(
    428      Cookie::GenerateUniqueCreationTimeInUSec(currentTimeInUsec));
    429  cookie->SetUpdateTimeInUSec(cookie->CreationTimeInUSec());
    430 
    431  aBaseDomain = baseDomain;
    432  aAttrs = cookiePrincipal->OriginAttributesRef();
    433 
    434  return cookie.forget();
    435 }
    436 
    437 // static
    438 already_AddRefed<nsICookieJarSettings> CookieCommons::GetCookieJarSettings(
    439    nsIChannel* aChannel) {
    440  nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
    441  bool shouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
    442      aChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
    443  if (aChannel) {
    444    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    445    nsresult rv =
    446        loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
    447    if (NS_WARN_IF(NS_FAILED(rv))) {
    448      cookieJarSettings =
    449          CookieJarSettings::GetBlockingAll(shouldResistFingerprinting);
    450    }
    451  } else {
    452    cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular,
    453                                                  shouldResistFingerprinting);
    454  }
    455 
    456  MOZ_ASSERT(cookieJarSettings);
    457  return cookieJarSettings.forget();
    458 }
    459 
    460 // static
    461 bool CookieCommons::ShouldIncludeCrossSiteCookie(
    462    Cookie* aCookie, nsIURI* aHostURI, bool aPartitionForeign,
    463    bool aInPrivateBrowsing, bool aUsingStorageAccess, bool aOn3pcbException) {
    464  MOZ_ASSERT(aCookie);
    465 
    466  int32_t sameSiteAttr = 0;
    467  aCookie->GetSameSite(&sameSiteAttr);
    468 
    469  return ShouldIncludeCrossSiteCookie(
    470      aHostURI, sameSiteAttr,
    471      aCookie->IsPartitioned() && aCookie->RawIsPartitioned(),
    472      aPartitionForeign, aInPrivateBrowsing, aUsingStorageAccess,
    473      aOn3pcbException);
    474 }
    475 
    476 // static
    477 bool CookieCommons::ShouldIncludeCrossSiteCookie(
    478    nsIURI* aHostURI, int32_t aSameSiteAttr, bool aCookiePartitioned,
    479    bool aPartitionForeign, bool aInPrivateBrowsing, bool aUsingStorageAccess,
    480    bool aOn3pcbException) {
    481  if (aSameSiteAttr == nsICookie::SAMESITE_UNSET) {
    482    bool laxByDefault =
    483        StaticPrefs::network_cookie_sameSite_laxByDefault() &&
    484        !nsContentUtils::IsURIInPrefList(
    485            aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
    486    aSameSiteAttr =
    487        laxByDefault ? nsICookie::SAMESITE_LAX : nsICookie::SAMESITE_NONE;
    488  }
    489 
    490  // CHIPS - If a third-party has storage access it can access both it's
    491  // partitioned and unpartitioned cookie jars, else its cookies are blocked.
    492  //
    493  // Note that we will only include partitioned cookies that have "partitioned"
    494  // attribution if we enable opt-in partitioning.
    495  if (aPartitionForeign &&
    496      (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() ||
    497       (aInPrivateBrowsing &&
    498        StaticPrefs::
    499            network_cookie_cookieBehavior_optInPartitioning_pbmode())) &&
    500      !aCookiePartitioned && !aUsingStorageAccess && !aOn3pcbException) {
    501    return false;
    502  }
    503 
    504  return aSameSiteAttr == nsICookie::SAMESITE_NONE;
    505 }
    506 
    507 // static
    508 bool CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS(
    509    Cookie* aCookie, const nsACString& aBaseDomain,
    510    const OriginAttributes& aOriginAttributes) {
    511  MOZ_ASSERT(aCookie);
    512 
    513  // The cookie is set with partitioned attribute. This is a CHIPS cookies.
    514  if (aCookie->RawIsPartitioned()) {
    515    return false;
    516  }
    517 
    518  // The originAttributes is not partitioned. This is not a partitioned cookie.
    519  if (aOriginAttributes.mPartitionKey.IsEmpty()) {
    520    return false;
    521  }
    522 
    523  nsAutoString scheme;
    524  nsAutoString baseDomain;
    525  int32_t port;
    526  bool foreignByAncestorContext;
    527  // Bail out early if the partition key is not valid.
    528  if (!OriginAttributes::ParsePartitionKey(aOriginAttributes.mPartitionKey,
    529                                           scheme, baseDomain, port,
    530                                           foreignByAncestorContext)) {
    531    return false;
    532  }
    533 
    534  // Check whether the base domain of the cookie match the base domain in the
    535  // partitionKey and it is not an ABA context
    536  return aBaseDomain.Equals(NS_ConvertUTF16toUTF8(baseDomain)) &&
    537         !foreignByAncestorContext;
    538 }
    539 
    540 // static
    541 bool CookieCommons::ShouldEnforceSessionForOriginAttributes(
    542    const OriginAttributes& aOriginAttributes) {
    543  // We don't need to enforce session for unpartitioned OAs.
    544  if (aOriginAttributes.mPartitionKey.IsEmpty()) {
    545    return false;
    546  }
    547 
    548  // We enforce session cookies if the partitionKey is from a null principal.
    549  // This ensures that we don't create dangling cookies that cannot be deleted.
    550  // A partitionKey from a null principal ends with ".mozilla".
    551  if (StringEndsWith(aOriginAttributes.mPartitionKey, u".mozilla"_ns)) {
    552    return true;
    553  }
    554 
    555  return false;
    556 }
    557 
    558 bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) {
    559  if (!aChannel) {
    560    return false;
    561  }
    562  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    563  nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
    564  if ((loadInfo->GetExternalContentPolicyType() !=
    565           ExtContentPolicy::TYPE_DOCUMENT &&
    566       loadInfo->GetExternalContentPolicyType() !=
    567           ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) &&
    568      !interceptionInfo) {
    569    return false;
    570  }
    571 
    572  if (interceptionInfo &&
    573      interceptionInfo->GetExtContentPolicyType() !=
    574          ExtContentPolicy::TYPE_DOCUMENT &&
    575      interceptionInfo->GetExtContentPolicyType() !=
    576          ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD &&
    577      interceptionInfo->GetExtContentPolicyType() !=
    578          ExtContentPolicy::TYPE_INVALID) {
    579    return false;
    580  }
    581 
    582  return NS_IsSafeMethodNav(aChannel);
    583 }
    584 
    585 // This function determines if two schemes are equal in the context of
    586 // "Schemeful SameSite cookies".
    587 //
    588 // Two schemes are considered equal:
    589 //   - if the "network.cookie.sameSite.schemeful" pref is set to false.
    590 // OR
    591 //   - if one of the schemes is not http or https.
    592 // OR
    593 //   - if both schemes are equal AND both are either http or https.
    594 bool IsSameSiteSchemeEqual(const nsACString& aFirstScheme,
    595                           const nsACString& aSecondScheme) {
    596  if (!StaticPrefs::network_cookie_sameSite_schemeful()) {
    597    return true;
    598  }
    599 
    600  auto isSchemeHttpOrHttps = [](const nsACString& scheme) -> bool {
    601    return scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https");
    602  };
    603 
    604  if (!isSchemeHttpOrHttps(aFirstScheme) ||
    605      !isSchemeHttpOrHttps(aSecondScheme)) {
    606    return true;
    607  }
    608 
    609  return aFirstScheme.Equals(aSecondScheme);
    610 }
    611 
    612 bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI,
    613                                      bool* aHadCrossSiteRedirects) {
    614  *aHadCrossSiteRedirects = false;
    615 
    616  if (!aChannel) {
    617    return false;
    618  }
    619  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    620  // Do not treat loads triggered by web extensions as foreign
    621  nsCOMPtr<nsIURI> channelURI;
    622  NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
    623 
    624  nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo();
    625 
    626  RefPtr<BasePrincipal> triggeringPrincipal;
    627  ExtContentPolicy contentPolicyType;
    628  if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
    629    triggeringPrincipal =
    630        BasePrincipal::Cast(interceptionInfo->TriggeringPrincipal());
    631    contentPolicyType = interceptionInfo->GetExtContentPolicyType();
    632  } else {
    633    triggeringPrincipal = BasePrincipal::Cast(loadInfo->TriggeringPrincipal());
    634    contentPolicyType = loadInfo->GetExternalContentPolicyType();
    635 
    636    if (triggeringPrincipal->AddonPolicy() &&
    637        triggeringPrincipal->AddonAllowsLoad(channelURI)) {
    638      return false;
    639    }
    640  }
    641  const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& redirectChain(
    642      interceptionInfo && interceptionInfo->TriggeringPrincipal()
    643          ? interceptionInfo->RedirectChain()
    644          : loadInfo->RedirectChain());
    645 
    646  nsAutoCString hostScheme, otherScheme;
    647  aHostURI->GetScheme(hostScheme);
    648 
    649  bool isForeign = true;
    650  nsresult rv;
    651  if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
    652      contentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
    653    // for loads of TYPE_DOCUMENT we query the hostURI from the
    654    // triggeringPrincipal which returns the URI of the document that caused the
    655    // navigation.
    656    rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
    657 
    658    triggeringPrincipal->GetScheme(otherScheme);
    659  } else {
    660    // If the load is caused by FetchEvent.request or NavigationPreload request,
    661    // check the original InterceptedHttpChannel is a third-party channel or
    662    // not.
    663    if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) {
    664      isForeign = interceptionInfo->FromThirdParty();
    665      if (isForeign) {
    666        return true;
    667      }
    668    }
    669    nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
    670        do_GetService(THIRDPARTYUTIL_CONTRACTID);
    671    if (!thirdPartyUtil) {
    672      return true;
    673    }
    674    rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
    675 
    676    channelURI->GetScheme(otherScheme);
    677  }
    678  // if we are dealing with a cross origin request, we can return here
    679  // because we already know the request is 'foreign'.
    680  if (NS_FAILED(rv) || isForeign) {
    681    return true;
    682  }
    683 
    684  if (!IsSameSiteSchemeEqual(otherScheme, hostScheme)) {
    685    // If the two schemes are not of the same http(s) scheme then we
    686    // consider the request as foreign.
    687    return true;
    688  }
    689 
    690  // for loads of TYPE_SUBDOCUMENT we have to perform an additional test,
    691  // because a cross-origin iframe might perform a navigation to a same-origin
    692  // iframe which would send same-site cookies. Hence, if the iframe navigation
    693  // was triggered by a cross-origin triggeringPrincipal, we treat the load as
    694  // foreign.
    695  if (contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
    696    rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
    697    if (NS_FAILED(rv) || isForeign) {
    698      return true;
    699    }
    700  }
    701 
    702  // for the purpose of same-site cookies we have to treat any cross-origin
    703  // redirects as foreign. E.g. cross-site to same-site redirect is a problem
    704  // with regards to CSRF.
    705 
    706  nsCOMPtr<nsIPrincipal> redirectPrincipal;
    707  for (nsIRedirectHistoryEntry* entry : redirectChain) {
    708    entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
    709    if (redirectPrincipal) {
    710      rv = redirectPrincipal->IsThirdPartyChannel(aChannel, &isForeign);
    711      // if at any point we encounter a cross-origin redirect we can return.
    712      if (NS_FAILED(rv) || isForeign) {
    713        *aHadCrossSiteRedirects = isForeign;
    714        return true;
    715      }
    716 
    717      nsAutoCString redirectScheme;
    718      redirectPrincipal->GetScheme(redirectScheme);
    719      if (!IsSameSiteSchemeEqual(redirectScheme, hostScheme)) {
    720        // If the two schemes are not of the same http(s) scheme then we
    721        // consider the request as foreign.
    722        *aHadCrossSiteRedirects = true;
    723        return true;
    724      }
    725    }
    726  }
    727  return isForeign;
    728 }
    729 
    730 // static
    731 nsICookie::schemeType CookieCommons::URIToSchemeType(nsIURI* aURI) {
    732  MOZ_ASSERT(aURI);
    733 
    734  nsAutoCString scheme;
    735  nsresult rv = aURI->GetScheme(scheme);
    736  if (NS_WARN_IF(NS_FAILED(rv))) {
    737    return nsICookie::SCHEME_UNSET;
    738  }
    739 
    740  return SchemeToSchemeType(scheme);
    741 }
    742 
    743 // static
    744 nsICookie::schemeType CookieCommons::PrincipalToSchemeType(
    745    nsIPrincipal* aPrincipal) {
    746  MOZ_ASSERT(aPrincipal);
    747 
    748  nsAutoCString scheme;
    749  nsresult rv = aPrincipal->GetScheme(scheme);
    750  if (NS_WARN_IF(NS_FAILED(rv))) {
    751    return nsICookie::SCHEME_UNSET;
    752  }
    753 
    754  return SchemeToSchemeType(scheme);
    755 }
    756 
    757 // static
    758 nsICookie::schemeType CookieCommons::SchemeToSchemeType(
    759    const nsACString& aScheme) {
    760  MOZ_ASSERT(IsSchemeSupported(aScheme));
    761 
    762  if (aScheme.Equals("https")) {
    763    return nsICookie::SCHEME_HTTPS;
    764  }
    765 
    766  if (aScheme.Equals("http")) {
    767    return nsICookie::SCHEME_HTTP;
    768  }
    769 
    770  if (aScheme.Equals("file")) {
    771    return nsICookie::SCHEME_FILE;
    772  }
    773 
    774  MOZ_CRASH("Unsupported scheme type");
    775 }
    776 
    777 // static
    778 bool CookieCommons::IsSchemeSupported(nsIPrincipal* aPrincipal) {
    779  MOZ_ASSERT(aPrincipal);
    780 
    781  nsAutoCString scheme;
    782  nsresult rv = aPrincipal->GetScheme(scheme);
    783  if (NS_WARN_IF(NS_FAILED(rv))) {
    784    return false;
    785  }
    786 
    787  return IsSchemeSupported(scheme);
    788 }
    789 
    790 // static
    791 bool CookieCommons::IsSchemeSupported(nsIURI* aURI) {
    792  MOZ_ASSERT(aURI);
    793 
    794  nsAutoCString scheme;
    795  nsresult rv = aURI->GetScheme(scheme);
    796  if (NS_WARN_IF(NS_FAILED(rv))) {
    797    return false;
    798  }
    799 
    800  return IsSchemeSupported(scheme);
    801 }
    802 
    803 // static
    804 bool CookieCommons::IsSchemeSupported(const nsACString& aScheme) {
    805  return aScheme.Equals("https") || aScheme.Equals("http") ||
    806         aScheme.Equals("file");
    807 }
    808 
    809 // static
    810 bool CookieCommons::ChipsLimitEnabledAndChipsCookie(
    811    const Cookie& cookie, dom::BrowsingContext* aBrowsingContext) {
    812  bool tcpEnabled = false;
    813  if (aBrowsingContext) {
    814    dom::CanonicalBrowsingContext* canonBC = aBrowsingContext->Canonical();
    815    if (canonBC) {
    816      dom::WindowGlobalParent* windowParent = canonBC->GetCurrentWindowGlobal();
    817      if (windowParent) {
    818        nsCOMPtr<nsICookieJarSettings> cjs = windowParent->CookieJarSettings();
    819        tcpEnabled = cjs->GetPartitionForeign();
    820      }
    821    }
    822  } else {
    823    // calls coming from addNative have no document, channel or browsingContext
    824    // to determine if TCP is enabled, so we just create a cookieJarSettings to
    825    // check the pref.
    826    nsCOMPtr<nsICookieJarSettings> cjs;
    827    cjs = CookieJarSettings::Create(CookieJarSettings::eRegular, false);
    828    tcpEnabled = cjs->GetPartitionForeign();
    829  }
    830 
    831  return StaticPrefs::network_cookie_CHIPS_enabled() &&
    832         StaticPrefs::network_cookie_chips_partitionLimitEnabled() &&
    833         cookie.IsPartitioned() && cookie.RawIsPartitioned() && tcpEnabled;
    834 }
    835 
    836 void CookieCommons::ComposeCookieString(nsTArray<RefPtr<Cookie>>& aCookieList,
    837                                        nsACString& aCookieString) {
    838  for (Cookie* cookie : aCookieList) {
    839    // check if we have anything to write
    840    if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
    841      // if we've already added a cookie to the return list, append a "; " so
    842      // that subsequent cookies are delimited in the final list.
    843      if (!aCookieString.IsEmpty()) {
    844        aCookieString.AppendLiteral("; ");
    845      }
    846 
    847      if (!cookie->Name().IsEmpty()) {
    848        // we have a name and value - write both
    849        aCookieString += cookie->Name() + "="_ns + cookie->Value();
    850      } else {
    851        // just write value
    852        aCookieString += cookie->Value();
    853      }
    854    }
    855  }
    856 }
    857 
    858 // static
    859 CookieCommons::SecurityChecksResult
    860 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
    861    Document* aDocument, nsIPrincipal** aCookiePrincipal,
    862    nsIPrincipal** aCookiePartitionedPrincipal) {
    863  MOZ_ASSERT(aCookiePrincipal);
    864 
    865  nsCOMPtr<nsIPrincipal> cookiePrincipal;
    866  nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal;
    867 
    868  if (!NS_IsMainThread()) {
    869    MOZ_ASSERT(!aDocument);
    870 
    871    dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
    872    MOZ_ASSERT(workerPrivate);
    873 
    874    StorageAccess storageAccess = workerPrivate->StorageAccess();
    875    if (storageAccess == StorageAccess::eDeny) {
    876      return SecurityChecksResult::eDoNotContinue;
    877    }
    878 
    879    cookiePrincipal = workerPrivate->GetPrincipal();
    880    if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) {
    881      return SecurityChecksResult::eSecurityError;
    882    }
    883 
    884    // CHIPS - If CHIPS is enabled the partitioned cookie jar is always
    885    // available (and therefore the partitioned principal), the unpartitioned
    886    // cookie jar is only available in first-party or third-party with
    887    // storageAccess contexts. In both cases, the Worker will have storage
    888    // access.
    889    bool isCHIPS =
    890        StaticPrefs::network_cookie_CHIPS_enabled() &&
    891        !workerPrivate->CookieJarSettings()->GetBlockingAllContexts();
    892    bool workerHasStorageAccess =
    893        workerPrivate->StorageAccess() == StorageAccess::eAllow;
    894 
    895    if (isCHIPS && workerHasStorageAccess) {
    896      // Only retrieve the partitioned originAttributes if the partitionKey is
    897      // set. The partitionKey could be empty for partitionKey in partitioned
    898      // originAttributes if the aWorker is for privilege context, such as the
    899      // extension's background page.
    900      nsCOMPtr<nsIPrincipal> partitionedPrincipal =
    901          workerPrivate->GetPartitionedPrincipal();
    902      if (partitionedPrincipal && !partitionedPrincipal->OriginAttributesRef()
    903                                       .mPartitionKey.IsEmpty()) {
    904        cookiePartitionedPrincipal = partitionedPrincipal;
    905      }
    906    }
    907  } else {
    908    if (!aDocument) {
    909      return SecurityChecksResult::eDoNotContinue;
    910    }
    911 
    912    // If the document's sandboxed origin flag is set, then reading cookies
    913    // is prohibited.
    914    if (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN) {
    915      return SecurityChecksResult::eSandboxedError;
    916    }
    917 
    918    cookiePrincipal = aDocument->EffectiveCookiePrincipal();
    919    if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) {
    920      return SecurityChecksResult::eSecurityError;
    921    }
    922 
    923    if (aDocument->CookieAccessDisabled()) {
    924      return SecurityChecksResult::eDoNotContinue;
    925    }
    926 
    927    // GTests do not create an inner window and because of these a few security
    928    // checks will block this method.
    929    if (!StaticPrefs::dom_cookie_testing_enabled()) {
    930      StorageAccess storageAccess = CookieAllowedForDocument(aDocument);
    931      if (storageAccess == StorageAccess::eDeny) {
    932        return SecurityChecksResult::eDoNotContinue;
    933      }
    934 
    935      if (ShouldPartitionStorage(storageAccess) &&
    936          !StoragePartitioningEnabled(storageAccess,
    937                                      aDocument->CookieJarSettings())) {
    938        return SecurityChecksResult::eDoNotContinue;
    939      }
    940 
    941      // If the document is a cookie-averse Document... return the empty string.
    942      if (aDocument->IsCookieAverse()) {
    943        return SecurityChecksResult::eDoNotContinue;
    944      }
    945    }
    946 
    947    // CHIPS - If CHIPS is enabled the partitioned cookie jar is always
    948    // available (and therefore the partitioned principal), the unpartitioned
    949    // cookie jar is only available in first-party or third-party with
    950    // storageAccess contexts. In both cases, the aDocument will have storage
    951    // access.
    952    bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() &&
    953                   !aDocument->CookieJarSettings()->GetBlockingAllContexts();
    954    bool documentHasStorageAccess = false;
    955    nsresult rv = aDocument->HasStorageAccessSync(documentHasStorageAccess);
    956    if (NS_WARN_IF(NS_FAILED(rv))) {
    957      return SecurityChecksResult::eDoNotContinue;
    958    }
    959 
    960    if (isCHIPS && documentHasStorageAccess) {
    961      // Only append the partitioned originAttributes if the partitionKey is
    962      // set. The partitionKey could be empty for partitionKey in partitioned
    963      // originAttributes if the aDocument is for privilege context, such as the
    964      // extension's background page.
    965      if (!aDocument->PartitionedPrincipal()
    966               ->OriginAttributesRef()
    967               .mPartitionKey.IsEmpty()) {
    968        cookiePartitionedPrincipal = aDocument->PartitionedPrincipal();
    969      }
    970    }
    971  }
    972 
    973  if (!IsSchemeSupported(cookiePrincipal)) {
    974    return SecurityChecksResult::eDoNotContinue;
    975  }
    976 
    977  cookiePrincipal.forget(aCookiePrincipal);
    978 
    979  if (aCookiePartitionedPrincipal) {
    980    cookiePartitionedPrincipal.forget(aCookiePartitionedPrincipal);
    981  }
    982 
    983  return SecurityChecksResult::eContinue;
    984 }
    985 // static
    986 void CookieCommons::GetServerDateHeader(nsIChannel* aChannel,
    987                                        nsACString& aServerDateHeader) {
    988  if (!aChannel) {
    989    return;
    990  }
    991 
    992  nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aChannel);
    993  if (NS_WARN_IF(!channel)) {
    994    return;
    995  }
    996 
    997  (void)channel->GetResponseHeader("Date"_ns, aServerDateHeader);
    998 }
    999 
   1000 // static
   1001 int64_t CookieCommons::MaybeCapExpiry(int64_t aCurrentTimeInMSec,
   1002                                      int64_t aExpiryInMSec) {
   1003  const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap();
   1004 
   1005  if (maxageCap) {
   1006    aExpiryInMSec =
   1007        std::min(aExpiryInMSec, aCurrentTimeInMSec + maxageCap * 1000);
   1008  }
   1009 
   1010  return aExpiryInMSec;
   1011 }
   1012 
   1013 int64_t CookieCommons::MaybeCapMaxAge(int64_t aCurrentTimeInMSec,
   1014                                      int64_t aMaxAgeInSec) {
   1015  const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap();
   1016  CheckedInt<int64_t> value(aCurrentTimeInMSec);
   1017 
   1018  value +=
   1019      (maxageCap ? std::min(aMaxAgeInSec, maxageCap) : aMaxAgeInSec) * 1000;
   1020  return value.isValid() ? value.value() : INT64_MAX;
   1021 }
   1022 
   1023 // static
   1024 bool CookieCommons::IsSubdomainOf(const nsACString& a, const nsACString& b) {
   1025  if (a == b) {
   1026    return true;
   1027  }
   1028  if (a.Length() > b.Length()) {
   1029    return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
   1030  }
   1031  return false;
   1032 }
   1033 
   1034 // static
   1035 int64_t CookieCommons::GetCurrentTimeInUSecFromChannel(nsIChannel* aChannel) {
   1036  nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aChannel);
   1037  if (timedChannel) {
   1038    PRTime currentTimeInUSec = 0;
   1039    nsresult rv = timedChannel->GetResponseStartTime(&currentTimeInUSec);
   1040    if (NS_SUCCEEDED(rv) && currentTimeInUSec) {
   1041      return currentTimeInUSec;
   1042    }
   1043  }
   1044 
   1045  return PR_Now();
   1046 }
   1047 
   1048 }  // namespace net
   1049 }  // namespace mozilla