mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
e18f339137
Differential Revision: https://phabricator.services.mozilla.com/D218495
1119 lines
40 KiB
C++
1119 lines
40 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
||
#include "Cookie.h"
|
||
#include "CookieCommons.h"
|
||
#include "CookieLogging.h"
|
||
#include "CookieParser.h"
|
||
#include "CookieNotification.h"
|
||
#include "mozilla/net/MozURL_ffi.h"
|
||
#include "CookieService.h"
|
||
#include "nsCOMPtr.h"
|
||
#include "nsICookieNotification.h"
|
||
#include "CookieStorage.h"
|
||
#include "mozilla/dom/nsMixedContentBlocker.h"
|
||
#include "mozilla/glean/GleanMetrics.h"
|
||
#include "mozilla/StaticPrefs_network.h"
|
||
#include "nsIMutableArray.h"
|
||
#include "nsTPriorityQueue.h"
|
||
#include "nsIScriptError.h"
|
||
#include "nsIUserIdleService.h"
|
||
#include "nsServiceManagerUtils.h"
|
||
#include "nsComponentManagerUtils.h"
|
||
#include "prprf.h"
|
||
#include "nsIPrefService.h"
|
||
|
||
#undef ADD_TEN_PERCENT
|
||
#define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
|
||
|
||
#undef LIMIT
|
||
#define LIMIT(x, low, high, default) \
|
||
((x) >= (low) && (x) <= (high) ? (x) : (default))
|
||
|
||
// in order to keep our metrics consistent
|
||
// we only send metrics when the pref hasn't been manipulated from the default
|
||
static const uint32_t kChipsPartitionByteCapacityDefault = 10240;
|
||
|
||
namespace mozilla {
|
||
namespace net {
|
||
|
||
namespace {
|
||
|
||
// comparator class for lastaccessed times of cookies.
|
||
class CompareCookiesByAge {
|
||
public:
|
||
static bool Equals(const CookieListIter& a, const CookieListIter& b) {
|
||
return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
|
||
a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
|
||
}
|
||
|
||
static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
|
||
// compare by lastAccessed time, and tiebreak by creationTime.
|
||
int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
|
||
if (result != 0) {
|
||
return result < 0;
|
||
}
|
||
|
||
return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
|
||
}
|
||
};
|
||
|
||
// Cookie comparator for the priority queue used in FindStaleCookies.
|
||
// Note that the expired cookie has the highest priority.
|
||
// Other non-expired cookies are sorted by their age.
|
||
class CookieIterComparator {
|
||
private:
|
||
int64_t mCurrentTime;
|
||
|
||
public:
|
||
explicit CookieIterComparator(int64_t aTime) : mCurrentTime(aTime) {}
|
||
|
||
bool LessThan(const CookieListIter& lhs, const CookieListIter& rhs) {
|
||
bool lExpired = lhs.Cookie()->Expiry() <= mCurrentTime;
|
||
bool rExpired = rhs.Cookie()->Expiry() <= mCurrentTime;
|
||
if (lExpired && !rExpired) {
|
||
return true;
|
||
}
|
||
|
||
if (!lExpired && rExpired) {
|
||
return false;
|
||
}
|
||
|
||
return mozilla::net::CompareCookiesByAge::LessThan(lhs, rhs);
|
||
}
|
||
};
|
||
|
||
// comparator class for sorting cookies by entry and index.
|
||
class CompareCookiesByIndex {
|
||
public:
|
||
static bool Equals(const CookieListIter& a, const CookieListIter& b) {
|
||
NS_ASSERTION(a.entry != b.entry || a.index != b.index,
|
||
"cookie indexes should never be equal");
|
||
return false;
|
||
}
|
||
|
||
static bool LessThan(const CookieListIter& a, const CookieListIter& b) {
|
||
// compare by entryclass pointer, then by index.
|
||
if (a.entry != b.entry) {
|
||
return a.entry < b.entry;
|
||
}
|
||
|
||
return a.index < b.index;
|
||
}
|
||
};
|
||
|
||
} // namespace
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// CookieEntry
|
||
|
||
size_t CookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
|
||
size_t amount = CookieKey::SizeOfExcludingThis(aMallocSizeOf);
|
||
|
||
amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||
for (uint32_t i = 0; i < mCookies.Length(); ++i) {
|
||
amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
|
||
}
|
||
|
||
return amount;
|
||
}
|
||
|
||
bool CookieEntry::IsPartitioned() const {
|
||
return !mOriginAttributes.mPartitionKey.IsEmpty();
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// CookieStorage
|
||
|
||
NS_IMPL_ISUPPORTS(CookieStorage, nsIObserver, nsISupportsWeakReference)
|
||
|
||
void CookieStorage::Init() {
|
||
// init our pref and observer
|
||
nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||
if (prefBranch) {
|
||
prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
|
||
prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
|
||
prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
|
||
PrefChanged(prefBranch);
|
||
}
|
||
|
||
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
|
||
NS_ENSURE_TRUE_VOID(observerService);
|
||
|
||
nsresult rv =
|
||
observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, true);
|
||
NS_ENSURE_SUCCESS_VOID(rv);
|
||
}
|
||
|
||
size_t CookieStorage::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
||
size_t amount = 0;
|
||
|
||
amount += aMallocSizeOf(this);
|
||
amount += mHostTable.SizeOfExcludingThis(aMallocSizeOf);
|
||
|
||
return amount;
|
||
}
|
||
|
||
void CookieStorage::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const {
|
||
aCookies.SetCapacity(mCookieCount);
|
||
for (const auto& entry : mHostTable) {
|
||
const CookieEntry::ArrayType& cookies = entry.GetCookies();
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
aCookies.AppendElement(cookies[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CookieStorage::GetSessionCookies(
|
||
nsTArray<RefPtr<nsICookie>>& aCookies) const {
|
||
aCookies.SetCapacity(mCookieCount);
|
||
for (const auto& entry : mHostTable) {
|
||
const CookieEntry::ArrayType& cookies = entry.GetCookies();
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
Cookie* cookie = cookies[i];
|
||
// Filter out non-session cookies.
|
||
if (cookie->IsSession()) {
|
||
aCookies.AppendElement(cookie);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// find an exact cookie specified by host, name, and path that hasn't expired.
|
||
bool CookieStorage::FindCookie(const nsACString& aBaseDomain,
|
||
const OriginAttributes& aOriginAttributes,
|
||
const nsACString& aHost, const nsACString& aName,
|
||
const nsACString& aPath, CookieListIter& aIter) {
|
||
CookieEntry* entry =
|
||
mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
|
||
if (!entry) {
|
||
return false;
|
||
}
|
||
|
||
const CookieEntry::ArrayType& cookies = entry->GetCookies();
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
Cookie* cookie = cookies[i];
|
||
|
||
if (aHost.Equals(cookie->Host()) && aPath.Equals(cookie->Path()) &&
|
||
aName.Equals(cookie->Name())) {
|
||
aIter = CookieListIter(entry, i);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// find an secure cookie specified by host and name
|
||
bool CookieStorage::FindSecureCookie(const nsACString& aBaseDomain,
|
||
const OriginAttributes& aOriginAttributes,
|
||
Cookie* aCookie) {
|
||
CookieEntry* entry =
|
||
mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
|
||
if (!entry) {
|
||
return false;
|
||
}
|
||
|
||
const CookieEntry::ArrayType& cookies = entry->GetCookies();
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
Cookie* cookie = cookies[i];
|
||
// isn't a match if insecure or a different name
|
||
if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name())) {
|
||
continue;
|
||
}
|
||
|
||
// The host must "domain-match" an existing cookie or vice-versa
|
||
if (CookieCommons::DomainMatches(cookie, aCookie->Host()) ||
|
||
CookieCommons::DomainMatches(aCookie, cookie->Host())) {
|
||
// If the path of new cookie and the path of existing cookie
|
||
// aren't "/", then this situation needs to compare paths to
|
||
// ensure only that a newly-created non-secure cookie does not
|
||
// overlay an existing secure cookie.
|
||
if (CookieCommons::PathMatches(cookie, aCookie->Path())) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
uint32_t CookieStorage::CountCookiesFromHost(const nsACString& aBaseDomain,
|
||
uint32_t aPrivateBrowsingId) {
|
||
OriginAttributes attrs;
|
||
attrs.mPrivateBrowsingId = aPrivateBrowsingId;
|
||
|
||
// Return a count of all cookies, including expired.
|
||
CookieEntry* entry = mHostTable.GetEntry(CookieKey(aBaseDomain, attrs));
|
||
return entry ? entry->GetCookies().Length() : 0;
|
||
}
|
||
|
||
uint32_t CookieStorage::CountCookieBytesNotMatchingCookie(
|
||
const Cookie& cookie, const nsACString& baseDomain) {
|
||
nsTArray<RefPtr<Cookie>> cookies;
|
||
GetCookiesFromHost(baseDomain, cookie.OriginAttributesRef(), cookies);
|
||
|
||
// count cookies with different name to the cookie being added
|
||
uint32_t cookieBytes = 0;
|
||
for (Cookie* c : cookies) {
|
||
nsAutoCString name;
|
||
nsAutoCString value;
|
||
c->GetName(name);
|
||
c->GetValue(value);
|
||
if (!cookie.Name().Equals(name)) {
|
||
cookieBytes += name.Length() + value.Length();
|
||
}
|
||
}
|
||
return cookieBytes;
|
||
}
|
||
|
||
void CookieStorage::GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const {
|
||
aResult.SetCapacity(mCookieCount);
|
||
|
||
for (const auto& entry : mHostTable) {
|
||
const CookieEntry::ArrayType& cookies = entry.GetCookies();
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
aResult.AppendElement(cookies[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CookieStorage::GetCookiesFromHost(
|
||
const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
|
||
nsTArray<RefPtr<Cookie>>& aCookies) {
|
||
CookieEntry* entry =
|
||
mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
|
||
if (!entry) {
|
||
return;
|
||
}
|
||
|
||
aCookies = entry->GetCookies().Clone();
|
||
}
|
||
|
||
void CookieStorage::GetCookiesWithOriginAttributes(
|
||
const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain,
|
||
nsTArray<RefPtr<nsICookie>>& aResult) {
|
||
for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
|
||
CookieEntry* entry = iter.Get();
|
||
|
||
if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
|
||
continue;
|
||
}
|
||
|
||
if (!aPattern.Matches(entry->mOriginAttributes)) {
|
||
continue;
|
||
}
|
||
|
||
const CookieEntry::ArrayType& entryCookies = entry->GetCookies();
|
||
|
||
for (CookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
|
||
aResult.AppendElement(entryCookies[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CookieStorage::RemoveCookie(const nsACString& aBaseDomain,
|
||
const OriginAttributes& aOriginAttributes,
|
||
const nsACString& aHost,
|
||
const nsACString& aName,
|
||
const nsACString& aPath,
|
||
const nsID* aOperationID) {
|
||
CookieListIter matchIter{};
|
||
RefPtr<Cookie> cookie;
|
||
if (FindCookie(aBaseDomain, aOriginAttributes, aHost, aName, aPath,
|
||
matchIter)) {
|
||
cookie = matchIter.Cookie();
|
||
RemoveCookieFromList(matchIter);
|
||
}
|
||
|
||
if (cookie) {
|
||
// Everything's done. Notify observers.
|
||
NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED, aBaseDomain,
|
||
aOperationID);
|
||
}
|
||
}
|
||
|
||
void CookieStorage::RemoveCookiesWithOriginAttributes(
|
||
const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
|
||
// Iterate the hash table of CookieEntry.
|
||
for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
|
||
CookieEntry* entry = iter.Get();
|
||
|
||
if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
|
||
continue;
|
||
}
|
||
|
||
if (!aPattern.Matches(entry->mOriginAttributes)) {
|
||
continue;
|
||
}
|
||
|
||
// Pattern matches. Delete all cookies within this CookieEntry.
|
||
uint32_t cookiesCount = entry->GetCookies().Length();
|
||
|
||
for (CookieEntry::IndexType i = 0; i < cookiesCount; ++i) {
|
||
// Remove the first cookie from the list.
|
||
CookieListIter iter(entry, 0);
|
||
RefPtr<Cookie> cookie = iter.Cookie();
|
||
|
||
// Remove the cookie.
|
||
RemoveCookieFromList(iter);
|
||
|
||
if (cookie) {
|
||
NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED,
|
||
aBaseDomain);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* static */ bool CookieStorage::SerializeIPv6BaseDomain(
|
||
nsACString& aBaseDomain) {
|
||
bool hasStartBracket = aBaseDomain.First() == '[';
|
||
bool hasEndBracket = aBaseDomain.Last() == ']';
|
||
|
||
// If only start or end bracket exists host is malformed.
|
||
if (hasStartBracket != hasEndBracket) {
|
||
return false;
|
||
}
|
||
|
||
// If the base domain is not in URL format (e.g. [::1]) add brackets so we
|
||
// can use rusturl_parse_ipv6addr().
|
||
if (!hasStartBracket) {
|
||
aBaseDomain.Insert('[', 0);
|
||
aBaseDomain.Append(']');
|
||
}
|
||
|
||
// Serialize base domain to "zero abbreviation" and lower-case hex
|
||
// representation.
|
||
nsAutoCString baseDomain;
|
||
nsresult rv = (nsresult)rusturl_parse_ipv6addr(&aBaseDomain, &baseDomain);
|
||
NS_ENSURE_SUCCESS(rv, false);
|
||
|
||
// Strip brackets to match principal representation.
|
||
aBaseDomain = Substring(baseDomain, 1, baseDomain.Length() - 2);
|
||
|
||
return true;
|
||
}
|
||
|
||
void CookieStorage::RemoveCookiesFromExactHost(
|
||
const nsACString& aHost, const nsACString& aBaseDomain,
|
||
const OriginAttributesPattern& aPattern) {
|
||
// Intermediate fix until Bug 1882259 is resolved.
|
||
// Bug 1860033 - Cookies do not serialize IPv6 host / base domain in contrast
|
||
// to principals. To allow deletion by principal serialize before comparison.
|
||
// We check the base domain since it is used as the CookieList key and equals
|
||
// the normalized (ASCII) host for IP addresses
|
||
// (it is equal to the CookieService::NormalizeHost() output).
|
||
nsAutoCString removeBaseDomain;
|
||
bool isIPv6 = CookieCommons::IsIPv6BaseDomain(aBaseDomain);
|
||
if (isIPv6) {
|
||
MOZ_ASSERT(!aBaseDomain.IsEmpty());
|
||
// Copy base domain since argument is immutable.
|
||
removeBaseDomain = aBaseDomain;
|
||
if (NS_WARN_IF(!SerializeIPv6BaseDomain(removeBaseDomain))) {
|
||
// Return on malformed base domains.
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Iterate the hash table of CookieEntry.
|
||
for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
|
||
CookieEntry* entry = iter.Get();
|
||
|
||
// IPv6 host / base domain cookies
|
||
if (isIPv6) {
|
||
// If we look for a IPv6 cookie skip non-IPv6 cookie entries.
|
||
if (!CookieCommons::IsIPv6BaseDomain(entry->mBaseDomain)) {
|
||
continue;
|
||
}
|
||
// Serialize IPv6 base domains before comparison.
|
||
// Copy base domain since argument is immutable.
|
||
nsAutoCString entryBaseDomain;
|
||
entryBaseDomain = entry->mBaseDomain;
|
||
if (NS_WARN_IF(!SerializeIPv6BaseDomain(entryBaseDomain))) {
|
||
continue;
|
||
}
|
||
if (!removeBaseDomain.Equals(entryBaseDomain)) {
|
||
continue;
|
||
}
|
||
// Non-IPv6 cookies
|
||
} else if (!aBaseDomain.Equals(entry->mBaseDomain)) {
|
||
continue;
|
||
}
|
||
|
||
if (!aPattern.Matches(entry->mOriginAttributes)) {
|
||
continue;
|
||
}
|
||
|
||
uint32_t cookiesCount = entry->GetCookies().Length();
|
||
for (CookieEntry::IndexType i = cookiesCount; i != 0; --i) {
|
||
CookieListIter iter(entry, i - 1);
|
||
RefPtr<Cookie> cookie = iter.Cookie();
|
||
|
||
// For IP addresses (ASCII normalized) host == baseDomain, we checked
|
||
// equality already.
|
||
if (!isIPv6 && !aHost.Equals(cookie->RawHost())) {
|
||
continue;
|
||
}
|
||
|
||
// Remove the cookie.
|
||
RemoveCookieFromList(iter);
|
||
|
||
if (cookie) {
|
||
NotifyChanged(cookie, nsICookieNotification::COOKIE_DELETED,
|
||
aBaseDomain);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CookieStorage::RemoveAll() {
|
||
// clearing the hashtable will call each CookieEntry's dtor,
|
||
// which releases all their respective children.
|
||
mHostTable.Clear();
|
||
mCookieCount = 0;
|
||
mCookieOldestTime = INT64_MAX;
|
||
|
||
RemoveAllInternal();
|
||
|
||
NotifyChanged(nullptr, nsICookieNotification::ALL_COOKIES_CLEARED, ""_ns);
|
||
}
|
||
|
||
// notify observers that the cookie list changed.
|
||
void CookieStorage::NotifyChanged(nsISupports* aSubject,
|
||
nsICookieNotification::Action aAction,
|
||
const nsACString& aBaseDomain,
|
||
bool aIsThirdParty,
|
||
dom::BrowsingContext* aBrowsingContext,
|
||
bool aOldCookieIsSession,
|
||
const nsID* aOperationID) {
|
||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||
if (!os) {
|
||
return;
|
||
}
|
||
|
||
nsCOMPtr<nsICookie> cookie;
|
||
nsCOMPtr<nsIArray> batchDeletedCookies;
|
||
|
||
if (aAction == nsICookieNotification::COOKIES_BATCH_DELETED) {
|
||
batchDeletedCookies = do_QueryInterface(aSubject);
|
||
} else {
|
||
cookie = do_QueryInterface(aSubject);
|
||
}
|
||
|
||
uint64_t browsingContextId = 0;
|
||
if (aBrowsingContext) {
|
||
browsingContextId = aBrowsingContext->Id();
|
||
}
|
||
|
||
nsCOMPtr<nsICookieNotification> notification = new CookieNotification(
|
||
aAction, cookie, aBaseDomain, aIsThirdParty, batchDeletedCookies,
|
||
browsingContextId, aOperationID);
|
||
// Notify for topic "private-cookie-changed" or "cookie-changed"
|
||
os->NotifyObservers(notification, NotificationTopic(), u"");
|
||
|
||
NotifyChangedInternal(notification, aOldCookieIsSession);
|
||
}
|
||
|
||
// return true if we finish within the partition byte limit
|
||
bool CookieStorage::RemoveCookiesFromBackUntilUnderLimit(
|
||
nsTArray<CookieListIter>& aCookieListIter, Cookie* aCookie,
|
||
const nsACString& aBaseDomain, nsCOMPtr<nsIArray>& aPurgedList) {
|
||
MOZ_ASSERT(aCookie);
|
||
auto it = aCookieListIter.rbegin();
|
||
while (it != aCookieListIter.rend()) {
|
||
RefPtr<Cookie> evictedCookie = (*it).Cookie();
|
||
COOKIE_LOGEVICTED(evictedCookie,
|
||
"Too many cookie bytes for this partition");
|
||
RemoveCookieFromList(*it);
|
||
CreateOrUpdatePurgeList(aPurgedList, evictedCookie);
|
||
MOZ_ASSERT((*it).entry);
|
||
if (PartitionLimitExceededBytes(aCookie, aBaseDomain, false) <= 0) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void CookieStorage::RemoveOlderCookiesUntilUnderLimit(
|
||
CookieEntry* aEntry, Cookie* aCookie, const nsACString& aBaseDomain,
|
||
nsCOMPtr<nsIArray>& aPurgedList) {
|
||
MOZ_ASSERT(aEntry);
|
||
// remove insecure older cookies until we are within the byte limit
|
||
// so CHIPS-partitioned cookies will not be detected here since they must be
|
||
// secure
|
||
const CookieEntry::ArrayType& cookies = aEntry->GetCookies();
|
||
using MaybePurgeList = nsTArray<CookieListIter>;
|
||
MaybePurgeList maybePurgeListInsecure(aEntry->GetCookies().Length());
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
CookieListIter iter(aEntry, i);
|
||
if (!iter.Cookie()->IsSecure()) {
|
||
maybePurgeListInsecure.AppendElement(iter);
|
||
}
|
||
}
|
||
maybePurgeListInsecure.Sort(CompareCookiesByAge());
|
||
maybePurgeListInsecure.Reverse();
|
||
bool underLimit = RemoveCookiesFromBackUntilUnderLimit(
|
||
maybePurgeListInsecure, aCookie, aBaseDomain, aPurgedList);
|
||
|
||
// remove secure older cookies until we are within the byte limit
|
||
if (!underLimit) {
|
||
MOZ_LOG(gCookieLog, LogLevel::Debug,
|
||
("Still too many cookies for partition, purging secure\n"));
|
||
MaybePurgeList maybePurgeList(aEntry->GetCookies().Length());
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
CookieListIter iter(aEntry, i);
|
||
maybePurgeList.AppendElement(iter);
|
||
}
|
||
maybePurgeList.Sort(CompareCookiesByAge());
|
||
maybePurgeList.Reverse();
|
||
Unused << RemoveCookiesFromBackUntilUnderLimit(maybePurgeList, aCookie,
|
||
aBaseDomain, aPurgedList);
|
||
}
|
||
}
|
||
|
||
int32_t CookieStorage::PartitionLimitExceededBytes(
|
||
Cookie* aCookie, const nsACString& aBaseDomain, bool aHardMax) {
|
||
uint32_t newCount = CountCookieBytesNotMatchingCookie(*aCookie, aBaseDomain) +
|
||
aCookie->NameAndValueBytes();
|
||
double factor = aHardMax ? 1.2 : 1;
|
||
// shouldn't expect more the 4000 cookies * 4000 bytes/cookie -> 16MB
|
||
return static_cast<int32_t>(
|
||
newCount -
|
||
StaticPrefs::network_cookie_chips_partitionLimitByteCapacity() * factor);
|
||
}
|
||
|
||
// this is a backend function for adding a cookie to the list, via SetCookie.
|
||
// also used in the cookie manager, for profile migration from IE. it either
|
||
// replaces an existing cookie; or adds the cookie to the hashtable, and
|
||
// deletes a cookie (if maximum number of cookies has been reached). also
|
||
// performs list maintenance by removing expired cookies.
|
||
void CookieStorage::AddCookie(CookieParser* aCookieParser,
|
||
const nsACString& aBaseDomain,
|
||
const OriginAttributes& aOriginAttributes,
|
||
Cookie* aCookie, int64_t aCurrentTimeInUsec,
|
||
nsIURI* aHostURI, const nsACString& aCookieHeader,
|
||
bool aFromHttp, bool aIsThirdParty,
|
||
dom::BrowsingContext* aBrowsingContext,
|
||
const nsID* aOperationID) {
|
||
int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
|
||
|
||
CookieListIter exactIter{};
|
||
bool foundCookie = false;
|
||
foundCookie = FindCookie(aBaseDomain, aOriginAttributes, aCookie->Host(),
|
||
aCookie->Name(), aCookie->Path(), exactIter);
|
||
bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
|
||
bool potentiallyTrustworthy = true;
|
||
if (aHostURI) {
|
||
potentiallyTrustworthy =
|
||
nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI);
|
||
}
|
||
bool oldCookieIsSession = false;
|
||
// Step1, call FindSecureCookie(). FindSecureCookie() would
|
||
// find the existing cookie with the security flag and has
|
||
// the same name, host and path of the new cookie, if there is any.
|
||
// Step2, Confirm new cookie's security setting. If any targeted
|
||
// cookie had been found in Step1, then confirm whether the
|
||
// new cookie could modify it. If the new created cookie’s
|
||
// "secure-only-flag" is not set, and the "scheme" component
|
||
// of the "request-uri" does not denote a "secure" protocol,
|
||
// then ignore the new cookie.
|
||
// (draft-ietf-httpbis-cookie-alone section 3.2)
|
||
if (!aCookie->IsSecure() &&
|
||
(foundSecureExact ||
|
||
FindSecureCookie(aBaseDomain, aOriginAttributes, aCookie)) &&
|
||
!potentiallyTrustworthy) {
|
||
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
|
||
"cookie can't save because older cookie is secure "
|
||
"cookie but newer cookie is non-secure cookie");
|
||
if (aCookieParser) {
|
||
aCookieParser->RejectCookie(CookieParser::RejectedNonsecureOverSecure);
|
||
}
|
||
return;
|
||
}
|
||
|
||
RefPtr<Cookie> oldCookie;
|
||
nsCOMPtr<nsIArray> purgedList;
|
||
if (foundCookie) {
|
||
oldCookie = exactIter.Cookie();
|
||
oldCookieIsSession = oldCookie->IsSession();
|
||
|
||
// Check if the old cookie is stale (i.e. has already expired). If so, we
|
||
// need to be careful about the semantics of removing it and adding the new
|
||
// cookie: we want the behavior wrt adding the new cookie to be the same as
|
||
// if it didn't exist, but we still want to fire a removal notification.
|
||
if (oldCookie->Expiry() <= currentTime) {
|
||
if (aCookie->Expiry() <= currentTime) {
|
||
// The new cookie has expired and the old one is stale. Nothing to do.
|
||
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
|
||
"cookie has already expired");
|
||
return;
|
||
}
|
||
|
||
// Remove the stale cookie. We save notification for later, once all list
|
||
// modifications are complete.
|
||
RemoveCookieFromList(exactIter);
|
||
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
|
||
"stale cookie was purged");
|
||
purgedList = CreatePurgeList(oldCookie);
|
||
|
||
// We've done all we need to wrt removing and notifying the stale cookie.
|
||
// From here on out, we pretend pretend it didn't exist, so that we
|
||
// preserve expected notification semantics when adding the new cookie.
|
||
foundCookie = false;
|
||
|
||
} else {
|
||
// If the old cookie is httponly, make sure we're not coming from script.
|
||
if (!aFromHttp && oldCookie->IsHttpOnly()) {
|
||
COOKIE_LOGFAILURE(
|
||
SET_COOKIE, aHostURI, aCookieHeader,
|
||
"previously stored cookie is httponly; coming from script");
|
||
if (aCookieParser) {
|
||
aCookieParser->RejectCookie(
|
||
CookieParser::RejectedHttpOnlyButFromScript);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// If the new cookie has the same value, expiry date, isSecure, isSession,
|
||
// isHttpOnly and SameSite flags then we can just keep the old one.
|
||
// Only if any of these differ we would want to override the cookie.
|
||
if (oldCookie->Value().Equals(aCookie->Value()) &&
|
||
oldCookie->Expiry() == aCookie->Expiry() &&
|
||
oldCookie->IsSecure() == aCookie->IsSecure() &&
|
||
oldCookie->IsSession() == aCookie->IsSession() &&
|
||
oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
|
||
oldCookie->SameSite() == aCookie->SameSite() &&
|
||
oldCookie->RawSameSite() == aCookie->RawSameSite() &&
|
||
oldCookie->SchemeMap() == aCookie->SchemeMap() &&
|
||
// We don't want to perform this optimization if the cookie is
|
||
// considered stale, since in this case we would need to update the
|
||
// database.
|
||
!oldCookie->IsStale()) {
|
||
// Update the last access time on the old cookie.
|
||
oldCookie->SetLastAccessed(aCookie->LastAccessed());
|
||
UpdateCookieOldestTime(oldCookie);
|
||
return;
|
||
}
|
||
|
||
// Merge the scheme map in case the old cookie and the new cookie are
|
||
// used with different schemes.
|
||
MergeCookieSchemeMap(oldCookie, aCookie);
|
||
|
||
// Remove the old cookie.
|
||
RemoveCookieFromList(exactIter);
|
||
|
||
// If the new cookie has expired -- i.e. the intent was simply to delete
|
||
// the old cookie -- then we're done.
|
||
if (aCookie->Expiry() <= currentTime) {
|
||
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
|
||
"previously stored cookie was deleted");
|
||
NotifyChanged(oldCookie, nsICookieNotification::COOKIE_DELETED,
|
||
aBaseDomain, false, aBrowsingContext, oldCookieIsSession,
|
||
aOperationID);
|
||
return;
|
||
}
|
||
|
||
// Preserve creation time of cookie for ordering purposes.
|
||
aCookie->SetCreationTime(oldCookie->CreationTime());
|
||
}
|
||
|
||
// check for CHIPS-partitioned exceeding byte limit
|
||
// when we overwrite a cookie with a cookie)
|
||
if (CookieCommons::ChipsLimitEnabledAndChipsCookie(*aCookie,
|
||
aBrowsingContext)) {
|
||
CookieEntry* entry =
|
||
mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
|
||
if (entry) {
|
||
int32_t exceededBytes =
|
||
PartitionLimitExceededBytes(aCookie, aBaseDomain, true);
|
||
if (exceededBytes > 0) {
|
||
MOZ_LOG(gCookieLog, LogLevel::Debug,
|
||
("Partition byte limit exceeded on cookie overwrite\n"));
|
||
if (!StaticPrefs::network_cookie_chips_partitionLimitDryRun()) {
|
||
RemoveOlderCookiesUntilUnderLimit(entry, aCookie, aBaseDomain,
|
||
purgedList);
|
||
}
|
||
if (StaticPrefs::network_cookie_chips_partitionLimitByteCapacity() ==
|
||
kChipsPartitionByteCapacityDefault) {
|
||
mozilla::glean::networking::cookie_chips_partition_limit_overflow
|
||
.AccumulateSingleSample(exceededBytes);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// check if cookie has already expired
|
||
if (aCookie->Expiry() <= currentTime) {
|
||
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
|
||
"cookie has already expired");
|
||
return;
|
||
}
|
||
|
||
// check if we have to delete an old cookie.
|
||
CookieEntry* entry =
|
||
mHostTable.GetEntry(CookieKey(aBaseDomain, aOriginAttributes));
|
||
int32_t partitionLimitExceededBytes = 0;
|
||
if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
|
||
nsTArray<CookieListIter> removedIterList;
|
||
// Prioritize evicting insecure cookies.
|
||
// (draft-ietf-httpbis-cookie-alone section 3.3)
|
||
uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost;
|
||
FindStaleCookies(entry, currentTime, false, removedIterList, limit);
|
||
if (removedIterList.Length() == 0) {
|
||
if (aCookie->IsSecure()) {
|
||
// It's valid to evict a secure cookie for another secure cookie.
|
||
FindStaleCookies(entry, currentTime, true, removedIterList, limit);
|
||
} else {
|
||
COOKIE_LOGEVICTED(aCookie,
|
||
"Too many cookies for this domain and the new "
|
||
"cookie is not a secure cookie");
|
||
return;
|
||
}
|
||
}
|
||
|
||
MOZ_ASSERT(!removedIterList.IsEmpty());
|
||
// Sort |removedIterList| by index again, since we have to remove the
|
||
// cookie in the reverse order.
|
||
removedIterList.Sort(CompareCookiesByIndex());
|
||
for (auto it = removedIterList.rbegin(); it != removedIterList.rend();
|
||
it++) {
|
||
RefPtr<Cookie> evictedCookie = (*it).Cookie();
|
||
COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain");
|
||
RemoveCookieFromList(*it);
|
||
CreateOrUpdatePurgeList(purgedList, evictedCookie);
|
||
MOZ_ASSERT((*it).entry);
|
||
}
|
||
uint32_t purgedLength = 0;
|
||
purgedList->GetLength(&purgedLength);
|
||
mozilla::glean::networking::cookie_purge_entry_max.AccumulateSingleSample(
|
||
purgedLength);
|
||
|
||
} else if (CookieCommons::ChipsLimitEnabledAndChipsCookie(
|
||
*aCookie, aBrowsingContext) &&
|
||
entry &&
|
||
(partitionLimitExceededBytes = PartitionLimitExceededBytes(
|
||
aCookie, aBaseDomain, true)) > 0) {
|
||
MOZ_LOG(gCookieLog, LogLevel::Debug,
|
||
("Partition byte limit exceeded on cookie add\n"));
|
||
if (!StaticPrefs::network_cookie_chips_partitionLimitDryRun()) {
|
||
RemoveOlderCookiesUntilUnderLimit(entry, aCookie, aBaseDomain,
|
||
purgedList);
|
||
}
|
||
if (StaticPrefs::network_cookie_chips_partitionLimitByteCapacity() ==
|
||
kChipsPartitionByteCapacityDefault) {
|
||
mozilla::glean::networking::cookie_chips_partition_limit_overflow
|
||
.AccumulateSingleSample(partitionLimitExceededBytes);
|
||
}
|
||
} else if (mCookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
|
||
int64_t maxAge = aCurrentTimeInUsec - mCookieOldestTime;
|
||
int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
|
||
if (maxAge >= purgeAge) {
|
||
// we're over both size and age limits by 10%; time to purge the table!
|
||
// do this by:
|
||
// 1) removing expired cookies;
|
||
// 2) evicting the balance of old cookies until we reach the size limit.
|
||
// note that the mCookieOldestTime indicator can be pessimistic - if
|
||
// it's older than the actual oldest cookie, we'll just purge more
|
||
// eagerly.
|
||
purgedList = PurgeCookies(aCurrentTimeInUsec, mMaxNumberOfCookies,
|
||
mCookiePurgeAge);
|
||
uint32_t purgedLength = 0;
|
||
purgedList->GetLength(&purgedLength);
|
||
mozilla::glean::networking::cookie_purge_max.AccumulateSingleSample(
|
||
purgedLength);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add the cookie to the db. We do not supply a params array for batching
|
||
// because this might result in removals and additions being out of order.
|
||
AddCookieToList(aBaseDomain, aOriginAttributes, aCookie);
|
||
StoreCookie(aBaseDomain, aOriginAttributes, aCookie);
|
||
|
||
COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
|
||
|
||
// Now that list mutations are complete, notify observers. We do it here
|
||
// because observers may themselves attempt to mutate the list.
|
||
if (purgedList) {
|
||
NotifyChanged(purgedList, nsICookieNotification::COOKIES_BATCH_DELETED,
|
||
""_ns, false, nullptr, false, aOperationID);
|
||
}
|
||
|
||
// Notify for topic "private-cookie-changed" or "cookie-changed"
|
||
NotifyChanged(aCookie,
|
||
foundCookie ? nsICookieNotification::COOKIE_CHANGED
|
||
: nsICookieNotification::COOKIE_ADDED,
|
||
aBaseDomain, aIsThirdParty, aBrowsingContext,
|
||
oldCookieIsSession, aOperationID);
|
||
}
|
||
|
||
void CookieStorage::UpdateCookieOldestTime(Cookie* aCookie) {
|
||
if (aCookie->LastAccessed() < mCookieOldestTime) {
|
||
mCookieOldestTime = aCookie->LastAccessed();
|
||
}
|
||
}
|
||
|
||
void CookieStorage::MergeCookieSchemeMap(Cookie* aOldCookie,
|
||
Cookie* aNewCookie) {
|
||
aNewCookie->SetSchemeMap(aOldCookie->SchemeMap() | aNewCookie->SchemeMap());
|
||
}
|
||
|
||
void CookieStorage::AddCookieToList(const nsACString& aBaseDomain,
|
||
const OriginAttributes& aOriginAttributes,
|
||
Cookie* aCookie) {
|
||
if (!aCookie) {
|
||
NS_WARNING("Attempting to AddCookieToList with null cookie");
|
||
return;
|
||
}
|
||
|
||
CookieKey key(aBaseDomain, aOriginAttributes);
|
||
|
||
CookieEntry* entry = mHostTable.PutEntry(key);
|
||
NS_ASSERTION(entry, "can't insert element into a null entry!");
|
||
|
||
entry->GetCookies().AppendElement(aCookie);
|
||
++mCookieCount;
|
||
|
||
// keep track of the oldest cookie, for when it comes time to purge
|
||
UpdateCookieOldestTime(aCookie);
|
||
}
|
||
|
||
// static
|
||
already_AddRefed<nsIArray> CookieStorage::CreatePurgeList(nsICookie* aCookie) {
|
||
nsCOMPtr<nsIMutableArray> removedList =
|
||
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||
removedList->AppendElement(aCookie);
|
||
return removedList.forget();
|
||
}
|
||
|
||
// Given the output iter array and the count limit, find cookies
|
||
// sort by expiry and lastAccessed time.
|
||
// static
|
||
void CookieStorage::FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTime,
|
||
bool aIsSecure,
|
||
nsTArray<CookieListIter>& aOutput,
|
||
uint32_t aLimit) {
|
||
MOZ_ASSERT(aLimit);
|
||
|
||
const CookieEntry::ArrayType& cookies = aEntry->GetCookies();
|
||
aOutput.Clear();
|
||
|
||
CookieIterComparator comp(aCurrentTime);
|
||
nsTPriorityQueue<CookieListIter, CookieIterComparator> queue(comp);
|
||
|
||
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
|
||
Cookie* cookie = cookies[i];
|
||
|
||
if (cookie->Expiry() <= aCurrentTime) {
|
||
queue.Push(CookieListIter(aEntry, i));
|
||
continue;
|
||
}
|
||
|
||
if (!aIsSecure) {
|
||
// We want to look for the non-secure cookie first time through,
|
||
// then find the secure cookie the second time this function is called.
|
||
if (cookie->IsSecure()) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
queue.Push(CookieListIter(aEntry, i));
|
||
}
|
||
|
||
uint32_t count = 0;
|
||
while (!queue.IsEmpty() && count < aLimit) {
|
||
aOutput.AppendElement(queue.Pop());
|
||
count++;
|
||
}
|
||
}
|
||
|
||
// static
|
||
void CookieStorage::CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList,
|
||
nsICookie* aCookie) {
|
||
if (!aPurgedList) {
|
||
COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
|
||
aPurgedList = CreatePurgeList(aCookie);
|
||
return;
|
||
}
|
||
|
||
nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(aPurgedList);
|
||
if (purgedList) {
|
||
COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list"));
|
||
purgedList->AppendElement(aCookie);
|
||
} else {
|
||
COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!"));
|
||
}
|
||
}
|
||
|
||
// purges expired and old cookies in a batch operation.
|
||
already_AddRefed<nsIArray> CookieStorage::PurgeCookiesWithCallbacks(
|
||
int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
|
||
int64_t aCookiePurgeAge,
|
||
std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
|
||
std::function<void()>&& aFinalizeCallback) {
|
||
NS_ASSERTION(mHostTable.Count() > 0, "table is empty");
|
||
|
||
uint32_t initialCookieCount = mCookieCount;
|
||
COOKIE_LOGSTRING(LogLevel::Debug,
|
||
("PurgeCookies(): beginning purge with %" PRIu32
|
||
" cookies and %" PRId64 " oldest age",
|
||
mCookieCount, aCurrentTimeInUsec - mCookieOldestTime));
|
||
|
||
using PurgeList = nsTArray<CookieListIter>;
|
||
PurgeList purgeList(kMaxNumberOfCookies);
|
||
|
||
nsCOMPtr<nsIMutableArray> removedList =
|
||
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||
|
||
int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
|
||
int64_t purgeTime = aCurrentTimeInUsec - aCookiePurgeAge;
|
||
int64_t oldestTime = INT64_MAX;
|
||
|
||
for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) {
|
||
CookieEntry* entry = iter.Get();
|
||
|
||
const CookieEntry::ArrayType& cookies = entry->GetCookies();
|
||
auto length = cookies.Length();
|
||
for (CookieEntry::IndexType i = 0; i < length;) {
|
||
CookieListIter iter(entry, i);
|
||
Cookie* cookie = cookies[i];
|
||
|
||
// check if the cookie has expired
|
||
if (cookie->Expiry() <= currentTime) {
|
||
removedList->AppendElement(cookie);
|
||
COOKIE_LOGEVICTED(cookie, "Cookie expired");
|
||
|
||
// remove from list; do not increment our iterator, but stop if we're
|
||
// done already.
|
||
aRemoveCookieCallback(iter);
|
||
if (i == --length) {
|
||
break;
|
||
}
|
||
} else {
|
||
// check if the cookie is over the age limit
|
||
if (cookie->LastAccessed() <= purgeTime) {
|
||
purgeList.AppendElement(iter);
|
||
|
||
} else if (cookie->LastAccessed() < oldestTime) {
|
||
// reset our indicator
|
||
oldestTime = cookie->LastAccessed();
|
||
}
|
||
|
||
++i;
|
||
}
|
||
MOZ_ASSERT(length == cookies.Length());
|
||
}
|
||
}
|
||
|
||
uint32_t postExpiryCookieCount = mCookieCount;
|
||
|
||
// now we have a list of iterators for cookies over the age limit.
|
||
// sort them by age, and then we'll see how many to remove...
|
||
purgeList.Sort(CompareCookiesByAge());
|
||
|
||
// only remove old cookies until we reach the max cookie limit, no more.
|
||
uint32_t excess = mCookieCount > aMaxNumberOfCookies
|
||
? mCookieCount - aMaxNumberOfCookies
|
||
: 0;
|
||
if (purgeList.Length() > excess) {
|
||
// We're not purging everything in the list, so update our indicator.
|
||
oldestTime = purgeList[excess].Cookie()->LastAccessed();
|
||
|
||
purgeList.SetLength(excess);
|
||
}
|
||
|
||
// sort the list again, this time grouping cookies with a common entryclass
|
||
// together, and with ascending index. this allows us to iterate backwards
|
||
// over the list removing cookies, without having to adjust indexes as we go.
|
||
purgeList.Sort(CompareCookiesByIndex());
|
||
for (PurgeList::index_type i = purgeList.Length(); i--;) {
|
||
Cookie* cookie = purgeList[i].Cookie();
|
||
removedList->AppendElement(cookie);
|
||
COOKIE_LOGEVICTED(cookie, "Cookie too old");
|
||
|
||
aRemoveCookieCallback(purgeList[i]);
|
||
}
|
||
|
||
// Update the database if we have entries to purge.
|
||
if (aFinalizeCallback) {
|
||
aFinalizeCallback();
|
||
}
|
||
|
||
// reset the oldest time indicator
|
||
mCookieOldestTime = oldestTime;
|
||
|
||
COOKIE_LOGSTRING(LogLevel::Debug,
|
||
("PurgeCookies(): %" PRIu32 " expired; %" PRIu32
|
||
" purged; %" PRIu32 " remain; %" PRId64 " oldest age",
|
||
initialCookieCount - postExpiryCookieCount,
|
||
postExpiryCookieCount - mCookieCount, mCookieCount,
|
||
aCurrentTimeInUsec - mCookieOldestTime));
|
||
|
||
return removedList.forget();
|
||
}
|
||
|
||
// remove a cookie from the hashtable, and update the iterator state.
|
||
void CookieStorage::RemoveCookieFromList(const CookieListIter& aIter) {
|
||
RemoveCookieFromDB(*aIter.Cookie());
|
||
RemoveCookieFromListInternal(aIter);
|
||
}
|
||
|
||
void CookieStorage::RemoveCookieFromListInternal(const CookieListIter& aIter) {
|
||
if (aIter.entry->GetCookies().Length() == 1) {
|
||
// we're removing the last element in the array - so just remove the entry
|
||
// from the hash. note that the entryclass' dtor will take care of
|
||
// releasing this last element for us!
|
||
mHostTable.RawRemoveEntry(aIter.entry);
|
||
|
||
} else {
|
||
// just remove the element from the list
|
||
aIter.entry->GetCookies().RemoveElementAt(aIter.index);
|
||
}
|
||
|
||
--mCookieCount;
|
||
}
|
||
|
||
void CookieStorage::PrefChanged(nsIPrefBranch* aPrefBranch) {
|
||
int32_t val;
|
||
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) {
|
||
mMaxNumberOfCookies =
|
||
static_cast<uint16_t> LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
|
||
}
|
||
|
||
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) {
|
||
mCookieQuotaPerHost = static_cast<uint16_t> LIMIT(
|
||
val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost);
|
||
}
|
||
|
||
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) {
|
||
mMaxCookiesPerHost = static_cast<uint16_t> LIMIT(
|
||
val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost);
|
||
}
|
||
|
||
if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
|
||
mCookiePurgeAge =
|
||
int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
|
||
}
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
CookieStorage::Observe(nsISupports* aSubject, const char* aTopic,
|
||
const char16_t* /*aData*/) {
|
||
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
||
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
|
||
if (prefBranch) {
|
||
PrefChanged(prefBranch);
|
||
}
|
||
} else if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
|
||
CollectCookieJarSizeData();
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
} // namespace net
|
||
} // namespace mozilla
|