Bug 1719578 log cubeb speaker enumeration results in MediaEngineWebRTC r=padenot
[gecko.git] / netwerk / cookie / CookieStorage.cpp
blob9389e67f80dfc7dbc62cc091b7dccafd20bf23f1
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/. */
6 #include "Cookie.h"
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "CookieStorage.h"
10 #include "mozilla/dom/nsMixedContentBlocker.h"
11 #include "nsIMutableArray.h"
12 #include "nsTPriorityQueue.h"
13 #include "nsIScriptError.h"
14 #include "nsServiceManagerUtils.h"
15 #include "nsComponentManagerUtils.h"
16 #include "prprf.h"
17 #include "nsIPrefService.h"
19 #undef ADD_TEN_PERCENT
20 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
22 #undef LIMIT
23 #define LIMIT(x, low, high, default) \
24 ((x) >= (low) && (x) <= (high) ? (x) : (default))
26 namespace mozilla {
27 namespace net {
29 namespace {
31 // comparator class for lastaccessed times of cookies.
32 class CompareCookiesByAge {
33 public:
34 static bool Equals(const CookieListIter& a, const CookieListIter& b) {
35 return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
36 a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
39 static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
40 // compare by lastAccessed time, and tiebreak by creationTime.
41 int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
42 if (result != 0) {
43 return result < 0;
46 return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
50 // Cookie comparator for the priority queue used in FindStaleCookies.
51 // Note that the expired cookie has the highest priority.
52 // Other non-expired cookies are sorted by their age.
53 class CookieIterComparator {
54 private:
55 int64_t mCurrentTime;
57 public:
58 explicit CookieIterComparator(int64_t aTime) : mCurrentTime(aTime) {}
60 bool LessThan(const CookieListIter& lhs, const CookieListIter& rhs) {
61 bool lExpired = lhs.Cookie()->Expiry() <= mCurrentTime;
62 bool rExpired = rhs.Cookie()->Expiry() <= mCurrentTime;
63 if (lExpired && !rExpired) {
64 return true;
67 if (!lExpired && rExpired) {
68 return false;
71 return mozilla::net::CompareCookiesByAge::LessThan(lhs, rhs);
75 // comparator class for sorting cookies by entry and index.
76 class CompareCookiesByIndex {
77 public:
78 static bool Equals(const CookieListIter& a, const CookieListIter& b) {
79 NS_ASSERTION(a.entry != b.entry || a.index != b.index,
80 "cookie indexes should never be equal");
81 return false;
84 static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
85 // compare by entryclass pointer, then by index.
86 if (a.entry != b.entry) {
87 return a.entry < b.entry;
90 return a.index < b.index;
94 } // namespace
96 // ---------------------------------------------------------------------------
97 // CookieEntry
99 size_t CookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
100 size_t amount = CookieKey::SizeOfExcludingThis(aMallocSizeOf);
102 amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
103 for (uint32_t i = 0; i < mCookies.Length(); ++i) {
104 amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
107 return amount;
110 // ---------------------------------------------------------------------------
111 // CookieStorage
113 NS_IMPL_ISUPPORTS(CookieStorage, nsIObserver, nsISupportsWeakReference)
115 void CookieStorage::Init() {
116 // init our pref and observer
117 nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
118 if (prefBranch) {
119 prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
120 prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
121 prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
122 PrefChanged(prefBranch);
126 size_t CookieStorage::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
127 size_t amount = 0;
129 amount += aMallocSizeOf(this);
130 amount += mHostTable.SizeOfExcludingThis(aMallocSizeOf);
132 return amount;
135 void CookieStorage::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const {
136 aCookies.SetCapacity(mCookieCount);
137 for (const auto& entry : mHostTable) {
138 const CookieEntry::ArrayType& cookies = entry.GetCookies();
139 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
140 aCookies.AppendElement(cookies[i]);
145 void CookieStorage::GetSessionCookies(
146 nsTArray<RefPtr<nsICookie>>& aCookies) const {
147 aCookies.SetCapacity(mCookieCount);
148 for (const auto& entry : mHostTable) {
149 const CookieEntry::ArrayType& cookies = entry.GetCookies();
150 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
151 Cookie* cookie = cookies[i];
152 // Filter out non-session cookies.
153 if (cookie->IsSession()) {
154 aCookies.AppendElement(cookie);
160 // find an exact cookie specified by host, name, and path that hasn't expired.
161 bool CookieStorage::FindCookie(const nsACString& aBaseDomain,
162 const OriginAttributes& aOriginAttributes,
163 const nsACString& aHost, const nsACString& aName,
164 const nsACString& aPath, CookieListIter& aIter) {
165 CookieEntry* entry =
166 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
167 if (!entry) {
168 return false;
171 const CookieEntry::ArrayType& cookies = entry->GetCookies();
172 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
173 Cookie* cookie = cookies[i];
175 if (aHost.Equals(cookie->Host()) && aPath.Equals(cookie->Path()) &&
176 aName.Equals(cookie->Name())) {
177 aIter = CookieListIter(entry, i);
178 return true;
182 return false;
185 // find an secure cookie specified by host and name
186 bool CookieStorage::FindSecureCookie(const nsACString& aBaseDomain,
187 const OriginAttributes& aOriginAttributes,
188 Cookie* aCookie) {
189 CookieEntry* entry =
190 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
191 if (!entry) {
192 return false;
195 const CookieEntry::ArrayType& cookies = entry->GetCookies();
196 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
197 Cookie* cookie = cookies[i];
198 // isn't a match if insecure or a different name
199 if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name())) {
200 continue;
203 // The host must "domain-match" an existing cookie or vice-versa
204 if (CookieCommons::DomainMatches(cookie, aCookie->Host()) ||
205 CookieCommons::DomainMatches(aCookie, cookie->Host())) {
206 // If the path of new cookie and the path of existing cookie
207 // aren't "/", then this situation needs to compare paths to
208 // ensure only that a newly-created non-secure cookie does not
209 // overlay an existing secure cookie.
210 if (CookieCommons::PathMatches(cookie, aCookie->GetFilePath())) {
211 return true;
216 return false;
219 uint32_t CookieStorage::CountCookiesFromHost(const nsACString& aBaseDomain,
220 uint32_t aPrivateBrowsingId) {
221 OriginAttributes attrs;
222 attrs.mPrivateBrowsingId = aPrivateBrowsingId;
224 // Return a count of all cookies, including expired.
225 CookieEntry* entry = mHostTable.GetEntry(CookieKey(aBaseDomain, attrs));
226 return entry ? entry->GetCookies().Length() : 0;
229 void CookieStorage::GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const {
230 aResult.SetCapacity(mCookieCount);
232 for (const auto& entry : mHostTable) {
233 const CookieEntry::ArrayType& cookies = entry.GetCookies();
234 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
235 aResult.AppendElement(cookies[i]);
240 const nsTArray<RefPtr<Cookie>>* CookieStorage::GetCookiesFromHost(
241 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes) {
242 CookieEntry* entry =
243 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
244 return entry ? &entry->GetCookies() : nullptr;
247 void CookieStorage::GetCookiesWithOriginAttributes(
248 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain,
249 nsTArray<RefPtr<nsICookie>>& aResult) {
250 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
251 CookieEntry* entry = iter.Get();
253 if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
254 continue;
257 if (!aPattern.Matches(entry->mOriginAttributes)) {
258 continue;
261 const CookieEntry::ArrayType& entryCookies = entry->GetCookies();
263 for (CookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
264 aResult.AppendElement(entryCookies[i]);
269 void CookieStorage::RemoveCookie(const nsACString& aBaseDomain,
270 const OriginAttributes& aOriginAttributes,
271 const nsACString& aHost,
272 const nsACString& aName,
273 const nsACString& aPath) {
274 CookieListIter matchIter{};
275 RefPtr<Cookie> cookie;
276 if (FindCookie(aBaseDomain, aOriginAttributes, aHost, aName, aPath,
277 matchIter)) {
278 cookie = matchIter.Cookie();
279 RemoveCookieFromList(matchIter);
282 if (cookie) {
283 // Everything's done. Notify observers.
284 NotifyChanged(cookie, u"deleted");
288 void CookieStorage::RemoveCookiesWithOriginAttributes(
289 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
290 // Iterate the hash table of CookieEntry.
291 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
292 CookieEntry* entry = iter.Get();
294 if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
295 continue;
298 if (!aPattern.Matches(entry->mOriginAttributes)) {
299 continue;
302 // Pattern matches. Delete all cookies within this CookieEntry.
303 uint32_t cookiesCount = entry->GetCookies().Length();
305 for (CookieEntry::IndexType i = 0; i < cookiesCount; ++i) {
306 // Remove the first cookie from the list.
307 CookieListIter iter(entry, 0);
308 RefPtr<Cookie> cookie = iter.Cookie();
310 // Remove the cookie.
311 RemoveCookieFromList(iter);
313 if (cookie) {
314 NotifyChanged(cookie, u"deleted");
320 void CookieStorage::RemoveCookiesFromExactHost(
321 const nsACString& aHost, const nsACString& aBaseDomain,
322 const OriginAttributesPattern& aPattern) {
323 // Iterate the hash table of CookieEntry.
324 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
325 CookieEntry* entry = iter.Get();
327 if (!aBaseDomain.Equals(entry->mBaseDomain)) {
328 continue;
331 if (!aPattern.Matches(entry->mOriginAttributes)) {
332 continue;
335 uint32_t cookiesCount = entry->GetCookies().Length();
336 for (CookieEntry::IndexType i = cookiesCount; i != 0; --i) {
337 CookieListIter iter(entry, i - 1);
338 RefPtr<Cookie> cookie = iter.Cookie();
340 if (!aHost.Equals(cookie->RawHost())) {
341 continue;
344 // Remove the cookie.
345 RemoveCookieFromList(iter);
347 if (cookie) {
348 NotifyChanged(cookie, u"deleted");
354 void CookieStorage::RemoveAll() {
355 // clearing the hashtable will call each CookieEntry's dtor,
356 // which releases all their respective children.
357 mHostTable.Clear();
358 mCookieCount = 0;
359 mCookieOldestTime = INT64_MAX;
361 RemoveAllInternal();
363 NotifyChanged(nullptr, u"cleared");
366 // notify observers that the cookie list changed. there are five possible
367 // values for aData:
368 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
369 // "added" means a cookie was added. aSubject is the added cookie.
370 // "changed" means a cookie was altered. aSubject is the new cookie.
371 // "cleared" means the entire cookie list was cleared. aSubject is null.
372 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
373 // cookies.
374 void CookieStorage::NotifyChanged(nsISupports* aSubject, const char16_t* aData,
375 bool aOldCookieIsSession) {
376 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
377 if (!os) {
378 return;
380 // Notify for topic "private-cookie-changed" or "cookie-changed"
381 os->NotifyObservers(aSubject, NotificationTopic(), aData);
383 NotifyChangedInternal(aSubject, aData, aOldCookieIsSession);
386 // this is a backend function for adding a cookie to the list, via SetCookie.
387 // also used in the cookie manager, for profile migration from IE. it either
388 // replaces an existing cookie; or adds the cookie to the hashtable, and
389 // deletes a cookie (if maximum number of cookies has been reached). also
390 // performs list maintenance by removing expired cookies.
391 void CookieStorage::AddCookie(nsIConsoleReportCollector* aCRC,
392 const nsACString& aBaseDomain,
393 const OriginAttributes& aOriginAttributes,
394 Cookie* aCookie, int64_t aCurrentTimeInUsec,
395 nsIURI* aHostURI, const nsACString& aCookieHeader,
396 bool aFromHttp) {
397 int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
399 CookieListIter exactIter{};
400 bool foundCookie = false;
401 foundCookie = FindCookie(aBaseDomain, aOriginAttributes, aCookie->Host(),
402 aCookie->Name(), aCookie->Path(), exactIter);
403 bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
404 bool potentiallyTrustworthy = true;
405 if (aHostURI) {
406 potentiallyTrustworthy =
407 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
409 constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
410 bool oldCookieIsSession = false;
411 // Step1, call FindSecureCookie(). FindSecureCookie() would
412 // find the existing cookie with the security flag and has
413 // the same name, host and path of the new cookie, if there is any.
414 // Step2, Confirm new cookie's security setting. If any targeted
415 // cookie had been found in Step1, then confirm whether the
416 // new cookie could modify it. If the new created cookie’s
417 // "secure-only-flag" is not set, and the "scheme" component
418 // of the "request-uri" does not denote a "secure" protocol,
419 // then ignore the new cookie.
420 // (draft-ietf-httpbis-cookie-alone section 3.2)
421 if (!aCookie->IsSecure() &&
422 (foundSecureExact ||
423 FindSecureCookie(aBaseDomain, aOriginAttributes, aCookie)) &&
424 !potentiallyTrustworthy) {
425 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
426 "cookie can't save because older cookie is secure "
427 "cookie but newer cookie is non-secure cookie");
428 CookieLogging::LogMessageToConsole(
429 aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY,
430 "CookieRejectedNonsecureOverSecure"_ns,
431 AutoTArray<nsString, 1>{
432 NS_ConvertUTF8toUTF16(aCookie->Name()),
434 return;
437 RefPtr<Cookie> oldCookie;
438 nsCOMPtr<nsIArray> purgedList;
439 if (foundCookie) {
440 oldCookie = exactIter.Cookie();
441 oldCookieIsSession = oldCookie->IsSession();
443 // Check if the old cookie is stale (i.e. has already expired). If so, we
444 // need to be careful about the semantics of removing it and adding the new
445 // cookie: we want the behavior wrt adding the new cookie to be the same as
446 // if it didn't exist, but we still want to fire a removal notification.
447 if (oldCookie->Expiry() <= currentTime) {
448 if (aCookie->Expiry() <= currentTime) {
449 // The new cookie has expired and the old one is stale. Nothing to do.
450 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
451 "cookie has already expired");
452 CookieLogging::LogMessageToConsole(
453 aCRC, aHostURI, nsIScriptError::warningFlag,
454 CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
455 AutoTArray<nsString, 1>{
456 NS_ConvertUTF8toUTF16(aCookie->Name()),
458 return;
461 // Remove the stale cookie. We save notification for later, once all list
462 // modifications are complete.
463 RemoveCookieFromList(exactIter);
464 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
465 "stale cookie was purged");
466 purgedList = CreatePurgeList(oldCookie);
468 // We've done all we need to wrt removing and notifying the stale cookie.
469 // From here on out, we pretend pretend it didn't exist, so that we
470 // preserve expected notification semantics when adding the new cookie.
471 foundCookie = false;
473 } else {
474 // If the old cookie is httponly, make sure we're not coming from script.
475 if (!aFromHttp && oldCookie->IsHttpOnly()) {
476 COOKIE_LOGFAILURE(
477 SET_COOKIE, aHostURI, aCookieHeader,
478 "previously stored cookie is httponly; coming from script");
479 CookieLogging::LogMessageToConsole(
480 aCRC, aHostURI, nsIScriptError::warningFlag,
481 CONSOLE_REJECTION_CATEGORY,
482 "CookieRejectedHttpOnlyButFromScript"_ns,
483 AutoTArray<nsString, 1>{
484 NS_ConvertUTF8toUTF16(aCookie->Name()),
486 return;
489 // If the new cookie has the same value, expiry date, isSecure, isSession,
490 // isHttpOnly and SameSite flags then we can just keep the old one.
491 // Only if any of these differ we would want to override the cookie.
492 if (oldCookie->Value().Equals(aCookie->Value()) &&
493 oldCookie->Expiry() == aCookie->Expiry() &&
494 oldCookie->IsSecure() == aCookie->IsSecure() &&
495 oldCookie->IsSession() == aCookie->IsSession() &&
496 oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
497 oldCookie->SameSite() == aCookie->SameSite() &&
498 oldCookie->SchemeMap() == aCookie->SchemeMap() &&
499 // We don't want to perform this optimization if the cookie is
500 // considered stale, since in this case we would need to update the
501 // database.
502 !oldCookie->IsStale()) {
503 // Update the last access time on the old cookie.
504 oldCookie->SetLastAccessed(aCookie->LastAccessed());
505 UpdateCookieOldestTime(oldCookie);
506 return;
509 // Merge the scheme map in case the old cookie and the new cookie are
510 // used with different schemes.
511 MergeCookieSchemeMap(oldCookie, aCookie);
513 // Remove the old cookie.
514 RemoveCookieFromList(exactIter);
516 // If the new cookie has expired -- i.e. the intent was simply to delete
517 // the old cookie -- then we're done.
518 if (aCookie->Expiry() <= currentTime) {
519 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
520 "previously stored cookie was deleted");
521 CookieLogging::LogMessageToConsole(
522 aCRC, aHostURI, nsIScriptError::warningFlag,
523 CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
524 AutoTArray<nsString, 1>{
525 NS_ConvertUTF8toUTF16(aCookie->Name()),
527 NotifyChanged(oldCookie, u"deleted", oldCookieIsSession);
528 return;
531 // Preserve creation time of cookie for ordering purposes.
532 aCookie->SetCreationTime(oldCookie->CreationTime());
535 } else {
536 // check if cookie has already expired
537 if (aCookie->Expiry() <= currentTime) {
538 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
539 "cookie has already expired");
540 CookieLogging::LogMessageToConsole(
541 aCRC, aHostURI, nsIScriptError::warningFlag,
542 CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
543 AutoTArray<nsString, 1>{
544 NS_ConvertUTF8toUTF16(aCookie->Name()),
546 return;
549 // check if we have to delete an old cookie.
550 CookieEntry* entry =
551 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
552 if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
553 nsTArray<CookieListIter> removedIterList;
554 // Prioritize evicting insecure cookies.
555 // (draft-ietf-httpbis-cookie-alone section 3.3)
556 uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost;
557 FindStaleCookies(entry, currentTime, false, removedIterList, limit);
558 if (removedIterList.Length() == 0) {
559 if (aCookie->IsSecure()) {
560 // It's valid to evict a secure cookie for another secure cookie.
561 FindStaleCookies(entry, currentTime, true, removedIterList, limit);
562 } else {
563 COOKIE_LOGEVICTED(aCookie,
564 "Too many cookies for this domain and the new "
565 "cookie is not a secure cookie");
566 return;
570 MOZ_ASSERT(!removedIterList.IsEmpty());
571 // Sort |removedIterList| by index again, since we have to remove the
572 // cookie in the reverse order.
573 removedIterList.Sort(CompareCookiesByIndex());
574 for (auto it = removedIterList.rbegin(); it != removedIterList.rend();
575 it++) {
576 RefPtr<Cookie> evictedCookie = (*it).Cookie();
577 COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain");
578 RemoveCookieFromList(*it);
579 CreateOrUpdatePurgeList(getter_AddRefs(purgedList), evictedCookie);
580 MOZ_ASSERT((*it).entry);
583 } else if (mCookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
584 int64_t maxAge = aCurrentTimeInUsec - mCookieOldestTime;
585 int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
586 if (maxAge >= purgeAge) {
587 // we're over both size and age limits by 10%; time to purge the table!
588 // do this by:
589 // 1) removing expired cookies;
590 // 2) evicting the balance of old cookies until we reach the size limit.
591 // note that the mCookieOldestTime indicator can be pessimistic - if
592 // it's older than the actual oldest cookie, we'll just purge more
593 // eagerly.
594 purgedList = PurgeCookies(aCurrentTimeInUsec, mMaxNumberOfCookies,
595 mCookiePurgeAge);
600 // Add the cookie to the db. We do not supply a params array for batching
601 // because this might result in removals and additions being out of order.
602 AddCookieToList(aBaseDomain, aOriginAttributes, aCookie);
603 StoreCookie(aBaseDomain, aOriginAttributes, aCookie);
605 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
607 // Now that list mutations are complete, notify observers. We do it here
608 // because observers may themselves attempt to mutate the list.
609 if (purgedList) {
610 NotifyChanged(purgedList, u"batch-deleted");
613 NotifyChanged(aCookie, foundCookie ? u"changed" : u"added",
614 oldCookieIsSession);
617 void CookieStorage::UpdateCookieOldestTime(Cookie* aCookie) {
618 if (aCookie->LastAccessed() < mCookieOldestTime) {
619 mCookieOldestTime = aCookie->LastAccessed();
623 void CookieStorage::MergeCookieSchemeMap(Cookie* aOldCookie,
624 Cookie* aNewCookie) {
625 aNewCookie->SetSchemeMap(aOldCookie->SchemeMap() | aNewCookie->SchemeMap());
628 void CookieStorage::AddCookieToList(const nsACString& aBaseDomain,
629 const OriginAttributes& aOriginAttributes,
630 Cookie* aCookie) {
631 if (!aCookie) {
632 NS_WARNING("Attempting to AddCookieToList with null cookie");
633 return;
636 CookieKey key(aBaseDomain, aOriginAttributes);
638 CookieEntry* entry = mHostTable.PutEntry(key);
639 NS_ASSERTION(entry, "can't insert element into a null entry!");
641 entry->GetCookies().AppendElement(aCookie);
642 ++mCookieCount;
644 // keep track of the oldest cookie, for when it comes time to purge
645 UpdateCookieOldestTime(aCookie);
648 // static
649 already_AddRefed<nsIArray> CookieStorage::CreatePurgeList(nsICookie* aCookie) {
650 nsCOMPtr<nsIMutableArray> removedList =
651 do_CreateInstance(NS_ARRAY_CONTRACTID);
652 removedList->AppendElement(aCookie);
653 return removedList.forget();
656 // Given the output iter array and the count limit, find cookies
657 // sort by expiry and lastAccessed time.
658 // static
659 void CookieStorage::FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTime,
660 bool aIsSecure,
661 nsTArray<CookieListIter>& aOutput,
662 uint32_t aLimit) {
663 MOZ_ASSERT(aLimit);
665 const CookieEntry::ArrayType& cookies = aEntry->GetCookies();
666 aOutput.Clear();
668 CookieIterComparator comp(aCurrentTime);
669 nsTPriorityQueue<CookieListIter, CookieIterComparator> queue(comp);
671 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
672 Cookie* cookie = cookies[i];
674 if (cookie->Expiry() <= aCurrentTime) {
675 queue.Push(CookieListIter(aEntry, i));
676 continue;
679 if (!aIsSecure) {
680 // We want to look for the non-secure cookie first time through,
681 // then find the secure cookie the second time this function is called.
682 if (cookie->IsSecure()) {
683 continue;
687 queue.Push(CookieListIter(aEntry, i));
690 uint32_t count = 0;
691 while (!queue.IsEmpty() && count < aLimit) {
692 aOutput.AppendElement(queue.Pop());
693 count++;
697 // static
698 void CookieStorage::CreateOrUpdatePurgeList(nsIArray** aPurgedList,
699 nsICookie* aCookie) {
700 if (!*aPurgedList) {
701 COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
702 nsCOMPtr<nsIArray> purgedList = CreatePurgeList(aCookie);
703 purgedList.forget(aPurgedList);
704 return;
707 nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(*aPurgedList);
708 if (purgedList) {
709 COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list"));
710 purgedList->AppendElement(aCookie);
711 } else {
712 COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!"));
716 // purges expired and old cookies in a batch operation.
717 already_AddRefed<nsIArray> CookieStorage::PurgeCookiesWithCallbacks(
718 int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
719 int64_t aCookiePurgeAge,
720 std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
721 std::function<void()>&& aFinalizeCallback) {
722 NS_ASSERTION(mHostTable.Count() > 0, "table is empty");
724 uint32_t initialCookieCount = mCookieCount;
725 COOKIE_LOGSTRING(LogLevel::Debug,
726 ("PurgeCookies(): beginning purge with %" PRIu32
727 " cookies and %" PRId64 " oldest age",
728 mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
730 using PurgeList = nsTArray<CookieListIter>;
731 PurgeList purgeList(kMaxNumberOfCookies);
733 nsCOMPtr<nsIMutableArray> removedList =
734 do_CreateInstance(NS_ARRAY_CONTRACTID);
736 int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
737 int64_t purgeTime = aCurrentTimeInUsec - aCookiePurgeAge;
738 int64_t oldestTime = INT64_MAX;
740 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
741 CookieEntry* entry = iter.Get();
743 const CookieEntry::ArrayType& cookies = entry->GetCookies();
744 auto length = cookies.Length();
745 for (CookieEntry::IndexType i = 0; i < length;) {
746 CookieListIter iter(entry, i);
747 Cookie* cookie = cookies[i];
749 // check if the cookie has expired
750 if (cookie->Expiry() <= currentTime) {
751 removedList->AppendElement(cookie);
752 COOKIE_LOGEVICTED(cookie, "Cookie expired");
754 // remove from list; do not increment our iterator, but stop if we're
755 // done already.
756 aRemoveCookieCallback(iter);
757 if (i == --length) {
758 break;
760 } else {
761 // check if the cookie is over the age limit
762 if (cookie->LastAccessed() <= purgeTime) {
763 purgeList.AppendElement(iter);
765 } else if (cookie->LastAccessed() < oldestTime) {
766 // reset our indicator
767 oldestTime = cookie->LastAccessed();
770 ++i;
772 MOZ_ASSERT(length == cookies.Length());
776 uint32_t postExpiryCookieCount = mCookieCount;
778 // now we have a list of iterators for cookies over the age limit.
779 // sort them by age, and then we'll see how many to remove...
780 purgeList.Sort(CompareCookiesByAge());
782 // only remove old cookies until we reach the max cookie limit, no more.
783 uint32_t excess = mCookieCount > aMaxNumberOfCookies
784 ? mCookieCount - aMaxNumberOfCookies
785 : 0;
786 if (purgeList.Length() > excess) {
787 // We're not purging everything in the list, so update our indicator.
788 oldestTime = purgeList[excess].Cookie()->LastAccessed();
790 purgeList.SetLength(excess);
793 // sort the list again, this time grouping cookies with a common entryclass
794 // together, and with ascending index. this allows us to iterate backwards
795 // over the list removing cookies, without having to adjust indexes as we go.
796 purgeList.Sort(CompareCookiesByIndex());
797 for (PurgeList::index_type i = purgeList.Length(); i--;) {
798 Cookie* cookie = purgeList[i].Cookie();
799 removedList->AppendElement(cookie);
800 COOKIE_LOGEVICTED(cookie, "Cookie too old");
802 aRemoveCookieCallback(purgeList[i]);
805 // Update the database if we have entries to purge.
806 if (aFinalizeCallback) {
807 aFinalizeCallback();
810 // reset the oldest time indicator
811 mCookieOldestTime = oldestTime;
813 COOKIE_LOGSTRING(LogLevel::Debug,
814 ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32
815 " purged; %" PRIu32 " remain; %" PRId64 " oldest age",
816 initialCookieCount - postExpiryCookieCount,
817 postExpiryCookieCount - mCookieCount, mCookieCount,
818 aCurrentTimeInUsec - mCookieOldestTime));
820 return removedList.forget();
823 // remove a cookie from the hashtable, and update the iterator state.
824 void CookieStorage::RemoveCookieFromList(const CookieListIter& aIter) {
825 RemoveCookieFromDB(aIter);
826 RemoveCookieFromListInternal(aIter);
829 void CookieStorage::RemoveCookieFromListInternal(const CookieListIter& aIter) {
830 if (aIter.entry->GetCookies().Length() == 1) {
831 // we're removing the last element in the array - so just remove the entry
832 // from the hash. note that the entryclass' dtor will take care of
833 // releasing this last element for us!
834 mHostTable.RawRemoveEntry(aIter.entry);
836 } else {
837 // just remove the element from the list
838 aIter.entry->GetCookies().RemoveElementAt(aIter.index);
841 --mCookieCount;
844 void CookieStorage::PrefChanged(nsIPrefBranch* aPrefBranch) {
845 int32_t val;
846 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) {
847 mMaxNumberOfCookies =
848 static_cast<uint16_t> LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
851 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) {
852 mCookieQuotaPerHost = static_cast<uint16_t> LIMIT(
853 val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost);
856 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) {
857 mMaxCookiesPerHost = static_cast<uint16_t> LIMIT(
858 val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost);
861 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
862 mCookiePurgeAge =
863 int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
867 NS_IMETHODIMP
868 CookieStorage::Observe(nsISupports* aSubject, const char* aTopic,
869 const char16_t* /*aData*/) {
870 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
871 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
872 if (prefBranch) {
873 PrefChanged(prefBranch);
877 return NS_OK;
880 } // namespace net
881 } // namespace mozilla