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