tor-browser

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

CookieParser.cpp (26085B)


      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 "CookieParser.h"
      7 #include "CookieLogging.h"
      8 #include "CookieValidation.h"
      9 
     10 #include "mozilla/CheckedInt.h"
     11 #include "mozilla/glean/NetwerkMetrics.h"
     12 #include "mozilla/net/Cookie.h"
     13 #include "mozilla/StaticPrefs_network.h"
     14 #include "mozilla/TextUtils.h"
     15 #include "nsIConsoleReportCollector.h"
     16 #include "nsIScriptError.h"
     17 #include "nsIURI.h"
     18 #include "nsIURL.h"
     19 #include "prprf.h"
     20 
     21 constexpr char ATTRIBUTE_PATH[] = "path";
     22 constexpr uint64_t ATTRIBUTE_MAX_LENGTH = 1024;
     23 
     24 constexpr auto CONSOLE_INVALID_ATTRIBUTE_CATEGORY = "cookieInvalidAttribute"_ns;
     25 
     26 namespace mozilla {
     27 namespace net {
     28 
     29 CookieParser::CookieParser(nsIConsoleReportCollector* aCRC, nsIURI* aHostURI)
     30    : mCRC(aCRC), mHostURI(aHostURI) {
     31  MOZ_COUNT_CTOR(CookieParser);
     32  MOZ_ASSERT(aCRC);
     33  MOZ_ASSERT(aHostURI);
     34 }
     35 
     36 CookieParser::~CookieParser() {
     37  MOZ_COUNT_DTOR(CookieParser);
     38 
     39  if (mValidation) {
     40    mValidation->ReportErrorsAndWarnings(mCRC, mHostURI);
     41  }
     42 
     43 #define COOKIE_LOGGING_WITH_NAME(category, x)                 \
     44  CookieLogging::LogMessageToConsole(                         \
     45      mCRC, mHostURI, nsIScriptError::errorFlag, category, x, \
     46      AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(mCookieData.name())});
     47 
     48  switch (mRejection) {
     49    case NoRejection:
     50      break;
     51 
     52    case RejectedInvalidCharAttributes:
     53      COOKIE_LOGGING_WITH_NAME(CONSOLE_REJECTION_CATEGORY,
     54                               "CookieRejectedInvalidCharAttributes"_ns);
     55      break;
     56 
     57    case RejectedHttpOnlyButFromScript:
     58      COOKIE_LOGGING_WITH_NAME(CONSOLE_REJECTION_CATEGORY,
     59                               "CookieRejectedHttpOnlyButFromScript"_ns);
     60      break;
     61 
     62    case RejectedForeignNoPartitionedError:
     63      COOKIE_LOGGING_WITH_NAME(CONSOLE_CHIPS_CATEGORY,
     64                               "CookieForeignNoPartitionedError"_ns);
     65      break;
     66 
     67    case RejectedByPermissionManager:
     68      COOKIE_LOGGING_WITH_NAME(CONSOLE_REJECTION_CATEGORY,
     69                               "CookieRejectedByPermissionManager"_ns);
     70      break;
     71 
     72    case RejectedNonsecureOverSecure:
     73      COOKIE_LOGGING_WITH_NAME(CONSOLE_REJECTION_CATEGORY,
     74                               "CookieRejectedNonsecureOverSecure"_ns);
     75      break;
     76  }
     77 
     78 #undef COOKIE_LOGGING_WITH_NAME
     79 
     80  if (mRejection != NoRejection || !mValidation ||
     81      mValidation->Result() != nsICookieValidation::eOK) {
     82    return;
     83  }
     84 
     85  for (const char* attribute : mWarnings.mAttributeOversize) {
     86    AutoTArray<nsString, 3> params = {NS_ConvertUTF8toUTF16(mCookieData.name()),
     87                                      NS_ConvertUTF8toUTF16(attribute)};
     88 
     89    nsString size;
     90    size.AppendInt(ATTRIBUTE_MAX_LENGTH);
     91    params.AppendElement(size);
     92 
     93    CookieLogging::LogMessageToConsole(
     94        mCRC, mHostURI, nsIScriptError::warningFlag, CONSOLE_OVERSIZE_CATEGORY,
     95        "CookieAttributeIgnored"_ns, params);
     96  }
     97 
     98  for (const char* attribute : mWarnings.mAttributeOverwritten) {
     99    CookieLogging::LogMessageToConsole(
    100        mCRC, mHostURI, nsIScriptError::warningFlag,
    101        CONSOLE_INVALID_ATTRIBUTE_CATEGORY, "CookieAttributeOverwritten"_ns,
    102        AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(mCookieData.name()),
    103                                NS_ConvertUTF8toUTF16(attribute)});
    104  }
    105 
    106  if (mWarnings.mInvalidSameSiteAttribute) {
    107    CookieLogging::LogMessageToConsole(
    108        mCRC, mHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
    109        "CookieSameSiteValueInvalid2"_ns,
    110        AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(mCookieData.name())});
    111  }
    112 
    113  if (mWarnings.mInvalidMaxAgeAttribute) {
    114    CookieLogging::LogMessageToConsole(
    115        mCRC, mHostURI, nsIScriptError::infoFlag,
    116        CONSOLE_INVALID_ATTRIBUTE_CATEGORY, "CookieInvalidMaxAgeAttribute"_ns,
    117        AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(mCookieData.name())});
    118  }
    119 
    120  if (mWarnings.mForeignNoPartitionedWarning) {
    121    CookieLogging::LogMessageToConsole(
    122        mCRC, mHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
    123        "CookieForeignNoPartitionedWarning"_ns,
    124        AutoTArray<nsString, 1>{
    125            NS_ConvertUTF8toUTF16(mCookieData.name()),
    126        });
    127  }
    128 }
    129 
    130 // clang-format off
    131 // The following comment block elucidates the function of ParseAttributes.
    132 /******************************************************************************
    133 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
    134 ** please note: this BNF deviates from both specifications, and reflects this
    135 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
    136 
    137 ** Differences from RFC2109/2616 and explanations:
    138    1. implied *LWS
    139         The grammar described by this specification is word-based. Except
    140         where noted otherwise, linear white space (<LWS>) can be included
    141         between any two adjacent words (token or quoted-string), and
    142         between adjacent words and separators, without changing the
    143         interpretation of a field.
    144       <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
    145 
    146    2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
    147       common use inside values.
    148 
    149    3. tokens and values have looser restrictions on allowed characters than
    150       spec. This is also due to certain characters being in common use inside
    151       values. We allow only '=' to separate token/value pairs, and ';' to
    152       terminate tokens or values. <LWS> is allowed within tokens and values
    153       (see bug 206022).
    154 
    155    4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
    156       reject control chars or non-ASCII chars. This is erring on the loose
    157       side, since there's probably no good reason to enforce this strictness.
    158 
    159    5. Attribute "HttpOnly", not covered in the RFCs, is supported
    160       (see bug 178993).
    161 
    162 ** Begin BNF:
    163    token         = 1*<any allowed-chars except separators>
    164    value         = 1*<any allowed-chars except value-sep>
    165    separators    = ";" | "="
    166    value-sep     = ";"
    167    cookie-sep    = CR | LF
    168    allowed-chars = <any OCTET except cookie-sep>
    169    OCTET         = <any 8-bit sequence of data>
    170    LWS           = SP | HT
    171    CR            = <US-ASCII CR, carriage return (13)>
    172    LF            = <US-ASCII LF, linefeed (10)>
    173    SP            = <US-ASCII SP, space (32)>
    174    HT            = <US-ASCII HT, horizontal-tab (9)>
    175 
    176    set-cookie    = "Set-Cookie:" cookies
    177    cookies       = cookie *( cookie-sep cookie )
    178    cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
    179    NAME          = token                                ; cookie name
    180    VALUE         = value                                ; cookie value
    181    cookie-av     = token ["=" value]
    182 
    183    valid values for cookie-av (checked post-parsing) are:
    184    cookie-av     = "Path"    "=" value
    185                  | "Domain"  "=" value
    186                  | "Expires" "=" value
    187                  | "Max-Age" "=" value
    188                  | "Comment" "=" value
    189                  | "Version" "=" value
    190                  | "Partitioned"
    191                  | "SameSite"
    192                  | "Secure"
    193                  | "HttpOnly"
    194 
    195 ******************************************************************************/
    196 // clang-format on
    197 
    198 // helper functions for GetTokenValue
    199 static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
    200 static inline bool isvalueseparator(char c) { return c == ';'; }
    201 static inline bool istokenseparator(char c) {
    202  return isvalueseparator(c) || c == '=';
    203 }
    204 
    205 // Parse a single token/value pair.
    206 // static
    207 void CookieParser::GetTokenValue(nsACString::const_char_iterator& aIter,
    208                                 nsACString::const_char_iterator& aEndIter,
    209                                 nsDependentCSubstring& aTokenString,
    210                                 nsDependentCSubstring& aTokenValue,
    211                                 bool& aEqualsFound) {
    212  nsACString::const_char_iterator start;
    213  nsACString::const_char_iterator lastSpace;
    214  // initialize value string to clear garbage
    215  aTokenValue.Rebind(aIter, aIter);
    216 
    217  // find <token>, including any <LWS> between the end-of-token and the
    218  // token separator. we'll remove trailing <LWS> next
    219  while (aIter != aEndIter && iswhitespace(*aIter)) {
    220    ++aIter;
    221  }
    222  start = aIter;
    223  while (aIter != aEndIter && !istokenseparator(*aIter)) {
    224    ++aIter;
    225  }
    226 
    227  // remove trailing <LWS>; first check we're not at the beginning
    228  lastSpace = aIter;
    229  if (lastSpace != start) {
    230    while (--lastSpace != start && iswhitespace(*lastSpace)) {
    231    }
    232    ++lastSpace;
    233  }
    234  aTokenString.Rebind(start, lastSpace);
    235 
    236  aEqualsFound = (*aIter == '=');
    237  if (aEqualsFound) {
    238    // find <value>
    239    while (++aIter != aEndIter && iswhitespace(*aIter)) {
    240    }
    241 
    242    start = aIter;
    243 
    244    // process <token>
    245    // just look for ';' to terminate ('=' allowed)
    246    while (aIter != aEndIter && !isvalueseparator(*aIter)) {
    247      ++aIter;
    248    }
    249 
    250    // remove trailing <LWS>; first check we're not at the beginning
    251    if (aIter != start) {
    252      lastSpace = aIter;
    253      while (--lastSpace != start && iswhitespace(*lastSpace)) {
    254      }
    255 
    256      aTokenValue.Rebind(start, ++lastSpace);
    257    }
    258  }
    259 
    260  // aIter is on ';', or terminator, or EOS
    261  if (aIter != aEndIter) {
    262    // fall-through: aIter is on ';', increment and return
    263    ++aIter;
    264  }
    265 }
    266 
    267 // Tests for control characters, defined by RFC 5234 to be %x00-1F / %x7F.
    268 // An exception is made for HTAB as the cookie spec treats that as whitespace.
    269 static bool ContainsControlChars(const nsACString& aString) {
    270  const auto* start = aString.BeginReading();
    271  const auto* end = aString.EndReading();
    272 
    273  return std::find_if(start, end, [](unsigned char c) {
    274           return (c <= 0x1F && c != 0x09) || c == 0x7F;
    275         }) != end;
    276 }
    277 
    278 // static
    279 bool CookieParser::CheckAttributeSize(const nsACString& currentValue,
    280                                      const char* aAttribute,
    281                                      const nsACString& aValue,
    282                                      CookieParser* aParser) {
    283  if (aValue.Length() > ATTRIBUTE_MAX_LENGTH) {
    284    if (aParser) {
    285      aParser->mWarnings.mAttributeOversize.AppendElement(aAttribute);
    286    }
    287    return false;
    288  }
    289 
    290  if (!currentValue.IsEmpty()) {
    291    if (aParser) {
    292      aParser->mWarnings.mAttributeOverwritten.AppendElement(aAttribute);
    293    }
    294  }
    295 
    296  return true;
    297 }
    298 
    299 // Parses attributes from cookie header. expires/max-age attributes aren't
    300 // folded into the cookie struct here, because we don't know which one to use
    301 // until we've parsed the header.
    302 void CookieParser::ParseAttributes(nsCString& aCookieHeader,
    303                                   nsACString& aExpires, nsACString& aMaxage,
    304                                   bool& aAcceptedByParser) {
    305  aAcceptedByParser = false;
    306 
    307  static const char kDomain[] = "domain";
    308  static const char kExpires[] = "expires";
    309  static const char kMaxage[] = "max-age";
    310  static const char kSecure[] = "secure";
    311  static const char kHttpOnly[] = "httponly";
    312  static const char kSameSite[] = "samesite";
    313  static const char kSameSiteLax[] = "lax";
    314  static const char kSameSiteNone[] = "none";
    315  static const char kSameSiteStrict[] = "strict";
    316  static const char kPartitioned[] = "partitioned";
    317 
    318  nsACString::const_char_iterator cookieStart;
    319  aCookieHeader.BeginReading(cookieStart);
    320 
    321  nsACString::const_char_iterator cookieEnd;
    322  aCookieHeader.EndReading(cookieEnd);
    323 
    324  mCookieData.isSecure() = false;
    325  mCookieData.isHttpOnly() = false;
    326  mCookieData.isPartitioned() = false;
    327  mCookieData.sameSite() = nsICookie::SAMESITE_UNSET;
    328 
    329  nsDependentCSubstring tokenString(cookieStart, cookieStart);
    330  nsDependentCSubstring tokenValue(cookieStart, cookieStart);
    331  bool equalsFound;
    332 
    333  // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
    334  // note: if there's no '=', we assume token is <VALUE>. this is required by
    335  //       some sites (see bug 169091).
    336  // XXX fix the parser to parse according to <VALUE> grammar for this case
    337  GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
    338  if (equalsFound) {
    339    mCookieData.name() = tokenString;
    340    mCookieData.value() = tokenValue;
    341  } else {
    342    mCookieData.value() = tokenString;
    343  }
    344 
    345  // extract remaining attributes
    346  while (cookieStart != cookieEnd) {
    347    GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
    348 
    349    if (ContainsControlChars(tokenString) || ContainsControlChars(tokenValue)) {
    350      RejectCookie(RejectedInvalidCharAttributes);
    351      return;
    352    }
    353 
    354    // decide which attribute we have, and copy the string
    355    if (tokenString.LowerCaseEqualsLiteral(ATTRIBUTE_PATH)) {
    356      if (CheckAttributeSize(mCookieData.path(), ATTRIBUTE_PATH, tokenValue,
    357                             this)) {
    358        mCookieData.path() = tokenValue;
    359      }
    360 
    361    } else if (tokenString.LowerCaseEqualsLiteral(kDomain)) {
    362      if (CheckAttributeSize(mCookieData.host(), kDomain, tokenValue, this)) {
    363        mCookieData.host() = tokenValue;
    364      }
    365 
    366    } else if (tokenString.LowerCaseEqualsLiteral(kExpires)) {
    367      if (CheckAttributeSize(aExpires, kExpires, tokenValue, this)) {
    368        aExpires = tokenValue;
    369      }
    370 
    371    } else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) {
    372      if (CheckAttributeSize(aMaxage, kMaxage, tokenValue, this)) {
    373        aMaxage = tokenValue;
    374      }
    375 
    376      // ignore any tokenValue for isSecure; just set the boolean
    377    } else if (tokenString.LowerCaseEqualsLiteral(kSecure)) {
    378      mCookieData.isSecure() = true;
    379 
    380      // ignore any tokenValue for isPartitioned; just set the boolean
    381    } else if (tokenString.LowerCaseEqualsLiteral(kPartitioned)) {
    382      mCookieData.isPartitioned() = true;
    383 
    384      // ignore any tokenValue for isHttpOnly (see bug 178993);
    385      // just set the boolean
    386    } else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) {
    387      mCookieData.isHttpOnly() = true;
    388 
    389    } else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
    390      if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
    391        mCookieData.sameSite() = nsICookie::SAMESITE_LAX;
    392      } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
    393        mCookieData.sameSite() = nsICookie::SAMESITE_STRICT;
    394      } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
    395        mCookieData.sameSite() = nsICookie::SAMESITE_NONE;
    396      } else {
    397        // Reset to Default if unknown token value (see Bug 1682450)
    398        mCookieData.sameSite() = nsICookie::SAMESITE_UNSET;
    399        mWarnings.mInvalidSameSiteAttribute = true;
    400      }
    401    }
    402  }
    403 
    404  // Cookie accepted.
    405  aAcceptedByParser = true;
    406 }
    407 
    408 namespace {
    409 
    410 nsAutoCString GetPathFromURI(nsIURI* aHostURI) {
    411  // strip down everything after the last slash to get the path,
    412  // ignoring slashes in the query string part.
    413  // if we can QI to nsIURL, that'll take care of the query string portion.
    414  // otherwise, it's not an nsIURL and can't have a query string, so just find
    415  // the last slash.
    416  nsAutoCString path;
    417  nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
    418  if (hostURL) {
    419    hostURL->GetDirectory(path);
    420  } else {
    421    aHostURI->GetPathQueryRef(path);
    422    int32_t slash = path.RFindChar('/');
    423    if (slash != kNotFound) {
    424      path.Truncate(slash + 1);
    425    }
    426  }
    427 
    428  // strip the right-most %x2F ("/") if the path doesn't contain only 1 '/'.
    429  int32_t lastSlash = path.RFindChar('/');
    430  int32_t firstSlash = path.FindChar('/');
    431  if (lastSlash != firstSlash && lastSlash != kNotFound &&
    432      lastSlash == static_cast<int32_t>(path.Length() - 1)) {
    433    path.Truncate(lastSlash);
    434  }
    435 
    436  return path;
    437 }
    438 
    439 }  // namespace
    440 
    441 // static
    442 void CookieParser::FixPath(CookieStruct& aCookieData, nsIURI* aHostURI) {
    443  // if a path is given, check the host has permission
    444  if (aCookieData.path().IsEmpty() || aCookieData.path().First() != '/') {
    445    nsAutoCString path = GetPathFromURI(aHostURI);
    446    if (CheckAttributeSize(aCookieData.path(), ATTRIBUTE_PATH, path)) {
    447      aCookieData.path() = path;
    448    }
    449  }
    450 }
    451 
    452 bool CookieParser::ParseMaxAgeAttribute(const nsACString& aMaxage,
    453                                        int64_t* aValue) {
    454  MOZ_ASSERT(aValue);
    455 
    456  if (aMaxage.IsEmpty()) {
    457    return false;
    458  }
    459 
    460  nsACString::const_char_iterator iter;
    461  aMaxage.BeginReading(iter);
    462 
    463  nsACString::const_char_iterator end;
    464  aMaxage.EndReading(end);
    465 
    466  // Negative values mean that the cookie is already expired. Don't bother to
    467  // parse.
    468  if (*iter == '-') {
    469    *aValue = INT64_MIN;
    470    return true;
    471  }
    472 
    473  CheckedInt<int64_t> value(0);
    474 
    475  for (; iter != end; ++iter) {
    476    if (!mozilla::IsAsciiDigit(*iter)) {
    477      mWarnings.mInvalidMaxAgeAttribute = true;
    478      return false;
    479    }
    480 
    481    value *= 10;
    482    if (!value.isValid()) {
    483      *aValue = INT64_MAX;
    484      return true;
    485    }
    486 
    487    value += *iter - '0';
    488    if (!value.isValid()) {
    489      *aValue = INT64_MAX;
    490      return true;
    491    }
    492  }
    493 
    494  *aValue = value.value();
    495  return true;
    496 }
    497 
    498 bool CookieParser::GetExpiry(CookieStruct& aCookieData,
    499                             const nsACString& aExpires,
    500                             const nsACString& aMaxage,
    501                             const nsACString& aDateHeader, bool aFromHttp) {
    502  int64_t creationTimeInMSec =
    503      aCookieData.creationTimeInUSec() / int64_t(PR_USEC_PER_MSEC);
    504 
    505  /* Determine when the cookie should expire. This is done by taking the
    506   * difference between the server time and the time the server wants the cookie
    507   * to expire, and adding that difference to the client time. This localizes
    508   * the client time regardless of whether or not the TZ environment variable
    509   * was set on the client.
    510   *
    511   * Note: We need to consider accounting for network lag here, per RFC.
    512   */
    513  // check for max-age attribute first; this overrides expires attribute
    514  int64_t maxage = 0;
    515  if (ParseMaxAgeAttribute(aMaxage, &maxage)) {
    516    if (maxage == INT64_MIN) {
    517      aCookieData.expiryInMSec() = maxage;
    518    } else {
    519      aCookieData.expiryInMSec() =
    520          CookieCommons::MaybeCapMaxAge(creationTimeInMSec, maxage);
    521    }
    522 
    523    return false;
    524  }
    525 
    526  // check for expires attribute
    527  if (!aExpires.IsEmpty()) {
    528    // parse expiry time
    529    PRTime expiresTimeInUSec;
    530    if (PR_ParseTimeString(aExpires.BeginReading(), true, &expiresTimeInUSec) !=
    531        PR_SUCCESS) {
    532      return true;
    533    }
    534 
    535    int64_t expiresInMSec = expiresTimeInUSec / int64_t(PR_USEC_PER_MSEC);
    536 
    537    // If we have the server time, we can adjust the "expire" attribute value
    538    // by adding the delta between the server and the local times.  If the
    539    // current time is set in the future, we can consider valid cookies that
    540    // are not expired for the server.
    541    if (!aDateHeader.IsEmpty()) {
    542      MOZ_ASSERT(aFromHttp);
    543 
    544      PRTime dateHeaderTimeInUSec;
    545      if (PR_ParseTimeString(aDateHeader.BeginReading(), true,
    546                             &dateHeaderTimeInUSec) == PR_SUCCESS &&
    547          StaticPrefs::network_cookie_useServerTime()) {
    548        int64_t serverTimeInMSec =
    549            dateHeaderTimeInUSec / int64_t(PR_USEC_PER_MSEC);
    550        int64_t delta = creationTimeInMSec - serverTimeInMSec;
    551        expiresInMSec += delta;
    552      }
    553    }
    554 
    555    // If set-cookie used absolute time to set expiration, and it can't use
    556    // client time to set expiration.
    557    // Because if current time be set in the future, but the cookie expire
    558    // time be set less than current time and more than server time.
    559    // The cookie item have to be used to the expired cookie.
    560 
    561    aCookieData.expiryInMSec() =
    562        CookieCommons::MaybeCapExpiry(creationTimeInMSec, expiresInMSec);
    563    return false;
    564  }
    565 
    566  // default to session cookie if no attributes found.  Here we don't need to
    567  // enforce the maxage cap, because session cookies are short-lived by
    568  // definition.
    569  return true;
    570 }
    571 
    572 // static
    573 void CookieParser::FixDomain(CookieStruct& aCookieData, nsIURI* aHostURI,
    574                             const nsACString& aBaseDomain,
    575                             bool aRequireHostMatch) {
    576  // Note: The logic in this function is mirrored in
    577  // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
    578  // If it changes, please update that function, or file a bug for someone
    579  // else to do so.
    580 
    581  // get host from aHostURI
    582  nsAutoCString hostFromURI;
    583  nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI);
    584 
    585  // no domain specified, use hostFromURI
    586  if (aCookieData.host().IsEmpty()) {
    587    aCookieData.host() = hostFromURI;
    588    return;
    589  }
    590 
    591  nsCString cookieHost = aCookieData.host();
    592 
    593  // Tolerate leading '.' characters, but not if it's otherwise an empty host.
    594  if (cookieHost.Length() > 1 && cookieHost.First() == '.') {
    595    cookieHost.Cut(0, 1);
    596  }
    597 
    598  // switch to lowercase now, to avoid case-insensitive compares everywhere
    599  ToLowerCase(cookieHost);
    600 
    601  if (aRequireHostMatch) {
    602    // check whether the host is either an IP address, an alias such as
    603    // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
    604    // cases, require an exact string match for the domain, and leave the cookie
    605    // as a non-domain one. bug 105917 originally noted the requirement to deal
    606    // with IP addresses.
    607    if (hostFromURI.Equals(cookieHost)) {
    608      aCookieData.host() = cookieHost;
    609    }
    610 
    611    // If the match fails, we keep the aCookieData.Host() as it was. The
    612    // Validator will reject the cookie with the correct reason.
    613    return;
    614  }
    615 
    616  // ensure the proposed domain is derived from the base domain; and also
    617  // that the host domain is derived from the proposed domain (per RFC2109).
    618  if (CookieCommons::IsSubdomainOf(cookieHost, aBaseDomain) &&
    619      CookieCommons::IsSubdomainOf(hostFromURI, cookieHost)) {
    620    // prepend a dot to indicate a domain cookie
    621    cookieHost.InsertLiteral(".", 0);
    622    aCookieData.host() = cookieHost;
    623  }
    624 
    625  /*
    626   * note: RFC2109 section 4.3.2 requires that we check the following:
    627   * that the portion of host not in domain does not contain a dot.
    628   * this prevents hosts of the form x.y.co.nz from setting cookies in the
    629   * entire .co.nz domain. however, it's only a only a partial solution and
    630   * it breaks sites (IE doesn't enforce it), so we don't perform this check.
    631   */
    632 }
    633 
    634 // Main entry point for cookie parsing. Parses a single cookie string
    635 // (from either document.cookie or a Set-Cookie header) and populates
    636 // the internal CookieStruct data.
    637 void CookieParser::Parse(const nsACString& aBaseDomain, bool aRequireHostMatch,
    638                         CookieStatus aStatus, nsCString& aCookieHeader,
    639                         const nsACString& aDateHeader, bool aFromHttp,
    640                         bool aIsForeignAndNotAddon, bool aPartitionedOnly,
    641                         bool aIsInPrivateBrowsing, bool aOn3pcbException,
    642                         int64_t aCurrentTimeInUSec) {
    643  MOZ_ASSERT(!mValidation);
    644 
    645  // init expiryTime such that session cookies won't prematurely expire
    646  mCookieData.expiryInMSec() = INT64_MAX;
    647  mCookieData.creationTimeInUSec() =
    648      Cookie::GenerateUniqueCreationTimeInUSec(aCurrentTimeInUSec);
    649  mCookieData.updateTimeInUSec() = mCookieData.creationTimeInUSec();
    650 
    651  mCookieData.schemeMap() = CookieCommons::URIToSchemeType(mHostURI);
    652 
    653  // aCookieHeader is an in/out param to point to the next cookie, if
    654  // there is one. Save the present value for logging purposes
    655  mCookieString.Assign(aCookieHeader);
    656 
    657  // newCookie says whether there are multiple cookies in the header;
    658  // so we can handle them separately.
    659  nsAutoCString expires;
    660  nsAutoCString maxage;
    661  bool acceptedByParser = false;
    662  ParseAttributes(aCookieHeader, expires, maxage, acceptedByParser);
    663  if (!acceptedByParser) {
    664    return;
    665  }
    666 
    667  // calculate expiry time of cookie.
    668  mCookieData.isSession() =
    669      GetExpiry(mCookieData, expires, maxage, aDateHeader, aFromHttp);
    670  if (aStatus == STATUS_ACCEPT_SESSION) {
    671    // force lifetime to session. note that the expiration time, if set above,
    672    // will still apply.
    673    mCookieData.isSession() = true;
    674  }
    675 
    676  FixDomain(mCookieData, mHostURI, aBaseDomain, aRequireHostMatch);
    677  FixPath(mCookieData, mHostURI);
    678 
    679  // If the cookie is on the 3pcd exception list, we apply partitioned
    680  // attribute to the cookie.
    681  if (aOn3pcbException) {
    682    // We send a warning if the cookie doesn't have the partitioned attribute
    683    // in the foreign context.
    684    if (aPartitionedOnly && !mCookieData.isPartitioned() &&
    685        aIsForeignAndNotAddon) {
    686      mWarnings.mForeignNoPartitionedWarning = true;
    687    }
    688 
    689    mCookieData.isPartitioned() = true;
    690  }
    691 
    692  // If the cookie does not have the partitioned attribute,
    693  // but is foreign we should give the developer a message.
    694  // If CHIPS isn't required yet, we will warn the console
    695  // that we have upcoming changes. Otherwise we give a rejection message.
    696  if (aPartitionedOnly && !mCookieData.isPartitioned() &&
    697      aIsForeignAndNotAddon) {
    698    if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() ||
    699        (aIsInPrivateBrowsing &&
    700         StaticPrefs::
    701             network_cookie_cookieBehavior_optInPartitioning_pbmode())) {
    702      RejectCookie(RejectedForeignNoPartitionedError);
    703      return;
    704    }
    705 
    706    mWarnings.mForeignNoPartitionedWarning = true;
    707  }
    708 
    709  mValidation = CookieValidation::ValidateInContext(
    710      mCookieData, mHostURI, aBaseDomain, aRequireHostMatch, aFromHttp,
    711      aIsForeignAndNotAddon, aPartitionedOnly, aIsInPrivateBrowsing);
    712  MOZ_ASSERT(mValidation);
    713 
    714  if (mValidation->Result() != nsICookieValidation::eOK) {
    715    return;
    716  }
    717 }
    718 
    719 void CookieParser::RejectCookie(Rejection aRejection) {
    720  MOZ_ASSERT(mRejection == NoRejection);
    721  MOZ_ASSERT(aRejection != NoRejection);
    722  mRejection = aRejection;
    723 }
    724 
    725 void CookieParser::GetCookieString(nsACString& aCookieString) const {
    726  aCookieString.Assign(mCookieString);
    727 }
    728 
    729 }  // namespace net
    730 }  // namespace mozilla