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/. */
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "CookieNotification.h"
11 #include "nsICookieNotification.h"
12 #include "CookieStorage.h"
13 #include "mozilla/dom/nsMixedContentBlocker.h"
14 #include "mozilla/glean/GleanMetrics.h"
15 #include "nsIMutableArray.h"
16 #include "nsTPriorityQueue.h"
17 #include "nsIScriptError.h"
18 #include "nsIUserIdleService.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsComponentManagerUtils.h"
22 #include "nsIPrefService.h"
24 #undef ADD_TEN_PERCENT
25 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
28 #define LIMIT(x, low, high, default) \
29 ((x) >= (low) && (x) <= (high) ? (x) : (default))
36 // comparator class for lastaccessed times of cookies.
37 class CompareCookiesByAge
{
39 static bool Equals(const CookieListIter
& a
, const CookieListIter
& b
) {
40 return a
.Cookie()->LastAccessed() == b
.Cookie()->LastAccessed() &&
41 a
.Cookie()->CreationTime() == b
.Cookie()->CreationTime();
44 static bool LessThan(const CookieListIter
& a
, const CookieListIter
& b
) {
45 // compare by lastAccessed time, and tiebreak by creationTime.
46 int64_t result
= a
.Cookie()->LastAccessed() - b
.Cookie()->LastAccessed();
51 return a
.Cookie()->CreationTime() < b
.Cookie()->CreationTime();
55 // Cookie comparator for the priority queue used in FindStaleCookies.
56 // Note that the expired cookie has the highest priority.
57 // Other non-expired cookies are sorted by their age.
58 class CookieIterComparator
{
63 explicit CookieIterComparator(int64_t aTime
) : mCurrentTime(aTime
) {}
65 bool LessThan(const CookieListIter
& lhs
, const CookieListIter
& rhs
) {
66 bool lExpired
= lhs
.Cookie()->Expiry() <= mCurrentTime
;
67 bool rExpired
= rhs
.Cookie()->Expiry() <= mCurrentTime
;
68 if (lExpired
&& !rExpired
) {
72 if (!lExpired
&& rExpired
) {
76 return mozilla::net::CompareCookiesByAge::LessThan(lhs
, rhs
);
80 // comparator class for sorting cookies by entry and index.
81 class CompareCookiesByIndex
{
83 static bool Equals(const CookieListIter
& a
, const CookieListIter
& b
) {
84 NS_ASSERTION(a
.entry
!= b
.entry
|| a
.index
!= b
.index
,
85 "cookie indexes should never be equal");
89 static bool LessThan(const CookieListIter
& a
, const CookieListIter
& b
) {
90 // compare by entryclass pointer, then by index.
91 if (a
.entry
!= b
.entry
) {
92 return a
.entry
< b
.entry
;
95 return a
.index
< b
.index
;
101 // ---------------------------------------------------------------------------
104 size_t CookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
105 size_t amount
= CookieKey::SizeOfExcludingThis(aMallocSizeOf
);
107 amount
+= mCookies
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
108 for (uint32_t i
= 0; i
< mCookies
.Length(); ++i
) {
109 amount
+= mCookies
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
115 bool CookieEntry::IsPartitioned() const {
116 return !mOriginAttributes
.mPartitionKey
.IsEmpty();
119 // ---------------------------------------------------------------------------
122 NS_IMPL_ISUPPORTS(CookieStorage
, nsIObserver
, nsISupportsWeakReference
)
124 void CookieStorage::Init() {
125 // init our pref and observer
126 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
128 prefBranch
->AddObserver(kPrefMaxNumberOfCookies
, this, true);
129 prefBranch
->AddObserver(kPrefMaxCookiesPerHost
, this, true);
130 prefBranch
->AddObserver(kPrefCookiePurgeAge
, this, true);
131 PrefChanged(prefBranch
);
134 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
135 NS_ENSURE_TRUE_VOID(observerService
);
138 observerService
->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY
, true);
139 NS_ENSURE_SUCCESS_VOID(rv
);
142 size_t CookieStorage::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
145 amount
+= aMallocSizeOf(this);
146 amount
+= mHostTable
.SizeOfExcludingThis(aMallocSizeOf
);
151 void CookieStorage::GetCookies(nsTArray
<RefPtr
<nsICookie
>>& aCookies
) const {
152 aCookies
.SetCapacity(mCookieCount
);
153 for (const auto& entry
: mHostTable
) {
154 const CookieEntry::ArrayType
& cookies
= entry
.GetCookies();
155 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
156 aCookies
.AppendElement(cookies
[i
]);
161 void CookieStorage::GetSessionCookies(
162 nsTArray
<RefPtr
<nsICookie
>>& aCookies
) const {
163 aCookies
.SetCapacity(mCookieCount
);
164 for (const auto& entry
: mHostTable
) {
165 const CookieEntry::ArrayType
& cookies
= entry
.GetCookies();
166 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
167 Cookie
* cookie
= cookies
[i
];
168 // Filter out non-session cookies.
169 if (cookie
->IsSession()) {
170 aCookies
.AppendElement(cookie
);
176 // find an exact cookie specified by host, name, and path that hasn't expired.
177 bool CookieStorage::FindCookie(const nsACString
& aBaseDomain
,
178 const OriginAttributes
& aOriginAttributes
,
179 const nsACString
& aHost
, const nsACString
& aName
,
180 const nsACString
& aPath
, CookieListIter
& aIter
) {
182 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
187 const CookieEntry::ArrayType
& cookies
= entry
->GetCookies();
188 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
189 Cookie
* cookie
= cookies
[i
];
191 if (aHost
.Equals(cookie
->Host()) && aPath
.Equals(cookie
->Path()) &&
192 aName
.Equals(cookie
->Name())) {
193 aIter
= CookieListIter(entry
, i
);
201 // find an secure cookie specified by host and name
202 bool CookieStorage::FindSecureCookie(const nsACString
& aBaseDomain
,
203 const OriginAttributes
& aOriginAttributes
,
206 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
211 const CookieEntry::ArrayType
& cookies
= entry
->GetCookies();
212 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
213 Cookie
* cookie
= cookies
[i
];
214 // isn't a match if insecure or a different name
215 if (!cookie
->IsSecure() || !aCookie
->Name().Equals(cookie
->Name())) {
219 // The host must "domain-match" an existing cookie or vice-versa
220 if (CookieCommons::DomainMatches(cookie
, aCookie
->Host()) ||
221 CookieCommons::DomainMatches(aCookie
, cookie
->Host())) {
222 // If the path of new cookie and the path of existing cookie
223 // aren't "/", then this situation needs to compare paths to
224 // ensure only that a newly-created non-secure cookie does not
225 // overlay an existing secure cookie.
226 if (CookieCommons::PathMatches(cookie
, aCookie
->GetFilePath())) {
235 uint32_t CookieStorage::CountCookiesFromHost(const nsACString
& aBaseDomain
,
236 uint32_t aPrivateBrowsingId
) {
237 OriginAttributes attrs
;
238 attrs
.mPrivateBrowsingId
= aPrivateBrowsingId
;
240 // Return a count of all cookies, including expired.
241 CookieEntry
* entry
= mHostTable
.GetEntry(CookieKey(aBaseDomain
, attrs
));
242 return entry
? entry
->GetCookies().Length() : 0;
245 void CookieStorage::GetAll(nsTArray
<RefPtr
<nsICookie
>>& aResult
) const {
246 aResult
.SetCapacity(mCookieCount
);
248 for (const auto& entry
: mHostTable
) {
249 const CookieEntry::ArrayType
& cookies
= entry
.GetCookies();
250 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
251 aResult
.AppendElement(cookies
[i
]);
256 const nsTArray
<RefPtr
<Cookie
>>* CookieStorage::GetCookiesFromHost(
257 const nsACString
& aBaseDomain
, const OriginAttributes
& aOriginAttributes
) {
259 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
260 return entry
? &entry
->GetCookies() : nullptr;
263 void CookieStorage::GetCookiesWithOriginAttributes(
264 const OriginAttributesPattern
& aPattern
, const nsACString
& aBaseDomain
,
265 nsTArray
<RefPtr
<nsICookie
>>& aResult
) {
266 for (auto iter
= mHostTable
.Iter(); !iter
.Done(); iter
.Next()) {
267 CookieEntry
* entry
= iter
.Get();
269 if (!aBaseDomain
.IsEmpty() && !aBaseDomain
.Equals(entry
->mBaseDomain
)) {
273 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
277 const CookieEntry::ArrayType
& entryCookies
= entry
->GetCookies();
279 for (CookieEntry::IndexType i
= 0; i
< entryCookies
.Length(); ++i
) {
280 aResult
.AppendElement(entryCookies
[i
]);
285 void CookieStorage::RemoveCookie(const nsACString
& aBaseDomain
,
286 const OriginAttributes
& aOriginAttributes
,
287 const nsACString
& aHost
,
288 const nsACString
& aName
,
289 const nsACString
& aPath
) {
290 CookieListIter matchIter
{};
291 RefPtr
<Cookie
> cookie
;
292 if (FindCookie(aBaseDomain
, aOriginAttributes
, aHost
, aName
, aPath
,
294 cookie
= matchIter
.Cookie();
295 RemoveCookieFromList(matchIter
);
299 // Everything's done. Notify observers.
300 NotifyChanged(cookie
, nsICookieNotification::COOKIE_DELETED
, aBaseDomain
);
304 void CookieStorage::RemoveCookiesWithOriginAttributes(
305 const OriginAttributesPattern
& aPattern
, const nsACString
& aBaseDomain
) {
306 // Iterate the hash table of CookieEntry.
307 for (auto iter
= mHostTable
.Iter(); !iter
.Done(); iter
.Next()) {
308 CookieEntry
* entry
= iter
.Get();
310 if (!aBaseDomain
.IsEmpty() && !aBaseDomain
.Equals(entry
->mBaseDomain
)) {
314 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
318 // Pattern matches. Delete all cookies within this CookieEntry.
319 uint32_t cookiesCount
= entry
->GetCookies().Length();
321 for (CookieEntry::IndexType i
= 0; i
< cookiesCount
; ++i
) {
322 // Remove the first cookie from the list.
323 CookieListIter
iter(entry
, 0);
324 RefPtr
<Cookie
> cookie
= iter
.Cookie();
326 // Remove the cookie.
327 RemoveCookieFromList(iter
);
330 NotifyChanged(cookie
, nsICookieNotification::COOKIE_DELETED
,
337 void CookieStorage::RemoveCookiesFromExactHost(
338 const nsACString
& aHost
, const nsACString
& aBaseDomain
,
339 const OriginAttributesPattern
& aPattern
) {
340 // Iterate the hash table of CookieEntry.
341 for (auto iter
= mHostTable
.Iter(); !iter
.Done(); iter
.Next()) {
342 CookieEntry
* entry
= iter
.Get();
344 if (!aBaseDomain
.Equals(entry
->mBaseDomain
)) {
348 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
352 uint32_t cookiesCount
= entry
->GetCookies().Length();
353 for (CookieEntry::IndexType i
= cookiesCount
; i
!= 0; --i
) {
354 CookieListIter
iter(entry
, i
- 1);
355 RefPtr
<Cookie
> cookie
= iter
.Cookie();
357 if (!aHost
.Equals(cookie
->RawHost())) {
361 // Remove the cookie.
362 RemoveCookieFromList(iter
);
365 NotifyChanged(cookie
, nsICookieNotification::COOKIE_DELETED
,
372 void CookieStorage::RemoveAll() {
373 // clearing the hashtable will call each CookieEntry's dtor,
374 // which releases all their respective children.
377 mCookieOldestTime
= INT64_MAX
;
381 NotifyChanged(nullptr, nsICookieNotification::ALL_COOKIES_CLEARED
, ""_ns
);
384 // notify observers that the cookie list changed.
385 void CookieStorage::NotifyChanged(nsISupports
* aSubject
,
386 nsICookieNotification::Action aAction
,
387 const nsACString
& aBaseDomain
,
388 dom::BrowsingContext
* aBrowsingContext
,
389 bool aOldCookieIsSession
) {
390 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
395 nsCOMPtr
<nsICookie
> cookie
;
396 nsCOMPtr
<nsIArray
> batchDeletedCookies
;
398 if (aAction
== nsICookieNotification::COOKIES_BATCH_DELETED
) {
399 batchDeletedCookies
= do_QueryInterface(aSubject
);
401 cookie
= do_QueryInterface(aSubject
);
404 uint64_t browsingContextId
= 0;
405 if (aBrowsingContext
) {
406 browsingContextId
= aBrowsingContext
->Id();
409 nsCOMPtr
<nsICookieNotification
> notification
= new CookieNotification(
410 aAction
, cookie
, aBaseDomain
, batchDeletedCookies
, browsingContextId
);
411 // Notify for topic "private-cookie-changed" or "cookie-changed"
412 os
->NotifyObservers(notification
, NotificationTopic(), u
"");
414 NotifyChangedInternal(notification
, aOldCookieIsSession
);
417 // this is a backend function for adding a cookie to the list, via SetCookie.
418 // also used in the cookie manager, for profile migration from IE. it either
419 // replaces an existing cookie; or adds the cookie to the hashtable, and
420 // deletes a cookie (if maximum number of cookies has been reached). also
421 // performs list maintenance by removing expired cookies.
422 void CookieStorage::AddCookie(nsIConsoleReportCollector
* aCRC
,
423 const nsACString
& aBaseDomain
,
424 const OriginAttributes
& aOriginAttributes
,
425 Cookie
* aCookie
, int64_t aCurrentTimeInUsec
,
426 nsIURI
* aHostURI
, const nsACString
& aCookieHeader
,
428 dom::BrowsingContext
* aBrowsingContext
) {
429 int64_t currentTime
= aCurrentTimeInUsec
/ PR_USEC_PER_SEC
;
431 CookieListIter exactIter
{};
432 bool foundCookie
= false;
433 foundCookie
= FindCookie(aBaseDomain
, aOriginAttributes
, aCookie
->Host(),
434 aCookie
->Name(), aCookie
->Path(), exactIter
);
435 bool foundSecureExact
= foundCookie
&& exactIter
.Cookie()->IsSecure();
436 bool potentiallyTrustworthy
= true;
438 potentiallyTrustworthy
=
439 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI
);
441 constexpr auto CONSOLE_REJECTION_CATEGORY
= "cookiesRejection"_ns
;
442 bool oldCookieIsSession
= false;
443 // Step1, call FindSecureCookie(). FindSecureCookie() would
444 // find the existing cookie with the security flag and has
445 // the same name, host and path of the new cookie, if there is any.
446 // Step2, Confirm new cookie's security setting. If any targeted
447 // cookie had been found in Step1, then confirm whether the
448 // new cookie could modify it. If the new created cookie’s
449 // "secure-only-flag" is not set, and the "scheme" component
450 // of the "request-uri" does not denote a "secure" protocol,
451 // then ignore the new cookie.
452 // (draft-ietf-httpbis-cookie-alone section 3.2)
453 if (!aCookie
->IsSecure() &&
455 FindSecureCookie(aBaseDomain
, aOriginAttributes
, aCookie
)) &&
456 !potentiallyTrustworthy
) {
457 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
458 "cookie can't save because older cookie is secure "
459 "cookie but newer cookie is non-secure cookie");
460 CookieLogging::LogMessageToConsole(
461 aCRC
, aHostURI
, nsIScriptError::warningFlag
, CONSOLE_REJECTION_CATEGORY
,
462 "CookieRejectedNonsecureOverSecure"_ns
,
463 AutoTArray
<nsString
, 1>{
464 NS_ConvertUTF8toUTF16(aCookie
->Name()),
469 RefPtr
<Cookie
> oldCookie
;
470 nsCOMPtr
<nsIArray
> purgedList
;
472 oldCookie
= exactIter
.Cookie();
473 oldCookieIsSession
= oldCookie
->IsSession();
475 // Check if the old cookie is stale (i.e. has already expired). If so, we
476 // need to be careful about the semantics of removing it and adding the new
477 // cookie: we want the behavior wrt adding the new cookie to be the same as
478 // if it didn't exist, but we still want to fire a removal notification.
479 if (oldCookie
->Expiry() <= currentTime
) {
480 if (aCookie
->Expiry() <= currentTime
) {
481 // The new cookie has expired and the old one is stale. Nothing to do.
482 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
483 "cookie has already expired");
487 // Remove the stale cookie. We save notification for later, once all list
488 // modifications are complete.
489 RemoveCookieFromList(exactIter
);
490 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
491 "stale cookie was purged");
492 purgedList
= CreatePurgeList(oldCookie
);
494 // We've done all we need to wrt removing and notifying the stale cookie.
495 // From here on out, we pretend pretend it didn't exist, so that we
496 // preserve expected notification semantics when adding the new cookie.
500 // If the old cookie is httponly, make sure we're not coming from script.
501 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
503 SET_COOKIE
, aHostURI
, aCookieHeader
,
504 "previously stored cookie is httponly; coming from script");
505 CookieLogging::LogMessageToConsole(
506 aCRC
, aHostURI
, nsIScriptError::warningFlag
,
507 CONSOLE_REJECTION_CATEGORY
,
508 "CookieRejectedHttpOnlyButFromScript"_ns
,
509 AutoTArray
<nsString
, 1>{
510 NS_ConvertUTF8toUTF16(aCookie
->Name()),
515 // If the new cookie has the same value, expiry date, isSecure, isSession,
516 // isHttpOnly and SameSite flags then we can just keep the old one.
517 // Only if any of these differ we would want to override the cookie.
518 if (oldCookie
->Value().Equals(aCookie
->Value()) &&
519 oldCookie
->Expiry() == aCookie
->Expiry() &&
520 oldCookie
->IsSecure() == aCookie
->IsSecure() &&
521 oldCookie
->IsSession() == aCookie
->IsSession() &&
522 oldCookie
->IsHttpOnly() == aCookie
->IsHttpOnly() &&
523 oldCookie
->SameSite() == aCookie
->SameSite() &&
524 oldCookie
->RawSameSite() == aCookie
->RawSameSite() &&
525 oldCookie
->SchemeMap() == aCookie
->SchemeMap() &&
526 // We don't want to perform this optimization if the cookie is
527 // considered stale, since in this case we would need to update the
529 !oldCookie
->IsStale()) {
530 // Update the last access time on the old cookie.
531 oldCookie
->SetLastAccessed(aCookie
->LastAccessed());
532 UpdateCookieOldestTime(oldCookie
);
536 // Merge the scheme map in case the old cookie and the new cookie are
537 // used with different schemes.
538 MergeCookieSchemeMap(oldCookie
, aCookie
);
540 // Remove the old cookie.
541 RemoveCookieFromList(exactIter
);
543 // If the new cookie has expired -- i.e. the intent was simply to delete
544 // the old cookie -- then we're done.
545 if (aCookie
->Expiry() <= currentTime
) {
546 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
547 "previously stored cookie was deleted");
548 NotifyChanged(oldCookie
, nsICookieNotification::COOKIE_DELETED
,
549 aBaseDomain
, aBrowsingContext
, oldCookieIsSession
);
553 // Preserve creation time of cookie for ordering purposes.
554 aCookie
->SetCreationTime(oldCookie
->CreationTime());
558 // check if cookie has already expired
559 if (aCookie
->Expiry() <= currentTime
) {
560 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
561 "cookie has already expired");
565 // check if we have to delete an old cookie.
567 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
568 if (entry
&& entry
->GetCookies().Length() >= mMaxCookiesPerHost
) {
569 nsTArray
<CookieListIter
> removedIterList
;
570 // Prioritize evicting insecure cookies.
571 // (draft-ietf-httpbis-cookie-alone section 3.3)
572 uint32_t limit
= mMaxCookiesPerHost
- mCookieQuotaPerHost
;
573 FindStaleCookies(entry
, currentTime
, false, removedIterList
, limit
);
574 if (removedIterList
.Length() == 0) {
575 if (aCookie
->IsSecure()) {
576 // It's valid to evict a secure cookie for another secure cookie.
577 FindStaleCookies(entry
, currentTime
, true, removedIterList
, limit
);
579 COOKIE_LOGEVICTED(aCookie
,
580 "Too many cookies for this domain and the new "
581 "cookie is not a secure cookie");
586 MOZ_ASSERT(!removedIterList
.IsEmpty());
587 // Sort |removedIterList| by index again, since we have to remove the
588 // cookie in the reverse order.
589 removedIterList
.Sort(CompareCookiesByIndex());
590 for (auto it
= removedIterList
.rbegin(); it
!= removedIterList
.rend();
592 RefPtr
<Cookie
> evictedCookie
= (*it
).Cookie();
593 COOKIE_LOGEVICTED(evictedCookie
, "Too many cookies for this domain");
594 RemoveCookieFromList(*it
);
595 CreateOrUpdatePurgeList(purgedList
, evictedCookie
);
596 MOZ_ASSERT((*it
).entry
);
598 uint32_t purgedLength
= 0;
599 purgedList
->GetLength(&purgedLength
);
600 mozilla::glean::networking::cookie_purge_entry_max
.AccumulateSamples(
603 } else if (mCookieCount
>= ADD_TEN_PERCENT(mMaxNumberOfCookies
)) {
604 int64_t maxAge
= aCurrentTimeInUsec
- mCookieOldestTime
;
605 int64_t purgeAge
= ADD_TEN_PERCENT(mCookiePurgeAge
);
606 if (maxAge
>= purgeAge
) {
607 // we're over both size and age limits by 10%; time to purge the table!
609 // 1) removing expired cookies;
610 // 2) evicting the balance of old cookies until we reach the size limit.
611 // note that the mCookieOldestTime indicator can be pessimistic - if
612 // it's older than the actual oldest cookie, we'll just purge more
614 purgedList
= PurgeCookies(aCurrentTimeInUsec
, mMaxNumberOfCookies
,
616 uint32_t purgedLength
= 0;
617 purgedList
->GetLength(&purgedLength
);
618 mozilla::glean::networking::cookie_purge_max
.AccumulateSamples(
624 // Add the cookie to the db. We do not supply a params array for batching
625 // because this might result in removals and additions being out of order.
626 AddCookieToList(aBaseDomain
, aOriginAttributes
, aCookie
);
627 StoreCookie(aBaseDomain
, aOriginAttributes
, aCookie
);
629 COOKIE_LOGSUCCESS(SET_COOKIE
, aHostURI
, aCookieHeader
, aCookie
, foundCookie
);
631 // Now that list mutations are complete, notify observers. We do it here
632 // because observers may themselves attempt to mutate the list.
634 NotifyChanged(purgedList
, nsICookieNotification::COOKIES_BATCH_DELETED
,
638 // Notify for topic "private-cookie-changed" or "cookie-changed"
639 NotifyChanged(aCookie
,
640 foundCookie
? nsICookieNotification::COOKIE_CHANGED
641 : nsICookieNotification::COOKIE_ADDED
,
642 aBaseDomain
, aBrowsingContext
, oldCookieIsSession
);
645 void CookieStorage::UpdateCookieOldestTime(Cookie
* aCookie
) {
646 if (aCookie
->LastAccessed() < mCookieOldestTime
) {
647 mCookieOldestTime
= aCookie
->LastAccessed();
651 void CookieStorage::MergeCookieSchemeMap(Cookie
* aOldCookie
,
652 Cookie
* aNewCookie
) {
653 aNewCookie
->SetSchemeMap(aOldCookie
->SchemeMap() | aNewCookie
->SchemeMap());
656 void CookieStorage::AddCookieToList(const nsACString
& aBaseDomain
,
657 const OriginAttributes
& aOriginAttributes
,
660 NS_WARNING("Attempting to AddCookieToList with null cookie");
664 CookieKey
key(aBaseDomain
, aOriginAttributes
);
666 CookieEntry
* entry
= mHostTable
.PutEntry(key
);
667 NS_ASSERTION(entry
, "can't insert element into a null entry!");
669 entry
->GetCookies().AppendElement(aCookie
);
672 // keep track of the oldest cookie, for when it comes time to purge
673 UpdateCookieOldestTime(aCookie
);
677 already_AddRefed
<nsIArray
> CookieStorage::CreatePurgeList(nsICookie
* aCookie
) {
678 nsCOMPtr
<nsIMutableArray
> removedList
=
679 do_CreateInstance(NS_ARRAY_CONTRACTID
);
680 removedList
->AppendElement(aCookie
);
681 return removedList
.forget();
684 // Given the output iter array and the count limit, find cookies
685 // sort by expiry and lastAccessed time.
687 void CookieStorage::FindStaleCookies(CookieEntry
* aEntry
, int64_t aCurrentTime
,
689 nsTArray
<CookieListIter
>& aOutput
,
693 const CookieEntry::ArrayType
& cookies
= aEntry
->GetCookies();
696 CookieIterComparator
comp(aCurrentTime
);
697 nsTPriorityQueue
<CookieListIter
, CookieIterComparator
> queue(comp
);
699 for (CookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
700 Cookie
* cookie
= cookies
[i
];
702 if (cookie
->Expiry() <= aCurrentTime
) {
703 queue
.Push(CookieListIter(aEntry
, i
));
708 // We want to look for the non-secure cookie first time through,
709 // then find the secure cookie the second time this function is called.
710 if (cookie
->IsSecure()) {
715 queue
.Push(CookieListIter(aEntry
, i
));
719 while (!queue
.IsEmpty() && count
< aLimit
) {
720 aOutput
.AppendElement(queue
.Pop());
726 void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr
<nsIArray
>& aPurgedList
,
727 nsICookie
* aCookie
) {
729 COOKIE_LOGSTRING(LogLevel::Debug
, ("Creating new purge list"));
730 aPurgedList
= CreatePurgeList(aCookie
);
734 nsCOMPtr
<nsIMutableArray
> purgedList
= do_QueryInterface(aPurgedList
);
736 COOKIE_LOGSTRING(LogLevel::Debug
, ("Updating existing purge list"));
737 purgedList
->AppendElement(aCookie
);
739 COOKIE_LOGSTRING(LogLevel::Debug
, ("Could not QI aPurgedList!"));
743 // purges expired and old cookies in a batch operation.
744 already_AddRefed
<nsIArray
> CookieStorage::PurgeCookiesWithCallbacks(
745 int64_t aCurrentTimeInUsec
, uint16_t aMaxNumberOfCookies
,
746 int64_t aCookiePurgeAge
,
747 std::function
<void(const CookieListIter
&)>&& aRemoveCookieCallback
,
748 std::function
<void()>&& aFinalizeCallback
) {
749 NS_ASSERTION(mHostTable
.Count() > 0, "table is empty");
751 uint32_t initialCookieCount
= mCookieCount
;
752 COOKIE_LOGSTRING(LogLevel::Debug
,
753 ("PurgeCookies(): beginning purge with %" PRIu32
754 " cookies and %" PRId64
" oldest age",
755 mCookieCount
, aCurrentTimeInUsec
- mCookieOldestTime
));
757 using PurgeList
= nsTArray
<CookieListIter
>;
758 PurgeList
purgeList(kMaxNumberOfCookies
);
760 nsCOMPtr
<nsIMutableArray
> removedList
=
761 do_CreateInstance(NS_ARRAY_CONTRACTID
);
763 int64_t currentTime
= aCurrentTimeInUsec
/ PR_USEC_PER_SEC
;
764 int64_t purgeTime
= aCurrentTimeInUsec
- aCookiePurgeAge
;
765 int64_t oldestTime
= INT64_MAX
;
767 for (auto iter
= mHostTable
.Iter(); !iter
.Done(); iter
.Next()) {
768 CookieEntry
* entry
= iter
.Get();
770 const CookieEntry::ArrayType
& cookies
= entry
->GetCookies();
771 auto length
= cookies
.Length();
772 for (CookieEntry::IndexType i
= 0; i
< length
;) {
773 CookieListIter
iter(entry
, i
);
774 Cookie
* cookie
= cookies
[i
];
776 // check if the cookie has expired
777 if (cookie
->Expiry() <= currentTime
) {
778 removedList
->AppendElement(cookie
);
779 COOKIE_LOGEVICTED(cookie
, "Cookie expired");
781 // remove from list; do not increment our iterator, but stop if we're
783 aRemoveCookieCallback(iter
);
788 // check if the cookie is over the age limit
789 if (cookie
->LastAccessed() <= purgeTime
) {
790 purgeList
.AppendElement(iter
);
792 } else if (cookie
->LastAccessed() < oldestTime
) {
793 // reset our indicator
794 oldestTime
= cookie
->LastAccessed();
799 MOZ_ASSERT(length
== cookies
.Length());
803 uint32_t postExpiryCookieCount
= mCookieCount
;
805 // now we have a list of iterators for cookies over the age limit.
806 // sort them by age, and then we'll see how many to remove...
807 purgeList
.Sort(CompareCookiesByAge());
809 // only remove old cookies until we reach the max cookie limit, no more.
810 uint32_t excess
= mCookieCount
> aMaxNumberOfCookies
811 ? mCookieCount
- aMaxNumberOfCookies
813 if (purgeList
.Length() > excess
) {
814 // We're not purging everything in the list, so update our indicator.
815 oldestTime
= purgeList
[excess
].Cookie()->LastAccessed();
817 purgeList
.SetLength(excess
);
820 // sort the list again, this time grouping cookies with a common entryclass
821 // together, and with ascending index. this allows us to iterate backwards
822 // over the list removing cookies, without having to adjust indexes as we go.
823 purgeList
.Sort(CompareCookiesByIndex());
824 for (PurgeList::index_type i
= purgeList
.Length(); i
--;) {
825 Cookie
* cookie
= purgeList
[i
].Cookie();
826 removedList
->AppendElement(cookie
);
827 COOKIE_LOGEVICTED(cookie
, "Cookie too old");
829 aRemoveCookieCallback(purgeList
[i
]);
832 // Update the database if we have entries to purge.
833 if (aFinalizeCallback
) {
837 // reset the oldest time indicator
838 mCookieOldestTime
= oldestTime
;
840 COOKIE_LOGSTRING(LogLevel::Debug
,
841 ("PurgeCookies(): %" PRIu32
" expired; %" PRIu32
842 " purged; %" PRIu32
" remain; %" PRId64
" oldest age",
843 initialCookieCount
- postExpiryCookieCount
,
844 postExpiryCookieCount
- mCookieCount
, mCookieCount
,
845 aCurrentTimeInUsec
- mCookieOldestTime
));
847 return removedList
.forget();
850 // remove a cookie from the hashtable, and update the iterator state.
851 void CookieStorage::RemoveCookieFromList(const CookieListIter
& aIter
) {
852 RemoveCookieFromDB(*aIter
.Cookie());
853 RemoveCookieFromListInternal(aIter
);
856 void CookieStorage::RemoveCookieFromListInternal(const CookieListIter
& aIter
) {
857 if (aIter
.entry
->GetCookies().Length() == 1) {
858 // we're removing the last element in the array - so just remove the entry
859 // from the hash. note that the entryclass' dtor will take care of
860 // releasing this last element for us!
861 mHostTable
.RawRemoveEntry(aIter
.entry
);
864 // just remove the element from the list
865 aIter
.entry
->GetCookies().RemoveElementAt(aIter
.index
);
871 void CookieStorage::PrefChanged(nsIPrefBranch
* aPrefBranch
) {
873 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxNumberOfCookies
, &val
))) {
874 mMaxNumberOfCookies
=
875 static_cast<uint16_t> LIMIT(val
, 1, 0xFFFF, kMaxNumberOfCookies
);
878 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookieQuotaPerHost
, &val
))) {
879 mCookieQuotaPerHost
= static_cast<uint16_t> LIMIT(
880 val
, 1, mMaxCookiesPerHost
- 1, kCookieQuotaPerHost
);
883 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxCookiesPerHost
, &val
))) {
884 mMaxCookiesPerHost
= static_cast<uint16_t> LIMIT(
885 val
, mCookieQuotaPerHost
+ 1, 0xFFFF, kMaxCookiesPerHost
);
888 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookiePurgeAge
, &val
))) {
890 int64_t(LIMIT(val
, 0, INT32_MAX
, INT32_MAX
)) * PR_USEC_PER_SEC
;
895 CookieStorage::Observe(nsISupports
* aSubject
, const char* aTopic
,
896 const char16_t
* /*aData*/) {
897 if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
898 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
900 PrefChanged(prefBranch
);
902 } else if (!strcmp(aTopic
, OBSERVER_TOPIC_IDLE_DAILY
)) {
903 CollectCookieJarSizeData();
910 } // namespace mozilla