Bug 1824634 disconnect from GtkIMContext OnDestroyWindow r=masayuki
[gecko.git] / netwerk / cookie / CookieStorage.cpp
blobe0abe74ee1391535c92be8fcd6e2304438c9a665
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 return;
455 // Remove the stale cookie. We save notification for later, once all list
456 // modifications are complete.
457 RemoveCookieFromList(exactIter);
458 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
459 "stale cookie was purged");
460 purgedList = CreatePurgeList(oldCookie);
462 // We've done all we need to wrt removing and notifying the stale cookie.
463 // From here on out, we pretend pretend it didn't exist, so that we
464 // preserve expected notification semantics when adding the new cookie.
465 foundCookie = false;
467 } else {
468 // If the old cookie is httponly, make sure we're not coming from script.
469 if (!aFromHttp && oldCookie->IsHttpOnly()) {
470 COOKIE_LOGFAILURE(
471 SET_COOKIE, aHostURI, aCookieHeader,
472 "previously stored cookie is httponly; coming from script");
473 CookieLogging::LogMessageToConsole(
474 aCRC, aHostURI, nsIScriptError::warningFlag,
475 CONSOLE_REJECTION_CATEGORY,
476 "CookieRejectedHttpOnlyButFromScript"_ns,
477 AutoTArray<nsString, 1>{
478 NS_ConvertUTF8toUTF16(aCookie->Name()),
480 return;
483 // If the new cookie has the same value, expiry date, isSecure, isSession,
484 // isHttpOnly and SameSite flags then we can just keep the old one.
485 // Only if any of these differ we would want to override the cookie.
486 if (oldCookie->Value().Equals(aCookie->Value()) &&
487 oldCookie->Expiry() == aCookie->Expiry() &&
488 oldCookie->IsSecure() == aCookie->IsSecure() &&
489 oldCookie->IsSession() == aCookie->IsSession() &&
490 oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
491 oldCookie->SameSite() == aCookie->SameSite() &&
492 oldCookie->RawSameSite() == aCookie->RawSameSite() &&
493 oldCookie->SchemeMap() == aCookie->SchemeMap() &&
494 // We don't want to perform this optimization if the cookie is
495 // considered stale, since in this case we would need to update the
496 // database.
497 !oldCookie->IsStale()) {
498 // Update the last access time on the old cookie.
499 oldCookie->SetLastAccessed(aCookie->LastAccessed());
500 UpdateCookieOldestTime(oldCookie);
501 return;
504 // Merge the scheme map in case the old cookie and the new cookie are
505 // used with different schemes.
506 MergeCookieSchemeMap(oldCookie, aCookie);
508 // Remove the old cookie.
509 RemoveCookieFromList(exactIter);
511 // If the new cookie has expired -- i.e. the intent was simply to delete
512 // the old cookie -- then we're done.
513 if (aCookie->Expiry() <= currentTime) {
514 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
515 "previously stored cookie was deleted");
516 NotifyChanged(oldCookie, u"deleted", oldCookieIsSession);
517 return;
520 // Preserve creation time of cookie for ordering purposes.
521 aCookie->SetCreationTime(oldCookie->CreationTime());
524 } else {
525 // check if cookie has already expired
526 if (aCookie->Expiry() <= currentTime) {
527 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
528 "cookie has already expired");
529 return;
532 // check if we have to delete an old cookie.
533 CookieEntry* entry =
534 mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
535 if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
536 nsTArray<CookieListIter> removedIterList;
537 // Prioritize evicting insecure cookies.
538 // (draft-ietf-httpbis-cookie-alone section 3.3)
539 uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost;
540 FindStaleCookies(entry, currentTime, false, removedIterList, limit);
541 if (removedIterList.Length() == 0) {
542 if (aCookie->IsSecure()) {
543 // It's valid to evict a secure cookie for another secure cookie.
544 FindStaleCookies(entry, currentTime, true, removedIterList, limit);
545 } else {
546 COOKIE_LOGEVICTED(aCookie,
547 "Too many cookies for this domain and the new "
548 "cookie is not a secure cookie");
549 return;
553 MOZ_ASSERT(!removedIterList.IsEmpty());
554 // Sort |removedIterList| by index again, since we have to remove the
555 // cookie in the reverse order.
556 removedIterList.Sort(CompareCookiesByIndex());
557 for (auto it = removedIterList.rbegin(); it != removedIterList.rend();
558 it++) {
559 RefPtr<Cookie> evictedCookie = (*it).Cookie();
560 COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain");
561 RemoveCookieFromList(*it);
562 CreateOrUpdatePurgeList(purgedList, evictedCookie);
563 MOZ_ASSERT((*it).entry);
566 } else if (mCookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
567 int64_t maxAge = aCurrentTimeInUsec - mCookieOldestTime;
568 int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
569 if (maxAge >= purgeAge) {
570 // we're over both size and age limits by 10%; time to purge the table!
571 // do this by:
572 // 1) removing expired cookies;
573 // 2) evicting the balance of old cookies until we reach the size limit.
574 // note that the mCookieOldestTime indicator can be pessimistic - if
575 // it's older than the actual oldest cookie, we'll just purge more
576 // eagerly.
577 purgedList = PurgeCookies(aCurrentTimeInUsec, mMaxNumberOfCookies,
578 mCookiePurgeAge);
583 // Add the cookie to the db. We do not supply a params array for batching
584 // because this might result in removals and additions being out of order.
585 AddCookieToList(aBaseDomain, aOriginAttributes, aCookie);
586 StoreCookie(aBaseDomain, aOriginAttributes, aCookie);
588 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
590 // Now that list mutations are complete, notify observers. We do it here
591 // because observers may themselves attempt to mutate the list.
592 if (purgedList) {
593 NotifyChanged(purgedList, u"batch-deleted");
596 NotifyChanged(aCookie, foundCookie ? u"changed" : u"added",
597 oldCookieIsSession);
600 void CookieStorage::UpdateCookieOldestTime(Cookie* aCookie) {
601 if (aCookie->LastAccessed() < mCookieOldestTime) {
602 mCookieOldestTime = aCookie->LastAccessed();
606 void CookieStorage::MergeCookieSchemeMap(Cookie* aOldCookie,
607 Cookie* aNewCookie) {
608 aNewCookie->SetSchemeMap(aOldCookie->SchemeMap() | aNewCookie->SchemeMap());
611 void CookieStorage::AddCookieToList(const nsACString& aBaseDomain,
612 const OriginAttributes& aOriginAttributes,
613 Cookie* aCookie) {
614 if (!aCookie) {
615 NS_WARNING("Attempting to AddCookieToList with null cookie");
616 return;
619 CookieKey key(aBaseDomain, aOriginAttributes);
621 CookieEntry* entry = mHostTable.PutEntry(key);
622 NS_ASSERTION(entry, "can't insert element into a null entry!");
624 entry->GetCookies().AppendElement(aCookie);
625 ++mCookieCount;
627 // keep track of the oldest cookie, for when it comes time to purge
628 UpdateCookieOldestTime(aCookie);
631 // static
632 already_AddRefed<nsIArray> CookieStorage::CreatePurgeList(nsICookie* aCookie) {
633 nsCOMPtr<nsIMutableArray> removedList =
634 do_CreateInstance(NS_ARRAY_CONTRACTID);
635 removedList->AppendElement(aCookie);
636 return removedList.forget();
639 // Given the output iter array and the count limit, find cookies
640 // sort by expiry and lastAccessed time.
641 // static
642 void CookieStorage::FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTime,
643 bool aIsSecure,
644 nsTArray<CookieListIter>& aOutput,
645 uint32_t aLimit) {
646 MOZ_ASSERT(aLimit);
648 const CookieEntry::ArrayType& cookies = aEntry->GetCookies();
649 aOutput.Clear();
651 CookieIterComparator comp(aCurrentTime);
652 nsTPriorityQueue<CookieListIter, CookieIterComparator> queue(comp);
654 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
655 Cookie* cookie = cookies[i];
657 if (cookie->Expiry() <= aCurrentTime) {
658 queue.Push(CookieListIter(aEntry, i));
659 continue;
662 if (!aIsSecure) {
663 // We want to look for the non-secure cookie first time through,
664 // then find the secure cookie the second time this function is called.
665 if (cookie->IsSecure()) {
666 continue;
670 queue.Push(CookieListIter(aEntry, i));
673 uint32_t count = 0;
674 while (!queue.IsEmpty() && count < aLimit) {
675 aOutput.AppendElement(queue.Pop());
676 count++;
680 // static
681 void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList,
682 nsICookie* aCookie) {
683 if (!aPurgedList) {
684 COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
685 aPurgedList = CreatePurgeList(aCookie);
686 return;
689 nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(aPurgedList);
690 if (purgedList) {
691 COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list"));
692 purgedList->AppendElement(aCookie);
693 } else {
694 COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!"));
698 // purges expired and old cookies in a batch operation.
699 already_AddRefed<nsIArray> CookieStorage::PurgeCookiesWithCallbacks(
700 int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
701 int64_t aCookiePurgeAge,
702 std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
703 std::function<void()>&& aFinalizeCallback) {
704 NS_ASSERTION(mHostTable.Count() > 0, "table is empty");
706 uint32_t initialCookieCount = mCookieCount;
707 COOKIE_LOGSTRING(LogLevel::Debug,
708 ("PurgeCookies(): beginning purge with %" PRIu32
709 " cookies and %" PRId64 " oldest age",
710 mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
712 using PurgeList = nsTArray<CookieListIter>;
713 PurgeList purgeList(kMaxNumberOfCookies);
715 nsCOMPtr<nsIMutableArray> removedList =
716 do_CreateInstance(NS_ARRAY_CONTRACTID);
718 int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
719 int64_t purgeTime = aCurrentTimeInUsec - aCookiePurgeAge;
720 int64_t oldestTime = INT64_MAX;
722 for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
723 CookieEntry* entry = iter.Get();
725 const CookieEntry::ArrayType& cookies = entry->GetCookies();
726 auto length = cookies.Length();
727 for (CookieEntry::IndexType i = 0; i < length;) {
728 CookieListIter iter(entry, i);
729 Cookie* cookie = cookies[i];
731 // check if the cookie has expired
732 if (cookie->Expiry() <= currentTime) {
733 removedList->AppendElement(cookie);
734 COOKIE_LOGEVICTED(cookie, "Cookie expired");
736 // remove from list; do not increment our iterator, but stop if we're
737 // done already.
738 aRemoveCookieCallback(iter);
739 if (i == --length) {
740 break;
742 } else {
743 // check if the cookie is over the age limit
744 if (cookie->LastAccessed() <= purgeTime) {
745 purgeList.AppendElement(iter);
747 } else if (cookie->LastAccessed() < oldestTime) {
748 // reset our indicator
749 oldestTime = cookie->LastAccessed();
752 ++i;
754 MOZ_ASSERT(length == cookies.Length());
758 uint32_t postExpiryCookieCount = mCookieCount;
760 // now we have a list of iterators for cookies over the age limit.
761 // sort them by age, and then we'll see how many to remove...
762 purgeList.Sort(CompareCookiesByAge());
764 // only remove old cookies until we reach the max cookie limit, no more.
765 uint32_t excess = mCookieCount > aMaxNumberOfCookies
766 ? mCookieCount - aMaxNumberOfCookies
767 : 0;
768 if (purgeList.Length() > excess) {
769 // We're not purging everything in the list, so update our indicator.
770 oldestTime = purgeList[excess].Cookie()->LastAccessed();
772 purgeList.SetLength(excess);
775 // sort the list again, this time grouping cookies with a common entryclass
776 // together, and with ascending index. this allows us to iterate backwards
777 // over the list removing cookies, without having to adjust indexes as we go.
778 purgeList.Sort(CompareCookiesByIndex());
779 for (PurgeList::index_type i = purgeList.Length(); i--;) {
780 Cookie* cookie = purgeList[i].Cookie();
781 removedList->AppendElement(cookie);
782 COOKIE_LOGEVICTED(cookie, "Cookie too old");
784 aRemoveCookieCallback(purgeList[i]);
787 // Update the database if we have entries to purge.
788 if (aFinalizeCallback) {
789 aFinalizeCallback();
792 // reset the oldest time indicator
793 mCookieOldestTime = oldestTime;
795 COOKIE_LOGSTRING(LogLevel::Debug,
796 ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32
797 " purged; %" PRIu32 " remain; %" PRId64 " oldest age",
798 initialCookieCount - postExpiryCookieCount,
799 postExpiryCookieCount - mCookieCount, mCookieCount,
800 aCurrentTimeInUsec - mCookieOldestTime));
802 return removedList.forget();
805 // remove a cookie from the hashtable, and update the iterator state.
806 void CookieStorage::RemoveCookieFromList(const CookieListIter& aIter) {
807 RemoveCookieFromDB(aIter);
808 RemoveCookieFromListInternal(aIter);
811 void CookieStorage::RemoveCookieFromListInternal(const CookieListIter& aIter) {
812 if (aIter.entry->GetCookies().Length() == 1) {
813 // we're removing the last element in the array - so just remove the entry
814 // from the hash. note that the entryclass' dtor will take care of
815 // releasing this last element for us!
816 mHostTable.RawRemoveEntry(aIter.entry);
818 } else {
819 // just remove the element from the list
820 aIter.entry->GetCookies().RemoveElementAt(aIter.index);
823 --mCookieCount;
826 void CookieStorage::PrefChanged(nsIPrefBranch* aPrefBranch) {
827 int32_t val;
828 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) {
829 mMaxNumberOfCookies =
830 static_cast<uint16_t> LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
833 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) {
834 mCookieQuotaPerHost = static_cast<uint16_t> LIMIT(
835 val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost);
838 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) {
839 mMaxCookiesPerHost = static_cast<uint16_t> LIMIT(
840 val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost);
843 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
844 mCookiePurgeAge =
845 int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
849 NS_IMETHODIMP
850 CookieStorage::Observe(nsISupports* aSubject, const char* aTopic,
851 const char16_t* /*aData*/) {
852 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
853 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
854 if (prefBranch) {
855 PrefChanged(prefBranch);
859 return NS_OK;
862 } // namespace net
863 } // namespace mozilla