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");
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.
468 // If the old cookie is httponly, make sure we're not coming from script.
469 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
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()),
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
497 !oldCookie
->IsStale()) {
498 // Update the last access time on the old cookie.
499 oldCookie
->SetLastAccessed(aCookie
->LastAccessed());
500 UpdateCookieOldestTime(oldCookie
);
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
);
520 // Preserve creation time of cookie for ordering purposes.
521 aCookie
->SetCreationTime(oldCookie
->CreationTime());
525 // check if cookie has already expired
526 if (aCookie
->Expiry() <= currentTime
) {
527 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
528 "cookie has already expired");
532 // check if we have to delete an old cookie.
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
);
546 COOKIE_LOGEVICTED(aCookie
,
547 "Too many cookies for this domain and the new "
548 "cookie is not a secure cookie");
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();
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!
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
577 purgedList
= PurgeCookies(aCurrentTimeInUsec
, mMaxNumberOfCookies
,
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.
593 NotifyChanged(purgedList
, u
"batch-deleted");
596 NotifyChanged(aCookie
, foundCookie
? u
"changed" : u
"added",
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
,
615 NS_WARNING("Attempting to AddCookieToList with null cookie");
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
);
627 // keep track of the oldest cookie, for when it comes time to purge
628 UpdateCookieOldestTime(aCookie
);
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.
642 void CookieStorage::FindStaleCookies(CookieEntry
* aEntry
, int64_t aCurrentTime
,
644 nsTArray
<CookieListIter
>& aOutput
,
648 const CookieEntry::ArrayType
& cookies
= aEntry
->GetCookies();
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
));
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()) {
670 queue
.Push(CookieListIter(aEntry
, i
));
674 while (!queue
.IsEmpty() && count
< aLimit
) {
675 aOutput
.AppendElement(queue
.Pop());
681 void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr
<nsIArray
>& aPurgedList
,
682 nsICookie
* aCookie
) {
684 COOKIE_LOGSTRING(LogLevel::Debug
, ("Creating new purge list"));
685 aPurgedList
= CreatePurgeList(aCookie
);
689 nsCOMPtr
<nsIMutableArray
> purgedList
= do_QueryInterface(aPurgedList
);
691 COOKIE_LOGSTRING(LogLevel::Debug
, ("Updating existing purge list"));
692 purgedList
->AppendElement(aCookie
);
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
738 aRemoveCookieCallback(iter
);
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();
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
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
) {
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
);
819 // just remove the element from the list
820 aIter
.entry
->GetCookies().RemoveElementAt(aIter
.index
);
826 void CookieStorage::PrefChanged(nsIPrefBranch
* aPrefBranch
) {
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
))) {
845 int64_t(LIMIT(val
, 0, INT32_MAX
, INT32_MAX
)) * PR_USEC_PER_SEC
;
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
);
855 PrefChanged(prefBranch
);
863 } // namespace mozilla