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 "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"
17 #include "nsIPrefService.h"
19 #undef ADD_TEN_PERCENT
20 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
23 #define LIMIT(x, low, high, default) \
24 ((x) >= (low) && (x) <= (high) ? (x) : (default))
31 // comparator class for lastaccessed times of cookies.
32 class CompareCookiesByAge
{
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();
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
{
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
) {
67 if (!lExpired
&& rExpired
) {
71 return mozilla::net::CompareCookiesByAge::LessThan(lhs
, rhs
);
75 // comparator class for sorting cookies by entry and index.
76 class CompareCookiesByIndex
{
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");
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
;
96 // ---------------------------------------------------------------------------
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
);
110 // ---------------------------------------------------------------------------
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
);
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 {
129 amount
+= aMallocSizeOf(this);
130 amount
+= mHostTable
.SizeOfExcludingThis(aMallocSizeOf
);
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
) {
166 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
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
);
185 // find an secure cookie specified by host and name
186 bool CookieStorage::FindSecureCookie(const nsACString
& aBaseDomain
,
187 const OriginAttributes
& aOriginAttributes
,
190 mHostTable
.GetEntry(CookieKey(aBaseDomain
, aOriginAttributes
));
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())) {
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())) {
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
) {
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
)) {
257 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
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
,
278 cookie
= matchIter
.Cookie();
279 RemoveCookieFromList(matchIter
);
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
)) {
298 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
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
);
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
)) {
331 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
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())) {
344 // Remove the cookie.
345 RemoveCookieFromList(iter
);
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.
359 mCookieOldestTime
= INT64_MAX
;
363 NotifyChanged(nullptr, u
"cleared");
366 // notify observers that the cookie list changed. there are five possible
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
374 void CookieStorage::NotifyChanged(nsISupports
* aSubject
, const char16_t
* aData
,
375 bool aOldCookieIsSession
) {
376 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
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
,
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;
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() &&
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()),
437 RefPtr
<Cookie
> oldCookie
;
438 nsCOMPtr
<nsIArray
> purgedList
;
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()),
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.
474 // If the old cookie is httponly, make sure we're not coming from script.
475 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
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()),
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
502 !oldCookie
->IsStale()) {
503 // Update the last access time on the old cookie.
504 oldCookie
->SetLastAccessed(aCookie
->LastAccessed());
505 UpdateCookieOldestTime(oldCookie
);
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
);
531 // Preserve creation time of cookie for ordering purposes.
532 aCookie
->SetCreationTime(oldCookie
->CreationTime());
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()),
549 // check if we have to delete an old cookie.
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
);
563 COOKIE_LOGEVICTED(aCookie
,
564 "Too many cookies for this domain and the new "
565 "cookie is not a secure cookie");
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();
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!
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
594 purgedList
= PurgeCookies(aCurrentTimeInUsec
, mMaxNumberOfCookies
,
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.
610 NotifyChanged(purgedList
, u
"batch-deleted");
613 NotifyChanged(aCookie
, foundCookie
? u
"changed" : u
"added",
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
,
632 NS_WARNING("Attempting to AddCookieToList with null cookie");
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
);
644 // keep track of the oldest cookie, for when it comes time to purge
645 UpdateCookieOldestTime(aCookie
);
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.
659 void CookieStorage::FindStaleCookies(CookieEntry
* aEntry
, int64_t aCurrentTime
,
661 nsTArray
<CookieListIter
>& aOutput
,
665 const CookieEntry::ArrayType
& cookies
= aEntry
->GetCookies();
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
));
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()) {
687 queue
.Push(CookieListIter(aEntry
, i
));
691 while (!queue
.IsEmpty() && count
< aLimit
) {
692 aOutput
.AppendElement(queue
.Pop());
698 void CookieStorage::CreateOrUpdatePurgeList(nsIArray
** aPurgedList
,
699 nsICookie
* aCookie
) {
701 COOKIE_LOGSTRING(LogLevel::Debug
, ("Creating new purge list"));
702 nsCOMPtr
<nsIArray
> purgedList
= CreatePurgeList(aCookie
);
703 purgedList
.forget(aPurgedList
);
707 nsCOMPtr
<nsIMutableArray
> purgedList
= do_QueryInterface(*aPurgedList
);
709 COOKIE_LOGSTRING(LogLevel::Debug
, ("Updating existing purge list"));
710 purgedList
->AppendElement(aCookie
);
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
756 aRemoveCookieCallback(iter
);
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();
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
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
) {
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
);
837 // just remove the element from the list
838 aIter
.entry
->GetCookies().RemoveElementAt(aIter
.index
);
844 void CookieStorage::PrefChanged(nsIPrefBranch
* aPrefBranch
) {
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
))) {
863 int64_t(LIMIT(val
, 0, INT32_MAX
, INT32_MAX
)) * PR_USEC_PER_SEC
;
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
);
873 PrefChanged(prefBranch
);
881 } // namespace mozilla