CookieStorage.cpp (44350B)
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 "CookieNotification.h" 11 #include "mozilla/net/MozURL_ffi.h" 12 #include "CookieService.h" 13 #include "nsCOMPtr.h" 14 #include "nsICookieNotification.h" 15 #include "CookieStorage.h" 16 #include "mozilla/dom/nsMixedContentBlocker.h" 17 #include "mozilla/glean/NetwerkMetrics.h" 18 #include "mozilla/StaticPrefs_network.h" 19 #include "nsIMutableArray.h" 20 #include "nsTPriorityQueue.h" 21 #include "nsIScriptError.h" 22 #include "nsIUserIdleService.h" 23 #include "nsServiceManagerUtils.h" 24 #include "nsComponentManagerUtils.h" 25 #include "prprf.h" 26 #include "nsIPrefService.h" 27 28 #undef ADD_TEN_PERCENT 29 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10) 30 31 #undef LIMIT 32 #define LIMIT(x, low, high, default) \ 33 ((x) >= (low) && (x) <= (high) ? (x) : (default)) 34 35 // in order to keep our metrics consistent 36 // we only send metrics when the pref hasn't been manipulated from the default 37 static const uint32_t kChipsPartitionByteCapacityDefault = 10240; 38 static const double kChipsHardLimitFactor = 1.2; 39 40 namespace mozilla { 41 namespace net { 42 43 // comparator class for lastaccessed times of cookies. 44 class CookieStorage::CompareCookiesByAge { 45 public: 46 static bool Equals(const CookieListIter& a, const CookieListIter& b) { 47 return a.Cookie()->LastAccessedInUSec() == 48 b.Cookie()->LastAccessedInUSec() && 49 a.Cookie()->CreationTimeInUSec() == b.Cookie()->CreationTimeInUSec(); 50 } 51 52 static bool LessThan(const CookieListIter& a, const CookieListIter& b) { 53 // compare by lastAccessedInUSec time, and tiebreak by creationTimeInUSec. 54 int64_t result = 55 a.Cookie()->LastAccessedInUSec() - b.Cookie()->LastAccessedInUSec(); 56 if (result != 0) { 57 return result < 0; 58 } 59 60 return a.Cookie()->CreationTimeInUSec() < b.Cookie()->CreationTimeInUSec(); 61 } 62 }; 63 64 // Cookie comparator for the priority queue used in FindStaleCookies. 65 // Note that the expired cookie has the highest priority. 66 // Other non-expired cookies are sorted by their age. 67 class CookieStorage::CookieIterComparator { 68 private: 69 int64_t mCurrentTimeInMSec; 70 71 public: 72 explicit CookieIterComparator(int64_t aTimeInMSec) 73 : mCurrentTimeInMSec(aTimeInMSec) {} 74 75 bool LessThan(const CookieListIter& lhs, const CookieListIter& rhs) { 76 bool lExpired = lhs.Cookie()->ExpiryInMSec() <= mCurrentTimeInMSec; 77 bool rExpired = rhs.Cookie()->ExpiryInMSec() <= mCurrentTimeInMSec; 78 if (lExpired && !rExpired) { 79 return true; 80 } 81 82 if (!lExpired && rExpired) { 83 return false; 84 } 85 86 return CompareCookiesByAge::LessThan(lhs, rhs); 87 } 88 }; 89 90 // comparator class for sorting cookies by entry and index. 91 class CookieStorage::CompareCookiesByIndex { 92 public: 93 static bool Equals(const CookieListIter& a, const CookieListIter& b) { 94 NS_ASSERTION(a.entry != b.entry || a.index != b.index, 95 "cookie indexes should never be equal"); 96 return false; 97 } 98 99 static bool LessThan(const CookieListIter& a, const CookieListIter& b) { 100 // compare by entryclass pointer, then by index. 101 if (a.entry != b.entry) { 102 return a.entry < b.entry; 103 } 104 105 return a.index < b.index; 106 } 107 }; 108 109 // --------------------------------------------------------------------------- 110 // CookieEntry 111 112 size_t CookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 113 size_t amount = CookieKey::SizeOfExcludingThis(aMallocSizeOf); 114 115 amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf); 116 for (uint32_t i = 0; i < mCookies.Length(); ++i) { 117 amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf); 118 } 119 120 return amount; 121 } 122 123 bool CookieEntry::IsPartitioned() const { 124 return !mOriginAttributes.mPartitionKey.IsEmpty(); 125 } 126 127 // --------------------------------------------------------------------------- 128 // CookieStorage 129 130 NS_IMPL_ISUPPORTS(CookieStorage, nsIObserver, nsISupportsWeakReference) 131 132 void CookieStorage::Init() { 133 // init our pref and observer 134 nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); 135 if (prefBranch) { 136 prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true); 137 prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true); 138 prefBranch->AddObserver(kPrefCookiePurgeAge, this, true); 139 PrefChanged(prefBranch); 140 } 141 142 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); 143 NS_ENSURE_TRUE_VOID(observerService); 144 145 nsresult rv = 146 observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, true); 147 NS_ENSURE_SUCCESS_VOID(rv); 148 } 149 150 size_t CookieStorage::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 151 size_t amount = 0; 152 153 amount += aMallocSizeOf(this); 154 amount += mHostTable.SizeOfExcludingThis(aMallocSizeOf); 155 156 return amount; 157 } 158 159 void CookieStorage::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const { 160 aCookies.SetCapacity(mCookieCount); 161 for (const auto& entry : mHostTable) { 162 const CookieEntry::ArrayType& cookies = entry.GetCookies(); 163 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 164 aCookies.AppendElement(cookies[i]); 165 } 166 } 167 } 168 169 void CookieStorage::GetSessionCookies( 170 nsTArray<RefPtr<nsICookie>>& aCookies) const { 171 aCookies.SetCapacity(mCookieCount); 172 for (const auto& entry : mHostTable) { 173 const CookieEntry::ArrayType& cookies = entry.GetCookies(); 174 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 175 Cookie* cookie = cookies[i]; 176 // Filter out non-session cookies. 177 if (cookie->IsSession()) { 178 aCookies.AppendElement(cookie); 179 } 180 } 181 } 182 } 183 184 // find an exact cookie specified by host, name, and path that hasn't expired. 185 already_AddRefed<Cookie> CookieStorage::FindCookie( 186 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, 187 const nsACString& aHost, const nsACString& aName, const nsACString& aPath) { 188 CookieListIter iter{}; 189 190 if (!FindCookie(aBaseDomain, aOriginAttributes, aHost, aName, aPath, iter)) { 191 return nullptr; 192 } 193 194 RefPtr<Cookie> cookie = iter.Cookie(); 195 return cookie.forget(); 196 } 197 198 // find an exact cookie specified by host, name, and path that hasn't expired. 199 bool CookieStorage::FindCookie(const nsACString& aBaseDomain, 200 const OriginAttributes& aOriginAttributes, 201 const nsACString& aHost, const nsACString& aName, 202 const nsACString& aPath, CookieListIter& aIter) { 203 CookieEntry* entry = 204 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes)); 205 if (!entry) { 206 return false; 207 } 208 209 const CookieEntry::ArrayType& cookies = entry->GetCookies(); 210 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 211 Cookie* cookie = cookies[i]; 212 213 if (aHost.Equals(cookie->Host()) && aPath.Equals(cookie->Path()) && 214 aName.Equals(cookie->Name())) { 215 aIter = CookieListIter(entry, i); 216 return true; 217 } 218 } 219 220 return false; 221 } 222 223 // find an secure cookie specified by host and name 224 bool CookieStorage::FindSecureCookie(const nsACString& aBaseDomain, 225 const OriginAttributes& aOriginAttributes, 226 Cookie* aCookie) { 227 CookieEntry* entry = 228 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes)); 229 if (!entry) { 230 return false; 231 } 232 233 const CookieEntry::ArrayType& cookies = entry->GetCookies(); 234 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 235 Cookie* cookie = cookies[i]; 236 // isn't a match if insecure or a different name 237 if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name())) { 238 continue; 239 } 240 241 // The host must "domain-match" an existing cookie or vice-versa 242 if (CookieCommons::DomainMatches(cookie, aCookie->Host()) || 243 CookieCommons::DomainMatches(aCookie, cookie->Host())) { 244 // If the path of new cookie and the path of existing cookie 245 // aren't "/", then this situation needs to compare paths to 246 // ensure only that a newly-created non-secure cookie does not 247 // overlay an existing secure cookie. 248 if (CookieCommons::PathMatches(cookie, aCookie->Path())) { 249 return true; 250 } 251 } 252 } 253 254 return false; 255 } 256 257 uint32_t CookieStorage::CountCookiesFromHost(const nsACString& aBaseDomain, 258 uint32_t aPrivateBrowsingId) { 259 OriginAttributes attrs; 260 attrs.mPrivateBrowsingId = aPrivateBrowsingId; 261 262 // Return a count of all cookies, including expired. 263 CookieEntry* entry = mHostTable.GetEntry(CookieKey(aBaseDomain, attrs)); 264 return entry ? entry->GetCookies().Length() : 0; 265 } 266 267 uint32_t CookieStorage::CountCookieBytesNotMatchingCookie( 268 const Cookie& cookie, const nsACString& baseDomain) { 269 nsTArray<RefPtr<Cookie>> cookies; 270 GetCookiesFromHost(baseDomain, cookie.OriginAttributesRef(), cookies); 271 272 // count cookies with different name to the cookie being added 273 uint32_t cookieBytes = 0; 274 for (Cookie* c : cookies) { 275 nsAutoCString name; 276 nsAutoCString value; 277 c->GetName(name); 278 c->GetValue(value); 279 if (!cookie.Name().Equals(name)) { 280 cookieBytes += name.Length() + value.Length(); 281 } 282 } 283 return cookieBytes; 284 } 285 286 void CookieStorage::GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const { 287 aResult.SetCapacity(mCookieCount); 288 289 for (const auto& entry : mHostTable) { 290 const CookieEntry::ArrayType& cookies = entry.GetCookies(); 291 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 292 aResult.AppendElement(cookies[i]); 293 } 294 } 295 } 296 297 void CookieStorage::GetCookiesFromHost( 298 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, 299 nsTArray<RefPtr<Cookie>>& aCookies) { 300 CookieEntry* entry = 301 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes)); 302 if (!entry) { 303 return; 304 } 305 306 aCookies = entry->GetCookies().Clone(); 307 } 308 309 void CookieStorage::GetCookiesWithOriginAttributes( 310 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain, 311 bool aSorted, nsTArray<RefPtr<nsICookie>>& aResult) { 312 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) { 313 CookieEntry* entry = iter.Get(); 314 315 if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) { 316 continue; 317 } 318 319 if (!aPattern.Matches(entry->mOriginAttributes)) { 320 continue; 321 } 322 323 const CookieEntry::ArrayType& entryCookies = entry->GetCookies(); 324 325 for (CookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) { 326 aResult.AppendElement(entryCookies[i]); 327 } 328 } 329 330 if (aSorted) { 331 aResult.Sort(CompareCookiesForSending()); 332 } 333 } 334 335 void CookieStorage::RemoveCookie(const nsACString& aBaseDomain, 336 const OriginAttributes& aOriginAttributes, 337 const nsACString& aHost, 338 const nsACString& aName, 339 const nsACString& aPath, bool aFromHttp, 340 const nsID* aOperationID) { 341 CookieListIter matchIter{}; 342 RefPtr<Cookie> cookie; 343 if (FindCookie(aBaseDomain, aOriginAttributes, aHost, aName, aPath, 344 matchIter)) { 345 cookie = matchIter.Cookie(); 346 347 // If the old cookie is httponly, make sure we're not coming from script. 348 if (cookie && !aFromHttp && cookie->IsHttpOnly()) { 349 return; 350 } 351 352 RemoveCookieFromList(matchIter); 353 } 354 355 if (cookie) { 356 // Everything's done. Notify observers. 357 NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED, aBaseDomain, 358 aOperationID); 359 } 360 } 361 362 void CookieStorage::RemoveCookiesWithOriginAttributes( 363 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) { 364 // Iterate the hash table of CookieEntry. 365 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) { 366 CookieEntry* entry = iter.Get(); 367 368 if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) { 369 continue; 370 } 371 372 if (!aPattern.Matches(entry->mOriginAttributes)) { 373 continue; 374 } 375 376 // Pattern matches. Delete all cookies within this CookieEntry. 377 uint32_t cookiesCount = entry->GetCookies().Length(); 378 379 for (CookieEntry::IndexType i = 0; i < cookiesCount; ++i) { 380 // Remove the first cookie from the list. 381 CookieListIter iter(entry, 0); 382 RefPtr<Cookie> cookie = iter.Cookie(); 383 384 // Remove the cookie. 385 RemoveCookieFromList(iter); 386 387 if (cookie) { 388 NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED, 389 aBaseDomain); 390 } 391 } 392 } 393 } 394 395 /* static */ bool CookieStorage::SerializeIPv6BaseDomain( 396 nsACString& aBaseDomain) { 397 bool hasStartBracket = aBaseDomain.First() == '['; 398 bool hasEndBracket = aBaseDomain.Last() == ']'; 399 400 // If only start or end bracket exists host is malformed. 401 if (hasStartBracket != hasEndBracket) { 402 return false; 403 } 404 405 // If the base domain is not in URL format (e.g. [::1]) add brackets so we 406 // can use rusturl_parse_ipv6addr(). 407 if (!hasStartBracket) { 408 aBaseDomain.Insert('[', 0); 409 aBaseDomain.Append(']'); 410 } 411 412 // Serialize base domain to "zero abbreviation" and lower-case hex 413 // representation. 414 nsAutoCString baseDomain; 415 nsresult rv = (nsresult)rusturl_parse_ipv6addr(&aBaseDomain, &baseDomain); 416 NS_ENSURE_SUCCESS(rv, false); 417 418 // Strip brackets to match principal representation. 419 aBaseDomain = Substring(baseDomain, 1, baseDomain.Length() - 2); 420 421 return true; 422 } 423 424 void CookieStorage::RemoveCookiesFromExactHost( 425 const nsACString& aHost, const nsACString& aBaseDomain, 426 const OriginAttributesPattern& aPattern) { 427 // Intermediate fix until Bug 1882259 is resolved. 428 // Bug 1860033 - Cookies do not serialize IPv6 host / base domain in contrast 429 // to principals. To allow deletion by principal serialize before comparison. 430 // We check the base domain since it is used as the CookieList key and equals 431 // the normalized (ASCII) host for IP addresses 432 // (it is equal to the CookieService::NormalizeHost() output). 433 nsAutoCString removeBaseDomain; 434 bool isIPv6 = CookieCommons::IsIPv6BaseDomain(aBaseDomain); 435 if (isIPv6) { 436 MOZ_ASSERT(!aBaseDomain.IsEmpty()); 437 // Copy base domain since argument is immutable. 438 removeBaseDomain = aBaseDomain; 439 if (NS_WARN_IF(!SerializeIPv6BaseDomain(removeBaseDomain))) { 440 // Return on malformed base domains. 441 return; 442 } 443 } 444 445 // Iterate the hash table of CookieEntry. 446 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) { 447 CookieEntry* entry = iter.Get(); 448 449 // IPv6 host / base domain cookies 450 if (isIPv6) { 451 // If we look for a IPv6 cookie skip non-IPv6 cookie entries. 452 if (!CookieCommons::IsIPv6BaseDomain(entry->mBaseDomain)) { 453 continue; 454 } 455 // Serialize IPv6 base domains before comparison. 456 // Copy base domain since argument is immutable. 457 nsAutoCString entryBaseDomain; 458 entryBaseDomain = entry->mBaseDomain; 459 if (NS_WARN_IF(!SerializeIPv6BaseDomain(entryBaseDomain))) { 460 continue; 461 } 462 if (!removeBaseDomain.Equals(entryBaseDomain)) { 463 continue; 464 } 465 // Non-IPv6 cookies 466 } else if (!aBaseDomain.Equals(entry->mBaseDomain)) { 467 continue; 468 } 469 470 if (!aPattern.Matches(entry->mOriginAttributes)) { 471 continue; 472 } 473 474 uint32_t cookiesCount = entry->GetCookies().Length(); 475 for (CookieEntry::IndexType i = cookiesCount; i != 0; --i) { 476 CookieListIter iter(entry, i - 1); 477 RefPtr<Cookie> cookie = iter.Cookie(); 478 479 // For IP addresses (ASCII normalized) host == baseDomain, we checked 480 // equality already. 481 if (!isIPv6 && !aHost.Equals(cookie->RawHost())) { 482 continue; 483 } 484 485 // Remove the cookie. 486 RemoveCookieFromList(iter); 487 488 if (cookie) { 489 NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED, 490 aBaseDomain); 491 } 492 } 493 } 494 } 495 496 void CookieStorage::RemoveAll() { 497 // clearing the hashtable will call each CookieEntry's dtor, 498 // which releases all their respective children. 499 mHostTable.Clear(); 500 mCookieCount = 0; 501 mCookieOldestTime = INT64_MAX; 502 503 RemoveAllInternal(); 504 505 NotifyChanged(nullptr, nsICookieNotification::ALL_COOKIES_CLEARED, ""_ns); 506 } 507 508 // notify observers that the cookie list changed. 509 void CookieStorage::NotifyChanged(nsISupports* aSubject, 510 nsICookieNotification::Action aAction, 511 const nsACString& aBaseDomain, 512 bool aIsThirdParty, 513 dom::BrowsingContext* aBrowsingContext, 514 bool aOldCookieIsSession, 515 const nsID* aOperationID) { 516 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 517 if (!os) { 518 return; 519 } 520 521 nsCOMPtr<nsICookie> cookie; 522 nsCOMPtr<nsIArray> batchDeletedCookies; 523 524 if (aAction == nsICookieNotification::COOKIES_BATCH_DELETED) { 525 batchDeletedCookies = do_QueryInterface(aSubject); 526 } else { 527 cookie = do_QueryInterface(aSubject); 528 } 529 530 uint64_t browsingContextId = 0; 531 if (aBrowsingContext) { 532 browsingContextId = aBrowsingContext->Id(); 533 } 534 535 nsCOMPtr<nsICookieNotification> notification = new CookieNotification( 536 aAction, cookie, aBaseDomain, aIsThirdParty, batchDeletedCookies, 537 browsingContextId, aOperationID); 538 // Notify for topic "private-cookie-changed" or "cookie-changed" 539 os->NotifyObservers(notification, NotificationTopic(), u""); 540 541 NotifyChangedInternal(notification, aOldCookieIsSession); 542 } 543 544 void CookieStorage::RemoveCookiesFromBack( 545 nsTArray<CookieListIter>& aCookieIters, nsCOMPtr<nsIArray>& aPurgedList) { 546 for (auto it = aCookieIters.rbegin(); it != aCookieIters.rend(); ++it) { 547 RefPtr<Cookie> cookie = (*it).Cookie(); 548 MOZ_ASSERT(cookie); 549 COOKIE_LOGEVICTED(cookie, "Too many cookie bytes for this partition"); 550 RemoveCookieFromList(*it); 551 CreateOrUpdatePurgeList(aPurgedList, cookie); 552 553 // if a sole cookie is ever removed, we would remove the entire entry 554 // but practically speaking, 555 // we should never be in a scenario when we remove the final cookie 556 // unless a single cookie puts us over the limit 557 // for that to occur a user would have to adjust the CHIPS limit to be < 4 558 // KB 559 MOZ_ASSERT((*it).entry); 560 } 561 } 562 563 uint32_t CookieStorage::RemoveOldestCookies(CookieEntry* aEntry, bool aSecure, 564 uint32_t aBytesToRemove, 565 nsCOMPtr<nsIArray>& aPurgedList) { 566 const CookieEntry::ArrayType& cookies = aEntry->GetCookies(); 567 using MaybePurgeList = nsTArray<CookieListIter>; 568 569 // note that because the maybePurgeList is populated exclusively by 570 // pre-existing cookie list, we will never remove the cookie that is currently 571 // being added 572 MaybePurgeList maybePurgeList(aEntry->GetCookies().Length()); 573 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 574 CookieListIter iter(aEntry, i); 575 // assumes that secure cookie removal will always happen after insecure 576 // cookie removal 577 // Ie. this function will will only be called with aSecure == true 578 // after it has already been called with aSecure == false 579 if (aSecure || !iter.Cookie()->IsSecure()) { 580 maybePurgeList.AppendElement(iter); 581 } 582 } 583 584 // sort by age to prep oldest cookies first 585 // since the underlying cookie list doesn't guarantee age-order because age 586 // is primarily determined by lastAccessed, not creation time 587 // todo: write test to assert oldest first 588 maybePurgeList.Sort(CompareCookiesByAge()); 589 590 // truncate the list if we don't need to remove all cookies 591 uint32_t bytesRemoved = 0; 592 uint32_t count = 0; 593 for (auto iter : maybePurgeList) { 594 bytesRemoved += iter.Cookie()->NameAndValueBytes(); 595 count++; 596 if (bytesRemoved >= static_cast<uint32_t>(aBytesToRemove)) { 597 maybePurgeList.SetLength(count); 598 break; 599 } 600 } 601 // sort for safe, orderly removal (by index) 602 // because CookieIters effectively are just pointers to the underlying cookie 603 // list we must remove them from the back (largest index first) 604 maybePurgeList.Sort(CompareCookiesByIndex()); 605 RemoveCookiesFromBack(maybePurgeList, aPurgedList); 606 return bytesRemoved; 607 } 608 609 void CookieStorage::RemoveOlderCookiesByBytes(CookieEntry* aEntry, 610 uint32_t removeBytes, 611 nsCOMPtr<nsIArray>& aPurgedList) { 612 MOZ_ASSERT(aEntry); 613 614 // remove insecure older cookies until we are within the byte limit 615 // (CHIPS cookies will not be detected here since they must be secure) 616 uint32_t bytesRemoved = 617 RemoveOldestCookies(aEntry, false, removeBytes, aPurgedList); 618 619 // remove secure cookies if we still have cookies to remove 620 if (bytesRemoved <= removeBytes) { 621 // remove secure older cookies until we are within the byte limit 622 MOZ_LOG(gCookieLog, LogLevel::Debug, 623 ("Still too many cookies for partition, purging secure\n")); 624 uint32_t bytesStillToRemove = removeBytes - bytesRemoved; 625 RemoveOldestCookies(aEntry, true, bytesStillToRemove, aPurgedList); 626 } 627 } 628 629 CookieStorage::ChipsLimitExcess CookieStorage::PartitionLimitExceededBytes( 630 Cookie* aCookie, const nsACString& aBaseDomain) { 631 uint32_t newByteCount = 632 CountCookieBytesNotMatchingCookie(*aCookie, aBaseDomain) + 633 aCookie->NameAndValueBytes(); 634 ChipsLimitExcess res{.hard = 0, .soft = 0}; 635 uint32_t softLimit = 636 StaticPrefs::network_cookie_chips_partitionLimitByteCapacity(); 637 // shouldn't expect more the 4000 cookies * 4000 bytes/cookie -> 16MB 638 uint32_t hardLimit = static_cast<uint32_t>(softLimit * kChipsHardLimitFactor); 639 if (newByteCount > hardLimit) { 640 res.hard = newByteCount - hardLimit; 641 res.soft = newByteCount - softLimit; 642 } 643 return res; 644 } 645 646 // this is a backend function for adding a cookie to the list, via SetCookie. 647 // also used in the cookie manager, for profile migration from IE. it either 648 // replaces an existing cookie; or adds the cookie to the hashtable, and 649 // deletes a cookie (if maximum number of cookies has been reached). also 650 // performs list maintenance by removing expired cookies. 651 void CookieStorage::AddCookie(CookieParser* aCookieParser, 652 const nsACString& aBaseDomain, 653 const OriginAttributes& aOriginAttributes, 654 Cookie* aCookie, int64_t aCurrentTimeInUsec, 655 nsIURI* aHostURI, const nsACString& aCookieHeader, 656 bool aFromHttp, bool aIsThirdParty, 657 dom::BrowsingContext* aBrowsingContext, 658 const nsID* aOperationID) { 659 if (CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS( 660 aCookie, aBaseDomain, aOriginAttributes)) { 661 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 662 "Invalid first-party partitioned cookie without " 663 "partitioned cookie attribution."); 664 MOZ_ASSERT(false); 665 return; 666 } 667 668 int64_t currentTimeInMSec = aCurrentTimeInUsec / PR_USEC_PER_MSEC; 669 670 CookieListIter exactIter{}; 671 bool foundCookie = false; 672 foundCookie = FindCookie(aBaseDomain, aOriginAttributes, aCookie->Host(), 673 aCookie->Name(), aCookie->Path(), exactIter); 674 bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure(); 675 bool potentiallyTrustworthy = true; 676 if (aHostURI) { 677 potentiallyTrustworthy = 678 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI); 679 } 680 bool oldCookieIsSession = false; 681 // Step1, call FindSecureCookie(). FindSecureCookie() would 682 // find the existing cookie with the security flag and has 683 // the same name, host and path of the new cookie, if there is any. 684 // Step2, Confirm new cookie's security setting. If any targeted 685 // cookie had been found in Step1, then confirm whether the 686 // new cookie could modify it. If the new created cookie’s 687 // "secure-only-flag" is not set, and the "scheme" component 688 // of the "request-uri" does not denote a "secure" protocol, 689 // then ignore the new cookie. 690 // (draft-ietf-httpbis-cookie-alone section 3.2) 691 if (!aCookie->IsSecure() && 692 (foundSecureExact || 693 FindSecureCookie(aBaseDomain, aOriginAttributes, aCookie)) && 694 !potentiallyTrustworthy) { 695 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 696 "cookie can't save because older cookie is secure " 697 "cookie but newer cookie is non-secure cookie"); 698 if (aCookieParser) { 699 aCookieParser->RejectCookie(CookieParser::RejectedNonsecureOverSecure); 700 } 701 return; 702 } 703 704 RefPtr<Cookie> oldCookie; 705 nsCOMPtr<nsIArray> purgedList; 706 if (foundCookie) { 707 oldCookie = exactIter.Cookie(); 708 oldCookieIsSession = oldCookie->IsSession(); 709 710 // Check if the old cookie is stale (i.e. has already expired). If so, we 711 // need to be careful about the semantics of removing it and adding the new 712 // cookie: we want the behavior wrt adding the new cookie to be the same as 713 // if it didn't exist, but we still want to fire a removal notification. 714 if (oldCookie->ExpiryInMSec() <= currentTimeInMSec) { 715 if (aCookie->ExpiryInMSec() <= currentTimeInMSec) { 716 // The new cookie has expired and the old one is stale. Nothing to do. 717 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 718 "cookie has already expired"); 719 return; 720 } 721 722 // Remove the stale cookie. We save notification for later, once all list 723 // modifications are complete. 724 RemoveCookieFromList(exactIter); 725 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 726 "stale cookie was purged"); 727 purgedList = CreatePurgeList(oldCookie); 728 729 // We've done all we need to wrt removing and notifying the stale cookie. 730 // From here on out, we pretend pretend it didn't exist, so that we 731 // preserve expected notification semantics when adding the new cookie. 732 foundCookie = false; 733 734 } else { 735 // If the old cookie is httponly, make sure we're not coming from script. 736 if (!aFromHttp && oldCookie->IsHttpOnly()) { 737 COOKIE_LOGFAILURE( 738 SET_COOKIE, aHostURI, aCookieHeader, 739 "previously stored cookie is httponly; coming from script"); 740 if (aCookieParser) { 741 aCookieParser->RejectCookie( 742 CookieParser::RejectedHttpOnlyButFromScript); 743 } 744 return; 745 } 746 747 // If the new cookie has the same value, expiry date, isSecure, isSession, 748 // isHttpOnly and SameSite flags then we can just keep the old one. 749 // Only if any of these differ we would want to override the cookie. 750 if (oldCookie->Value().Equals(aCookie->Value()) && 751 oldCookie->ExpiryInMSec() == aCookie->ExpiryInMSec() && 752 oldCookie->IsSecure() == aCookie->IsSecure() && 753 oldCookie->IsSession() == aCookie->IsSession() && 754 oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() && 755 oldCookie->SameSite() == aCookie->SameSite() && 756 oldCookie->SchemeMap() == aCookie->SchemeMap() && 757 // We don't want to perform this optimization if the cookie is 758 // considered stale, since in this case we would need to update the 759 // database. 760 !oldCookie->IsStale()) { 761 // Update the last access time on the old cookie. 762 oldCookie->SetLastAccessedInUSec(aCookie->LastAccessedInUSec()); 763 UpdateCookieOldestTime(oldCookie); 764 return; 765 } 766 767 // Merge the scheme map in case the old cookie and the new cookie are 768 // used with different schemes. 769 MergeCookieSchemeMap(oldCookie, aCookie); 770 771 // Remove the old cookie. 772 RemoveCookieFromList(exactIter); 773 774 // If the new cookie has expired -- i.e. the intent was simply to delete 775 // the old cookie -- then we're done. 776 if (aCookie->ExpiryInMSec() <= currentTimeInMSec) { 777 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 778 "previously stored cookie was deleted"); 779 NotifyChanged(oldCookie, nsICookieNotification::COOKIE_DELETED, 780 aBaseDomain, false, aBrowsingContext, oldCookieIsSession, 781 aOperationID); 782 return; 783 } 784 785 // Preserve creation time of cookie for ordering purposes. 786 aCookie->SetCreationTimeInUSec(oldCookie->CreationTimeInUSec()); 787 } 788 789 // check for CHIPS-partitioned exceeding byte limit 790 // when we overwrite a cookie with a cookie) 791 if (CookieCommons::ChipsLimitEnabledAndChipsCookie(*aCookie, 792 aBrowsingContext)) { 793 CookieEntry* entry = 794 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes)); 795 if (entry) { 796 ChipsLimitExcess exceededBytes = 797 PartitionLimitExceededBytes(aCookie, aBaseDomain); 798 if (exceededBytes.hard > 0) { 799 MOZ_LOG(gCookieLog, LogLevel::Debug, 800 ("Partition byte limit exceeded on cookie overwrite\n")); 801 if (!StaticPrefs::network_cookie_chips_partitionLimitDryRun()) { 802 RemoveOlderCookiesByBytes(entry, exceededBytes.soft, purgedList); 803 } 804 if (StaticPrefs::network_cookie_chips_partitionLimitByteCapacity() == 805 kChipsPartitionByteCapacityDefault) { 806 mozilla::glean::networking::cookie_chips_partition_limit_overflow 807 .AccumulateSingleSample(exceededBytes.hard); 808 } 809 } 810 } 811 } 812 } else { 813 // check if cookie has already expired 814 if (aCookie->ExpiryInMSec() <= currentTimeInMSec) { 815 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 816 "cookie has already expired"); 817 return; 818 } 819 820 // check if we have to delete an old cookie. 821 CookieEntry* entry = 822 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes)); 823 ChipsLimitExcess partitionLimitExceededBytes{}; 824 // we haven't yet added the new cookie so we compare cookie list with >= 825 if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) { 826 nsTArray<CookieListIter> removedIterList; 827 // +1 to account for the cookie that we are adding, 828 // to ensure that we end up with mCookieQuotaPerHost cookies. 829 // "excess" should only be > 1 when prefs have been manipulated 830 uint32_t excess = entry->GetCookies().Length() - mMaxCookiesPerHost + 1; 831 uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost + excess; 832 // Prioritize evicting insecure cookies. 833 // (draft-ietf-httpbis-cookie-alone section 3.3) 834 FindStaleCookies(entry, currentTimeInMSec, false, removedIterList, limit); 835 if (removedIterList.Length() == 0) { 836 if (aCookie->IsSecure()) { 837 // It's valid to evict a secure cookie for another secure cookie. 838 FindStaleCookies(entry, currentTimeInMSec, true, removedIterList, 839 limit); 840 } else { 841 COOKIE_LOGEVICTED(aCookie, 842 "Too many cookies for this domain and the new " 843 "cookie is not a secure cookie"); 844 return; 845 } 846 } 847 848 MOZ_ASSERT(!removedIterList.IsEmpty()); 849 // Sort |removedIterList| by index again, since we have to remove the 850 // cookie in the reverse order. 851 removedIterList.Sort(CompareCookiesByIndex()); 852 for (auto it = removedIterList.rbegin(); it != removedIterList.rend(); 853 it++) { 854 RefPtr<Cookie> evictedCookie = (*it).Cookie(); 855 COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain"); 856 RemoveCookieFromList(*it); 857 CreateOrUpdatePurgeList(purgedList, evictedCookie); 858 MOZ_ASSERT((*it).entry); 859 } 860 uint32_t purgedLength = 0; 861 purgedList->GetLength(&purgedLength); 862 mozilla::glean::networking::cookie_purge_entry_max.AccumulateSingleSample( 863 purgedLength); 864 865 } else if (CookieCommons::ChipsLimitEnabledAndChipsCookie( 866 *aCookie, aBrowsingContext) && 867 entry && 868 (partitionLimitExceededBytes = 869 PartitionLimitExceededBytes(aCookie, aBaseDomain)) 870 .hard > 0) { 871 MOZ_LOG(gCookieLog, LogLevel::Debug, 872 ("Partition byte limit exceeded on cookie add\n")); 873 874 if (!StaticPrefs::network_cookie_chips_partitionLimitDryRun()) { 875 RemoveOlderCookiesByBytes(entry, partitionLimitExceededBytes.soft, 876 purgedList); 877 } 878 if (StaticPrefs::network_cookie_chips_partitionLimitByteCapacity() == 879 kChipsPartitionByteCapacityDefault) { 880 mozilla::glean::networking::cookie_chips_partition_limit_overflow 881 .AccumulateSingleSample(partitionLimitExceededBytes.hard); 882 } 883 } else if (mCookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) { 884 int64_t maxAge = aCurrentTimeInUsec - mCookieOldestTime; 885 int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge); 886 if (maxAge >= purgeAge) { 887 // we're over both size and age limits by 10%; time to purge the table! 888 // do this by: 889 // 1) removing expired cookies; 890 // 2) evicting the balance of old cookies until we reach the size limit. 891 // note that the mCookieOldestTime indicator can be pessimistic - if 892 // it's older than the actual oldest cookie, we'll just purge more 893 // eagerly. 894 purgedList = PurgeCookies(aCurrentTimeInUsec, mMaxNumberOfCookies, 895 mCookiePurgeAge); 896 uint32_t purgedLength = 0; 897 purgedList->GetLength(&purgedLength); 898 mozilla::glean::networking::cookie_purge_max.AccumulateSingleSample( 899 purgedLength); 900 } 901 } 902 } 903 904 // Add the cookie to the db. We do not supply a params array for batching 905 // because this might result in removals and additions being out of order. 906 AddCookieToList(aBaseDomain, aOriginAttributes, aCookie); 907 StoreCookie(aBaseDomain, aOriginAttributes, aCookie); 908 909 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie); 910 911 // Now that list mutations are complete, notify observers. We do it here 912 // because observers may themselves attempt to mutate the list. 913 if (purgedList) { 914 NotifyChanged(purgedList, nsICookieNotification::COOKIES_BATCH_DELETED, 915 ""_ns, false, nullptr, false, aOperationID); 916 } 917 918 // Notify for topic "private-cookie-changed" or "cookie-changed" 919 NotifyChanged(aCookie, 920 foundCookie ? nsICookieNotification::COOKIE_CHANGED 921 : nsICookieNotification::COOKIE_ADDED, 922 aBaseDomain, aIsThirdParty, aBrowsingContext, 923 oldCookieIsSession, aOperationID); 924 } 925 926 void CookieStorage::UpdateCookieOldestTime(Cookie* aCookie) { 927 if (aCookie->LastAccessedInUSec() < mCookieOldestTime) { 928 mCookieOldestTime = aCookie->LastAccessedInUSec(); 929 } 930 } 931 932 void CookieStorage::MergeCookieSchemeMap(Cookie* aOldCookie, 933 Cookie* aNewCookie) { 934 aNewCookie->SetSchemeMap(aOldCookie->SchemeMap() | aNewCookie->SchemeMap()); 935 } 936 937 void CookieStorage::AddCookieToList(const nsACString& aBaseDomain, 938 const OriginAttributes& aOriginAttributes, 939 Cookie* aCookie) { 940 if (!aCookie) { 941 NS_WARNING("Attempting to AddCookieToList with null cookie"); 942 return; 943 } 944 945 CookieKey key(aBaseDomain, aOriginAttributes); 946 947 CookieEntry* entry = mHostTable.PutEntry(key); 948 NS_ASSERTION(entry, "can't insert element into a null entry!"); 949 950 entry->GetCookies().AppendElement(aCookie); 951 ++mCookieCount; 952 953 // keep track of the oldest cookie, for when it comes time to purge 954 UpdateCookieOldestTime(aCookie); 955 } 956 957 // static 958 already_AddRefed<nsIArray> CookieStorage::CreatePurgeList(nsICookie* aCookie) { 959 nsCOMPtr<nsIMutableArray> removedList = 960 do_CreateInstance(NS_ARRAY_CONTRACTID); 961 removedList->AppendElement(aCookie); 962 return removedList.forget(); 963 } 964 965 // Given the output iter array and the count limit, find cookies 966 // sort by expiry and lastAccessedInUSec time. 967 // static 968 void CookieStorage::FindStaleCookies(CookieEntry* aEntry, 969 int64_t aCurrentTimeInMSec, bool aIsSecure, 970 nsTArray<CookieListIter>& aOutput, 971 uint32_t aLimit) { 972 MOZ_ASSERT(aLimit); 973 974 const CookieEntry::ArrayType& cookies = aEntry->GetCookies(); 975 aOutput.Clear(); 976 977 CookieIterComparator comp(aCurrentTimeInMSec); 978 nsTPriorityQueue<CookieListIter, CookieIterComparator> queue(comp); 979 980 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 981 Cookie* cookie = cookies[i]; 982 983 if (cookie->ExpiryInMSec() <= aCurrentTimeInMSec) { 984 queue.Push(CookieListIter(aEntry, i)); 985 continue; 986 } 987 988 if (!aIsSecure) { 989 // We want to look for the non-secure cookie first time through, 990 // then find the secure cookie the second time this function is called. 991 if (cookie->IsSecure()) { 992 continue; 993 } 994 } 995 996 queue.Push(CookieListIter(aEntry, i)); 997 } 998 999 uint32_t count = 0; 1000 while (!queue.IsEmpty() && count < aLimit) { 1001 aOutput.AppendElement(queue.Pop()); 1002 count++; 1003 } 1004 } 1005 1006 // static 1007 void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList, 1008 nsICookie* aCookie) { 1009 if (!aPurgedList) { 1010 COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list")); 1011 aPurgedList = CreatePurgeList(aCookie); 1012 return; 1013 } 1014 1015 nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(aPurgedList); 1016 if (purgedList) { 1017 COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list")); 1018 purgedList->AppendElement(aCookie); 1019 } else { 1020 COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!")); 1021 } 1022 } 1023 1024 // purges expired and old cookies in a batch operation. 1025 already_AddRefed<nsIArray> CookieStorage::PurgeCookiesWithCallbacks( 1026 int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies, 1027 int64_t aCookiePurgeAge, 1028 std::function<void(const CookieListIter&)>&& aRemoveCookieCallback, 1029 std::function<void()>&& aFinalizeCallback) { 1030 NS_ASSERTION(mHostTable.Count() > 0, "table is empty"); 1031 1032 uint32_t initialCookieCount = mCookieCount; 1033 COOKIE_LOGSTRING(LogLevel::Debug, 1034 ("PurgeCookies(): beginning purge with %" PRIu32 1035 " cookies and %" PRId64 " oldest age", 1036 mCookieCount, aCurrentTimeInUsec - mCookieOldestTime)); 1037 1038 using PurgeList = nsTArray<CookieListIter>; 1039 PurgeList purgeList(kMaxNumberOfCookies); 1040 1041 nsCOMPtr<nsIMutableArray> removedList = 1042 do_CreateInstance(NS_ARRAY_CONTRACTID); 1043 1044 int64_t currentTimeInMSec = aCurrentTimeInUsec / PR_USEC_PER_MSEC; 1045 int64_t purgeTime = aCurrentTimeInUsec - aCookiePurgeAge; 1046 int64_t oldestTime = INT64_MAX; 1047 1048 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) { 1049 CookieEntry* entry = iter.Get(); 1050 1051 const CookieEntry::ArrayType& cookies = entry->GetCookies(); 1052 auto length = cookies.Length(); 1053 for (CookieEntry::IndexType i = 0; i < length;) { 1054 CookieListIter iter(entry, i); 1055 Cookie* cookie = cookies[i]; 1056 1057 // check if the cookie has expired 1058 if (cookie->ExpiryInMSec() <= currentTimeInMSec) { 1059 removedList->AppendElement(cookie); 1060 COOKIE_LOGEVICTED(cookie, "Cookie expired"); 1061 1062 // remove from list; do not increment our iterator, but stop if we're 1063 // done already. 1064 aRemoveCookieCallback(iter); 1065 if (i == --length) { 1066 break; 1067 } 1068 } else { 1069 // check if the cookie is over the age limit 1070 if (cookie->LastAccessedInUSec() <= purgeTime) { 1071 purgeList.AppendElement(iter); 1072 1073 } else if (cookie->LastAccessedInUSec() < oldestTime) { 1074 // reset our indicator 1075 oldestTime = cookie->LastAccessedInUSec(); 1076 } 1077 1078 ++i; 1079 } 1080 MOZ_ASSERT(length == cookies.Length()); 1081 } 1082 } 1083 1084 uint32_t postExpiryCookieCount = mCookieCount; 1085 1086 // now we have a list of iterators for cookies over the age limit. 1087 // sort them by age, and then we'll see how many to remove... 1088 purgeList.Sort(CompareCookiesByAge()); 1089 1090 // only remove old cookies until we reach the max cookie limit, no more. 1091 uint32_t excess = mCookieCount > aMaxNumberOfCookies 1092 ? mCookieCount - aMaxNumberOfCookies 1093 : 0; 1094 if (purgeList.Length() > excess) { 1095 // We're not purging everything in the list, so update our indicator. 1096 oldestTime = purgeList[excess].Cookie()->LastAccessedInUSec(); 1097 1098 purgeList.SetLength(excess); 1099 } 1100 1101 // sort the list again, this time grouping cookies with a common entryclass 1102 // together, and with ascending index. this allows us to iterate backwards 1103 // over the list removing cookies, without having to adjust indexes as we go. 1104 purgeList.Sort(CompareCookiesByIndex()); 1105 for (PurgeList::index_type i = purgeList.Length(); i--;) { 1106 Cookie* cookie = purgeList[i].Cookie(); 1107 removedList->AppendElement(cookie); 1108 COOKIE_LOGEVICTED(cookie, "Cookie too old"); 1109 1110 aRemoveCookieCallback(purgeList[i]); 1111 } 1112 1113 // Update the database if we have entries to purge. 1114 if (aFinalizeCallback) { 1115 aFinalizeCallback(); 1116 } 1117 1118 // reset the oldest time indicator 1119 mCookieOldestTime = oldestTime; 1120 1121 COOKIE_LOGSTRING(LogLevel::Debug, 1122 ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32 1123 " purged; %" PRIu32 " remain; %" PRId64 " oldest age", 1124 initialCookieCount - postExpiryCookieCount, 1125 postExpiryCookieCount - mCookieCount, mCookieCount, 1126 aCurrentTimeInUsec - mCookieOldestTime)); 1127 1128 return removedList.forget(); 1129 } 1130 1131 // remove a cookie from the hashtable, and update the iterator state. 1132 void CookieStorage::RemoveCookieFromList(const CookieListIter& aIter) { 1133 RemoveCookieFromDB(*aIter.Cookie()); 1134 RemoveCookieFromListInternal(aIter); 1135 } 1136 1137 void CookieStorage::RemoveCookieFromListInternal(const CookieListIter& aIter) { 1138 if (aIter.entry->GetCookies().Length() == 1) { 1139 // we're removing the last element in the array - so just remove the entry 1140 // from the hash. note that the entryclass' dtor will take care of 1141 // releasing this last element for us! 1142 mHostTable.RawRemoveEntry(aIter.entry); 1143 1144 } else { 1145 // just remove the element from the list 1146 aIter.entry->GetCookies().RemoveElementAt(aIter.index); 1147 } 1148 1149 --mCookieCount; 1150 } 1151 1152 void CookieStorage::PrefChanged(nsIPrefBranch* aPrefBranch) { 1153 int32_t val; 1154 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) { 1155 mMaxNumberOfCookies = 1156 static_cast<uint16_t> LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies); 1157 } 1158 1159 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) { 1160 mCookieQuotaPerHost = static_cast<uint16_t> LIMIT( 1161 val, 1, mMaxCookiesPerHost, kCookieQuotaPerHost); 1162 } 1163 1164 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) { 1165 mMaxCookiesPerHost = static_cast<uint16_t> LIMIT( 1166 val, mCookieQuotaPerHost, 0xFFFF, kMaxCookiesPerHost); 1167 } 1168 1169 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) { 1170 mCookiePurgeAge = 1171 int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC; 1172 } 1173 } 1174 1175 NS_IMETHODIMP 1176 CookieStorage::Observe(nsISupports* aSubject, const char* aTopic, 1177 const char16_t* /*aData*/) { 1178 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { 1179 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject); 1180 if (prefBranch) { 1181 PrefChanged(prefBranch); 1182 } 1183 } else if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) { 1184 CollectCookieJarSizeData(); 1185 } 1186 1187 return NS_OK; 1188 } 1189 1190 } // namespace net 1191 } // namespace mozilla