1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/AntiTrackingCommon.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/Likely.h"
12 #include "mozilla/Printf.h"
13 #include "mozilla/Unused.h"
15 #include "mozilla/net/CookieServiceChild.h"
16 #include "mozilla/net/NeckoCommon.h"
18 #include "nsCookieService.h"
19 #include "nsContentUtils.h"
20 #include "nsIServiceManager.h"
21 #include "nsIScriptSecurityManager.h"
22 #include "nsIWebProgressListener.h"
24 #include "nsIIOService.h"
25 #include "nsIPermissionManager.h"
26 #include "nsIProtocolHandler.h"
27 #include "nsIPrefBranch.h"
28 #include "nsIPrefService.h"
29 #include "nsIScriptError.h"
30 #include "nsCookiePermission.h"
33 #include "nsIChannel.h"
35 #include "nsIObserverService.h"
36 #include "nsILineInputStream.h"
37 #include "nsIEffectiveTLDService.h"
38 #include "nsIIDNService.h"
39 #include "nsIThread.h"
40 #include "mozIThirdPartyUtil.h"
43 #include "nsCOMArray.h"
44 #include "nsIMutableArray.h"
45 #include "nsArrayEnumerator.h"
46 #include "nsEnumeratorUtils.h"
47 #include "nsAutoPtr.h"
48 #include "nsReadableUtils.h"
51 #include "nsNetUtil.h"
53 #include "nsISimpleEnumerator.h"
54 #include "nsIInputStream.h"
55 #include "nsAppDirectoryServiceDefs.h"
57 #include "mozilla/storage.h"
58 #include "mozilla/AutoRestore.h"
59 #include "mozilla/FileUtils.h"
60 #include "mozilla/ScopeExit.h"
61 #include "mozilla/StaticPrefs.h"
62 #include "mozilla/Telemetry.h"
63 #include "nsIConsoleService.h"
64 #include "nsTPriorityQueue.h"
65 #include "nsVariant.h"
67 using namespace mozilla
;
68 using namespace mozilla::net
;
70 // Create key from baseDomain that will access the default cookie namespace.
71 // TODO: When we figure out what the API will look like for nsICookieManager{2}
72 // on content processes (see bug 777620), change to use the appropriate app
73 // namespace. For now those IDLs aren't supported on child processes.
74 #define DEFAULT_APP_KEY(baseDomain) \
75 nsCookieKey(baseDomain, OriginAttributes())
77 /******************************************************************************
78 * nsCookieService impl:
79 * useful types & constants
80 ******************************************************************************/
82 static StaticRefPtr
<nsCookieService
> gCookieService
;
83 bool nsCookieService::sSameSiteEnabled
= false;
85 // XXX_hack. See bug 178993.
86 // This is a hack to hide HttpOnly cookies from older browsers
87 #define HTTP_ONLY_PREFIX "#HttpOnly_"
89 #define COOKIES_FILE "cookies.sqlite"
90 #define COOKIES_SCHEMA_VERSION 9
92 // parameter indexes; see |Read|
98 #define IDX_LAST_ACCESSED 5
99 #define IDX_CREATION_TIME 6
101 #define IDX_HTTPONLY 8
102 #define IDX_BASE_DOMAIN 9
103 #define IDX_ORIGIN_ATTRIBUTES 10
104 #define IDX_SAME_SITE 11
106 #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
108 static const int64_t kCookiePurgeAge
=
109 int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC
; // 30 days in microseconds
111 #define OLD_COOKIE_FILE_NAME "cookies.txt"
114 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
116 #undef ADD_TEN_PERCENT
117 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
119 // default limits for the cookie list. these can be tuned by the
120 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
121 static const uint32_t kMaxNumberOfCookies
= 3000;
122 static const uint32_t kMaxCookiesPerHost
= 180;
123 static const uint32_t kCookieQuotaPerHost
= 150;
124 static const uint32_t kMaxBytesPerCookie
= 4096;
125 static const uint32_t kMaxBytesPerPath
= 1024;
127 // pref string constants
128 static const char kPrefCookieBehavior
[] = "network.cookie.cookieBehavior";
129 static const char kPrefMaxNumberOfCookies
[] = "network.cookie.maxNumber";
130 static const char kPrefMaxCookiesPerHost
[] = "network.cookie.maxPerHost";
131 static const char kPrefCookieQuotaPerHost
[] = "network.cookie.quotaPerHost";
132 static const char kPrefCookiePurgeAge
[] = "network.cookie.purgeAge";
133 static const char kPrefThirdPartySession
[] = "network.cookie.thirdparty.sessionOnly";
134 static const char kPrefThirdPartyNonsecureSession
[] = "network.cookie.thirdparty.nonsecureSessionOnly";
135 static const char kCookieLeaveSecurityAlone
[] = "network.cookie.leave-secure-alone";
137 // For telemetry COOKIE_LEAVE_SECURE_ALONE
138 #define BLOCKED_SECURE_SET_FROM_HTTP 0
139 #define BLOCKED_DOWNGRADE_SECURE_INEXACT 1
140 #define DOWNGRADE_SECURE_FROM_SECURE_INEXACT 2
141 #define EVICTED_NEWER_INSECURE 3
142 #define EVICTED_OLDEST_COOKIE 4
143 #define EVICTED_PREFERRED_COOKIE 5
144 #define EVICTING_SECURE_BLOCKED 6
145 #define BLOCKED_DOWNGRADE_SECURE_EXACT 7
146 #define DOWNGRADE_SECURE_FROM_SECURE_EXACT 8
149 bindCookieParameters(mozIStorageBindingParamsArray
*aParamsArray
,
150 const nsCookieKey
&aKey
,
151 const nsCookie
*aCookie
);
153 // stores the nsCookieEntry entryclass and an index into the cookie array
154 // within that entryclass, for purposes of storing an iteration state that
155 // points to a certain cookie.
158 // default (non-initializing) constructor.
159 nsListIter() = default;
161 // explicit constructor to a given iterator state with entryclass 'aEntry'
162 // and index 'aIndex'.
164 nsListIter(nsCookieEntry
*aEntry
, nsCookieEntry::IndexType aIndex
)
170 // get the nsCookie * the iterator currently points to.
171 nsCookie
* Cookie() const
173 return entry
->GetCookies()[index
];
176 nsCookieEntry
*entry
;
177 nsCookieEntry::IndexType index
;
180 /******************************************************************************
181 * Cookie logging handlers
182 * used for logging in nsCookieService
183 ******************************************************************************/
187 // in order to do logging, the following environment variables need to be set:
189 // set MOZ_LOG=cookie:3 -- shows rejected cookies
190 // set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
191 // set MOZ_LOG_FILE=cookie.log
193 #include "mozilla/Logging.h"
196 // define logging macros for convenience
197 #define SET_COOKIE true
198 #define GET_COOKIE false
200 static LazyLogModule
gCookieLog("cookie");
202 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
203 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
205 #define COOKIE_LOGEVICTED(a, details) \
207 if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \
208 LogEvicted(a, details); \
211 #define COOKIE_LOGSTRING(lvl, fmt) \
213 MOZ_LOG(gCookieLog, lvl, fmt); \
214 MOZ_LOG(gCookieLog, lvl, ("\n")); \
218 LogFailure(bool aSetCookie
, nsIURI
*aHostURI
, const char *aCookieString
, const char *aReason
)
220 // if logging isn't enabled, return now to save cycles
221 if (!MOZ_LOG_TEST(gCookieLog
, LogLevel::Warning
))
226 aHostURI
->GetAsciiSpec(spec
);
228 MOZ_LOG(gCookieLog
, LogLevel::Warning
,
229 ("===== %s =====\n", aSetCookie
? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
230 MOZ_LOG(gCookieLog
, LogLevel::Warning
,("request URL: %s\n", spec
.get()));
232 MOZ_LOG(gCookieLog
, LogLevel::Warning
,("cookie string: %s\n", aCookieString
));
234 PRExplodedTime explodedTime
;
235 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &explodedTime
);
237 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
239 MOZ_LOG(gCookieLog
, LogLevel::Warning
,("current time: %s", timeString
));
240 MOZ_LOG(gCookieLog
, LogLevel::Warning
,("rejected because %s\n", aReason
));
241 MOZ_LOG(gCookieLog
, LogLevel::Warning
,("\n"));
245 LogCookie(nsCookie
*aCookie
)
247 PRExplodedTime explodedTime
;
248 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &explodedTime
);
250 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
252 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("current time: %s", timeString
));
255 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("----------------\n"));
256 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("name: %s\n", aCookie
->Name().get()));
257 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("value: %s\n", aCookie
->Value().get()));
258 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("%s: %s\n", aCookie
->IsDomain() ? "domain" : "host", aCookie
->Host().get()));
259 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("path: %s\n", aCookie
->Path().get()));
261 PR_ExplodeTime(aCookie
->Expiry() * int64_t(PR_USEC_PER_SEC
),
262 PR_GMTParameters
, &explodedTime
);
263 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
264 MOZ_LOG(gCookieLog
, LogLevel::Debug
,
265 ("expires: %s%s", timeString
, aCookie
->IsSession() ? " (at end of session)" : ""));
267 PR_ExplodeTime(aCookie
->CreationTime(), PR_GMTParameters
, &explodedTime
);
268 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
269 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("created: %s", timeString
));
271 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("is secure: %s\n", aCookie
->IsSecure() ? "true" : "false"));
272 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("is httpOnly: %s\n", aCookie
->IsHttpOnly() ? "true" : "false"));
274 nsAutoCString suffix
;
275 aCookie
->OriginAttributesRef().CreateSuffix(suffix
);
276 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("origin attributes: %s\n",
277 suffix
.IsEmpty() ? "{empty}" : suffix
.get()));
282 LogSuccess(bool aSetCookie
, nsIURI
*aHostURI
, const char *aCookieString
, nsCookie
*aCookie
, bool aReplacing
)
284 // if logging isn't enabled, return now to save cycles
285 if (!MOZ_LOG_TEST(gCookieLog
, LogLevel::Debug
)) {
291 aHostURI
->GetAsciiSpec(spec
);
293 MOZ_LOG(gCookieLog
, LogLevel::Debug
,
294 ("===== %s =====\n", aSetCookie
? "COOKIE ACCEPTED" : "COOKIE SENT"));
295 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("request URL: %s\n", spec
.get()));
296 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("cookie string: %s\n", aCookieString
));
298 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("replaces existing cookie: %s\n", aReplacing
? "true" : "false"));
302 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("\n"));
306 LogEvicted(nsCookie
*aCookie
, const char* details
)
308 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("===== COOKIE EVICTED =====\n"));
309 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("%s\n", details
));
313 MOZ_LOG(gCookieLog
, LogLevel::Debug
,("\n"));
316 // inline wrappers to make passing in nsCStrings easier
318 LogFailure(bool aSetCookie
, nsIURI
*aHostURI
, const nsCString
& aCookieString
, const char *aReason
)
320 LogFailure(aSetCookie
, aHostURI
, aCookieString
.get(), aReason
);
324 LogSuccess(bool aSetCookie
, nsIURI
*aHostURI
, const nsCString
& aCookieString
, nsCookie
*aCookie
, bool aReplacing
)
326 LogSuccess(aSetCookie
, aHostURI
, aCookieString
.get(), aCookie
, aReplacing
);
330 #define NS_ASSERT_SUCCESS(res) \
332 nsresult __rv = res; /* Do not evaluate |res| more than once! */ \
333 if (NS_FAILED(__rv)) { \
334 SmprintfPointer msg = mozilla::Smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%" PRIX32, \
335 #res, static_cast<uint32_t>(__rv)); \
336 NS_ASSERTION(NS_SUCCEEDED(__rv), msg.get()); \
340 #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
343 /******************************************************************************
344 * DBListenerErrorHandler impl:
345 * Parent class for our async storage listeners that handles the logging of
347 ******************************************************************************/
348 class DBListenerErrorHandler
: public mozIStorageStatementCallback
351 explicit DBListenerErrorHandler(DBState
* dbState
) : mDBState(dbState
) { }
352 RefPtr
<DBState
> mDBState
;
353 virtual const char *GetOpType() = 0;
356 NS_IMETHOD
HandleError(mozIStorageError
* aError
) override
358 if (MOZ_LOG_TEST(gCookieLog
, LogLevel::Warning
)) {
360 aError
->GetResult(&result
);
362 nsAutoCString message
;
363 aError
->GetMessage(message
);
364 COOKIE_LOGSTRING(LogLevel::Warning
,
365 ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
366 "performing operation '%s' with message '%s'; rebuilding database.",
367 result
, GetOpType(), message
.get()));
370 // Rebuild the database.
371 gCookieService
->HandleCorruptDB(mDBState
);
377 /******************************************************************************
378 * InsertCookieDBListener impl:
379 * mozIStorageStatementCallback used to track asynchronous insertion operations.
380 ******************************************************************************/
381 class InsertCookieDBListener final
: public DBListenerErrorHandler
384 const char *GetOpType() override
{ return "INSERT"; }
386 ~InsertCookieDBListener() = default;
391 explicit InsertCookieDBListener(DBState
* dbState
) : DBListenerErrorHandler(dbState
) { }
392 NS_IMETHOD
HandleResult(mozIStorageResultSet
*) override
394 MOZ_ASSERT_UNREACHABLE("Unexpected call to "
395 "InsertCookieDBListener::HandleResult");
398 NS_IMETHOD
HandleCompletion(uint16_t aReason
) override
400 // If we were rebuilding the db and we succeeded, make our corruptFlag say
402 if (mDBState
->corruptFlag
== DBState::REBUILDING
&&
403 aReason
== mozIStorageStatementCallback::REASON_FINISHED
) {
404 COOKIE_LOGSTRING(LogLevel::Debug
,
405 ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
406 mDBState
->corruptFlag
= DBState::OK
;
412 NS_IMPL_ISUPPORTS(InsertCookieDBListener
, mozIStorageStatementCallback
)
414 /******************************************************************************
415 * UpdateCookieDBListener impl:
416 * mozIStorageStatementCallback used to track asynchronous update operations.
417 ******************************************************************************/
418 class UpdateCookieDBListener final
: public DBListenerErrorHandler
421 const char *GetOpType() override
{ return "UPDATE"; }
423 ~UpdateCookieDBListener() = default;
428 explicit UpdateCookieDBListener(DBState
* dbState
) : DBListenerErrorHandler(dbState
) { }
429 NS_IMETHOD
HandleResult(mozIStorageResultSet
*) override
431 MOZ_ASSERT_UNREACHABLE("Unexpected call to "
432 "UpdateCookieDBListener::HandleResult");
435 NS_IMETHOD
HandleCompletion(uint16_t aReason
) override
441 NS_IMPL_ISUPPORTS(UpdateCookieDBListener
, mozIStorageStatementCallback
)
443 /******************************************************************************
444 * RemoveCookieDBListener impl:
445 * mozIStorageStatementCallback used to track asynchronous removal operations.
446 ******************************************************************************/
447 class RemoveCookieDBListener final
: public DBListenerErrorHandler
450 const char *GetOpType() override
{ return "REMOVE"; }
452 ~RemoveCookieDBListener() = default;
457 explicit RemoveCookieDBListener(DBState
* dbState
) : DBListenerErrorHandler(dbState
) { }
458 NS_IMETHOD
HandleResult(mozIStorageResultSet
*) override
460 MOZ_ASSERT_UNREACHABLE("Unexpected call to "
461 "RemoveCookieDBListener::HandleResult");
464 NS_IMETHOD
HandleCompletion(uint16_t aReason
) override
470 NS_IMPL_ISUPPORTS(RemoveCookieDBListener
, mozIStorageStatementCallback
)
472 /******************************************************************************
473 * CloseCookieDBListener imp:
474 * Static mozIStorageCompletionCallback used to notify when the database is
475 * successfully closed.
476 ******************************************************************************/
477 class CloseCookieDBListener final
: public mozIStorageCompletionCallback
479 ~CloseCookieDBListener() = default;
482 explicit CloseCookieDBListener(DBState
* dbState
) : mDBState(dbState
) { }
483 RefPtr
<DBState
> mDBState
;
486 NS_IMETHOD
Complete(nsresult
, nsISupports
*) override
488 gCookieService
->HandleDBClosed(mDBState
);
493 NS_IMPL_ISUPPORTS(CloseCookieDBListener
, mozIStorageCompletionCallback
)
497 class AppClearDataObserver final
: public nsIObserver
{
499 ~AppClearDataObserver() = default;
504 // nsIObserver implementation.
506 Observe(nsISupports
*aSubject
, const char *aTopic
, const char16_t
*aData
) override
508 MOZ_ASSERT(!nsCRT::strcmp(aTopic
, TOPIC_CLEAR_ORIGIN_DATA
));
510 MOZ_ASSERT(XRE_IsParentProcess());
512 nsCOMPtr
<nsICookieManager
> cookieManager
513 = do_GetService(NS_COOKIEMANAGER_CONTRACTID
);
514 MOZ_ASSERT(cookieManager
);
516 return cookieManager
->RemoveCookiesWithOriginAttributes(nsDependentString(aData
), EmptyCString());
520 NS_IMPL_ISUPPORTS(AppClearDataObserver
, nsIObserver
)
522 // comparator class for sorting cookies by entry and index.
523 class CompareCookiesByIndex
{
525 bool Equals(const nsListIter
&a
, const nsListIter
&b
) const
527 NS_ASSERTION(a
.entry
!= b
.entry
|| a
.index
!= b
.index
,
528 "cookie indexes should never be equal");
532 bool LessThan(const nsListIter
&a
, const nsListIter
&b
) const
534 // compare by entryclass pointer, then by index.
535 if (a
.entry
!= b
.entry
)
536 return a
.entry
< b
.entry
;
538 return a
.index
< b
.index
;
545 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const
547 size_t amount
= nsCookieKey::SizeOfExcludingThis(aMallocSizeOf
);
549 amount
+= mCookies
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
550 for (uint32_t i
= 0; i
< mCookies
.Length(); ++i
) {
551 amount
+= mCookies
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
558 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const
562 amount
+= aMallocSizeOf(this);
563 amount
+= hostTable
.SizeOfExcludingThis(aMallocSizeOf
);
568 /******************************************************************************
569 * nsCookieService impl:
570 * singleton instance ctor/dtor methods
571 ******************************************************************************/
573 already_AddRefed
<nsICookieService
>
574 nsCookieService::GetXPCOMSingleton()
577 return CookieServiceChild::GetSingleton();
579 return GetSingleton();
582 already_AddRefed
<nsCookieService
>
583 nsCookieService::GetSingleton()
585 NS_ASSERTION(!IsNeckoChild(), "not a parent process");
587 if (gCookieService
) {
588 return do_AddRef(gCookieService
);
591 // Create a new singleton nsCookieService.
592 // We AddRef only once since XPCOM has rules about the ordering of module
593 // teardowns - by the time our module destructor is called, it's too late to
594 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
595 // cycles have already been completed and would result in serious leaks.
597 gCookieService
= new nsCookieService();
598 if (gCookieService
) {
599 if (NS_SUCCEEDED(gCookieService
->Init())) {
600 ClearOnShutdown(&gCookieService
);
602 gCookieService
= nullptr;
606 return do_AddRef(gCookieService
);
610 nsCookieService::AppClearDataObserverInit()
612 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
613 nsCOMPtr
<nsIObserver
> obs
= new AppClearDataObserver();
614 observerService
->AddObserver(obs
, TOPIC_CLEAR_ORIGIN_DATA
,
615 /* ownsWeak= */ false);
618 /******************************************************************************
619 * nsCookieService impl:
621 ******************************************************************************/
623 NS_IMPL_ISUPPORTS(nsCookieService
,
627 nsISupportsWeakReference
,
630 nsCookieService::nsCookieService()
632 , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT
)
633 , mThirdPartySession(false)
634 , mThirdPartyNonsecureSession(false)
635 , mLeaveSecureAlone(true)
636 , mMaxNumberOfCookies(kMaxNumberOfCookies
)
637 , mMaxCookiesPerHost(kMaxCookiesPerHost
)
638 , mCookieQuotaPerHost(kCookieQuotaPerHost
)
639 , mCookiePurgeAge(kCookiePurgeAge
)
641 , mMonitor("CookieThread")
642 , mInitializedDBStates(false)
643 , mInitializedDBConn(false)
648 nsCookieService::Init()
651 mTLDService
= do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
, &rv
);
652 NS_ENSURE_SUCCESS(rv
, rv
);
654 mIDNService
= do_GetService(NS_IDNSERVICE_CONTRACTID
, &rv
);
655 NS_ENSURE_SUCCESS(rv
, rv
);
657 mThirdPartyUtil
= do_GetService(THIRDPARTYUTIL_CONTRACTID
);
658 NS_ENSURE_SUCCESS(rv
, rv
);
660 // init our pref and observer
661 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
663 prefBranch
->AddObserver(kPrefCookieBehavior
, this, true);
664 prefBranch
->AddObserver(kPrefMaxNumberOfCookies
, this, true);
665 prefBranch
->AddObserver(kPrefMaxCookiesPerHost
, this, true);
666 prefBranch
->AddObserver(kPrefCookiePurgeAge
, this, true);
667 prefBranch
->AddObserver(kPrefThirdPartySession
, this, true);
668 prefBranch
->AddObserver(kPrefThirdPartyNonsecureSession
, this, true);
669 prefBranch
->AddObserver(kCookieLeaveSecurityAlone
, this, true);
670 PrefChanged(prefBranch
);
673 mStorageService
= do_GetService("@mozilla.org/storage/service;1", &rv
);
674 NS_ENSURE_SUCCESS(rv
, rv
);
676 // Init our default, and possibly private DBStates.
679 RegisterWeakMemoryReporter(this);
681 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
683 os
->AddObserver(this, "profile-before-change", true);
684 os
->AddObserver(this, "profile-do-change", true);
685 os
->AddObserver(this, "last-pb-context-exited", true);
687 mPermissionService
= nsCookiePermission::GetOrCreate();
693 nsCookieService::InitDBStates()
695 NS_ASSERTION(!mDBState
, "already have a DBState");
696 NS_ASSERTION(!mDefaultDBState
, "already have a default DBState");
697 NS_ASSERTION(!mPrivateDBState
, "already have a private DBState");
698 NS_ASSERTION(!mInitializedDBStates
, "already initialized");
699 NS_ASSERTION(!mThread
, "already have a cookie thread");
701 // Create a new default DBState and set our current one.
702 mDefaultDBState
= new DBState();
703 mDBState
= mDefaultDBState
;
705 mPrivateDBState
= new DBState();
707 // Get our cookie file.
708 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
709 getter_AddRefs(mDefaultDBState
->cookieFile
));
711 // We've already set up our DBStates appropriately; nothing more to do.
712 COOKIE_LOGSTRING(LogLevel::Warning
,
713 ("InitDBStates(): couldn't get cookie file"));
715 mInitializedDBConn
= true;
716 mInitializedDBStates
= true;
719 mDefaultDBState
->cookieFile
->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE
));
721 NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread
)));
723 nsCOMPtr
<nsIRunnable
> runnable
= NS_NewRunnableFunction("InitDBStates.TryInitDB", [] {
724 NS_ENSURE_TRUE_VOID(gCookieService
&&
725 gCookieService
->mDBState
&&
726 gCookieService
->mDefaultDBState
);
728 MonitorAutoLock
lock(gCookieService
->mMonitor
);
730 // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
732 OpenDBResult result
= gCookieService
->TryInitDB(false);
733 if (result
== RESULT_RETRY
) {
734 // Database may be corrupt. Synchronously close the connection, clean up the
735 // default DBState, and try again.
736 COOKIE_LOGSTRING(LogLevel::Warning
, ("InitDBStates(): retrying TryInitDB()"));
737 gCookieService
->CleanupCachedStatements();
738 gCookieService
->CleanupDefaultDBConnection();
739 result
= gCookieService
->TryInitDB(true);
740 if (result
== RESULT_RETRY
) {
741 // We're done. Change the code to failure so we clean up below.
742 result
= RESULT_FAILURE
;
746 if (result
== RESULT_FAILURE
) {
747 COOKIE_LOGSTRING(LogLevel::Warning
,
748 ("InitDBStates(): TryInitDB() failed, closing connection"));
750 // Connection failure is unrecoverable. Clean up our connection. We can run
751 // fine without persistent storage -- e.g. if there's no profile.
752 gCookieService
->CleanupCachedStatements();
753 gCookieService
->CleanupDefaultDBConnection();
755 // No need to initialize dbConn
756 gCookieService
->mInitializedDBConn
= true;
759 gCookieService
->mInitializedDBStates
= true;
761 NS_DispatchToMainThread(
762 NS_NewRunnableFunction("TryInitDB.InitDBConn", [] {
763 NS_ENSURE_TRUE_VOID(gCookieService
);
764 gCookieService
->InitDBConn();
767 gCookieService
->mMonitor
.Notify();
770 mThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
775 class ConvertAppIdToOriginAttrsSQLFunction final
: public mozIStorageFunction
777 ~ConvertAppIdToOriginAttrsSQLFunction() = default;
780 NS_DECL_MOZISTORAGEFUNCTION
783 NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction
, mozIStorageFunction
);
786 ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
787 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
)
790 int32_t inIsolatedMozBrowser
;
792 rv
= aFunctionArguments
->GetInt32(1, &inIsolatedMozBrowser
);
793 NS_ENSURE_SUCCESS(rv
, rv
);
795 // Create an originAttributes object by inIsolatedMozBrowser.
796 // Then create the originSuffix string from this object.
797 OriginAttributes
attrs(nsIScriptSecurityManager::NO_APP_ID
,
798 (inIsolatedMozBrowser
? true : false));
799 nsAutoCString suffix
;
800 attrs
.CreateSuffix(suffix
);
802 RefPtr
<nsVariant
> outVar(new nsVariant());
803 rv
= outVar
->SetAsAUTF8String(suffix
);
804 NS_ENSURE_SUCCESS(rv
, rv
);
806 outVar
.forget(aResult
);
810 class SetAppIdFromOriginAttributesSQLFunction final
: public mozIStorageFunction
812 ~SetAppIdFromOriginAttributesSQLFunction() = default;
815 NS_DECL_MOZISTORAGEFUNCTION
818 NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction
, mozIStorageFunction
);
821 SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
822 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
)
825 nsAutoCString suffix
;
826 OriginAttributes attrs
;
828 rv
= aFunctionArguments
->GetUTF8String(0, suffix
);
829 NS_ENSURE_SUCCESS(rv
, rv
);
830 bool success
= attrs
.PopulateFromSuffix(suffix
);
831 NS_ENSURE_TRUE(success
, NS_ERROR_FAILURE
);
833 RefPtr
<nsVariant
> outVar(new nsVariant());
834 rv
= outVar
->SetAsInt32(attrs
.mAppId
);
835 NS_ENSURE_SUCCESS(rv
, rv
);
837 outVar
.forget(aResult
);
841 class SetInBrowserFromOriginAttributesSQLFunction final
:
842 public mozIStorageFunction
844 ~SetInBrowserFromOriginAttributesSQLFunction() = default;
847 NS_DECL_MOZISTORAGEFUNCTION
850 NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction
,
851 mozIStorageFunction
);
854 SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
855 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
)
858 nsAutoCString suffix
;
859 OriginAttributes attrs
;
861 rv
= aFunctionArguments
->GetUTF8String(0, suffix
);
862 NS_ENSURE_SUCCESS(rv
, rv
);
863 bool success
= attrs
.PopulateFromSuffix(suffix
);
864 NS_ENSURE_TRUE(success
, NS_ERROR_FAILURE
);
866 RefPtr
<nsVariant
> outVar(new nsVariant());
867 rv
= outVar
->SetAsInt32(attrs
.mInIsolatedMozBrowser
);
868 NS_ENSURE_SUCCESS(rv
, rv
);
870 outVar
.forget(aResult
);
876 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
877 * move the existing database file out of the way and create a new one.
879 * @returns RESULT_OK if opening or creating the database succeeded;
880 * RESULT_RETRY if the database cannot be opened, is corrupt, or some
881 * other failure occurred that might be resolved by recreating the
882 * database; or RESULT_FAILED if there was an unrecoverable error and
883 * we must run without a database.
885 * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
886 * cleanup of the default DBState.
889 nsCookieService::TryInitDB(bool aRecreateDB
)
891 NS_ASSERTION(!mDefaultDBState
->dbConn
, "nonnull dbConn");
892 NS_ASSERTION(!mDefaultDBState
->stmtInsert
, "nonnull stmtInsert");
893 NS_ASSERTION(!mDefaultDBState
->insertListener
, "nonnull insertListener");
894 NS_ASSERTION(!mDefaultDBState
->syncConn
, "nonnull syncConn");
895 NS_ASSERTION(NS_GetCurrentThread() == mThread
, "non cookie thread");
897 // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
898 // want to delete it outright, since it may be useful for debugging purposes,
899 // so we move it out of the way.
902 nsCOMPtr
<nsIFile
> backupFile
;
903 mDefaultDBState
->cookieFile
->Clone(getter_AddRefs(backupFile
));
904 rv
= backupFile
->MoveToNative(nullptr,
905 NS_LITERAL_CSTRING(COOKIES_FILE
".bak"));
906 NS_ENSURE_SUCCESS(rv
, RESULT_FAILURE
);
909 // This block provides scope for the Telemetry AutoTimer
911 Telemetry::AutoTimer
<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS
>
913 ReadAheadFile(mDefaultDBState
->cookieFile
);
915 // open a connection to the cookie database, and only cache our connection
916 // and statements upon success. The connection is opened unshared to eliminate
917 // cache contention between the main and background threads.
918 rv
= mStorageService
->OpenUnsharedDatabase(mDefaultDBState
->cookieFile
,
919 getter_AddRefs(mDefaultDBState
->syncConn
));
920 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
923 auto guard
= MakeScopeExit([&] {
924 mDefaultDBState
->syncConn
= nullptr;
927 bool tableExists
= false;
928 mDefaultDBState
->syncConn
->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
932 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
935 // table already exists; check the schema version before reading
936 int32_t dbSchemaVersion
;
937 rv
= mDefaultDBState
->syncConn
->GetSchemaVersion(&dbSchemaVersion
);
938 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
940 // Start a transaction for the whole migration block.
941 mozStorageTransaction
transaction(mDefaultDBState
->syncConn
, true);
943 switch (dbSchemaVersion
) {
945 // Every time you increment the database schema, you need to implement
946 // the upgrading code from the previous version to the new one. If migration
947 // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
948 // the original database will be saved, in the hopes that we might one day
949 // see it and fix it.
952 // Add the lastAccessed column to the table.
953 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
954 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
955 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
957 // Fall through to the next upgrade.
962 // Add the baseDomain column and index to the table.
963 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
964 "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
965 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
967 // Compute the baseDomains for the table. This must be done eagerly
968 // otherwise we won't be able to synchronously read in individual
969 // domains on demand.
970 const int64_t SCHEMA2_IDX_ID
= 0;
971 const int64_t SCHEMA2_IDX_HOST
= 1;
972 nsCOMPtr
<mozIStorageStatement
> select
;
973 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
974 "SELECT id, host FROM moz_cookies"), getter_AddRefs(select
));
975 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
977 nsCOMPtr
<mozIStorageStatement
> update
;
978 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
979 "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
980 getter_AddRefs(update
));
981 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
983 nsCString baseDomain
, host
;
986 rv
= select
->ExecuteStep(&hasResult
);
987 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
992 int64_t id
= select
->AsInt64(SCHEMA2_IDX_ID
);
993 select
->GetUTF8String(SCHEMA2_IDX_HOST
, host
);
995 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
996 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
998 mozStorageStatementScoper
scoper(update
);
1000 rv
= update
->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
1002 NS_ASSERT_SUCCESS(rv
);
1003 rv
= update
->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1005 NS_ASSERT_SUCCESS(rv
);
1007 rv
= update
->ExecuteStep(&hasResult
);
1008 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1011 // Create an index on baseDomain.
1012 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1013 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
1014 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1016 // Fall through to the next upgrade.
1021 // Add the creationTime column to the table, and create a unique index
1022 // on (name, host, path). Before we do this, we have to purge the table
1023 // of expired cookies such that we know that the (name, host, path)
1024 // index is truly unique -- otherwise we can't create the index. Note
1025 // that we can't just execute a statement to delete all rows where the
1026 // expiry column is in the past -- doing so would rely on the clock
1027 // (both now and when previous cookies were set) being monotonic.
1029 // Select the whole table, and order by the fields we're interested in.
1030 // This means we can simply do a linear traversal of the results and
1031 // check for duplicates as we go.
1032 const int64_t SCHEMA3_IDX_ID
= 0;
1033 const int64_t SCHEMA3_IDX_NAME
= 1;
1034 const int64_t SCHEMA3_IDX_HOST
= 2;
1035 const int64_t SCHEMA3_IDX_PATH
= 3;
1036 nsCOMPtr
<mozIStorageStatement
> select
;
1037 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
1038 "SELECT id, name, host, path FROM moz_cookies "
1039 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
1040 getter_AddRefs(select
));
1041 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1043 nsCOMPtr
<mozIStorageStatement
> deleteExpired
;
1044 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
1045 "DELETE FROM moz_cookies WHERE id = :id"),
1046 getter_AddRefs(deleteExpired
));
1047 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1049 // Read the first row.
1051 rv
= select
->ExecuteStep(&hasResult
);
1052 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1055 nsCString name1
, host1
, path1
;
1056 int64_t id1
= select
->AsInt64(SCHEMA3_IDX_ID
);
1057 select
->GetUTF8String(SCHEMA3_IDX_NAME
, name1
);
1058 select
->GetUTF8String(SCHEMA3_IDX_HOST
, host1
);
1059 select
->GetUTF8String(SCHEMA3_IDX_PATH
, path1
);
1061 nsCString name2
, host2
, path2
;
1063 // Read the second row.
1064 rv
= select
->ExecuteStep(&hasResult
);
1065 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1070 int64_t id2
= select
->AsInt64(SCHEMA3_IDX_ID
);
1071 select
->GetUTF8String(SCHEMA3_IDX_NAME
, name2
);
1072 select
->GetUTF8String(SCHEMA3_IDX_HOST
, host2
);
1073 select
->GetUTF8String(SCHEMA3_IDX_PATH
, path2
);
1075 // If the two rows match in (name, host, path), we know the earlier
1076 // row has an earlier expiry time. Delete it.
1077 if (name1
== name2
&& host1
== host2
&& path1
== path2
) {
1078 mozStorageStatementScoper
scoper(deleteExpired
);
1080 rv
= deleteExpired
->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1082 NS_ASSERT_SUCCESS(rv
);
1084 rv
= deleteExpired
->ExecuteStep(&hasResult
);
1085 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1088 // Make the second row the first for the next iteration.
1096 // Add the creationTime column to the table.
1097 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1098 "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
1099 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1101 // Copy the id of each row into the new creationTime column.
1102 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1103 "UPDATE moz_cookies SET creationTime = "
1104 "(SELECT id WHERE id = moz_cookies.id)"));
1105 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1107 // Create a unique index on (name, host, path) to allow fast lookup.
1108 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1109 "CREATE UNIQUE INDEX moz_uniqueid "
1110 "ON moz_cookies (name, host, path)"));
1111 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1113 // Fall through to the next upgrade.
1118 // We need to add appId/inBrowserElement, plus change a constraint on
1119 // the table (unique entries now include appId/inBrowserElement):
1120 // this requires creating a new table and copying the data to it. We
1121 // then rename the new table to the old name.
1123 // Why we made this change: appId/inBrowserElement allow "cookie jars"
1124 // for Firefox OS. We create a separate cookie namespace per {appId,
1125 // inBrowserElement}. When upgrading, we convert existing cookies
1126 // (which imply we're on desktop/mobile) to use {0, false}, as that is
1127 // the only namespace used by a non-Firefox-OS implementation.
1129 // Rename existing table
1130 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1131 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1132 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1134 // Drop existing index (CreateTable will create new one for new table)
1135 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1136 "DROP INDEX moz_basedomain"));
1137 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1139 // Create new table (with new fields and new unique constraint)
1140 rv
= CreateTableForSchemaVersion5();
1141 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1143 // Copy data from old table, using appId/inBrowser=0 for existing rows
1144 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1145 "INSERT INTO moz_cookies "
1146 "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
1147 " lastAccessed, creationTime, isSecure, isHttpOnly) "
1148 "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
1149 " lastAccessed, creationTime, isSecure, isHttpOnly "
1150 "FROM moz_cookies_old"));
1151 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1154 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1155 "DROP TABLE moz_cookies_old"));
1156 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1158 COOKIE_LOGSTRING(LogLevel::Debug
,
1159 ("Upgraded database to schema version 5"));
1161 // Fall through to the next upgrade.
1166 // Change in the version: Replace the columns |appId| and
1167 // |inBrowserElement| by a single column |originAttributes|.
1169 // Why we made this change: FxOS new security model (NSec) encapsulates
1170 // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to make
1171 // it easier to modify the contents of this structure in the future.
1173 // We do the migration in several steps:
1174 // 1. Rename the old table.
1175 // 2. Create a new table.
1176 // 3. Copy data from the old table to the new table; convert appId and
1177 // inBrowserElement to originAttributes in the meantime.
1179 // Rename existing table.
1180 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1181 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1182 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1184 // Drop existing index (CreateTable will create new one for new table).
1185 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1186 "DROP INDEX moz_basedomain"));
1187 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1189 // Create new table with new fields and new unique constraint.
1190 rv
= CreateTableForSchemaVersion6();
1191 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1193 // Copy data from old table without the two deprecated columns appId and
1194 // inBrowserElement.
1195 nsCOMPtr
<mozIStorageFunction
>
1196 convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction());
1197 NS_ENSURE_TRUE(convertToOriginAttrs
, RESULT_RETRY
);
1199 NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName
,
1200 "CONVERT_TO_ORIGIN_ATTRIBUTES");
1202 rv
= mDefaultDBState
->syncConn
->CreateFunction(convertToOriginAttrsName
,
1203 2, convertToOriginAttrs
);
1204 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1206 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1207 "INSERT INTO moz_cookies "
1208 "(baseDomain, originAttributes, name, value, host, path, expiry,"
1209 " lastAccessed, creationTime, isSecure, isHttpOnly) "
1210 "SELECT baseDomain, "
1211 " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
1212 " name, value, host, path, expiry, lastAccessed, creationTime, "
1213 " isSecure, isHttpOnly "
1214 "FROM moz_cookies_old"));
1215 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1217 rv
= mDefaultDBState
->syncConn
->RemoveFunction(convertToOriginAttrsName
);
1218 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1221 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1222 "DROP TABLE moz_cookies_old"));
1223 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1225 COOKIE_LOGSTRING(LogLevel::Debug
,
1226 ("Upgraded database to schema version 6"));
1232 // We made a mistake in schema version 6. We cannot remove expected
1233 // columns of any version (checked in the default case) from cookie
1234 // database, because doing this would destroy the possibility of
1235 // downgrading database.
1237 // This version simply restores appId and inBrowserElement columns in
1238 // order to fix downgrading issue even though these two columns are no
1239 // longer used in the latest schema.
1240 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1241 "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
1242 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1244 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1245 "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
1246 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1248 // Compute and populate the values of appId and inBrwoserElement from
1249 // originAttributes.
1250 nsCOMPtr
<mozIStorageFunction
>
1251 setAppId(new SetAppIdFromOriginAttributesSQLFunction());
1252 NS_ENSURE_TRUE(setAppId
, RESULT_RETRY
);
1254 NS_NAMED_LITERAL_CSTRING(setAppIdName
, "SET_APP_ID");
1256 rv
= mDefaultDBState
->syncConn
->CreateFunction(setAppIdName
, 1, setAppId
);
1257 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1259 nsCOMPtr
<mozIStorageFunction
>
1260 setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
1261 NS_ENSURE_TRUE(setInBrowser
, RESULT_RETRY
);
1263 NS_NAMED_LITERAL_CSTRING(setInBrowserName
, "SET_IN_BROWSER");
1265 rv
= mDefaultDBState
->syncConn
->CreateFunction(setInBrowserName
, 1,
1267 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1269 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1270 "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
1271 "inBrowserElement = SET_IN_BROWSER(originAttributes);"
1273 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1275 rv
= mDefaultDBState
->syncConn
->RemoveFunction(setAppIdName
);
1276 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1278 rv
= mDefaultDBState
->syncConn
->RemoveFunction(setInBrowserName
);
1279 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1281 COOKIE_LOGSTRING(LogLevel::Debug
,
1282 ("Upgraded database to schema version 7"));
1288 // Remove the appId field from moz_cookies.
1290 // Unfortunately sqlite doesn't support dropping columns using ALTER
1291 // TABLE, so we need to go through the procedure documented in
1292 // https://www.sqlite.org/lang_altertable.html.
1294 // Drop existing index
1295 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1296 "DROP INDEX moz_basedomain"));
1297 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1299 // Create a new_moz_cookies table without the appId field.
1300 rv
= CreateTableWorker("new_moz_cookies");
1301 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1303 // Move the data over.
1304 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1305 "INSERT INTO new_moz_cookies ("
1308 "originAttributes, "
1322 "originAttributes, "
1333 "FROM moz_cookies;"));
1334 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1336 // Drop the old table
1337 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1338 "DROP TABLE moz_cookies;"));
1339 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1341 // Rename new_moz_cookies to moz_cookies.
1342 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1343 "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
1344 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1346 // Recreate our index.
1348 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1350 COOKIE_LOGSTRING(LogLevel::Debug
,
1351 ("Upgraded database to schema version 8"));
1357 // Add the sameSite column to the table.
1358 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(
1359 NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
1360 COOKIE_LOGSTRING(LogLevel::Debug
,
1361 ("Upgraded database to schema version 9"));
1364 // No more upgrades. Update the schema version.
1365 rv
= mDefaultDBState
->syncConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
1366 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1370 case COOKIES_SCHEMA_VERSION
:
1375 NS_WARNING("couldn't get schema version!");
1377 // the table may be usable; someone might've just clobbered the schema
1378 // version. we can treat this case like a downgrade using the codepath
1379 // below, by verifying the columns we care about are all there. for now,
1380 // re-set the schema version in the db, in case the checks succeed (if
1381 // they don't, we're dropping the table anyway).
1382 rv
= mDefaultDBState
->syncConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
1383 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1385 // fall through to downgrade check
1389 // if columns have been added to the table, we can still use the ones we
1390 // understand safely. if columns have been deleted or altered, just
1391 // blow away the table and start from scratch! if you change the way
1392 // a column is interpreted, make sure you also change its name so this
1393 // check will catch it.
1396 // check if all the expected columns exist
1397 nsCOMPtr
<mozIStorageStatement
> stmt
;
1398 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
1402 "originAttributes, "
1413 "FROM moz_cookies"), getter_AddRefs(stmt
));
1414 if (NS_SUCCEEDED(rv
))
1417 // our columns aren't there - drop the table!
1418 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1419 "DROP TABLE moz_cookies"));
1420 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1423 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
1429 // if we deleted a corrupt db, don't attempt to import - return now
1434 // check whether to import or just read in the db
1439 nsCOMPtr
<nsIRunnable
> runnable
=
1440 NS_NewRunnableFunction("TryInitDB.ImportCookies", [] {
1441 NS_ENSURE_TRUE_VOID(gCookieService
);
1442 NS_ENSURE_TRUE_VOID(gCookieService
->mDefaultDBState
);
1443 nsCOMPtr
<nsIFile
> oldCookieFile
;
1444 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
1445 getter_AddRefs(oldCookieFile
));
1446 if (NS_FAILED(rv
)) {
1450 // Import cookies, and clean up the old file regardless of success or failure.
1451 // Note that we have to switch out our DBState temporarily, in case we're in
1452 // private browsing mode; otherwise ImportCookies() won't be happy.
1453 DBState
* initialState
= gCookieService
->mDBState
;
1454 gCookieService
->mDBState
= gCookieService
->mDefaultDBState
;
1455 oldCookieFile
->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME
));
1456 gCookieService
->ImportCookies(oldCookieFile
);
1457 oldCookieFile
->Remove(false);
1458 gCookieService
->mDBState
= initialState
;
1461 NS_DispatchToMainThread(runnable
);
1467 nsCookieService::InitDBConn()
1469 MOZ_ASSERT(NS_IsMainThread());
1471 // We should skip InitDBConn if we close profile during initializing DBStates
1472 // and then InitDBConn is called after we close the DBStates.
1473 if (!mInitializedDBStates
|| mInitializedDBConn
|| !mDefaultDBState
) {
1477 for (uint32_t i
= 0; i
< mReadArray
.Length(); ++i
) {
1478 CookieDomainTuple
& tuple
= mReadArray
[i
];
1479 RefPtr
<nsCookie
> cookie
= nsCookie::Create(tuple
.cookie
->name
,
1480 tuple
.cookie
->value
,
1483 tuple
.cookie
->expiry
,
1484 tuple
.cookie
->lastAccessed
,
1485 tuple
.cookie
->creationTime
,
1487 tuple
.cookie
->isSecure
,
1488 tuple
.cookie
->isHttpOnly
,
1489 tuple
.cookie
->originAttributes
,
1490 tuple
.cookie
->sameSite
);
1492 AddCookieToList(tuple
.key
, cookie
, mDefaultDBState
, nullptr, false);
1495 if (NS_FAILED(InitDBConnInternal())) {
1496 COOKIE_LOGSTRING(LogLevel::Warning
, ("InitDBConn(): retrying InitDBConnInternal()"));
1497 CleanupCachedStatements();
1498 CleanupDefaultDBConnection();
1499 if (NS_FAILED(InitDBConnInternal())) {
1500 COOKIE_LOGSTRING(LogLevel::Warning
,
1501 ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
1503 // Game over, clean the connections.
1504 CleanupCachedStatements();
1505 CleanupDefaultDBConnection();
1508 mInitializedDBConn
= true;
1510 COOKIE_LOGSTRING(LogLevel::Debug
, ("InitDBConn(): mInitializedDBConn = true"));
1511 mEndInitDBConn
= mozilla::TimeStamp::Now();
1513 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
1515 os
->NotifyObservers(nullptr, "cookie-db-read", nullptr);
1521 nsCookieService::InitDBConnInternal()
1523 MOZ_ASSERT(NS_IsMainThread());
1525 nsresult rv
= mStorageService
->OpenUnsharedDatabase(mDefaultDBState
->cookieFile
,
1526 getter_AddRefs(mDefaultDBState
->dbConn
));
1527 NS_ENSURE_SUCCESS(rv
, rv
);
1529 // Set up our listeners.
1530 mDefaultDBState
->insertListener
= new InsertCookieDBListener(mDefaultDBState
);
1531 mDefaultDBState
->updateListener
= new UpdateCookieDBListener(mDefaultDBState
);
1532 mDefaultDBState
->removeListener
= new RemoveCookieDBListener(mDefaultDBState
);
1533 mDefaultDBState
->closeListener
= new CloseCookieDBListener(mDefaultDBState
);
1535 // Grow cookie db in 512KB increments
1536 mDefaultDBState
->dbConn
->SetGrowthIncrement(512 * 1024, EmptyCString());
1538 // make operations on the table asynchronous, for performance
1539 mDefaultDBState
->dbConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1540 "PRAGMA synchronous = OFF"));
1542 // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1543 // 16 pages (around 500KB).
1544 mDefaultDBState
->dbConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1545 MOZ_STORAGE_UNIQUIFY_QUERY_STR
"PRAGMA journal_mode = WAL"));
1546 mDefaultDBState
->dbConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1547 "PRAGMA wal_autocheckpoint = 16"));
1549 // cache frequently used statements (for insertion, deletion, and updating)
1550 rv
= mDefaultDBState
->dbConn
->CreateAsyncStatement(NS_LITERAL_CSTRING(
1551 "INSERT INTO moz_cookies ("
1553 "originAttributes, "
1566 ":originAttributes, "
1578 getter_AddRefs(mDefaultDBState
->stmtInsert
));
1579 NS_ENSURE_SUCCESS(rv
, rv
);
1581 rv
= mDefaultDBState
->dbConn
->CreateAsyncStatement(NS_LITERAL_CSTRING(
1582 "DELETE FROM moz_cookies "
1583 "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
1584 getter_AddRefs(mDefaultDBState
->stmtDelete
));
1585 NS_ENSURE_SUCCESS(rv
, rv
);
1587 rv
= mDefaultDBState
->dbConn
->CreateAsyncStatement(NS_LITERAL_CSTRING(
1588 "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1589 "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
1590 getter_AddRefs(mDefaultDBState
->stmtUpdate
));
1594 // Sets the schema version and creates the moz_cookies table.
1596 nsCookieService::CreateTableWorker(const char* aName
)
1598 // Create the table.
1599 // We default originAttributes to empty string: this is so if users revert to
1600 // an older Firefox version that doesn't know about this field, any cookies
1601 // set will still work once they upgrade back.
1602 nsAutoCString
command("CREATE TABLE ");
1603 command
.Append(aName
);
1604 command
.AppendLiteral(" ("
1605 "id INTEGER PRIMARY KEY, "
1607 "originAttributes TEXT NOT NULL DEFAULT '', "
1613 "lastAccessed INTEGER, "
1614 "creationTime INTEGER, "
1615 "isSecure INTEGER, "
1616 "isHttpOnly INTEGER, "
1617 "inBrowserElement INTEGER DEFAULT 0, "
1618 "sameSite INTEGER DEFAULT 0, "
1619 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1621 return mDefaultDBState
->syncConn
->ExecuteSimpleSQL(command
);
1624 // Sets the schema version and creates the moz_cookies table.
1626 nsCookieService::CreateTable()
1628 // Set the schema version, before creating the table.
1629 nsresult rv
= mDefaultDBState
->syncConn
->SetSchemaVersion(
1630 COOKIES_SCHEMA_VERSION
);
1631 if (NS_FAILED(rv
)) return rv
;
1633 rv
= CreateTableWorker("moz_cookies");
1634 if (NS_FAILED(rv
)) return rv
;
1636 return CreateIndex();
1640 nsCookieService::CreateIndex()
1642 // Create an index on baseDomain.
1643 return mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1644 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1645 "originAttributes)"));
1648 // Sets the schema version and creates the moz_cookies table.
1650 nsCookieService::CreateTableForSchemaVersion6()
1652 // Set the schema version, before creating the table.
1653 nsresult rv
= mDefaultDBState
->syncConn
->SetSchemaVersion(6);
1654 if (NS_FAILED(rv
)) return rv
;
1656 // Create the table.
1657 // We default originAttributes to empty string: this is so if users revert to
1658 // an older Firefox version that doesn't know about this field, any cookies
1659 // set will still work once they upgrade back.
1660 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1661 "CREATE TABLE moz_cookies ("
1662 "id INTEGER PRIMARY KEY, "
1664 "originAttributes TEXT NOT NULL DEFAULT '', "
1670 "lastAccessed INTEGER, "
1671 "creationTime INTEGER, "
1672 "isSecure INTEGER, "
1673 "isHttpOnly INTEGER, "
1674 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1676 if (NS_FAILED(rv
)) return rv
;
1678 // Create an index on baseDomain.
1679 return mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1680 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1681 "originAttributes)"));
1684 // Sets the schema version and creates the moz_cookies table.
1686 nsCookieService::CreateTableForSchemaVersion5()
1688 // Set the schema version, before creating the table.
1689 nsresult rv
= mDefaultDBState
->syncConn
->SetSchemaVersion(5);
1690 if (NS_FAILED(rv
)) return rv
;
1692 // Create the table. We default appId/inBrowserElement to 0: this is so if
1693 // users revert to an older Firefox version that doesn't know about these
1694 // fields, any cookies set will still work once they upgrade back.
1695 rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1696 "CREATE TABLE moz_cookies ("
1697 "id INTEGER PRIMARY KEY, "
1699 "appId INTEGER DEFAULT 0, "
1700 "inBrowserElement INTEGER DEFAULT 0, "
1706 "lastAccessed INTEGER, "
1707 "creationTime INTEGER, "
1708 "isSecure INTEGER, "
1709 "isHttpOnly INTEGER, "
1710 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
1712 if (NS_FAILED(rv
)) return rv
;
1714 // Create an index on baseDomain.
1715 return mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1716 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1718 "inBrowserElement)"));
1722 nsCookieService::CloseDBStates()
1724 // return if we already closed
1730 mThread
->Shutdown();
1734 // Null out our private and pointer DBStates regardless.
1735 mPrivateDBState
= nullptr;
1738 // If we don't have a default DBState, we're done.
1739 if (!mDefaultDBState
)
1742 // Cleanup cached statements before we can close anything.
1743 CleanupCachedStatements();
1745 if (mDefaultDBState
->dbConn
) {
1746 // Asynchronously close the connection. We will null it below.
1747 mDefaultDBState
->dbConn
->AsyncClose(mDefaultDBState
->closeListener
);
1750 CleanupDefaultDBConnection();
1752 mDefaultDBState
= nullptr;
1753 mInitializedDBConn
= false;
1754 mInitializedDBStates
= false;
1757 // Null out the statements.
1758 // This must be done before closing the connection.
1760 nsCookieService::CleanupCachedStatements()
1762 mDefaultDBState
->stmtInsert
= nullptr;
1763 mDefaultDBState
->stmtDelete
= nullptr;
1764 mDefaultDBState
->stmtUpdate
= nullptr;
1767 // Null out the listeners, and the database connection itself. This
1768 // will not null out the statements, cancel a pending read or
1769 // asynchronously close the connection -- these must be done
1770 // beforehand if necessary.
1772 nsCookieService::CleanupDefaultDBConnection()
1774 MOZ_ASSERT(!mDefaultDBState
->stmtInsert
, "stmtInsert has been cleaned up");
1775 MOZ_ASSERT(!mDefaultDBState
->stmtDelete
, "stmtDelete has been cleaned up");
1776 MOZ_ASSERT(!mDefaultDBState
->stmtUpdate
, "stmtUpdate has been cleaned up");
1778 // Null out the database connections. If 'dbConn' has not been used for any
1779 // asynchronous operations yet, this will synchronously close it; otherwise,
1780 // it's expected that the caller has performed an AsyncClose prior.
1781 mDefaultDBState
->dbConn
= nullptr;
1783 // Manually null out our listeners. This is necessary because they hold a
1784 // strong ref to the DBState itself. They'll stay alive until whatever
1785 // statements are still executing complete.
1786 mDefaultDBState
->insertListener
= nullptr;
1787 mDefaultDBState
->updateListener
= nullptr;
1788 mDefaultDBState
->removeListener
= nullptr;
1789 mDefaultDBState
->closeListener
= nullptr;
1793 nsCookieService::HandleDBClosed(DBState
* aDBState
)
1795 COOKIE_LOGSTRING(LogLevel::Debug
,
1796 ("HandleDBClosed(): DBState %p closed", aDBState
));
1798 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
1800 switch (aDBState
->corruptFlag
) {
1802 // Database is healthy. Notify of closure.
1804 os
->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1808 case DBState::CLOSING_FOR_REBUILD
: {
1809 // Our close finished. Start the rebuild, and notify of db closure later.
1810 RebuildCorruptDB(aDBState
);
1813 case DBState::REBUILDING
: {
1814 // We encountered an error during rebuild, closed the database, and now
1815 // here we are. We already have a 'cookies.sqlite.bak' from the original
1816 // dead database; we don't want to overwrite it, so let's move this one to
1817 // 'cookies.sqlite.bak-rebuild'.
1818 nsCOMPtr
<nsIFile
> backupFile
;
1819 aDBState
->cookieFile
->Clone(getter_AddRefs(backupFile
));
1820 nsresult rv
= backupFile
->MoveToNative(nullptr,
1821 NS_LITERAL_CSTRING(COOKIES_FILE
".bak-rebuild"));
1823 COOKIE_LOGSTRING(LogLevel::Warning
,
1824 ("HandleDBClosed(): DBState %p encountered error rebuilding db; move to "
1825 "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32
,
1826 aDBState
, static_cast<uint32_t>(rv
)));
1828 os
->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1836 nsCookieService::HandleCorruptDB(DBState
* aDBState
)
1838 if (mDefaultDBState
!= aDBState
) {
1839 // We've either closed the state or we've switched profiles. It's getting
1840 // a bit late to rebuild -- bail instead.
1841 COOKIE_LOGSTRING(LogLevel::Warning
,
1842 ("HandleCorruptDB(): DBState %p is already closed, aborting", aDBState
));
1846 COOKIE_LOGSTRING(LogLevel::Debug
,
1847 ("HandleCorruptDB(): DBState %p has corruptFlag %u", aDBState
,
1848 aDBState
->corruptFlag
));
1850 // Mark the database corrupt, so the close listener can begin reconstructing
1852 switch (mDefaultDBState
->corruptFlag
) {
1854 // Move to 'closing' state.
1855 mDefaultDBState
->corruptFlag
= DBState::CLOSING_FOR_REBUILD
;
1857 CleanupCachedStatements();
1858 mDefaultDBState
->dbConn
->AsyncClose(mDefaultDBState
->closeListener
);
1859 CleanupDefaultDBConnection();
1862 case DBState::CLOSING_FOR_REBUILD
: {
1863 // We had an error while waiting for close completion. That's OK, just
1864 // ignore it -- we're rebuilding anyway.
1867 case DBState::REBUILDING
: {
1868 // We had an error while rebuilding the DB. Game over. Close the database
1869 // and let the close handler do nothing; then we'll move it out of the way.
1870 CleanupCachedStatements();
1871 if (mDefaultDBState
->dbConn
) {
1872 mDefaultDBState
->dbConn
->AsyncClose(mDefaultDBState
->closeListener
);
1874 CleanupDefaultDBConnection();
1881 nsCookieService::RebuildCorruptDB(DBState
* aDBState
)
1883 NS_ASSERTION(!aDBState
->dbConn
, "shouldn't have an open db connection");
1884 NS_ASSERTION(aDBState
->corruptFlag
== DBState::CLOSING_FOR_REBUILD
,
1885 "should be in CLOSING_FOR_REBUILD state");
1887 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
1889 aDBState
->corruptFlag
= DBState::REBUILDING
;
1891 if (mDefaultDBState
!= aDBState
) {
1892 // We've either closed the state or we've switched profiles. It's getting
1893 // a bit late to rebuild -- bail instead. In any case, we were waiting
1894 // on rebuild completion to notify of the db closure, which won't happen --
1896 COOKIE_LOGSTRING(LogLevel::Warning
,
1897 ("RebuildCorruptDB(): DBState %p is stale, aborting", aDBState
));
1899 os
->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1904 COOKIE_LOGSTRING(LogLevel::Debug
,
1905 ("RebuildCorruptDB(): creating new database"));
1907 nsCOMPtr
<nsIRunnable
> runnable
=
1908 NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [] {
1909 NS_ENSURE_TRUE_VOID(gCookieService
&& gCookieService
->mDefaultDBState
);
1911 // The database has been closed, and we're ready to rebuild. Open a
1913 OpenDBResult result
= gCookieService
->TryInitDB(true);
1915 nsCOMPtr
<nsIRunnable
> innerRunnable
=
1916 NS_NewRunnableFunction("RebuildCorruptDB.TryInitDBComplete", [result
] {
1917 NS_ENSURE_TRUE_VOID(gCookieService
&& gCookieService
->mDefaultDBState
);
1919 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
1920 if (result
!= RESULT_OK
) {
1921 // We're done. Reset our DB connection and statements, and notify of
1923 COOKIE_LOGSTRING(LogLevel::Warning
,
1924 ("RebuildCorruptDB(): TryInitDB() failed with result %u", result
));
1925 gCookieService
->CleanupCachedStatements();
1926 gCookieService
->CleanupDefaultDBConnection();
1927 gCookieService
->mDefaultDBState
->corruptFlag
= DBState::OK
;
1929 os
->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1934 // Notify observers that we're beginning the rebuild.
1936 os
->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1939 gCookieService
->InitDBConnInternal();
1941 // Enumerate the hash, and add cookies to the params array.
1942 mozIStorageAsyncStatement
* stmt
= gCookieService
->mDefaultDBState
->stmtInsert
;
1943 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray
;
1944 stmt
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
1945 for (auto iter
= gCookieService
->mDefaultDBState
->hostTable
.Iter();
1948 nsCookieEntry
* entry
= iter
.Get();
1950 const nsCookieEntry::ArrayType
& cookies
= entry
->GetCookies();
1951 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
1952 nsCookie
* cookie
= cookies
[i
];
1954 if (!cookie
->IsSession()) {
1955 bindCookieParameters(paramsArray
, nsCookieKey(entry
), cookie
);
1960 // Make sure we've got something to write. If we don't, we're done.
1962 paramsArray
->GetLength(&length
);
1964 COOKIE_LOGSTRING(LogLevel::Debug
,
1965 ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1966 gCookieService
->mDefaultDBState
->corruptFlag
= DBState::OK
;
1970 // Execute the statement. If any errors crop up, we won't try again.
1971 DebugOnly
<nsresult
> rv
= stmt
->BindParameters(paramsArray
);
1972 NS_ASSERT_SUCCESS(rv
);
1973 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
1974 rv
= stmt
->ExecuteAsync(gCookieService
->mDefaultDBState
->insertListener
,
1975 getter_AddRefs(handle
));
1976 NS_ASSERT_SUCCESS(rv
);
1978 NS_DispatchToMainThread(innerRunnable
);
1980 mThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
1983 nsCookieService::~nsCookieService()
1987 UnregisterWeakMemoryReporter(this);
1989 gCookieService
= nullptr;
1993 nsCookieService::Observe(nsISupports
*aSubject
,
1995 const char16_t
*aData
)
1998 if (!strcmp(aTopic
, "profile-before-change")) {
1999 // The profile is about to change,
2000 // or is going away because the application is shutting down.
2002 // Close the default DB connection and null out our DBStates before
2006 } else if (!strcmp(aTopic
, "profile-do-change")) {
2007 NS_ASSERTION(!mDefaultDBState
, "shouldn't have a default DBState");
2008 NS_ASSERTION(!mPrivateDBState
, "shouldn't have a private DBState");
2010 // the profile has already changed; init the db from the new location.
2011 // if we are in the private browsing state, however, we do not want to read
2012 // data into it - we should instead put it into the default state, so it's
2013 // ready for us if and when we switch back to it.
2016 } else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
2017 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
2019 PrefChanged(prefBranch
);
2021 } else if (!strcmp(aTopic
, "last-pb-context-exited")) {
2022 // Flush all the cookies stored by private browsing contexts
2023 mozilla::OriginAttributesPattern pattern
;
2024 pattern
.mPrivateBrowsingId
.Construct(1);
2025 RemoveCookiesWithOriginAttributes(pattern
, EmptyCString());
2026 mPrivateDBState
= new DBState();
2034 nsCookieService::GetCookieString(nsIURI
*aHostURI
,
2035 nsIChannel
*aChannel
,
2038 return GetCookieStringCommon(aHostURI
, aChannel
, false, aCookie
);
2042 nsCookieService::GetCookieStringFromHttp(nsIURI
*aHostURI
,
2044 nsIChannel
*aChannel
,
2047 return GetCookieStringCommon(aHostURI
, aChannel
, true, aCookie
);
2051 nsCookieService::GetCookieStringCommon(nsIURI
*aHostURI
,
2052 nsIChannel
*aChannel
,
2056 NS_ENSURE_ARG(aHostURI
);
2057 NS_ENSURE_ARG(aCookie
);
2059 // Determine whether the request is foreign. Failure is acceptable.
2060 bool isForeign
= true;
2061 mThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aHostURI
, &isForeign
);
2063 bool isTrackingResource
= false;
2064 bool firstPartyStorageAccessGranted
= false;
2065 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
2067 isTrackingResource
= httpChannel
->GetIsTrackingResource();
2069 // Check first-party storage access even for non-tracking resources, since
2070 // we will need the result when computing the access rights for the reject
2071 // foreign cookie behavior mode.
2073 AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(httpChannel
,
2076 firstPartyStorageAccessGranted
= true;
2080 OriginAttributes attrs
;
2082 NS_GetOriginAttributes(aChannel
, attrs
);
2085 bool isSafeTopLevelNav
= NS_IsSafeTopLevelNav(aChannel
);
2086 bool isSameSiteForeign
= NS_IsSameSiteForeign(aChannel
, aHostURI
);
2087 nsAutoCString result
;
2088 GetCookieStringInternal(aHostURI
, isForeign
, isTrackingResource
,
2089 firstPartyStorageAccessGranted
, isSafeTopLevelNav
,
2090 isSameSiteForeign
, aHttpBound
, attrs
, result
);
2091 *aCookie
= result
.IsEmpty() ? nullptr : ToNewCString(result
);
2096 nsCookieService::SetCookieString(nsIURI
*aHostURI
,
2098 const char *aCookieHeader
,
2099 nsIChannel
*aChannel
)
2101 // The aPrompt argument is deprecated and unused. Avoid introducing new
2102 // code that uses this argument by warning if the value is non-null.
2103 MOZ_ASSERT(!aPrompt
);
2105 nsCOMPtr
<nsIConsoleService
> aConsoleService
=
2106 do_GetService("@mozilla.org/consoleservice;1");
2107 if (aConsoleService
) {
2108 aConsoleService
->LogStringMessage(
2109 u
"Non-null prompt ignored by nsCookieService.");
2112 return SetCookieStringCommon(aHostURI
, aCookieHeader
, nullptr, aChannel
,
2117 nsCookieService::SetCookieStringFromHttp(nsIURI
*aHostURI
,
2120 const char *aCookieHeader
,
2121 const char *aServerTime
,
2122 nsIChannel
*aChannel
)
2124 // The aPrompt argument is deprecated and unused. Avoid introducing new
2125 // code that uses this argument by warning if the value is non-null.
2126 MOZ_ASSERT(!aPrompt
);
2128 nsCOMPtr
<nsIConsoleService
> aConsoleService
=
2129 do_GetService("@mozilla.org/consoleservice;1");
2130 if (aConsoleService
) {
2131 aConsoleService
->LogStringMessage(
2132 u
"Non-null prompt ignored by nsCookieService.");
2135 return SetCookieStringCommon(aHostURI
, aCookieHeader
, aServerTime
, aChannel
,
2140 nsCookieService::ParseServerTime(const nsCString
&aServerTime
)
2142 // parse server local time. this is not just done here for efficiency
2143 // reasons - if there's an error parsing it, and we need to default it
2144 // to the current time, we must do it here since the current time in
2145 // SetCookieInternal() will change for each cookie processed (e.g. if the
2146 // user is prompted).
2147 PRTime tempServerTime
;
2149 PRStatus result
= PR_ParseTimeString(aServerTime
.get(), true,
2151 if (result
== PR_SUCCESS
) {
2152 serverTime
= tempServerTime
/ int64_t(PR_USEC_PER_SEC
);
2154 serverTime
= PR_Now() / PR_USEC_PER_SEC
;
2161 nsCookieService::SetCookieStringCommon(nsIURI
*aHostURI
,
2162 const char *aCookieHeader
,
2163 const char *aServerTime
,
2164 nsIChannel
*aChannel
,
2167 NS_ENSURE_ARG(aHostURI
);
2168 NS_ENSURE_ARG(aCookieHeader
);
2170 // Determine whether the request is foreign. Failure is acceptable.
2171 bool isForeign
= true;
2172 mThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aHostURI
, &isForeign
);
2174 bool isTrackingResource
= false;
2175 bool firstPartyStorageAccessGranted
= false;
2176 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
2178 isTrackingResource
= httpChannel
->GetIsTrackingResource();
2180 // Check first-party storage access even for non-tracking resources, since
2181 // we will need the result when computing the access rights for the reject
2182 // foreign cookie behavior mode.
2184 AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(httpChannel
,
2187 firstPartyStorageAccessGranted
= true;
2191 OriginAttributes attrs
;
2193 NS_GetOriginAttributes(aChannel
, attrs
);
2196 nsDependentCString
cookieString(aCookieHeader
);
2197 nsDependentCString
serverTime(aServerTime
? aServerTime
: "");
2198 SetCookieStringInternal(aHostURI
, isForeign
, isTrackingResource
,
2199 firstPartyStorageAccessGranted
, cookieString
,
2200 serverTime
, aFromHttp
, attrs
, aChannel
);
2205 nsCookieService::SetCookieStringInternal(nsIURI
*aHostURI
,
2207 bool aIsTrackingResource
,
2208 bool aFirstPartyStorageAccessGranted
,
2209 nsDependentCString
&aCookieHeader
,
2210 const nsCString
&aServerTime
,
2212 const OriginAttributes
&aOriginAttrs
,
2213 nsIChannel
*aChannel
)
2215 NS_ASSERTION(aHostURI
, "null host!");
2218 NS_WARNING("No DBState! Profile already closed?");
2222 EnsureReadComplete(true);
2224 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
2225 mDBState
= (aOriginAttrs
.mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
2227 // get the base domain for the host URI.
2228 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2229 // file:// URI's (i.e. with an empty host) are allowed, but any other
2230 // scheme must have a non-empty host. A trailing dot in the host
2232 bool requireHostMatch
;
2233 nsAutoCString baseDomain
;
2234 nsresult rv
= GetBaseDomain(mTLDService
, aHostURI
, baseDomain
, requireHostMatch
);
2235 if (NS_FAILED(rv
)) {
2236 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
2237 "couldn't get base domain from URI");
2241 nsCookieKey
key(baseDomain
, aOriginAttrs
);
2243 // check default prefs
2244 uint32_t priorCookieCount
= 0;
2245 uint32_t rejectedReason
= 0;
2246 nsAutoCString hostFromURI
;
2247 aHostURI
->GetHost(hostFromURI
);
2248 CountCookiesFromHost(hostFromURI
, &priorCookieCount
);
2249 CookieStatus cookieStatus
= CheckPrefs(mPermissionService
, mCookieBehavior
,
2251 mThirdPartyNonsecureSession
, aHostURI
,
2252 aIsForeign
, aIsTrackingResource
,
2253 aFirstPartyStorageAccessGranted
,
2254 aCookieHeader
.get(), priorCookieCount
,
2255 aOriginAttrs
, &rejectedReason
);
2257 MOZ_ASSERT_IF(rejectedReason
, cookieStatus
== STATUS_REJECTED
);
2259 // fire a notification if third party or if cookie was rejected
2260 // (but not if there was an error)
2261 switch (cookieStatus
) {
2262 case STATUS_REJECTED
:
2263 NotifyRejected(aHostURI
, aChannel
, rejectedReason
);
2265 NotifyThirdParty(aHostURI
, false, aChannel
);
2267 return; // Stop here
2268 case STATUS_REJECTED_WITH_ERROR
:
2270 case STATUS_ACCEPTED
: // Fallthrough
2271 case STATUS_ACCEPT_SESSION
:
2273 NotifyThirdParty(aHostURI
, true, aChannel
);
2280 int64_t serverTime
= ParseServerTime(aServerTime
);
2282 // process each cookie in the header
2283 while (SetCookieInternal(aHostURI
, key
, requireHostMatch
, cookieStatus
,
2284 aCookieHeader
, serverTime
, aFromHttp
, aChannel
)) {
2285 // document.cookie can only set one cookie at a time
2291 // notify observers that a cookie was rejected due to the users' prefs.
2293 nsCookieService::NotifyRejected(nsIURI
*aHostURI
, nsIChannel
* aChannel
,
2294 uint32_t aRejectedReason
)
2296 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
2298 os
->NotifyObservers(aHostURI
, "cookie-rejected", nullptr);
2301 AntiTrackingCommon::NotifyRejection(aChannel
, aRejectedReason
);
2304 // notify observers that a third-party cookie was accepted/rejected
2305 // if the cookie issuer is unknown, it defaults to "?"
2307 nsCookieService::NotifyThirdParty(nsIURI
*aHostURI
, bool aIsAccepted
, nsIChannel
*aChannel
)
2309 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
2316 if (mDBState
!= mPrivateDBState
) {
2317 // Regular (non-private) browsing
2319 topic
= "third-party-cookie-accepted";
2321 topic
= "third-party-cookie-rejected";
2326 topic
= "private-third-party-cookie-accepted";
2328 topic
= "private-third-party-cookie-rejected";
2333 // Attempt to find the host of aChannel.
2337 nsCOMPtr
<nsIURI
> channelURI
;
2338 nsresult rv
= aChannel
->GetURI(getter_AddRefs(channelURI
));
2339 if (NS_FAILED(rv
)) {
2343 nsAutoCString referringHost
;
2344 rv
= channelURI
->GetHost(referringHost
);
2345 if (NS_FAILED(rv
)) {
2349 nsAutoString referringHostUTF16
= NS_ConvertUTF8toUTF16(referringHost
);
2350 os
->NotifyObservers(aHostURI
, topic
, referringHostUTF16
.get());
2354 // This can fail for a number of reasons, in which kind we fallback to "?"
2355 os
->NotifyObservers(aHostURI
, topic
, u
"?");
2358 // notify observers that the cookie list changed. there are five possible
2359 // values for aData:
2360 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
2361 // "added" means a cookie was added. aSubject is the added cookie.
2362 // "changed" means a cookie was altered. aSubject is the new cookie.
2363 // "cleared" means the entire cookie list was cleared. aSubject is null.
2364 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
2367 nsCookieService::NotifyChanged(nsISupports
*aSubject
,
2368 const char16_t
*aData
,
2369 bool aOldCookieIsSession
,
2372 const char* topic
= mDBState
== mPrivateDBState
?
2373 "private-cookie-changed" : "cookie-changed";
2374 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
2378 // Notify for topic "private-cookie-changed" or "cookie-changed"
2379 os
->NotifyObservers(aSubject
, topic
, aData
);
2381 // Notify for topic "session-cookie-changed" to update the copy of session
2382 // cookies in session restore component.
2383 // Ignore private session cookies since they will not be restored.
2384 if (mDBState
== mPrivateDBState
) {
2387 // Filter out notifications for individual non-session cookies.
2388 if (NS_LITERAL_STRING("changed").Equals(aData
) ||
2389 NS_LITERAL_STRING("deleted").Equals(aData
) ||
2390 NS_LITERAL_STRING("added").Equals(aData
)) {
2391 nsCOMPtr
<nsICookie
> xpcCookie
= do_QueryInterface(aSubject
);
2392 MOZ_ASSERT(xpcCookie
);
2393 auto cookie
= static_cast<nsCookie
*>(xpcCookie
.get());
2394 if (!cookie
->IsSession() && !aOldCookieIsSession
) {
2398 os
->NotifyObservers(aSubject
, "session-cookie-changed", aData
);
2401 already_AddRefed
<nsIArray
>
2402 nsCookieService::CreatePurgeList(nsICookie2
* aCookie
)
2404 nsCOMPtr
<nsIMutableArray
> removedList
=
2405 do_CreateInstance(NS_ARRAY_CONTRACTID
);
2406 removedList
->AppendElement(aCookie
);
2407 return removedList
.forget();
2411 nsCookieService::CreateOrUpdatePurgeList(nsIArray
** aPurgedList
, nsICookie2
* aCookie
)
2413 if (!*aPurgedList
) {
2414 COOKIE_LOGSTRING(LogLevel::Debug
, ("Creating new purge list"));
2415 nsCOMPtr
<nsIArray
> purgedList
= CreatePurgeList(aCookie
);
2416 purgedList
.forget(aPurgedList
);
2420 nsCOMPtr
<nsIMutableArray
> purgedList
= do_QueryInterface(*aPurgedList
);
2422 COOKIE_LOGSTRING(LogLevel::Debug
, ("Updating existing purge list"));
2423 purgedList
->AppendElement(aCookie
);
2425 COOKIE_LOGSTRING(LogLevel::Debug
, ("Could not QI aPurgedList!"));
2429 /******************************************************************************
2431 * public transaction helper impl
2432 ******************************************************************************/
2435 nsCookieService::RunInTransaction(nsICookieTransactionCallback
* aCallback
)
2437 NS_ENSURE_ARG(aCallback
);
2439 NS_WARNING("No DBState! Profile already closed?");
2440 return NS_ERROR_NOT_AVAILABLE
;
2443 EnsureReadComplete(true);
2445 if (NS_WARN_IF(!mDefaultDBState
->dbConn
)) {
2446 return NS_ERROR_NOT_AVAILABLE
;
2448 mozStorageTransaction
transaction(mDefaultDBState
->dbConn
, true);
2450 if (NS_FAILED(aCallback
->Callback())) {
2451 Unused
<< transaction
.Rollback();
2452 return NS_ERROR_FAILURE
;
2457 /******************************************************************************
2459 * pref observer impl
2460 ******************************************************************************/
2463 nsCookieService::PrefChanged(nsIPrefBranch
*aPrefBranch
)
2466 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookieBehavior
, &val
)))
2467 mCookieBehavior
= (uint8_t) LIMIT(val
, 0, nsICookieService::BEHAVIOR_LAST
, 0);
2469 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxNumberOfCookies
, &val
)))
2470 mMaxNumberOfCookies
= (uint16_t) LIMIT(val
, 1, 0xFFFF, kMaxNumberOfCookies
);
2472 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookieQuotaPerHost
, &val
))) {
2473 mCookieQuotaPerHost
=
2474 (uint16_t) LIMIT(val
, 1, mMaxCookiesPerHost
- 1, kCookieQuotaPerHost
);
2477 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxCookiesPerHost
, &val
))) {
2478 mMaxCookiesPerHost
=
2479 (uint16_t) LIMIT(val
, mCookieQuotaPerHost
+ 1, 0xFFFF, kMaxCookiesPerHost
);
2482 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookiePurgeAge
, &val
))) {
2484 int64_t(LIMIT(val
, 0, INT32_MAX
, INT32_MAX
)) * PR_USEC_PER_SEC
;
2488 if (NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kPrefThirdPartySession
, &boolval
)))
2489 mThirdPartySession
= boolval
;
2491 if (NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kPrefThirdPartyNonsecureSession
, &boolval
)))
2492 mThirdPartyNonsecureSession
= boolval
;
2494 if (NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kCookieLeaveSecurityAlone
, &boolval
)))
2495 mLeaveSecureAlone
= boolval
;
2498 /******************************************************************************
2499 * nsICookieManager impl:
2501 ******************************************************************************/
2504 nsCookieService::RemoveAll()
2507 NS_WARNING("No DBState! Profile already closed?");
2508 return NS_ERROR_NOT_AVAILABLE
;
2511 EnsureReadComplete(true);
2513 RemoveAllFromMemory();
2515 // clear the cookie file
2516 if (mDBState
->dbConn
) {
2517 NS_ASSERTION(mDBState
== mDefaultDBState
, "not in default DB state");
2519 nsCOMPtr
<mozIStorageAsyncStatement
> stmt
;
2520 nsresult rv
= mDefaultDBState
->dbConn
->CreateAsyncStatement(NS_LITERAL_CSTRING(
2521 "DELETE FROM moz_cookies"), getter_AddRefs(stmt
));
2522 if (NS_SUCCEEDED(rv
)) {
2523 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
2524 rv
= stmt
->ExecuteAsync(mDefaultDBState
->removeListener
,
2525 getter_AddRefs(handle
));
2526 NS_ASSERT_SUCCESS(rv
);
2528 // Recreate the database.
2529 COOKIE_LOGSTRING(LogLevel::Debug
,
2530 ("RemoveAll(): corruption detected with rv 0x%" PRIx32
, static_cast<uint32_t>(rv
)));
2531 HandleCorruptDB(mDefaultDBState
);
2535 NotifyChanged(nullptr, u
"cleared");
2540 nsCookieService::GetEnumerator(nsISimpleEnumerator
**aEnumerator
)
2543 NS_WARNING("No DBState! Profile already closed?");
2544 return NS_ERROR_NOT_AVAILABLE
;
2547 EnsureReadComplete(true);
2549 nsCOMArray
<nsICookie
> cookieList(mDBState
->cookieCount
);
2550 for (auto iter
= mDBState
->hostTable
.Iter(); !iter
.Done(); iter
.Next()) {
2551 const nsCookieEntry::ArrayType
& cookies
= iter
.Get()->GetCookies();
2552 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
2553 cookieList
.AppendObject(cookies
[i
]);
2557 return NS_NewArrayEnumerator(aEnumerator
, cookieList
, NS_GET_IID(nsICookie2
));
2561 nsCookieService::GetSessionEnumerator(nsISimpleEnumerator
**aEnumerator
)
2564 NS_WARNING("No DBState! Profile already closed?");
2565 return NS_ERROR_NOT_AVAILABLE
;
2568 EnsureReadComplete(true);
2570 nsCOMArray
<nsICookie
> cookieList(mDBState
->cookieCount
);
2571 for (auto iter
= mDBState
->hostTable
.Iter(); !iter
.Done(); iter
.Next()) {
2572 const nsCookieEntry::ArrayType
& cookies
= iter
.Get()->GetCookies();
2573 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
2574 nsCookie
* cookie
= cookies
[i
];
2575 // Filter out non-session cookies.
2576 if (cookie
->IsSession()) {
2577 cookieList
.AppendObject(cookie
);
2582 return NS_NewArrayEnumerator(aEnumerator
, cookieList
, NS_GET_IID(nsICookie2
));
2586 nsCookieService::Add(const nsACString
&aHost
,
2587 const nsACString
&aPath
,
2588 const nsACString
&aName
,
2589 const nsACString
&aValue
,
2594 JS::HandleValue aOriginAttributes
,
2598 OriginAttributes attrs
;
2600 if (!aOriginAttributes
.isObject() ||
2601 !attrs
.Init(aCx
, aOriginAttributes
)) {
2602 return NS_ERROR_INVALID_ARG
;
2605 return AddNative(aHost
, aPath
, aName
, aValue
, aIsSecure
, aIsHttpOnly
,
2606 aIsSession
, aExpiry
, &attrs
, aSameSite
);
2609 NS_IMETHODIMP_(nsresult
)
2610 nsCookieService::AddNative(const nsACString
&aHost
,
2611 const nsACString
&aPath
,
2612 const nsACString
&aName
,
2613 const nsACString
&aValue
,
2618 OriginAttributes
* aOriginAttributes
,
2621 if (NS_WARN_IF(!aOriginAttributes
)) {
2622 return NS_ERROR_FAILURE
;
2626 NS_WARNING("No DBState! Profile already closed?");
2627 return NS_ERROR_NOT_AVAILABLE
;
2630 EnsureReadComplete(true);
2632 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
2633 mDBState
= (aOriginAttributes
->mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
2635 // first, normalize the hostname, and fail if it contains illegal characters.
2636 nsAutoCString
host(aHost
);
2637 nsresult rv
= NormalizeHost(host
);
2638 NS_ENSURE_SUCCESS(rv
, rv
);
2640 // get the base domain for the host URI.
2641 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2642 nsAutoCString baseDomain
;
2643 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2644 NS_ENSURE_SUCCESS(rv
, rv
);
2646 int64_t currentTimeInUsec
= PR_Now();
2647 nsCookieKey key
= nsCookieKey(baseDomain
, *aOriginAttributes
);
2649 RefPtr
<nsCookie
> cookie
=
2650 nsCookie::Create(aName
, aValue
, host
, aPath
,
2653 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec
),
2657 key
.mOriginAttributes
,
2660 return NS_ERROR_OUT_OF_MEMORY
;
2663 AddInternal(key
, cookie
, currentTimeInUsec
, nullptr, nullptr, true);
2669 nsCookieService::Remove(const nsACString
& aHost
, const OriginAttributes
& aAttrs
,
2670 const nsACString
& aName
, const nsACString
& aPath
,
2674 NS_WARNING("No DBState! Profile already closed?");
2675 return NS_ERROR_NOT_AVAILABLE
;
2678 EnsureReadComplete(true);
2680 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
2681 mDBState
= (aAttrs
.mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
2683 // first, normalize the hostname, and fail if it contains illegal characters.
2684 nsAutoCString
host(aHost
);
2685 nsresult rv
= NormalizeHost(host
);
2686 NS_ENSURE_SUCCESS(rv
, rv
);
2688 nsAutoCString baseDomain
;
2689 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
2690 NS_ENSURE_SUCCESS(rv
, rv
);
2692 nsListIter matchIter
;
2693 RefPtr
<nsCookie
> cookie
;
2694 if (FindCookie(nsCookieKey(baseDomain
, aAttrs
),
2696 PromiseFlatCString(aName
),
2697 PromiseFlatCString(aPath
),
2699 cookie
= matchIter
.Cookie();
2700 RemoveCookieFromList(matchIter
);
2703 // check if we need to add the host to the permissions blacklist.
2704 if (aBlocked
&& mPermissionService
) {
2705 // strip off the domain dot, if necessary
2706 if (!host
.IsEmpty() && host
.First() == '.')
2709 host
.InsertLiteral("http://", 0);
2711 nsCOMPtr
<nsIURI
> uri
;
2712 NS_NewURI(getter_AddRefs(uri
), host
);
2715 mPermissionService
->SetAccess(uri
, nsICookiePermission::ACCESS_DENY
);
2719 // Everything's done. Notify observers.
2720 NotifyChanged(cookie
, u
"deleted");
2727 nsCookieService::Remove(const nsACString
&aHost
,
2728 const nsACString
&aName
,
2729 const nsACString
&aPath
,
2731 JS::HandleValue aOriginAttributes
,
2734 OriginAttributes attrs
;
2736 if (!aOriginAttributes
.isObject() ||
2737 !attrs
.Init(aCx
, aOriginAttributes
)) {
2738 return NS_ERROR_INVALID_ARG
;
2741 return RemoveNative(aHost
, aName
, aPath
, aBlocked
, &attrs
);
2744 NS_IMETHODIMP_(nsresult
)
2745 nsCookieService::RemoveNative(const nsACString
&aHost
,
2746 const nsACString
&aName
,
2747 const nsACString
&aPath
,
2749 OriginAttributes
* aOriginAttributes
)
2751 if (NS_WARN_IF(!aOriginAttributes
)) {
2752 return NS_ERROR_FAILURE
;
2755 nsresult rv
= Remove(aHost
, *aOriginAttributes
, aName
, aPath
, aBlocked
);
2756 if (NS_WARN_IF(NS_FAILED(rv
))) {
2763 /******************************************************************************
2764 * nsCookieService impl:
2765 * private file I/O functions
2766 ******************************************************************************/
2768 // Extract data from a single result row and create an nsCookie.
2769 mozilla::UniquePtr
<ConstCookie
>
2770 nsCookieService::GetCookieFromRow(mozIStorageStatement
*aRow
,
2771 const OriginAttributes
&aOriginAttributes
)
2773 // Skip reading 'baseDomain' -- up to the caller.
2774 nsCString name
, value
, host
, path
;
2775 DebugOnly
<nsresult
> rv
= aRow
->GetUTF8String(IDX_NAME
, name
);
2776 NS_ASSERT_SUCCESS(rv
);
2777 rv
= aRow
->GetUTF8String(IDX_VALUE
, value
);
2778 NS_ASSERT_SUCCESS(rv
);
2779 rv
= aRow
->GetUTF8String(IDX_HOST
, host
);
2780 NS_ASSERT_SUCCESS(rv
);
2781 rv
= aRow
->GetUTF8String(IDX_PATH
, path
);
2782 NS_ASSERT_SUCCESS(rv
);
2784 int64_t expiry
= aRow
->AsInt64(IDX_EXPIRY
);
2785 int64_t lastAccessed
= aRow
->AsInt64(IDX_LAST_ACCESSED
);
2786 int64_t creationTime
= aRow
->AsInt64(IDX_CREATION_TIME
);
2787 bool isSecure
= 0 != aRow
->AsInt32(IDX_SECURE
);
2788 bool isHttpOnly
= 0 != aRow
->AsInt32(IDX_HTTPONLY
);
2789 int32_t sameSite
= aRow
->AsInt32(IDX_SAME_SITE
);
2791 // Create a new constCookie and assign the data.
2792 return mozilla::MakeUnique
<ConstCookie
>(name
,
2806 nsCookieService::EnsureReadComplete(bool aInitDBConn
)
2808 MOZ_ASSERT(NS_IsMainThread());
2810 bool isAccumulated
= false;
2812 if (!mInitializedDBStates
) {
2813 TimeStamp startBlockTime
= TimeStamp::Now();
2814 MonitorAutoLock
lock(mMonitor
);
2816 while (!mInitializedDBStates
) {
2819 Telemetry::AccumulateTimeDelta(Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2
,
2821 Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS
, 0);
2822 isAccumulated
= true;
2823 } else if (!mEndInitDBConn
.IsNull()) {
2824 // We didn't block main thread, and here comes the first cookie request.
2825 // Collect how close we're going to block main thread.
2826 Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS
,
2827 (TimeStamp::Now() - mEndInitDBConn
).ToMilliseconds());
2828 // Nullify the timestamp so wo don't accumulate this telemetry probe again.
2829 mEndInitDBConn
= TimeStamp();
2830 isAccumulated
= true;
2831 } else if (!mInitializedDBConn
&& aInitDBConn
) {
2832 // A request comes while we finished cookie thread task and InitDBConn is
2833 // on the way from cookie thread to main thread. We're very close to block
2835 Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS
, 0);
2836 isAccumulated
= true;
2839 if (!mInitializedDBConn
&& aInitDBConn
&& mDefaultDBState
) {
2841 if (isAccumulated
) {
2842 // Nullify the timestamp so wo don't accumulate this telemetry probe again.
2843 mEndInitDBConn
= TimeStamp();
2849 nsCookieService::Read()
2851 MOZ_ASSERT(NS_GetCurrentThread() == mThread
);
2853 // Set up a statement to delete any rows with a nullptr 'baseDomain'
2854 // column. This takes care of any cookies set by browsers that don't
2855 // understand the 'baseDomain' column, where the database schema version
2856 // is from one that does. (This would occur when downgrading.)
2857 nsresult rv
= mDefaultDBState
->syncConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
2858 "DELETE FROM moz_cookies WHERE baseDomain ISNULL"));
2859 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
2861 // Read in the data synchronously.
2862 // see IDX_NAME, etc. for parameter indexes
2863 nsCOMPtr
<mozIStorageStatement
> stmt
;
2864 rv
= mDefaultDBState
->syncConn
->CreateStatement(NS_LITERAL_CSTRING(
2876 "originAttributes, "
2879 "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt
));
2881 NS_ENSURE_SUCCESS(rv
, RESULT_RETRY
);
2883 if (NS_WARN_IF(!mReadArray
.IsEmpty())) {
2886 mReadArray
.SetCapacity(kMaxNumberOfCookies
);
2888 nsCString baseDomain
, name
, value
, host
, path
;
2891 rv
= stmt
->ExecuteStep(&hasResult
);
2892 if (NS_WARN_IF(NS_FAILED(rv
))) {
2894 return RESULT_RETRY
;
2900 // Make sure we haven't already read the data.
2901 stmt
->GetUTF8String(IDX_BASE_DOMAIN
, baseDomain
);
2903 nsAutoCString suffix
;
2904 OriginAttributes attrs
;
2905 stmt
->GetUTF8String(IDX_ORIGIN_ATTRIBUTES
, suffix
);
2906 // If PopulateFromSuffix failed we just ignore the OA attributes
2907 // that we don't support
2908 Unused
<< attrs
.PopulateFromSuffix(suffix
);
2910 nsCookieKey
key(baseDomain
, attrs
);
2911 CookieDomainTuple
* tuple
= mReadArray
.AppendElement();
2912 tuple
->key
= std::move(key
);
2913 tuple
->cookie
= GetCookieFromRow(stmt
, attrs
);
2916 COOKIE_LOGSTRING(LogLevel::Debug
, ("Read(): %zu cookies read", mReadArray
.Length()));
2922 nsCookieService::ImportCookies(nsIFile
*aCookieFile
)
2925 NS_WARNING("No DBState! Profile already closed?");
2926 return NS_ERROR_NOT_AVAILABLE
;
2929 EnsureReadComplete(true);
2931 // Make sure we're in the default DB state. We don't want people importing
2932 // cookies into a private browsing session!
2933 if (mDBState
!= mDefaultDBState
) {
2934 NS_WARNING("Trying to import cookies in a private browsing session!");
2935 return NS_ERROR_NOT_AVAILABLE
;
2939 nsCOMPtr
<nsIInputStream
> fileInputStream
;
2940 rv
= NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream
), aCookieFile
);
2941 if (NS_FAILED(rv
)) return rv
;
2943 nsCOMPtr
<nsILineInputStream
> lineInputStream
= do_QueryInterface(fileInputStream
, &rv
);
2944 if (NS_FAILED(rv
)) return rv
;
2946 static const char kTrue
[] = "TRUE";
2948 nsAutoCString buffer
, baseDomain
;
2950 int32_t hostIndex
, isDomainIndex
, pathIndex
, secureIndex
, expiresIndex
, nameIndex
, cookieIndex
;
2953 bool isDomain
, isHttpOnly
= false;
2954 uint32_t originalCookieCount
= mDefaultDBState
->cookieCount
;
2956 int64_t currentTimeInUsec
= PR_Now();
2957 int64_t currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
2958 // we use lastAccessedCounter to keep cookies in recently-used order,
2959 // so we start by initializing to currentTime (somewhat arbitrary)
2960 int64_t lastAccessedCounter
= currentTimeInUsec
;
2964 * host \t isDomain \t path \t secure \t expires \t name \t cookie
2966 * if this format isn't respected we move onto the next line in the file.
2967 * isDomain is "TRUE" or "FALSE" (default to "FALSE")
2968 * isSecure is "TRUE" or "FALSE" (default to "TRUE")
2969 * expires is a int64_t integer
2970 * note 1: cookie can contain tabs.
2971 * note 2: cookies will be stored in order of lastAccessed time:
2972 * most-recently used come first; least-recently-used come last.
2976 * ...but due to bug 178933, we hide HttpOnly cookies from older code
2977 * in a comment, so they don't expose HttpOnly cookies to JS.
2979 * The format for HttpOnly cookies is
2981 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
2985 // We will likely be adding a bunch of cookies to the DB, so we use async
2986 // batching with storage to make this super fast.
2987 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray
;
2988 if (originalCookieCount
== 0 && mDefaultDBState
->dbConn
) {
2989 mDefaultDBState
->stmtInsert
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
2992 while (isMore
&& NS_SUCCEEDED(lineInputStream
->ReadLine(buffer
, &isMore
))) {
2993 if (StringBeginsWith(buffer
, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX
))) {
2995 hostIndex
= sizeof(HTTP_ONLY_PREFIX
) - 1;
2996 } else if (buffer
.IsEmpty() || buffer
.First() == '#') {
3003 // this is a cheap, cheesy way of parsing a tab-delimited line into
3004 // string indexes, which can be lopped off into substrings. just for
3005 // purposes of obfuscation, it also checks that each token was found.
3006 // todo: use iterators?
3007 if ((isDomainIndex
= buffer
.FindChar('\t', hostIndex
) + 1) == 0 ||
3008 (pathIndex
= buffer
.FindChar('\t', isDomainIndex
) + 1) == 0 ||
3009 (secureIndex
= buffer
.FindChar('\t', pathIndex
) + 1) == 0 ||
3010 (expiresIndex
= buffer
.FindChar('\t', secureIndex
) + 1) == 0 ||
3011 (nameIndex
= buffer
.FindChar('\t', expiresIndex
) + 1) == 0 ||
3012 (cookieIndex
= buffer
.FindChar('\t', nameIndex
) + 1) == 0) {
3016 // check the expirytime first - if it's expired, ignore
3017 // nullstomp the trailing tab, to avoid copying the string
3018 auto iter
= buffer
.BeginWriting() + nameIndex
- 1;
3020 numInts
= PR_sscanf(buffer
.get() + expiresIndex
, "%lld", &expires
);
3021 if (numInts
!= 1 || expires
< currentTime
) {
3025 isDomain
= Substring(buffer
, isDomainIndex
, pathIndex
- isDomainIndex
- 1).EqualsLiteral(kTrue
);
3026 const nsACString
& host
= Substring(buffer
, hostIndex
, isDomainIndex
- hostIndex
- 1);
3027 // check for bad legacy cookies (domain not starting with a dot, or containing a port),
3029 if ((isDomain
&& !host
.IsEmpty() && host
.First() != '.') ||
3030 host
.Contains(':')) {
3034 // compute the baseDomain from the host
3035 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
3039 // pre-existing cookies have inIsolatedMozBrowser=false set by default
3040 // constructor of OriginAttributes().
3041 nsCookieKey key
= DEFAULT_APP_KEY(baseDomain
);
3043 // Create a new nsCookie and assign the data. We don't know the cookie
3044 // creation time, so just use the current time to generate a unique one.
3045 RefPtr
<nsCookie
> newCookie
=
3046 nsCookie::Create(Substring(buffer
, nameIndex
, cookieIndex
- nameIndex
- 1),
3047 Substring(buffer
, cookieIndex
, buffer
.Length() - cookieIndex
),
3049 Substring(buffer
, pathIndex
, secureIndex
- pathIndex
- 1),
3051 lastAccessedCounter
,
3052 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec
),
3054 Substring(buffer
, secureIndex
, expiresIndex
- secureIndex
- 1).EqualsLiteral(kTrue
),
3056 key
.mOriginAttributes
,
3057 nsICookie2::SAMESITE_UNSET
);
3059 return NS_ERROR_OUT_OF_MEMORY
;
3062 // trick: preserve the most-recently-used cookie ordering,
3063 // by successively decrementing the lastAccessed time
3064 lastAccessedCounter
--;
3066 if (originalCookieCount
== 0) {
3067 AddCookieToList(key
, newCookie
, mDefaultDBState
, paramsArray
);
3070 AddInternal(key
, newCookie
, currentTimeInUsec
,
3071 nullptr, nullptr, true);
3075 // If we need to write to disk, do so now.
3078 paramsArray
->GetLength(&length
);
3080 rv
= mDefaultDBState
->stmtInsert
->BindParameters(paramsArray
);
3081 NS_ASSERT_SUCCESS(rv
);
3082 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
3083 rv
= mDefaultDBState
->stmtInsert
->ExecuteAsync(
3084 mDefaultDBState
->insertListener
, getter_AddRefs(handle
));
3085 NS_ASSERT_SUCCESS(rv
);
3089 COOKIE_LOGSTRING(LogLevel::Debug
, ("ImportCookies(): %" PRIu32
" cookies imported",
3090 mDefaultDBState
->cookieCount
));
3095 /******************************************************************************
3096 * nsCookieService impl:
3097 * private GetCookie/SetCookie helpers
3098 ******************************************************************************/
3100 // helper function for GetCookieList
3101 static inline bool ispathdelimiter(char c
) { return c
== '/' || c
== '?' || c
== '#' || c
== ';'; }
3104 nsCookieService::DomainMatches(nsCookie
* aCookie
,
3105 const nsACString
& aHost
)
3107 // first, check for an exact host or domain cookie match, e.g. "google.com"
3108 // or ".google.com"; second a subdomain match, e.g.
3109 // host = "mail.google.com", cookie domain = ".google.com".
3110 return aCookie
->RawHost() == aHost
||
3111 (aCookie
->IsDomain() && StringEndsWith(aHost
, aCookie
->Host()));
3115 nsCookieService::IsSameSiteEnabled()
3117 static bool prefInitialized
= false;
3118 if (!prefInitialized
) {
3119 Preferences::AddBoolVarCache(&sSameSiteEnabled
,
3120 "network.cookie.same-site.enabled", false);
3121 prefInitialized
= true;
3123 return sSameSiteEnabled
;
3127 nsCookieService::PathMatches(nsCookie
* aCookie
,
3128 const nsACString
& aPath
)
3130 // calculate cookie path length, excluding trailing '/'
3131 uint32_t cookiePathLen
= aCookie
->Path().Length();
3132 if (cookiePathLen
> 0 && aCookie
->Path().Last() == '/')
3135 // if the given path is shorter than the cookie path, it doesn't match
3136 // if the given path doesn't start with the cookie path, it doesn't match.
3137 if (!StringBeginsWith(aPath
, Substring(aCookie
->Path(), 0, cookiePathLen
)))
3140 // if the given path is longer than the cookie path, and the first char after
3141 // the cookie path is not a path delimiter, it doesn't match.
3142 if (aPath
.Length() > cookiePathLen
&&
3143 !ispathdelimiter(aPath
.CharAt(cookiePathLen
))) {
3145 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
3146 * '/' is the "standard" case; the '?' test allows a site at host/abc?def
3147 * to receive a cookie that has a path attribute of abc. this seems
3148 * strange but at least one major site (citibank, bug 156725) depends
3149 * on it. The test for # and ; are put in to proactively avoid problems
3150 * with other sites - these are the only other chars allowed in the path.
3155 // either the paths match exactly, or the cookie path is a prefix of
3161 nsCookieService::GetCookiesForURI(nsIURI
*aHostURI
,
3163 bool aIsTrackingResource
,
3164 bool aFirstPartyStorageAccessGranted
,
3165 bool aIsSafeTopLevelNav
,
3166 bool aIsSameSiteForeign
,
3168 const OriginAttributes
& aOriginAttrs
,
3169 nsTArray
<nsCookie
*>& aCookieList
)
3171 NS_ASSERTION(aHostURI
, "null host!");
3174 NS_WARNING("No DBState! Profile already closed?");
3178 EnsureReadComplete(true);
3180 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
3181 mDBState
= (aOriginAttrs
.mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
3183 // get the base domain, host, and path from the URI.
3184 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
3185 // file:// URI's (i.e. with an empty host) are allowed, but any other
3186 // scheme must have a non-empty host. A trailing dot in the host
3188 bool requireHostMatch
;
3189 nsAutoCString baseDomain
, hostFromURI
, pathFromURI
;
3190 nsresult rv
= GetBaseDomain(mTLDService
, aHostURI
, baseDomain
, requireHostMatch
);
3191 if (NS_SUCCEEDED(rv
))
3192 rv
= aHostURI
->GetAsciiHost(hostFromURI
);
3193 if (NS_SUCCEEDED(rv
))
3194 rv
= aHostURI
->GetPathQueryRef(pathFromURI
);
3195 if (NS_FAILED(rv
)) {
3196 COOKIE_LOGFAILURE(GET_COOKIE
, aHostURI
, nullptr, "invalid host/path from URI");
3200 // check default prefs
3201 uint32_t priorCookieCount
= 0;
3202 CountCookiesFromHost(hostFromURI
, &priorCookieCount
);
3203 CookieStatus cookieStatus
= CheckPrefs(mPermissionService
, mCookieBehavior
,
3205 mThirdPartyNonsecureSession
, aHostURI
,
3206 aIsForeign
, aIsTrackingResource
,
3207 aFirstPartyStorageAccessGranted
,
3208 nullptr, priorCookieCount
,
3209 aOriginAttrs
, nullptr);
3211 // for GetCookie(), we don't fire rejection notifications.
3212 switch (cookieStatus
) {
3213 case STATUS_REJECTED
:
3214 case STATUS_REJECTED_WITH_ERROR
:
3220 // Note: The following permissions logic is mirrored in
3221 // extensions::MatchPattern::MatchesCookie.
3222 // If it changes, please update that function, or file a bug for someone
3225 // check if aHostURI is using an https secure protocol.
3226 // if it isn't, then we can't send a secure cookie over the connection.
3227 // if SchemeIs fails, assume an insecure connection, to be on the safe side
3229 if (NS_FAILED(aHostURI
->SchemeIs("https", &isSecure
))) {
3234 int64_t currentTimeInUsec
= PR_Now();
3235 int64_t currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
3238 nsCookieKey
key(baseDomain
, aOriginAttrs
);
3240 // perform the hash lookup
3241 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(key
);
3245 // iterate the cookies!
3246 const nsCookieEntry::ArrayType
&cookies
= entry
->GetCookies();
3247 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
3248 cookie
= cookies
[i
];
3250 // check the host, since the base domain lookup is conservative.
3251 if (!DomainMatches(cookie
, hostFromURI
))
3254 // if the cookie is secure and the host scheme isn't, we can't send it
3255 if (cookie
->IsSecure() && !isSecure
)
3258 int32_t sameSiteAttr
= 0;
3259 cookie
->GetSameSite(&sameSiteAttr
);
3260 if (aIsSameSiteForeign
&& IsSameSiteEnabled()) {
3261 // it if's a cross origin request and the cookie is same site only (strict)
3263 if (sameSiteAttr
== nsICookie2::SAMESITE_STRICT
) {
3266 // if it's a cross origin request, the cookie is same site lax, but it's not
3267 // a top-level navigation, don't send it
3268 if (sameSiteAttr
== nsICookie2::SAMESITE_LAX
&& !aIsSafeTopLevelNav
) {
3273 // if the cookie is httpOnly and it's not going directly to the HTTP
3274 // connection, don't send it
3275 if (cookie
->IsHttpOnly() && !aHttpBound
)
3278 // if the nsIURI path doesn't match the cookie path, don't send it back
3279 if (!PathMatches(cookie
, pathFromURI
))
3282 // check if the cookie has expired
3283 if (cookie
->Expiry() <= currentTime
) {
3287 // all checks passed - add to list and check if lastAccessed stamp needs updating
3288 aCookieList
.AppendElement(cookie
);
3289 if (cookie
->IsStale()) {
3294 int32_t count
= aCookieList
.Length();
3298 // update lastAccessed timestamps. we only do this if the timestamp is stale
3299 // by a certain amount, to avoid thrashing the db during pageload.
3301 // Create an array of parameters to bind to our update statement. Batching
3302 // is OK here since we're updating cookies with no interleaved operations.
3303 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray
;
3304 mozIStorageAsyncStatement
* stmt
= mDBState
->stmtUpdate
;
3305 if (mDBState
->dbConn
) {
3306 stmt
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
3309 for (int32_t i
= 0; i
< count
; ++i
) {
3310 cookie
= aCookieList
.ElementAt(i
);
3312 if (cookie
->IsStale()) {
3313 UpdateCookieInList(cookie
, currentTimeInUsec
, paramsArray
);
3316 // Update the database now if necessary.
3319 paramsArray
->GetLength(&length
);
3321 DebugOnly
<nsresult
> rv
= stmt
->BindParameters(paramsArray
);
3322 NS_ASSERT_SUCCESS(rv
);
3323 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
3324 rv
= stmt
->ExecuteAsync(mDBState
->updateListener
,
3325 getter_AddRefs(handle
));
3326 NS_ASSERT_SUCCESS(rv
);
3331 // return cookies in order of path length; longest to shortest.
3332 // this is required per RFC2109. if cookies match in length,
3333 // then sort by creation time (see bug 236772).
3334 aCookieList
.Sort(CompareCookiesForSending());
3338 nsCookieService::GetCookieStringInternal(nsIURI
*aHostURI
,
3340 bool aIsTrackingResource
,
3341 bool aFirstPartyStorageAccessGranted
,
3342 bool aIsSafeTopLevelNav
,
3343 bool aIsSameSiteForeign
,
3345 const OriginAttributes
& aOriginAttrs
,
3346 nsCString
&aCookieString
)
3348 AutoTArray
<nsCookie
*, 8> foundCookieList
;
3349 GetCookiesForURI(aHostURI
, aIsForeign
, aIsTrackingResource
,
3350 aFirstPartyStorageAccessGranted
, aIsSafeTopLevelNav
,
3351 aIsSameSiteForeign
, aHttpBound
, aOriginAttrs
,
3355 for (uint32_t i
= 0; i
< foundCookieList
.Length(); ++i
) {
3356 cookie
= foundCookieList
.ElementAt(i
);
3358 // check if we have anything to write
3359 if (!cookie
->Name().IsEmpty() || !cookie
->Value().IsEmpty()) {
3360 // if we've already added a cookie to the return list, append a "; " so
3361 // that subsequent cookies are delimited in the final list.
3362 if (!aCookieString
.IsEmpty()) {
3363 aCookieString
.AppendLiteral("; ");
3366 if (!cookie
->Name().IsEmpty()) {
3367 // we have a name and value - write both
3368 aCookieString
+= cookie
->Name() + NS_LITERAL_CSTRING("=") + cookie
->Value();
3371 aCookieString
+= cookie
->Value();
3376 if (!aCookieString
.IsEmpty())
3377 COOKIE_LOGSUCCESS(GET_COOKIE
, aHostURI
, aCookieString
, nullptr, false);
3380 // processes a single cookie, and returns true if there are more cookies
3383 nsCookieService::CanSetCookie(nsIURI
* aHostURI
,
3384 const nsCookieKey
& aKey
,
3385 nsCookieAttributes
& aCookieAttributes
,
3386 bool aRequireHostMatch
,
3387 CookieStatus aStatus
,
3388 nsDependentCString
& aCookieHeader
,
3389 int64_t aServerTime
,
3391 nsIChannel
* aChannel
,
3392 bool aLeaveSecureAlone
,
3394 mozIThirdPartyUtil
* aThirdPartyUtil
)
3396 NS_ASSERTION(aHostURI
, "null host!");
3400 // init expiryTime such that session cookies won't prematurely expire
3401 aCookieAttributes
.expiryTime
= INT64_MAX
;
3403 // aCookieHeader is an in/out param to point to the next cookie, if
3404 // there is one. Save the present value for logging purposes
3405 nsDependentCString
savedCookieHeader(aCookieHeader
);
3407 // newCookie says whether there are multiple cookies in the header;
3408 // so we can handle them separately.
3409 bool newCookie
= ParseAttributes(aCookieHeader
, aCookieAttributes
);
3411 // Collect telemetry on how often secure cookies are set from non-secure
3412 // origins, and vice-versa.
3414 // 0 = nonsecure and "http:"
3415 // 1 = nonsecure and "https:"
3416 // 2 = secure and "http:"
3417 // 3 = secure and "https:"
3419 nsresult rv
= aHostURI
->SchemeIs("https", &isHTTPS
);
3420 if (NS_SUCCEEDED(rv
)) {
3421 Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY
,
3422 ((aCookieAttributes
.isSecure
)? 0x02 : 0x00) |
3423 ((isHTTPS
)? 0x01 : 0x00));
3425 // Collect telemetry on how often are first- and third-party cookies set
3426 // from HTTPS origins:
3428 // 0 (000) = first-party and "http:"
3429 // 1 (001) = first-party and "http:" with bogus Secure cookie flag?!
3430 // 2 (010) = first-party and "https:"
3431 // 3 (011) = first-party and "https:" with Secure cookie flag
3432 // 4 (100) = third-party and "http:"
3433 // 5 (101) = third-party and "http:" with bogus Secure cookie flag?!
3434 // 6 (110) = third-party and "https:"
3435 // 7 (111) = third-party and "https:" with Secure cookie flag
3436 if (aThirdPartyUtil
) {
3437 bool isThirdParty
= true;
3438 aThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aHostURI
, &isThirdParty
);
3439 Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_HTTPS
,
3440 (isThirdParty
? 0x04 : 0x00) |
3441 (isHTTPS
? 0x02 : 0x00) |
3442 (aCookieAttributes
.isSecure
? 0x01 : 0x00));
3446 int64_t currentTimeInUsec
= PR_Now();
3448 // calculate expiry time of cookie.
3449 aCookieAttributes
.isSession
= GetExpiry(aCookieAttributes
, aServerTime
,
3450 currentTimeInUsec
/ PR_USEC_PER_SEC
);
3451 if (aStatus
== STATUS_ACCEPT_SESSION
) {
3452 // force lifetime to session. note that the expiration time, if set above,
3453 // will still apply.
3454 aCookieAttributes
.isSession
= true;
3457 // reject cookie if it's over the size limit, per RFC2109
3458 if ((aCookieAttributes
.name
.Length() + aCookieAttributes
.value
.Length()) > kMaxBytesPerCookie
) {
3459 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie too big (> 4kb)");
3463 const char illegalNameCharacters
[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
3464 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
3465 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
3466 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
3467 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
3469 if (aCookieAttributes
.name
.FindCharInSet(illegalNameCharacters
, 0) != -1) {
3470 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "invalid name character");
3474 // domain & path checks
3475 if (!CheckDomain(aCookieAttributes
, aHostURI
, aKey
.mBaseDomain
, aRequireHostMatch
)) {
3476 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the domain tests");
3479 if (!CheckPath(aCookieAttributes
, aHostURI
)) {
3480 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the path tests");
3483 // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
3484 if (!CheckPrefixes(aCookieAttributes
, isHTTPS
)) {
3485 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the prefix tests");
3489 // reject cookie if value contains an RFC 6265 disallowed character - see
3490 // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
3491 // NOTE: this is not the full set of characters disallowed by 6265 - notably
3492 // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is
3493 // for parity with Chrome. This only applies to cookies set via the Set-Cookie
3494 // header, as document.cookie is defined to be UTF-8. Hooray for
3495 // symmetry!</sarcasm>
3496 const char illegalCharacters
[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
3497 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
3498 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
3499 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
3500 0x1E, 0x1F, 0x3B, 0x00 };
3501 if (aFromHttp
&& (aCookieAttributes
.value
.FindCharInSet(illegalCharacters
, 0) != -1)) {
3502 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "invalid value character");
3506 // if the new cookie is httponly, make sure we're not coming from script
3507 if (!aFromHttp
&& aCookieAttributes
.isHttpOnly
) {
3508 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
3509 "cookie is httponly; coming from script");
3513 bool isSecure
= true;
3515 aHostURI
->SchemeIs("https", &isSecure
);
3518 // If the new cookie is non-https and wants to set secure flag,
3519 // browser have to ignore this new cookie.
3520 // (draft-ietf-httpbis-cookie-alone section 3.1)
3521 if (aLeaveSecureAlone
&& aCookieAttributes
.isSecure
&& !isSecure
) {
3522 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3523 "non-https cookie can't set secure flag");
3524 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3525 BLOCKED_SECURE_SET_FROM_HTTP
);
3529 // If the new cookie is same-site but in a cross site context,
3530 // browser must ignore the cookie.
3531 if ((aCookieAttributes
.sameSite
!= nsICookie2::SAMESITE_UNSET
) &&
3533 IsSameSiteEnabled()) {
3535 // Do not treat loads triggered by web extensions as foreign
3536 bool addonAllowsLoad
= false;
3538 nsCOMPtr
<nsIURI
> channelURI
;
3539 NS_GetFinalChannelURI(aChannel
, getter_AddRefs(channelURI
));
3540 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->GetLoadInfo();
3541 addonAllowsLoad
= loadInfo
&&
3542 BasePrincipal::Cast(loadInfo
->TriggeringPrincipal())->
3543 AddonAllowsLoad(channelURI
);
3546 if (!addonAllowsLoad
) {
3547 bool isThirdParty
= false;
3548 nsresult rv
= aThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aHostURI
, &isThirdParty
);
3549 if (NS_FAILED(rv
) || isThirdParty
) {
3550 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
,
3551 "failed the samesite tests");
3561 // processes a single cookie, and returns true if there are more cookies
3564 nsCookieService::SetCookieInternal(nsIURI
*aHostURI
,
3565 const mozilla::net::nsCookieKey
&aKey
,
3566 bool aRequireHostMatch
,
3567 CookieStatus aStatus
,
3568 nsDependentCString
&aCookieHeader
,
3569 int64_t aServerTime
,
3571 nsIChannel
*aChannel
)
3573 NS_ASSERTION(aHostURI
, "null host!");
3574 bool canSetCookie
= false;
3575 nsDependentCString
savedCookieHeader(aCookieHeader
);
3576 nsCookieAttributes cookieAttributes
;
3577 bool newCookie
= CanSetCookie(aHostURI
, aKey
, cookieAttributes
, aRequireHostMatch
,
3578 aStatus
, aCookieHeader
, aServerTime
, aFromHttp
,
3579 aChannel
, mLeaveSecureAlone
, canSetCookie
,
3582 if (!canSetCookie
) {
3586 int64_t currentTimeInUsec
= PR_Now();
3587 // create a new nsCookie and copy attributes
3588 RefPtr
<nsCookie
> cookie
=
3589 nsCookie::Create(cookieAttributes
.name
,
3590 cookieAttributes
.value
,
3591 cookieAttributes
.host
,
3592 cookieAttributes
.path
,
3593 cookieAttributes
.expiryTime
,
3595 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec
),
3596 cookieAttributes
.isSession
,
3597 cookieAttributes
.isSecure
,
3598 cookieAttributes
.isHttpOnly
,
3599 aKey
.mOriginAttributes
,
3600 cookieAttributes
.sameSite
);
3604 // check permissions from site permission list, or ask the user,
3605 // to determine if we can set the cookie
3606 if (mPermissionService
) {
3608 mPermissionService
->CanSetCookie(aHostURI
,
3610 static_cast<nsICookie2
*>(static_cast<nsCookie
*>(cookie
)),
3611 &cookieAttributes
.isSession
,
3612 &cookieAttributes
.expiryTime
,
3615 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie rejected by permission manager");
3616 NotifyRejected(aHostURI
, aChannel
,
3617 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
);
3621 // update isSession and expiry attributes, in case they changed
3622 cookie
->SetIsSession(cookieAttributes
.isSession
);
3623 cookie
->SetExpiry(cookieAttributes
.expiryTime
);
3626 // add the cookie to the list. AddInternal() takes care of logging.
3627 // we get the current time again here, since it may have changed during prompting
3628 AddInternal(aKey
, cookie
, PR_Now(), aHostURI
, savedCookieHeader
.get(),
3633 // this is a backend function for adding a cookie to the list, via SetCookie.
3634 // also used in the cookie manager, for profile migration from IE.
3635 // it either replaces an existing cookie; or adds the cookie to the hashtable,
3636 // and deletes a cookie (if maximum number of cookies has been
3637 // reached). also performs list maintenance by removing expired cookies.
3639 nsCookieService::AddInternal(const nsCookieKey
&aKey
,
3641 int64_t aCurrentTimeInUsec
,
3643 const char *aCookieHeader
,
3646 MOZ_ASSERT(mInitializedDBStates
);
3647 MOZ_ASSERT(mInitializedDBConn
);
3649 int64_t currentTime
= aCurrentTimeInUsec
/ PR_USEC_PER_SEC
;
3651 nsListIter exactIter
;
3652 bool foundCookie
= false;
3653 foundCookie
= FindCookie(aKey
, aCookie
->Host(),
3654 aCookie
->Name(), aCookie
->Path(), exactIter
);
3655 bool foundSecureExact
= foundCookie
&& exactIter
.Cookie()->IsSecure();
3656 bool isSecure
= true;
3657 if (aHostURI
&& NS_FAILED(aHostURI
->SchemeIs("https", &isSecure
))) {
3660 bool oldCookieIsSession
= false;
3661 if (mLeaveSecureAlone
) {
3662 // Step1, call FindSecureCookie(). FindSecureCookie() would
3663 // find the existing cookie with the security flag and has
3664 // the same name, host and path of the new cookie, if there is any.
3665 // Step2, Confirm new cookie's security setting. If any targeted
3666 // cookie had been found in Step1, then confirm whether the
3667 // new cookie could modify it. If the new created cookie’s
3668 // "secure-only-flag" is not set, and the "scheme" component
3669 // of the "request-uri" does not denote a "secure" protocol,
3670 // then ignore the new cookie.
3671 // (draft-ietf-httpbis-cookie-alone section 3.2)
3672 if (!aCookie
->IsSecure()
3673 && (foundSecureExact
|| FindSecureCookie(aKey
, aCookie
))) {
3675 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3676 "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
3677 if (foundSecureExact
) {
3678 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3679 BLOCKED_DOWNGRADE_SECURE_EXACT
);
3681 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3682 BLOCKED_DOWNGRADE_SECURE_INEXACT
);
3686 // A secure site is allowed to downgrade a secure cookie
3687 // but we want to measure anyway.
3688 if (foundSecureExact
) {
3689 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3690 DOWNGRADE_SECURE_FROM_SECURE_EXACT
);
3692 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3693 DOWNGRADE_SECURE_FROM_SECURE_INEXACT
);
3698 RefPtr
<nsCookie
> oldCookie
;
3699 nsCOMPtr
<nsIArray
> purgedList
;
3701 oldCookie
= exactIter
.Cookie();
3702 oldCookieIsSession
= oldCookie
->IsSession();
3704 // Check if the old cookie is stale (i.e. has already expired). If so, we
3705 // need to be careful about the semantics of removing it and adding the new
3706 // cookie: we want the behavior wrt adding the new cookie to be the same as
3707 // if it didn't exist, but we still want to fire a removal notification.
3708 if (oldCookie
->Expiry() <= currentTime
) {
3709 if (aCookie
->Expiry() <= currentTime
) {
3710 // The new cookie has expired and the old one is stale. Nothing to do.
3711 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3712 "cookie has already expired");
3716 // Remove the stale cookie. We save notification for later, once all list
3717 // modifications are complete.
3718 RemoveCookieFromList(exactIter
);
3719 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3720 "stale cookie was purged");
3721 purgedList
= CreatePurgeList(oldCookie
);
3723 // We've done all we need to wrt removing and notifying the stale cookie.
3724 // From here on out, we pretend pretend it didn't exist, so that we
3725 // preserve expected notification semantics when adding the new cookie.
3726 foundCookie
= false;
3729 // If the old cookie is httponly, make sure we're not coming from script.
3730 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
3731 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3732 "previously stored cookie is httponly; coming from script");
3736 // If the new cookie has the same value, expiry date, isSecure, isSession,
3737 // isHttpOnly and sameSite flags then we can just keep the old one.
3738 // Only if any of these differ we would want to override the cookie.
3739 if (oldCookie
->Value().Equals(aCookie
->Value()) &&
3740 oldCookie
->Expiry() == aCookie
->Expiry() &&
3741 oldCookie
->IsSecure() == aCookie
->IsSecure() &&
3742 oldCookie
->IsSession() == aCookie
->IsSession() &&
3743 oldCookie
->IsHttpOnly() == aCookie
->IsHttpOnly() &&
3744 oldCookie
->SameSite() == aCookie
->SameSite() &&
3745 // We don't want to perform this optimization if the cookie is
3746 // considered stale, since in this case we would need to update the
3748 !oldCookie
->IsStale()) {
3749 // Update the last access time on the old cookie.
3750 oldCookie
->SetLastAccessed(aCookie
->LastAccessed());
3751 UpdateCookieOldestTime(mDBState
, oldCookie
);
3755 // Remove the old cookie.
3756 RemoveCookieFromList(exactIter
);
3758 // If the new cookie has expired -- i.e. the intent was simply to delete
3759 // the old cookie -- then we're done.
3760 if (aCookie
->Expiry() <= currentTime
) {
3761 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3762 "previously stored cookie was deleted");
3763 NotifyChanged(oldCookie
, u
"deleted", oldCookieIsSession
, aFromHttp
);
3767 // Preserve creation time of cookie for ordering purposes.
3768 aCookie
->SetCreationTime(oldCookie
->CreationTime());
3772 // check if cookie has already expired
3773 if (aCookie
->Expiry() <= currentTime
) {
3774 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
,
3775 "cookie has already expired");
3779 // check if we have to delete an old cookie.
3780 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(aKey
);
3781 if (entry
&& entry
->GetCookies().Length() >= mMaxCookiesPerHost
) {
3782 nsTArray
<nsListIter
> removedIterList
;
3783 // Prioritize evicting insecure cookies.
3784 // (draft-ietf-httpbis-cookie-alone section 3.3)
3785 mozilla::Maybe
<bool> optionalSecurity
= mLeaveSecureAlone
? Some(false) : Nothing();
3786 uint32_t limit
= mMaxCookiesPerHost
- mCookieQuotaPerHost
;
3787 FindStaleCookies(entry
, currentTime
, optionalSecurity
, removedIterList
, limit
);
3788 if (removedIterList
.Length() == 0) {
3789 if (aCookie
->IsSecure()) {
3790 // It's valid to evict a secure cookie for another secure cookie.
3791 FindStaleCookies(entry
, currentTime
, Some(true), removedIterList
, limit
);
3793 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
3794 EVICTING_SECURE_BLOCKED
);
3795 COOKIE_LOGEVICTED(aCookie
,
3796 "Too many cookies for this domain and the new cookie is not a secure cookie");
3801 MOZ_ASSERT(!removedIterList
.IsEmpty());
3802 // Sort |removedIterList| by index again, since we have to remove the cookie
3803 // in the reverse order.
3804 removedIterList
.Sort(CompareCookiesByIndex());
3805 for (auto it
= removedIterList
.rbegin(); it
!= removedIterList
.rend(); it
++) {
3806 RefPtr
<nsCookie
> evictedCookie
= (*it
).Cookie();
3807 if (mLeaveSecureAlone
&& evictedCookie
->Expiry() <= currentTime
) {
3808 TelemetryForEvictingStaleCookie(evictedCookie
,
3809 evictedCookie
->LastAccessed());
3811 COOKIE_LOGEVICTED(evictedCookie
, "Too many cookies for this domain");
3812 RemoveCookieFromList(*it
);
3813 CreateOrUpdatePurgeList(getter_AddRefs(purgedList
), evictedCookie
);
3814 MOZ_ASSERT((*it
).entry
);
3817 } else if (mDBState
->cookieCount
>= ADD_TEN_PERCENT(mMaxNumberOfCookies
)) {
3818 int64_t maxAge
= aCurrentTimeInUsec
- mDBState
->cookieOldestTime
;
3819 int64_t purgeAge
= ADD_TEN_PERCENT(mCookiePurgeAge
);
3820 if (maxAge
>= purgeAge
) {
3821 // we're over both size and age limits by 10%; time to purge the table!
3823 // 1) removing expired cookies;
3824 // 2) evicting the balance of old cookies until we reach the size limit.
3825 // note that the cookieOldestTime indicator can be pessimistic - if it's
3826 // older than the actual oldest cookie, we'll just purge more eagerly.
3827 purgedList
= PurgeCookies(aCurrentTimeInUsec
);
3832 // Add the cookie to the db. We do not supply a params array for batching
3833 // because this might result in removals and additions being out of order.
3834 AddCookieToList(aKey
, aCookie
, mDBState
, nullptr);
3835 COOKIE_LOGSUCCESS(SET_COOKIE
, aHostURI
, aCookieHeader
, aCookie
, foundCookie
);
3837 // Now that list mutations are complete, notify observers. We do it here
3838 // because observers may themselves attempt to mutate the list.
3840 NotifyChanged(purgedList
, u
"batch-deleted");
3843 NotifyChanged(aCookie
, foundCookie
? u
"changed" : u
"added", oldCookieIsSession
, aFromHttp
);
3846 /******************************************************************************
3847 * nsCookieService impl:
3848 * private cookie header parsing functions
3849 ******************************************************************************/
3851 // The following comment block elucidates the function of ParseAttributes.
3852 /******************************************************************************
3853 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
3854 ** please note: this BNF deviates from both specifications, and reflects this
3855 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
3857 ** Differences from RFC2109/2616 and explanations:
3859 The grammar described by this specification is word-based. Except
3860 where noted otherwise, linear white space (<LWS>) can be included
3861 between any two adjacent words (token or quoted-string), and
3862 between adjacent words and separators, without changing the
3863 interpretation of a field.
3864 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
3866 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
3867 common use inside values.
3869 3. tokens and values have looser restrictions on allowed characters than
3870 spec. This is also due to certain characters being in common use inside
3871 values. We allow only '=' to separate token/value pairs, and ';' to
3872 terminate tokens or values. <LWS> is allowed within tokens and values
3875 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
3876 reject control chars or non-ASCII chars. This is erring on the loose
3877 side, since there's probably no good reason to enforce this strictness.
3879 5. cookie <NAME> is optional, where spec requires it. This is a fairly
3880 trivial case, but allows the flexibility of setting only a cookie <VALUE>
3881 with a blank <NAME> and is required by some sites (see bug 169091).
3883 6. Attribute "HttpOnly", not covered in the RFCs, is supported
3887 token = 1*<any allowed-chars except separators>
3888 value = 1*<any allowed-chars except value-sep>
3889 separators = ";" | "="
3891 cookie-sep = CR | LF
3892 allowed-chars = <any OCTET except NUL or cookie-sep>
3893 OCTET = <any 8-bit sequence of data>
3895 NUL = <US-ASCII NUL, null control character (0)>
3896 CR = <US-ASCII CR, carriage return (13)>
3897 LF = <US-ASCII LF, linefeed (10)>
3898 SP = <US-ASCII SP, space (32)>
3899 HT = <US-ASCII HT, horizontal-tab (9)>
3901 set-cookie = "Set-Cookie:" cookies
3902 cookies = cookie *( cookie-sep cookie )
3903 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
3904 NAME = token ; cookie name
3905 VALUE = value ; cookie value
3906 cookie-av = token ["=" value]
3908 valid values for cookie-av (checked post-parsing) are:
3909 cookie-av = "Path" "=" value
3910 | "Domain" "=" value
3911 | "Expires" "=" value
3912 | "Max-Age" "=" value
3913 | "Comment" "=" value
3914 | "Version" "=" value
3918 ******************************************************************************/
3920 // helper functions for GetTokenValue
3921 static inline bool iswhitespace (char c
) { return c
== ' ' || c
== '\t'; }
3922 static inline bool isterminator (char c
) { return c
== '\n' || c
== '\r'; }
3923 static inline bool isvalueseparator (char c
) { return isterminator(c
) || c
== ';'; }
3924 static inline bool istokenseparator (char c
) { return isvalueseparator(c
) || c
== '='; }
3926 // Parse a single token/value pair.
3927 // Returns true if a cookie terminator is found, so caller can parse new cookie.
3929 nsCookieService::GetTokenValue(nsACString::const_char_iterator
&aIter
,
3930 nsACString::const_char_iterator
&aEndIter
,
3931 nsDependentCSubstring
&aTokenString
,
3932 nsDependentCSubstring
&aTokenValue
,
3935 nsACString::const_char_iterator start
, lastSpace
;
3936 // initialize value string to clear garbage
3937 aTokenValue
.Rebind(aIter
, aIter
);
3939 // find <token>, including any <LWS> between the end-of-token and the
3940 // token separator. we'll remove trailing <LWS> next
3941 while (aIter
!= aEndIter
&& iswhitespace(*aIter
))
3944 while (aIter
!= aEndIter
&& !istokenseparator(*aIter
))
3947 // remove trailing <LWS>; first check we're not at the beginning
3949 if (lastSpace
!= start
) {
3950 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
))
3954 aTokenString
.Rebind(start
, lastSpace
);
3956 aEqualsFound
= (*aIter
== '=');
3959 while (++aIter
!= aEndIter
&& iswhitespace(*aIter
))
3965 // just look for ';' to terminate ('=' allowed)
3966 while (aIter
!= aEndIter
&& !isvalueseparator(*aIter
))
3969 // remove trailing <LWS>; first check we're not at the beginning
3970 if (aIter
!= start
) {
3972 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
))
3974 aTokenValue
.Rebind(start
, ++lastSpace
);
3978 // aIter is on ';', or terminator, or EOS
3979 if (aIter
!= aEndIter
) {
3980 // if on terminator, increment past & return true to process new cookie
3981 if (isterminator(*aIter
)) {
3985 // fall-through: aIter is on ';', increment and return false
3991 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
3992 // cookie struct here, because we don't know which one to use until we've parsed the header.
3994 nsCookieService::ParseAttributes(nsDependentCString
&aCookieHeader
,
3995 nsCookieAttributes
&aCookieAttributes
)
3997 static const char kPath
[] = "path";
3998 static const char kDomain
[] = "domain";
3999 static const char kExpires
[] = "expires";
4000 static const char kMaxage
[] = "max-age";
4001 static const char kSecure
[] = "secure";
4002 static const char kHttpOnly
[] = "httponly";
4003 static const char kSameSite
[] = "samesite";
4004 static const char kSameSiteLax
[] = "lax";
4005 static const char kSameSiteStrict
[] = "strict";
4007 nsACString::const_char_iterator tempBegin
, tempEnd
;
4008 nsACString::const_char_iterator cookieStart
, cookieEnd
;
4009 aCookieHeader
.BeginReading(cookieStart
);
4010 aCookieHeader
.EndReading(cookieEnd
);
4012 aCookieAttributes
.isSecure
= false;
4013 aCookieAttributes
.isHttpOnly
= false;
4014 aCookieAttributes
.sameSite
= nsICookie2::SAMESITE_UNSET
;
4016 nsDependentCSubstring
tokenString(cookieStart
, cookieStart
);
4017 nsDependentCSubstring
tokenValue (cookieStart
, cookieStart
);
4018 bool newCookie
, equalsFound
;
4020 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
4021 // if we find multiple cookies, return for processing
4022 // note: if there's no '=', we assume token is <VALUE>. this is required by
4023 // some sites (see bug 169091).
4024 // XXX fix the parser to parse according to <VALUE> grammar for this case
4025 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
4027 aCookieAttributes
.name
= tokenString
;
4028 aCookieAttributes
.value
= tokenValue
;
4030 aCookieAttributes
.value
= tokenString
;
4033 // extract remaining attributes
4034 while (cookieStart
!= cookieEnd
&& !newCookie
) {
4035 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
4037 if (!tokenValue
.IsEmpty()) {
4038 tokenValue
.BeginReading(tempBegin
);
4039 tokenValue
.EndReading(tempEnd
);
4042 // decide which attribute we have, and copy the string
4043 if (tokenString
.LowerCaseEqualsLiteral(kPath
))
4044 aCookieAttributes
.path
= tokenValue
;
4046 else if (tokenString
.LowerCaseEqualsLiteral(kDomain
))
4047 aCookieAttributes
.host
= tokenValue
;
4049 else if (tokenString
.LowerCaseEqualsLiteral(kExpires
))
4050 aCookieAttributes
.expires
= tokenValue
;
4052 else if (tokenString
.LowerCaseEqualsLiteral(kMaxage
))
4053 aCookieAttributes
.maxage
= tokenValue
;
4055 // ignore any tokenValue for isSecure; just set the boolean
4056 else if (tokenString
.LowerCaseEqualsLiteral(kSecure
))
4057 aCookieAttributes
.isSecure
= true;
4059 // ignore any tokenValue for isHttpOnly (see bug 178993);
4060 // just set the boolean
4061 else if (tokenString
.LowerCaseEqualsLiteral(kHttpOnly
))
4062 aCookieAttributes
.isHttpOnly
= true;
4064 else if (tokenString
.LowerCaseEqualsLiteral(kSameSite
)) {
4065 if (tokenValue
.LowerCaseEqualsLiteral(kSameSiteLax
)) {
4066 aCookieAttributes
.sameSite
= nsICookie2::SAMESITE_LAX
;
4067 } else if (tokenValue
.LowerCaseEqualsLiteral(kSameSiteStrict
)) {
4068 aCookieAttributes
.sameSite
= nsICookie2::SAMESITE_STRICT
;
4073 // rebind aCookieHeader, in case we need to process another cookie
4074 aCookieHeader
.Rebind(cookieStart
, cookieEnd
);
4078 /******************************************************************************
4079 * nsCookieService impl:
4080 * private domain & permission compliance enforcement functions
4081 ******************************************************************************/
4083 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
4084 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
4085 // dot may be present. If aHostURI is an IP address, an alias such as
4086 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
4087 // be the exact host, and aRequireHostMatch will be true to indicate that
4088 // substring matches should not be performed.
4090 nsCookieService::GetBaseDomain(nsIEffectiveTLDService
*aTLDService
,
4092 nsCString
&aBaseDomain
,
4093 bool &aRequireHostMatch
)
4095 // get the base domain. this will fail if the host contains a leading dot,
4096 // more than one trailing dot, or is otherwise malformed.
4097 nsresult rv
= aTLDService
->GetBaseDomain(aHostURI
, 0, aBaseDomain
);
4098 aRequireHostMatch
= rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
4099 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
;
4100 if (aRequireHostMatch
) {
4101 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
4102 // such as 'co.uk', or the empty string. use the host as a key in such
4104 rv
= aHostURI
->GetAsciiHost(aBaseDomain
);
4106 NS_ENSURE_SUCCESS(rv
, rv
);
4108 // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
4109 if (aBaseDomain
.Length() == 1 && aBaseDomain
.Last() == '.')
4110 return NS_ERROR_INVALID_ARG
;
4112 // block any URIs without a host that aren't file:// URIs.
4113 if (aBaseDomain
.IsEmpty()) {
4114 bool isFileURI
= false;
4115 aHostURI
->SchemeIs("file", &isFileURI
);
4117 return NS_ERROR_INVALID_ARG
;
4123 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
4124 // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it is assumed
4125 // that aHost is already normalized, and it may contain a leading dot
4126 // (indicating that it represents a domain). A trailing dot may be present.
4127 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
4128 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
4129 // leading dot will be treated as an error.
4131 nsCookieService::GetBaseDomainFromHost(nsIEffectiveTLDService
*aTLDService
,
4132 const nsACString
&aHost
,
4133 nsCString
&aBaseDomain
)
4135 // aHost must not be the string '.'.
4136 if (aHost
.Length() == 1 && aHost
.Last() == '.')
4137 return NS_ERROR_INVALID_ARG
;
4139 // aHost may contain a leading dot; if so, strip it now.
4140 bool domain
= !aHost
.IsEmpty() && aHost
.First() == '.';
4142 // get the base domain. this will fail if the host contains a leading dot,
4143 // more than one trailing dot, or is otherwise malformed.
4144 nsresult rv
= aTLDService
->GetBaseDomainFromHost(Substring(aHost
, domain
), 0, aBaseDomain
);
4145 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
4146 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
4147 // aHost is either an IP address, an alias such as 'localhost', an eTLD
4148 // such as 'co.uk', or the empty string. use the host as a key in such
4149 // cases; however, we reject any such hosts with a leading dot, since it
4150 // doesn't make sense for them to be domain cookies.
4152 return NS_ERROR_INVALID_ARG
;
4154 aBaseDomain
= aHost
;
4160 // Normalizes the given hostname, component by component. ASCII/ACE
4161 // components are lower-cased, and UTF-8 components are normalized per
4162 // RFC 3454 and converted to ACE.
4164 nsCookieService::NormalizeHost(nsCString
&aHost
)
4166 if (!IsASCII(aHost
)) {
4168 nsresult rv
= mIDNService
->ConvertUTF8toACE(aHost
, host
);
4179 // returns true if 'a' is equal to or a subdomain of 'b',
4180 // assuming no leading dots are present.
4181 static inline bool IsSubdomainOf(const nsCString
&a
, const nsCString
&b
)
4185 if (a
.Length() > b
.Length())
4186 return a
[a
.Length() - b
.Length() - 1] == '.' && StringEndsWith(a
, b
);
4191 nsCookieService::CheckPrefs(nsICookiePermission
*aPermissionService
,
4192 uint8_t aCookieBehavior
,
4193 bool aThirdPartySession
,
4194 bool aThirdPartyNonsecureSession
,
4197 bool aIsTrackingResource
,
4198 bool aFirstPartyStorageAccessGranted
,
4199 const char *aCookieHeader
,
4200 const int aNumOfCookies
,
4201 const OriginAttributes
&aOriginAttrs
,
4202 uint32_t *aRejectedReason
)
4206 // Let's use a internal value in order to avoid a null check on
4207 // aRejectedReason everywhere.
4208 uint32_t rejectedReason
= 0;
4209 if (!aRejectedReason
) {
4210 aRejectedReason
= &rejectedReason
;
4213 *aRejectedReason
= 0;
4215 // don't let ftp sites get/set cookies (could be a security issue)
4217 if (NS_SUCCEEDED(aHostURI
->SchemeIs("ftp", &ftp
)) && ftp
) {
4218 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "ftp sites cannot read cookies");
4219 return STATUS_REJECTED_WITH_ERROR
;
4222 nsCOMPtr
<nsIPrincipal
> principal
=
4223 BasePrincipal::CreateCodebasePrincipal(aHostURI
, aOriginAttrs
);
4226 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "non-codebase principals cannot get/set cookies");
4227 return STATUS_REJECTED_WITH_ERROR
;
4230 // check the permission list first; if we find an entry, it overrides
4231 // default prefs. see bug 184059.
4232 if (aPermissionService
) {
4233 nsCookieAccess access
;
4234 // Not passing an nsIChannel here is probably OK; our implementation
4235 // doesn't do anything with it anyway.
4236 rv
= aPermissionService
->CanAccess(principal
, &access
);
4238 // if we found an entry, use it
4239 if (NS_SUCCEEDED(rv
)) {
4241 case nsICookiePermission::ACCESS_DENY
:
4242 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
,
4243 aCookieHeader
, "cookies are blocked for this site");
4244 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
;
4245 return STATUS_REJECTED
;
4247 case nsICookiePermission::ACCESS_ALLOW
:
4248 return STATUS_ACCEPTED
;
4250 case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY
:
4252 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
,
4253 aCookieHeader
, "third party cookies are blocked "
4255 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
;
4256 return STATUS_REJECTED
;
4259 return STATUS_ACCEPTED
;
4261 case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY
:
4263 return STATUS_ACCEPTED
;
4264 if (aNumOfCookies
== 0) {
4265 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
,
4266 aCookieHeader
, "third party cookies are blocked "
4268 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
;
4269 return STATUS_REJECTED
;
4271 return STATUS_ACCEPTED
;
4276 // No cookies allowed if this request comes from a tracker, in a 3rd party
4277 // context, when anti-tracking protection is enabled and when we don't have
4278 // access to the first-party cookie jar.
4279 if (aIsForeign
&& aIsTrackingResource
&& !aFirstPartyStorageAccessGranted
&&
4280 aCookieBehavior
== nsICookieService::BEHAVIOR_REJECT_TRACKER
) {
4281 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are disabled in trackers");
4282 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
;
4283 return STATUS_REJECTED
;
4286 // check default prefs
4287 if (aCookieBehavior
== nsICookieService::BEHAVIOR_REJECT
) {
4288 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are disabled");
4289 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
;
4290 return STATUS_REJECTED
;
4293 // check if cookie is foreign
4295 // Check aFirstPartyStorageAccessGranted when rejecting all third-party cookies,
4296 // so that we take things such as the content blocking allow list into account.
4297 if (aCookieBehavior
== nsICookieService::BEHAVIOR_REJECT_FOREIGN
&&
4298 !aFirstPartyStorageAccessGranted
) {
4299 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "context is third party");
4300 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
;
4301 return STATUS_REJECTED
;
4304 if (aCookieBehavior
== nsICookieService::BEHAVIOR_LIMIT_FOREIGN
) {
4305 if (aNumOfCookies
== 0) {
4306 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "context is third party");
4307 *aRejectedReason
= nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
;
4308 return STATUS_REJECTED
;
4312 MOZ_ASSERT(aCookieBehavior
== nsICookieService::BEHAVIOR_ACCEPT
||
4313 aCookieBehavior
== nsICookieService::BEHAVIOR_LIMIT_FOREIGN
||
4314 // But with permission granted.
4315 aCookieBehavior
== nsICookieService::BEHAVIOR_REJECT_FOREIGN
||
4316 aCookieBehavior
== nsICookieService::BEHAVIOR_REJECT_TRACKER
);
4318 if (aThirdPartySession
)
4319 return STATUS_ACCEPT_SESSION
;
4321 if (aThirdPartyNonsecureSession
) {
4322 bool isHTTPS
= false;
4323 aHostURI
->SchemeIs("https", &isHTTPS
);
4325 return STATUS_ACCEPT_SESSION
;
4329 // if nothing has complained, accept cookie
4330 return STATUS_ACCEPTED
;
4333 // processes domain attribute, and returns true if host has permission to set for this domain.
4335 nsCookieService::CheckDomain(nsCookieAttributes
&aCookieAttributes
,
4337 const nsCString
&aBaseDomain
,
4338 bool aRequireHostMatch
)
4340 // Note: The logic in this function is mirrored in
4341 // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
4342 // If it changes, please update that function, or file a bug for someone
4345 // get host from aHostURI
4346 nsAutoCString hostFromURI
;
4347 aHostURI
->GetAsciiHost(hostFromURI
);
4349 // if a domain is given, check the host has permission
4350 if (!aCookieAttributes
.host
.IsEmpty()) {
4351 // Tolerate leading '.' characters, but not if it's otherwise an empty host.
4352 if (aCookieAttributes
.host
.Length() > 1 &&
4353 aCookieAttributes
.host
.First() == '.') {
4354 aCookieAttributes
.host
.Cut(0, 1);
4357 // switch to lowercase now, to avoid case-insensitive compares everywhere
4358 ToLowerCase(aCookieAttributes
.host
);
4360 // check whether the host is either an IP address, an alias such as
4361 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
4362 // cases, require an exact string match for the domain, and leave the cookie
4363 // as a non-domain one. bug 105917 originally noted the requirement to deal
4364 // with IP addresses.
4365 if (aRequireHostMatch
)
4366 return hostFromURI
.Equals(aCookieAttributes
.host
);
4368 // ensure the proposed domain is derived from the base domain; and also
4369 // that the host domain is derived from the proposed domain (per RFC2109).
4370 if (IsSubdomainOf(aCookieAttributes
.host
, aBaseDomain
) &&
4371 IsSubdomainOf(hostFromURI
, aCookieAttributes
.host
)) {
4372 // prepend a dot to indicate a domain cookie
4373 aCookieAttributes
.host
.InsertLiteral(".", 0);
4378 * note: RFC2109 section 4.3.2 requires that we check the following:
4379 * that the portion of host not in domain does not contain a dot.
4380 * this prevents hosts of the form x.y.co.nz from setting cookies in the
4381 * entire .co.nz domain. however, it's only a only a partial solution and
4382 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
4387 // no domain specified, use hostFromURI
4388 aCookieAttributes
.host
= hostFromURI
;
4393 nsCookieService::GetPathFromURI(nsIURI
* aHostURI
)
4395 // strip down everything after the last slash to get the path,
4396 // ignoring slashes in the query string part.
4397 // if we can QI to nsIURL, that'll take care of the query string portion.
4398 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
4400 nsCOMPtr
<nsIURL
> hostURL
= do_QueryInterface(aHostURI
);
4402 hostURL
->GetDirectory(path
);
4404 aHostURI
->GetPathQueryRef(path
);
4405 int32_t slash
= path
.RFindChar('/');
4406 if (slash
!= kNotFound
) {
4407 path
.Truncate(slash
+ 1);
4414 nsCookieService::CheckPath(nsCookieAttributes
&aCookieAttributes
,
4417 // if a path is given, check the host has permission
4418 if (aCookieAttributes
.path
.IsEmpty() || aCookieAttributes
.path
.First() != '/') {
4419 aCookieAttributes
.path
= GetPathFromURI(aHostURI
);
4424 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
4425 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
4426 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
4427 * been disabled, unless we can evangelize these sites.
4429 // get path from aHostURI
4430 nsAutoCString pathFromURI
;
4431 if (NS_FAILED(aHostURI
->GetPathQueryRef(pathFromURI
)) ||
4432 !StringBeginsWith(pathFromURI
, aCookieAttributes
.path
)) {
4438 if (aCookieAttributes
.path
.Length() > kMaxBytesPerPath
||
4439 aCookieAttributes
.path
.Contains('\t'))
4447 // Reject cookies whose name starts with the magic prefixes from
4448 // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
4449 // if they do not meet the criteria required by the prefix.
4451 // Must not be called until after CheckDomain() and CheckPath() have
4452 // regularized and validated the nsCookieAttributes values!
4454 nsCookieService::CheckPrefixes(nsCookieAttributes
&aCookieAttributes
,
4455 bool aSecureRequest
)
4457 static const char kSecure
[] = "__Secure-";
4458 static const char kHost
[] = "__Host-";
4459 static const int kSecureLen
= sizeof( kSecure
) - 1;
4460 static const int kHostLen
= sizeof( kHost
) - 1;
4462 bool isSecure
= strncmp( aCookieAttributes
.name
.get(), kSecure
, kSecureLen
) == 0;
4463 bool isHost
= strncmp( aCookieAttributes
.name
.get(), kHost
, kHostLen
) == 0;
4465 if ( !isSecure
&& !isHost
) {
4466 // not one of the magic prefixes: carry on
4470 if ( !aSecureRequest
|| !aCookieAttributes
.isSecure
) {
4471 // the magic prefixes may only be used from a secure request and
4472 // the secure attribute must be set on the cookie
4477 // The host prefix requires that the path is "/" and that the cookie
4478 // had no domain attribute. CheckDomain() and CheckPath() MUST be run
4479 // first to make sure invalid attributes are rejected and to regularlize
4480 // them. In particular all explicit domain attributes result in a host
4481 // that starts with a dot, and if the host doesn't start with a dot it
4482 // correctly matches the true host.
4483 if ( aCookieAttributes
.host
[0] == '.' ||
4484 !aCookieAttributes
.path
.EqualsLiteral( "/" )) {
4493 nsCookieService::GetExpiry(nsCookieAttributes
&aCookieAttributes
,
4494 int64_t aServerTime
,
4495 int64_t aCurrentTime
)
4497 /* Determine when the cookie should expire. This is done by taking the difference between
4498 * the server time and the time the server wants the cookie to expire, and adding that
4499 * difference to the client time. This localizes the client time regardless of whether or
4500 * not the TZ environment variable was set on the client.
4502 * Note: We need to consider accounting for network lag here, per RFC.
4504 // check for max-age attribute first; this overrides expires attribute
4505 if (!aCookieAttributes
.maxage
.IsEmpty()) {
4506 // obtain numeric value of maxageAttribute
4508 int32_t numInts
= PR_sscanf(aCookieAttributes
.maxage
.get(), "%lld", &maxage
);
4510 // default to session cookie if the conversion failed
4515 // if this addition overflows, expiryTime will be less than currentTime
4516 // and the cookie will be expired - that's okay.
4517 aCookieAttributes
.expiryTime
= aCurrentTime
+ maxage
;
4519 // check for expires attribute
4520 } else if (!aCookieAttributes
.expires
.IsEmpty()) {
4523 // parse expiry time
4524 if (PR_ParseTimeString(aCookieAttributes
.expires
.get(), true, &expires
) != PR_SUCCESS
) {
4528 // If set-cookie used absolute time to set expiration, and it can't use
4529 // client time to set expiration.
4530 // Because if current time be set in the future, but the cookie expire
4531 // time be set less than current time and more than server time.
4532 // The cookie item have to be used to the expired cookie.
4533 aCookieAttributes
.expiryTime
= expires
/ int64_t(PR_USEC_PER_SEC
);
4535 // default to session cookie if no attributes found
4543 /******************************************************************************
4544 * nsCookieService impl:
4545 * private cookielist management functions
4546 ******************************************************************************/
4549 nsCookieService::RemoveAllFromMemory()
4551 // clearing the hashtable will call each nsCookieEntry's dtor,
4552 // which releases all their respective children.
4553 mDBState
->hostTable
.Clear();
4554 mDBState
->cookieCount
= 0;
4555 mDBState
->cookieOldestTime
= INT64_MAX
;
4558 // comparator class for lastaccessed times of cookies.
4559 class CompareCookiesByAge
{
4561 bool Equals(const nsListIter
&a
, const nsListIter
&b
) const
4563 return a
.Cookie()->LastAccessed() == b
.Cookie()->LastAccessed() &&
4564 a
.Cookie()->CreationTime() == b
.Cookie()->CreationTime();
4567 bool LessThan(const nsListIter
&a
, const nsListIter
&b
) const
4569 // compare by lastAccessed time, and tiebreak by creationTime.
4570 int64_t result
= a
.Cookie()->LastAccessed() - b
.Cookie()->LastAccessed();
4574 return a
.Cookie()->CreationTime() < b
.Cookie()->CreationTime();
4578 // purges expired and old cookies in a batch operation.
4579 already_AddRefed
<nsIArray
>
4580 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec
)
4582 NS_ASSERTION(mDBState
->hostTable
.Count() > 0, "table is empty");
4584 uint32_t initialCookieCount
= mDBState
->cookieCount
;
4585 COOKIE_LOGSTRING(LogLevel::Debug
,
4586 ("PurgeCookies(): beginning purge with %" PRIu32
" cookies and %" PRId64
" oldest age",
4587 mDBState
->cookieCount
, aCurrentTimeInUsec
- mDBState
->cookieOldestTime
));
4589 typedef nsTArray
<nsListIter
> PurgeList
;
4590 PurgeList
purgeList(kMaxNumberOfCookies
);
4592 nsCOMPtr
<nsIMutableArray
> removedList
= do_CreateInstance(NS_ARRAY_CONTRACTID
);
4594 // Create a params array to batch the removals. This is OK here because
4595 // all the removals are in order, and there are no interleaved additions.
4596 mozIStorageAsyncStatement
*stmt
= mDBState
->stmtDelete
;
4597 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray
;
4598 if (mDBState
->dbConn
) {
4599 stmt
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
4602 int64_t currentTime
= aCurrentTimeInUsec
/ PR_USEC_PER_SEC
;
4603 int64_t purgeTime
= aCurrentTimeInUsec
- mCookiePurgeAge
;
4604 int64_t oldestTime
= INT64_MAX
;
4606 for (auto iter
= mDBState
->hostTable
.Iter(); !iter
.Done(); iter
.Next()) {
4607 nsCookieEntry
* entry
= iter
.Get();
4609 const nsCookieEntry::ArrayType
& cookies
= entry
->GetCookies();
4610 auto length
= cookies
.Length();
4611 for (nsCookieEntry::IndexType i
= 0; i
< length
; ) {
4612 nsListIter
iter(entry
, i
);
4613 nsCookie
* cookie
= cookies
[i
];
4615 // check if the cookie has expired
4616 if (cookie
->Expiry() <= currentTime
) {
4617 removedList
->AppendElement(cookie
);
4618 COOKIE_LOGEVICTED(cookie
, "Cookie expired");
4620 // remove from list; do not increment our iterator, but stop if we're
4622 gCookieService
->RemoveCookieFromList(iter
, paramsArray
);
4623 if (i
== --length
) {
4627 // check if the cookie is over the age limit
4628 if (cookie
->LastAccessed() <= purgeTime
) {
4629 purgeList
.AppendElement(iter
);
4631 } else if (cookie
->LastAccessed() < oldestTime
) {
4632 // reset our indicator
4633 oldestTime
= cookie
->LastAccessed();
4638 MOZ_ASSERT(length
== cookies
.Length());
4642 uint32_t postExpiryCookieCount
= mDBState
->cookieCount
;
4644 // now we have a list of iterators for cookies over the age limit.
4645 // sort them by age, and then we'll see how many to remove...
4646 purgeList
.Sort(CompareCookiesByAge());
4648 // only remove old cookies until we reach the max cookie limit, no more.
4649 uint32_t excess
= mDBState
->cookieCount
> mMaxNumberOfCookies
?
4650 mDBState
->cookieCount
- mMaxNumberOfCookies
: 0;
4651 if (purgeList
.Length() > excess
) {
4652 // We're not purging everything in the list, so update our indicator.
4653 oldestTime
= purgeList
[excess
].Cookie()->LastAccessed();
4655 purgeList
.SetLength(excess
);
4658 // sort the list again, this time grouping cookies with a common entryclass
4659 // together, and with ascending index. this allows us to iterate backwards
4660 // over the list removing cookies, without having to adjust indexes as we go.
4661 purgeList
.Sort(CompareCookiesByIndex());
4662 for (PurgeList::index_type i
= purgeList
.Length(); i
--; ) {
4663 nsCookie
*cookie
= purgeList
[i
].Cookie();
4664 removedList
->AppendElement(cookie
);
4665 COOKIE_LOGEVICTED(cookie
, "Cookie too old");
4667 RemoveCookieFromList(purgeList
[i
], paramsArray
);
4670 // Update the database if we have entries to purge.
4673 paramsArray
->GetLength(&length
);
4675 DebugOnly
<nsresult
> rv
= stmt
->BindParameters(paramsArray
);
4676 NS_ASSERT_SUCCESS(rv
);
4677 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
4678 rv
= stmt
->ExecuteAsync(mDBState
->removeListener
, getter_AddRefs(handle
));
4679 NS_ASSERT_SUCCESS(rv
);
4683 // reset the oldest time indicator
4684 mDBState
->cookieOldestTime
= oldestTime
;
4686 COOKIE_LOGSTRING(LogLevel::Debug
,
4687 ("PurgeCookies(): %" PRIu32
" expired; %" PRIu32
" purged; %" PRIu32
4688 " remain; %" PRId64
" oldest age",
4689 initialCookieCount
- postExpiryCookieCount
,
4690 postExpiryCookieCount
- mDBState
->cookieCount
,
4691 mDBState
->cookieCount
,
4692 aCurrentTimeInUsec
- mDBState
->cookieOldestTime
));
4694 return removedList
.forget();
4697 // find whether a given cookie has been previously set. this is provided by the
4698 // nsICookieManager interface.
4700 nsCookieService::CookieExists(const nsACString
& aHost
,
4701 const nsACString
& aPath
,
4702 const nsACString
& aName
,
4703 JS::HandleValue aOriginAttributes
,
4707 NS_ENSURE_ARG_POINTER(aCx
);
4708 NS_ENSURE_ARG_POINTER(aFoundCookie
);
4710 OriginAttributes attrs
;
4711 if (!aOriginAttributes
.isObject() ||
4712 !attrs
.Init(aCx
, aOriginAttributes
)) {
4713 return NS_ERROR_INVALID_ARG
;
4715 return CookieExistsNative(aHost
, aPath
, aName
, &attrs
, aFoundCookie
);
4718 NS_IMETHODIMP_(nsresult
)
4719 nsCookieService::CookieExistsNative(const nsACString
& aHost
,
4720 const nsACString
& aPath
,
4721 const nsACString
& aName
,
4722 OriginAttributes
* aOriginAttributes
,
4725 NS_ENSURE_ARG_POINTER(aOriginAttributes
);
4726 NS_ENSURE_ARG_POINTER(aFoundCookie
);
4729 NS_WARNING("No DBState! Profile already closed?");
4730 return NS_ERROR_NOT_AVAILABLE
;
4733 EnsureReadComplete(true);
4735 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
4736 mDBState
= (aOriginAttributes
->mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
4738 nsAutoCString baseDomain
;
4739 nsresult rv
= GetBaseDomainFromHost(mTLDService
, aHost
, baseDomain
);
4740 NS_ENSURE_SUCCESS(rv
, rv
);
4743 *aFoundCookie
= FindCookie(nsCookieKey(baseDomain
, *aOriginAttributes
),
4744 PromiseFlatCString(aHost
),
4745 PromiseFlatCString(aName
),
4746 PromiseFlatCString(aPath
), iter
);
4750 // Cookie comparator for the priority queue used in FindStaleCookies.
4751 // Note that the expired cookie has the highest priority.
4752 // Other non-expired cookies are sorted by their age.
4753 class CookieIterComparator
{
4755 CompareCookiesByAge mAgeComparator
;
4756 int64_t mCurrentTime
;
4759 explicit CookieIterComparator(int64_t aTime
)
4760 : mCurrentTime(aTime
) {}
4762 bool LessThan(const nsListIter
& lhs
, const nsListIter
& rhs
)
4764 bool lExpired
= lhs
.Cookie()->Expiry() <= mCurrentTime
;
4765 bool rExpired
= rhs
.Cookie()->Expiry() <= mCurrentTime
;
4766 if (lExpired
&& !rExpired
) {
4770 if (!lExpired
&& rExpired
) {
4774 return mAgeComparator
.LessThan(lhs
, rhs
);
4778 // Given the output iter array and the count limit, find cookies
4779 // sort by expiry and lastAccessed time.
4781 nsCookieService::FindStaleCookies(nsCookieEntry
*aEntry
,
4782 int64_t aCurrentTime
,
4783 const mozilla::Maybe
<bool> &aIsSecure
,
4784 nsTArray
<nsListIter
>& aOutput
,
4789 const nsCookieEntry::ArrayType
&cookies
= aEntry
->GetCookies();
4792 CookieIterComparator
comp(aCurrentTime
);
4793 nsTPriorityQueue
<nsListIter
, CookieIterComparator
> queue(comp
);
4795 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
4796 nsCookie
*cookie
= cookies
[i
];
4798 if (cookie
->Expiry() <= aCurrentTime
) {
4799 queue
.Push(nsListIter(aEntry
, i
));
4803 if (aIsSecure
.isSome() && !aIsSecure
.value()) {
4804 // We want to look for the non-secure cookie first time through,
4805 // then find the secure cookie the second time this function is called.
4806 if (cookie
->IsSecure()) {
4811 queue
.Push(nsListIter(aEntry
, i
));
4815 while (!queue
.IsEmpty() && count
< aLimit
) {
4816 aOutput
.AppendElement(queue
.Pop());
4822 nsCookieService::TelemetryForEvictingStaleCookie(nsCookie
*aEvicted
,
4823 int64_t oldestCookieTime
)
4825 // We need to record the evicting cookie to telemetry.
4826 if (!aEvicted
->IsSecure()) {
4827 if (aEvicted
->LastAccessed() > oldestCookieTime
) {
4828 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
4829 EVICTED_NEWER_INSECURE
);
4831 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
4832 EVICTED_OLDEST_COOKIE
);
4835 Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE
,
4836 EVICTED_PREFERRED_COOKIE
);
4840 // count the number of cookies stored by a particular host. this is provided by the
4841 // nsICookieManager interface.
4843 nsCookieService::CountCookiesFromHost(const nsACString
&aHost
,
4844 uint32_t *aCountFromHost
)
4847 NS_WARNING("No DBState! Profile already closed?");
4848 return NS_ERROR_NOT_AVAILABLE
;
4851 EnsureReadComplete(true);
4853 // first, normalize the hostname, and fail if it contains illegal characters.
4854 nsAutoCString
host(aHost
);
4855 nsresult rv
= NormalizeHost(host
);
4856 NS_ENSURE_SUCCESS(rv
, rv
);
4858 nsAutoCString baseDomain
;
4859 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
4860 NS_ENSURE_SUCCESS(rv
, rv
);
4862 nsCookieKey key
= DEFAULT_APP_KEY(baseDomain
);
4864 // Return a count of all cookies, including expired.
4865 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(key
);
4866 *aCountFromHost
= entry
? entry
->GetCookies().Length() : 0;
4870 // get an enumerator of cookies stored by a particular host. this is provided by the
4871 // nsICookieManager interface.
4873 nsCookieService::GetCookiesFromHost(const nsACString
&aHost
,
4874 JS::HandleValue aOriginAttributes
,
4876 nsISimpleEnumerator
**aEnumerator
)
4879 NS_WARNING("No DBState! Profile already closed?");
4880 return NS_ERROR_NOT_AVAILABLE
;
4883 EnsureReadComplete(true);
4885 // first, normalize the hostname, and fail if it contains illegal characters.
4886 nsAutoCString
host(aHost
);
4887 nsresult rv
= NormalizeHost(host
);
4888 NS_ENSURE_SUCCESS(rv
, rv
);
4890 nsAutoCString baseDomain
;
4891 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
4892 NS_ENSURE_SUCCESS(rv
, rv
);
4894 OriginAttributes attrs
;
4895 if (!aOriginAttributes
.isObject() ||
4896 !attrs
.Init(aCx
, aOriginAttributes
)) {
4897 return NS_ERROR_INVALID_ARG
;
4900 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
4901 mDBState
= (attrs
.mPrivateBrowsingId
> 0) ? mPrivateDBState
: mDefaultDBState
;
4903 nsCookieKey key
= nsCookieKey(baseDomain
, attrs
);
4905 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(key
);
4907 return NS_NewEmptyEnumerator(aEnumerator
);
4909 nsCOMArray
<nsICookie
> cookieList(mMaxCookiesPerHost
);
4910 const nsCookieEntry::ArrayType
&cookies
= entry
->GetCookies();
4911 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
4912 cookieList
.AppendObject(cookies
[i
]);
4915 return NS_NewArrayEnumerator(aEnumerator
, cookieList
, NS_GET_IID(nsICookie2
));
4919 nsCookieService::GetCookiesWithOriginAttributes(const nsAString
& aPattern
,
4920 const nsACString
& aHost
,
4921 nsISimpleEnumerator
**aEnumerator
)
4923 mozilla::OriginAttributesPattern pattern
;
4924 if (!pattern
.Init(aPattern
)) {
4925 return NS_ERROR_INVALID_ARG
;
4928 nsAutoCString
host(aHost
);
4929 nsresult rv
= NormalizeHost(host
);
4930 NS_ENSURE_SUCCESS(rv
, rv
);
4932 nsAutoCString baseDomain
;
4933 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
4934 NS_ENSURE_SUCCESS(rv
, rv
);
4936 return GetCookiesWithOriginAttributes(pattern
, baseDomain
, aEnumerator
);
4940 nsCookieService::GetCookiesWithOriginAttributes(
4941 const mozilla::OriginAttributesPattern
& aPattern
,
4942 const nsCString
& aBaseDomain
,
4943 nsISimpleEnumerator
**aEnumerator
)
4946 NS_WARNING("No DBState! Profile already closed?");
4947 return NS_ERROR_NOT_AVAILABLE
;
4949 EnsureReadComplete(true);
4951 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
4952 mDBState
= (aPattern
.mPrivateBrowsingId
.WasPassed() &&
4953 aPattern
.mPrivateBrowsingId
.Value() > 0) ? mPrivateDBState
: mDefaultDBState
;
4955 nsCOMArray
<nsICookie
> cookies
;
4956 for (auto iter
= mDBState
->hostTable
.Iter(); !iter
.Done(); iter
.Next()) {
4957 nsCookieEntry
* entry
= iter
.Get();
4959 if (!aBaseDomain
.IsEmpty() && !aBaseDomain
.Equals(entry
->mBaseDomain
)) {
4963 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
4967 const nsCookieEntry::ArrayType
& entryCookies
= entry
->GetCookies();
4969 for (nsCookieEntry::IndexType i
= 0; i
< entryCookies
.Length(); ++i
) {
4970 cookies
.AppendObject(entryCookies
[i
]);
4974 return NS_NewArrayEnumerator(aEnumerator
, cookies
, NS_GET_IID(nsICookie2
));
4978 nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString
& aPattern
,
4979 const nsACString
& aHost
)
4981 MOZ_ASSERT(XRE_IsParentProcess());
4983 mozilla::OriginAttributesPattern pattern
;
4984 if (!pattern
.Init(aPattern
)) {
4985 return NS_ERROR_INVALID_ARG
;
4988 nsAutoCString
host(aHost
);
4989 nsresult rv
= NormalizeHost(host
);
4990 NS_ENSURE_SUCCESS(rv
, rv
);
4992 nsAutoCString baseDomain
;
4993 rv
= GetBaseDomainFromHost(mTLDService
, host
, baseDomain
);
4994 NS_ENSURE_SUCCESS(rv
, rv
);
4996 return RemoveCookiesWithOriginAttributes(pattern
, baseDomain
);
5000 nsCookieService::RemoveCookiesWithOriginAttributes(
5001 const mozilla::OriginAttributesPattern
& aPattern
,
5002 const nsCString
& aBaseDomain
)
5005 NS_WARNING("No DBState! Profile already close?");
5006 return NS_ERROR_NOT_AVAILABLE
;
5009 EnsureReadComplete(true);
5011 AutoRestore
<DBState
*> savePrevDBState(mDBState
);
5012 mDBState
= (aPattern
.mPrivateBrowsingId
.WasPassed() &&
5013 aPattern
.mPrivateBrowsingId
.Value() > 0) ? mPrivateDBState
: mDefaultDBState
;
5015 mozStorageTransaction
transaction(mDBState
->dbConn
, false);
5016 // Iterate the hash table of nsCookieEntry.
5017 for (auto iter
= mDBState
->hostTable
.Iter(); !iter
.Done(); iter
.Next()) {
5018 nsCookieEntry
* entry
= iter
.Get();
5020 if (!aBaseDomain
.IsEmpty() && !aBaseDomain
.Equals(entry
->mBaseDomain
)) {
5024 if (!aPattern
.Matches(entry
->mOriginAttributes
)) {
5028 // Pattern matches. Delete all cookies within this nsCookieEntry.
5029 uint32_t cookiesCount
= entry
->GetCookies().Length();
5031 for (nsCookieEntry::IndexType i
= 0 ; i
< cookiesCount
; ++i
) {
5032 // Remove the first cookie from the list.
5033 nsListIter
iter(entry
, 0);
5034 RefPtr
<nsCookie
> cookie
= iter
.Cookie();
5036 // Remove the cookie.
5037 RemoveCookieFromList(iter
);
5040 NotifyChanged(cookie
, u
"deleted");
5044 DebugOnly
<nsresult
> rv
= transaction
.Commit();
5045 MOZ_ASSERT(NS_SUCCEEDED(rv
));
5050 // find an secure cookie specified by host and name
5052 nsCookieService::FindSecureCookie(const nsCookieKey
&aKey
,
5055 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(aKey
);
5059 const nsCookieEntry::ArrayType
&cookies
= entry
->GetCookies();
5060 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
5061 nsCookie
*cookie
= cookies
[i
];
5062 // isn't a match if insecure or a different name
5063 if (!cookie
->IsSecure() || !aCookie
->Name().Equals(cookie
->Name()))
5066 // The host must "domain-match" an existing cookie or vice-versa
5067 if (DomainMatches(cookie
, aCookie
->Host()) ||
5068 DomainMatches(aCookie
, cookie
->Host())) {
5069 // If the path of new cookie and the path of existing cookie
5070 // aren't "/", then this situation needs to compare paths to
5071 // ensure only that a newly-created non-secure cookie does not
5072 // overlay an existing secure cookie.
5073 if (PathMatches(cookie
, aCookie
->Path())) {
5082 // find an exact cookie specified by host, name, and path that hasn't expired.
5084 nsCookieService::FindCookie(const nsCookieKey
&aKey
,
5085 const nsCString
& aHost
,
5086 const nsCString
& aName
,
5087 const nsCString
& aPath
,
5090 // Should |EnsureReadComplete| before.
5091 MOZ_ASSERT(mInitializedDBStates
);
5092 MOZ_ASSERT(mInitializedDBConn
);
5094 nsCookieEntry
*entry
= mDBState
->hostTable
.GetEntry(aKey
);
5098 const nsCookieEntry::ArrayType
&cookies
= entry
->GetCookies();
5099 for (nsCookieEntry::IndexType i
= 0; i
< cookies
.Length(); ++i
) {
5100 nsCookie
*cookie
= cookies
[i
];
5102 if (aHost
.Equals(cookie
->Host()) &&
5103 aPath
.Equals(cookie
->Path()) &&
5104 aName
.Equals(cookie
->Name())) {
5105 aIter
= nsListIter(entry
, i
);
5113 // remove a cookie from the hashtable, and update the iterator state.
5115 nsCookieService::RemoveCookieFromList(const nsListIter
&aIter
,
5116 mozIStorageBindingParamsArray
*aParamsArray
)
5118 // if it's a non-session cookie, remove it from the db
5119 if (!aIter
.Cookie()->IsSession() && mDBState
->dbConn
) {
5120 // Use the asynchronous binding methods to ensure that we do not acquire
5121 // the database lock.
5122 mozIStorageAsyncStatement
*stmt
= mDBState
->stmtDelete
;
5123 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray(aParamsArray
);
5125 stmt
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
5128 nsCOMPtr
<mozIStorageBindingParams
> params
;
5129 paramsArray
->NewBindingParams(getter_AddRefs(params
));
5131 DebugOnly
<nsresult
> rv
=
5132 params
->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5133 aIter
.Cookie()->Name());
5134 NS_ASSERT_SUCCESS(rv
);
5136 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5137 aIter
.Cookie()->Host());
5138 NS_ASSERT_SUCCESS(rv
);
5140 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5141 aIter
.Cookie()->Path());
5142 NS_ASSERT_SUCCESS(rv
);
5144 nsAutoCString suffix
;
5145 aIter
.Cookie()->OriginAttributesRef().CreateSuffix(suffix
);
5146 rv
= params
->BindUTF8StringByName(
5147 NS_LITERAL_CSTRING("originAttributes"), suffix
);
5148 NS_ASSERT_SUCCESS(rv
);
5150 rv
= paramsArray
->AddParams(params
);
5151 NS_ASSERT_SUCCESS(rv
);
5153 // If we weren't given a params array, we'll need to remove it ourselves.
5154 if (!aParamsArray
) {
5155 rv
= stmt
->BindParameters(paramsArray
);
5156 NS_ASSERT_SUCCESS(rv
);
5157 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
5158 rv
= stmt
->ExecuteAsync(mDBState
->removeListener
, getter_AddRefs(handle
));
5159 NS_ASSERT_SUCCESS(rv
);
5163 if (aIter
.entry
->GetCookies().Length() == 1) {
5164 // we're removing the last element in the array - so just remove the entry
5165 // from the hash. note that the entryclass' dtor will take care of
5166 // releasing this last element for us!
5167 mDBState
->hostTable
.RawRemoveEntry(aIter
.entry
);
5170 // just remove the element from the list
5171 aIter
.entry
->GetCookies().RemoveElementAt(aIter
.index
);
5174 --mDBState
->cookieCount
;
5178 bindCookieParameters(mozIStorageBindingParamsArray
*aParamsArray
,
5179 const nsCookieKey
&aKey
,
5180 const nsCookie
*aCookie
)
5182 NS_ASSERTION(aParamsArray
, "Null params array passed to bindCookieParameters!");
5183 NS_ASSERTION(aCookie
, "Null cookie passed to bindCookieParameters!");
5185 // Use the asynchronous binding methods to ensure that we do not acquire the
5187 nsCOMPtr
<mozIStorageBindingParams
> params
;
5188 DebugOnly
<nsresult
> rv
=
5189 aParamsArray
->NewBindingParams(getter_AddRefs(params
));
5190 NS_ASSERT_SUCCESS(rv
);
5192 // Bind our values to params
5193 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
5195 NS_ASSERT_SUCCESS(rv
);
5197 nsAutoCString suffix
;
5198 aKey
.mOriginAttributes
.CreateSuffix(suffix
);
5199 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
5201 NS_ASSERT_SUCCESS(rv
);
5203 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5205 NS_ASSERT_SUCCESS(rv
);
5207 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
5209 NS_ASSERT_SUCCESS(rv
);
5211 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5213 NS_ASSERT_SUCCESS(rv
);
5215 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5217 NS_ASSERT_SUCCESS(rv
);
5219 rv
= params
->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
5221 NS_ASSERT_SUCCESS(rv
);
5223 rv
= params
->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
5224 aCookie
->LastAccessed());
5225 NS_ASSERT_SUCCESS(rv
);
5227 rv
= params
->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
5228 aCookie
->CreationTime());
5229 NS_ASSERT_SUCCESS(rv
);
5231 rv
= params
->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
5232 aCookie
->IsSecure());
5233 NS_ASSERT_SUCCESS(rv
);
5235 rv
= params
->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
5236 aCookie
->IsHttpOnly());
5237 NS_ASSERT_SUCCESS(rv
);
5239 rv
= params
->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"),
5240 aCookie
->SameSite());
5241 NS_ASSERT_SUCCESS(rv
);
5243 // Bind the params to the array.
5244 rv
= aParamsArray
->AddParams(params
);
5245 NS_ASSERT_SUCCESS(rv
);
5249 nsCookieService::UpdateCookieOldestTime(DBState
* aDBState
,
5252 if (aCookie
->LastAccessed() < aDBState
->cookieOldestTime
) {
5253 aDBState
->cookieOldestTime
= aCookie
->LastAccessed();
5258 nsCookieService::AddCookieToList(const nsCookieKey
&aKey
,
5261 mozIStorageBindingParamsArray
*aParamsArray
,
5264 NS_ASSERTION(!(aDBState
->dbConn
&& !aWriteToDB
&& aParamsArray
),
5265 "Not writing to the DB but have a params array?");
5266 NS_ASSERTION(!(!aDBState
->dbConn
&& aParamsArray
),
5267 "Do not have a DB connection but have a params array?");
5270 NS_WARNING("Attempting to AddCookieToList with null cookie");
5274 nsCookieEntry
*entry
= aDBState
->hostTable
.PutEntry(aKey
);
5275 NS_ASSERTION(entry
, "can't insert element into a null entry!");
5277 entry
->GetCookies().AppendElement(aCookie
);
5278 ++aDBState
->cookieCount
;
5280 // keep track of the oldest cookie, for when it comes time to purge
5281 UpdateCookieOldestTime(aDBState
, aCookie
);
5283 // if it's a non-session cookie and hasn't just been read from the db, write it out.
5284 if (aWriteToDB
&& !aCookie
->IsSession() && aDBState
->dbConn
) {
5285 mozIStorageAsyncStatement
*stmt
= aDBState
->stmtInsert
;
5286 nsCOMPtr
<mozIStorageBindingParamsArray
> paramsArray(aParamsArray
);
5288 stmt
->NewBindingParamsArray(getter_AddRefs(paramsArray
));
5290 bindCookieParameters(paramsArray
, aKey
, aCookie
);
5292 // If we were supplied an array to store parameters, we shouldn't call
5293 // executeAsync - someone up the stack will do this for us.
5294 if (!aParamsArray
) {
5295 DebugOnly
<nsresult
> rv
= stmt
->BindParameters(paramsArray
);
5296 NS_ASSERT_SUCCESS(rv
);
5297 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
5298 rv
= stmt
->ExecuteAsync(mDBState
->insertListener
, getter_AddRefs(handle
));
5299 NS_ASSERT_SUCCESS(rv
);
5305 nsCookieService::UpdateCookieInList(nsCookie
*aCookie
,
5306 int64_t aLastAccessed
,
5307 mozIStorageBindingParamsArray
*aParamsArray
)
5309 NS_ASSERTION(aCookie
, "Passing a null cookie to UpdateCookieInList!");
5311 // udpate the lastAccessed timestamp
5312 aCookie
->SetLastAccessed(aLastAccessed
);
5314 // if it's a non-session cookie, update it in the db too
5315 if (!aCookie
->IsSession() && aParamsArray
) {
5316 // Create our params holder.
5317 nsCOMPtr
<mozIStorageBindingParams
> params
;
5318 aParamsArray
->NewBindingParams(getter_AddRefs(params
));
5320 // Bind our parameters.
5321 DebugOnly
<nsresult
> rv
=
5322 params
->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
5324 NS_ASSERT_SUCCESS(rv
);
5326 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5328 NS_ASSERT_SUCCESS(rv
);
5330 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5332 NS_ASSERT_SUCCESS(rv
);
5334 rv
= params
->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5336 NS_ASSERT_SUCCESS(rv
);
5338 nsAutoCString suffix
;
5339 aCookie
->OriginAttributesRef().CreateSuffix(suffix
);
5340 rv
= params
->BindUTF8StringByName(
5341 NS_LITERAL_CSTRING("originAttributes"), suffix
);
5342 NS_ASSERT_SUCCESS(rv
);
5344 // Add our bound parameters to the array.
5345 rv
= aParamsArray
->AddParams(params
);
5346 NS_ASSERT_SUCCESS(rv
);
5351 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const
5353 size_t n
= aMallocSizeOf(this);
5355 if (mDefaultDBState
) {
5356 n
+= mDefaultDBState
->SizeOfIncludingThis(aMallocSizeOf
);
5358 if (mPrivateDBState
) {
5359 n
+= mPrivateDBState
->SizeOfIncludingThis(aMallocSizeOf
);
5365 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf
)
5368 nsCookieService::CollectReports(nsIHandleReportCallback
* aHandleReport
,
5369 nsISupports
* aData
, bool aAnonymize
)
5372 "explicit/cookie-service", KIND_HEAP
, UNITS_BYTES
,
5373 SizeOfIncludingThis(CookieServiceMallocSizeOf
),
5374 "Memory used by the cookie service.");