Line data Source code
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/. */
6 :
7 : #include "mozilla/Attributes.h"
8 : #include "mozilla/DebugOnly.h"
9 : #include "mozilla/Likely.h"
10 : #include "mozilla/Printf.h"
11 : #include "mozilla/SizePrintfMacros.h"
12 : #include "mozilla/Unused.h"
13 :
14 : #include "mozilla/net/CookieServiceChild.h"
15 : #include "mozilla/net/NeckoCommon.h"
16 :
17 : #include "nsCookieService.h"
18 : #include "nsContentUtils.h"
19 : #include "nsIServiceManager.h"
20 : #include "nsIScriptSecurityManager.h"
21 :
22 : #include "nsIIOService.h"
23 : #include "nsIPrefBranch.h"
24 : #include "nsIPrefService.h"
25 : #include "nsIScriptError.h"
26 : #include "nsICookiePermission.h"
27 : #include "nsIURI.h"
28 : #include "nsIURL.h"
29 : #include "nsIChannel.h"
30 : #include "nsIFile.h"
31 : #include "nsIObserverService.h"
32 : #include "nsILineInputStream.h"
33 : #include "nsIEffectiveTLDService.h"
34 : #include "nsIIDNService.h"
35 : #include "mozIThirdPartyUtil.h"
36 :
37 : #include "nsTArray.h"
38 : #include "nsCOMArray.h"
39 : #include "nsIMutableArray.h"
40 : #include "nsArrayEnumerator.h"
41 : #include "nsEnumeratorUtils.h"
42 : #include "nsAutoPtr.h"
43 : #include "nsReadableUtils.h"
44 : #include "nsCRT.h"
45 : #include "prprf.h"
46 : #include "nsNetUtil.h"
47 : #include "nsNetCID.h"
48 : #include "nsISimpleEnumerator.h"
49 : #include "nsIInputStream.h"
50 : #include "nsAppDirectoryServiceDefs.h"
51 : #include "nsNetCID.h"
52 : #include "mozilla/storage.h"
53 : #include "mozilla/AutoRestore.h"
54 : #include "mozilla/FileUtils.h"
55 : #include "mozilla/Telemetry.h"
56 : #include "nsIConsoleService.h"
57 : #include "nsVariant.h"
58 :
59 : using namespace mozilla;
60 : using namespace mozilla::net;
61 :
62 : // Create key from baseDomain that will access the default cookie namespace.
63 : // TODO: When we figure out what the API will look like for nsICookieManager{2}
64 : // on content processes (see bug 777620), change to use the appropriate app
65 : // namespace. For now those IDLs aren't supported on child processes.
66 : #define DEFAULT_APP_KEY(baseDomain) \
67 : nsCookieKey(baseDomain, OriginAttributes())
68 :
69 : /******************************************************************************
70 : * nsCookieService impl:
71 : * useful types & constants
72 : ******************************************************************************/
73 :
74 : static nsCookieService *gCookieService;
75 :
76 : // XXX_hack. See bug 178993.
77 : // This is a hack to hide HttpOnly cookies from older browsers
78 : #define HTTP_ONLY_PREFIX "#HttpOnly_"
79 :
80 : #define COOKIES_FILE "cookies.sqlite"
81 : #define COOKIES_SCHEMA_VERSION 8
82 :
83 : // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
84 : // ReadCookieDBListener::HandleResult
85 : #define IDX_NAME 0
86 : #define IDX_VALUE 1
87 : #define IDX_HOST 2
88 : #define IDX_PATH 3
89 : #define IDX_EXPIRY 4
90 : #define IDX_LAST_ACCESSED 5
91 : #define IDX_CREATION_TIME 6
92 : #define IDX_SECURE 7
93 : #define IDX_HTTPONLY 8
94 : #define IDX_BASE_DOMAIN 9
95 : #define IDX_ORIGIN_ATTRIBUTES 10
96 :
97 : #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
98 :
99 : static const int64_t kCookiePurgeAge =
100 : int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
101 :
102 : #define OLD_COOKIE_FILE_NAME "cookies.txt"
103 :
104 : #undef LIMIT
105 : #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
106 :
107 : #undef ADD_TEN_PERCENT
108 : #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
109 :
110 : // default limits for the cookie list. these can be tuned by the
111 : // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
112 : static const uint32_t kMaxNumberOfCookies = 3000;
113 : static const uint32_t kMaxCookiesPerHost = 150;
114 : static const uint32_t kMaxBytesPerCookie = 4096;
115 : static const uint32_t kMaxBytesPerPath = 1024;
116 :
117 : // pref string constants
118 : static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior";
119 : static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
120 : static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
121 : static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
122 : static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly";
123 : static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
124 :
125 : // For telemetry COOKIE_LEAVE_SECURE_ALONE
126 : #define BLOCKED_SECURE_SET_FROM_HTTP 0
127 : #define BLOCKED_DOWNGRADE_SECURE_INEXACT 1
128 : #define DOWNGRADE_SECURE_FROM_SECURE_INEXACT 2
129 : #define EVICTED_NEWER_INSECURE 3
130 : #define EVICTED_OLDEST_COOKIE 4
131 : #define EVICTED_PREFERRED_COOKIE 5
132 : #define EVICTING_SECURE_BLOCKED 6
133 : #define BLOCKED_DOWNGRADE_SECURE_EXACT 7
134 : #define DOWNGRADE_SECURE_FROM_SECURE_EXACT 8
135 :
136 : static void
137 : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
138 : const nsCookieKey &aKey,
139 : const nsCookie *aCookie);
140 :
141 : // struct for temporarily storing cookie attributes during header parsing
142 0 : struct nsCookieAttributes
143 : {
144 : nsAutoCString name;
145 : nsAutoCString value;
146 : nsAutoCString host;
147 : nsAutoCString path;
148 : nsAutoCString expires;
149 : nsAutoCString maxage;
150 : int64_t expiryTime;
151 : bool isSession;
152 : bool isSecure;
153 : bool isHttpOnly;
154 : };
155 :
156 : // stores the nsCookieEntry entryclass and an index into the cookie array
157 : // within that entryclass, for purposes of storing an iteration state that
158 : // points to a certain cookie.
159 : struct nsListIter
160 : {
161 : // default (non-initializing) constructor.
162 0 : nsListIter() = default;
163 :
164 : // explicit constructor to a given iterator state with entryclass 'aEntry'
165 : // and index 'aIndex'.
166 : explicit
167 0 : nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
168 0 : : entry(aEntry)
169 0 : , index(aIndex)
170 : {
171 0 : }
172 :
173 : // get the nsCookie * the iterator currently points to.
174 0 : nsCookie * Cookie() const
175 : {
176 0 : return entry->GetCookies()[index];
177 : }
178 :
179 : nsCookieEntry *entry;
180 : nsCookieEntry::IndexType index;
181 : };
182 :
183 : /******************************************************************************
184 : * Cookie logging handlers
185 : * used for logging in nsCookieService
186 : ******************************************************************************/
187 :
188 : // logging handlers
189 : #ifdef MOZ_LOGGING
190 : // in order to do logging, the following environment variables need to be set:
191 : //
192 : // set MOZ_LOG=cookie:3 -- shows rejected cookies
193 : // set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
194 : // set MOZ_LOG_FILE=cookie.log
195 : //
196 : #include "mozilla/Logging.h"
197 : #endif
198 :
199 : // define logging macros for convenience
200 : #define SET_COOKIE true
201 : #define GET_COOKIE false
202 :
203 : static LazyLogModule gCookieLog("cookie");
204 :
205 : #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
206 : #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
207 :
208 : #define COOKIE_LOGEVICTED(a, details) \
209 : PR_BEGIN_MACRO \
210 : if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \
211 : LogEvicted(a, details); \
212 : PR_END_MACRO
213 :
214 : #define COOKIE_LOGSTRING(lvl, fmt) \
215 : PR_BEGIN_MACRO \
216 : MOZ_LOG(gCookieLog, lvl, fmt); \
217 : MOZ_LOG(gCookieLog, lvl, ("\n")); \
218 : PR_END_MACRO
219 :
220 : static void
221 0 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
222 : {
223 : // if logging isn't enabled, return now to save cycles
224 0 : if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning))
225 0 : return;
226 :
227 0 : nsAutoCString spec;
228 0 : if (aHostURI)
229 0 : aHostURI->GetAsciiSpec(spec);
230 :
231 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,
232 : ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
233 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,("request URL: %s\n", spec.get()));
234 0 : if (aSetCookie)
235 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,("cookie string: %s\n", aCookieString));
236 :
237 : PRExplodedTime explodedTime;
238 0 : PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
239 : char timeString[40];
240 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
241 :
242 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,("current time: %s", timeString));
243 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,("rejected because %s\n", aReason));
244 0 : MOZ_LOG(gCookieLog, LogLevel::Warning,("\n"));
245 : }
246 :
247 : static void
248 0 : LogCookie(nsCookie *aCookie)
249 : {
250 : PRExplodedTime explodedTime;
251 0 : PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
252 : char timeString[40];
253 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
254 :
255 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("current time: %s", timeString));
256 :
257 0 : if (aCookie) {
258 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("----------------\n"));
259 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("name: %s\n", aCookie->Name().get()));
260 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("value: %s\n", aCookie->Value().get()));
261 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
262 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("path: %s\n", aCookie->Path().get()));
263 :
264 0 : PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
265 0 : PR_GMTParameters, &explodedTime);
266 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
267 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,
268 : ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
269 :
270 0 : PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
271 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
272 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("created: %s", timeString));
273 :
274 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
275 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
276 :
277 0 : nsAutoCString suffix;
278 0 : aCookie->OriginAttributesRef().CreateSuffix(suffix);
279 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("origin attributes: %s\n",
280 : suffix.IsEmpty() ? "{empty}" : suffix.get()));
281 : }
282 0 : }
283 :
284 : static void
285 0 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
286 : {
287 : // if logging isn't enabled, return now to save cycles
288 0 : if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
289 0 : return;
290 : }
291 :
292 0 : nsAutoCString spec;
293 0 : if (aHostURI)
294 0 : aHostURI->GetAsciiSpec(spec);
295 :
296 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,
297 : ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
298 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("request URL: %s\n", spec.get()));
299 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("cookie string: %s\n", aCookieString));
300 0 : if (aSetCookie)
301 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
302 :
303 0 : LogCookie(aCookie);
304 :
305 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
306 : }
307 :
308 : static void
309 0 : LogEvicted(nsCookie *aCookie, const char* details)
310 : {
311 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("===== COOKIE EVICTED =====\n"));
312 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("%s\n", details));
313 :
314 0 : LogCookie(aCookie);
315 :
316 0 : MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
317 0 : }
318 :
319 : // inline wrappers to make passing in nsCStrings easier
320 : static inline void
321 0 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, const char *aReason)
322 : {
323 0 : LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
324 0 : }
325 :
326 : static inline void
327 0 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, nsCookie *aCookie, bool aReplacing)
328 : {
329 0 : LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
330 0 : }
331 :
332 : #ifdef DEBUG
333 : #define NS_ASSERT_SUCCESS(res) \
334 : PR_BEGIN_MACRO \
335 : nsresult __rv = res; /* Do not evaluate |res| more than once! */ \
336 : if (NS_FAILED(__rv)) { \
337 : SmprintfPointer msg = mozilla::Smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%" PRIX32, \
338 : #res, static_cast<uint32_t>(__rv)); \
339 : NS_ASSERTION(NS_SUCCEEDED(__rv), msg.get()); \
340 : } \
341 : PR_END_MACRO
342 : #else
343 : #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
344 : #endif
345 :
346 : /******************************************************************************
347 : * DBListenerErrorHandler impl:
348 : * Parent class for our async storage listeners that handles the logging of
349 : * errors.
350 : ******************************************************************************/
351 1 : class DBListenerErrorHandler : public mozIStorageStatementCallback
352 : {
353 : protected:
354 4 : explicit DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
355 : RefPtr<DBState> mDBState;
356 : virtual const char *GetOpType() = 0;
357 :
358 : public:
359 0 : NS_IMETHOD HandleError(mozIStorageError* aError) override
360 : {
361 0 : if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
362 0 : int32_t result = -1;
363 0 : aError->GetResult(&result);
364 :
365 0 : nsAutoCString message;
366 0 : aError->GetMessage(message);
367 0 : COOKIE_LOGSTRING(LogLevel::Warning,
368 : ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
369 : "performing operation '%s' with message '%s'; rebuilding database.",
370 : result, GetOpType(), message.get()));
371 : }
372 :
373 : // Rebuild the database.
374 0 : gCookieService->HandleCorruptDB(mDBState);
375 :
376 0 : return NS_OK;
377 : }
378 : };
379 :
380 : /******************************************************************************
381 : * InsertCookieDBListener impl:
382 : * mozIStorageStatementCallback used to track asynchronous insertion operations.
383 : ******************************************************************************/
384 : class InsertCookieDBListener final : public DBListenerErrorHandler
385 : {
386 : private:
387 0 : const char *GetOpType() override { return "INSERT"; }
388 :
389 0 : ~InsertCookieDBListener() = default;
390 :
391 : public:
392 : NS_DECL_ISUPPORTS
393 :
394 1 : explicit InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
395 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*) override
396 : {
397 0 : NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
398 0 : return NS_OK;
399 : }
400 0 : NS_IMETHOD HandleCompletion(uint16_t aReason) override
401 : {
402 : // If we were rebuilding the db and we succeeded, make our corruptFlag say
403 : // so.
404 0 : if (mDBState->corruptFlag == DBState::REBUILDING &&
405 : aReason == mozIStorageStatementCallback::REASON_FINISHED) {
406 0 : COOKIE_LOGSTRING(LogLevel::Debug,
407 : ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
408 0 : mDBState->corruptFlag = DBState::OK;
409 : }
410 0 : return NS_OK;
411 : }
412 : };
413 :
414 4 : NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
415 :
416 : /******************************************************************************
417 : * UpdateCookieDBListener impl:
418 : * mozIStorageStatementCallback used to track asynchronous update operations.
419 : ******************************************************************************/
420 : class UpdateCookieDBListener final : public DBListenerErrorHandler
421 : {
422 : private:
423 0 : const char *GetOpType() override { return "UPDATE"; }
424 :
425 0 : ~UpdateCookieDBListener() = default;
426 :
427 : public:
428 : NS_DECL_ISUPPORTS
429 :
430 1 : explicit UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
431 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*) override
432 : {
433 0 : NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
434 0 : return NS_OK;
435 : }
436 0 : NS_IMETHOD HandleCompletion(uint16_t aReason) override
437 : {
438 0 : return NS_OK;
439 : }
440 : };
441 :
442 4 : NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
443 :
444 : /******************************************************************************
445 : * RemoveCookieDBListener impl:
446 : * mozIStorageStatementCallback used to track asynchronous removal operations.
447 : ******************************************************************************/
448 : class RemoveCookieDBListener final : public DBListenerErrorHandler
449 : {
450 : private:
451 0 : const char *GetOpType() override { return "REMOVE"; }
452 :
453 0 : ~RemoveCookieDBListener() = default;
454 :
455 : public:
456 : NS_DECL_ISUPPORTS
457 :
458 1 : explicit RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
459 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*) override
460 : {
461 0 : NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
462 0 : return NS_OK;
463 : }
464 1 : NS_IMETHOD HandleCompletion(uint16_t aReason) override
465 : {
466 1 : return NS_OK;
467 : }
468 : };
469 :
470 12 : NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
471 :
472 : /******************************************************************************
473 : * ReadCookieDBListener impl:
474 : * mozIStorageStatementCallback used to track asynchronous removal operations.
475 : ******************************************************************************/
476 : class ReadCookieDBListener final : public DBListenerErrorHandler
477 : {
478 : private:
479 0 : const char *GetOpType() override { return "READ"; }
480 : bool mCanceled;
481 :
482 1 : ~ReadCookieDBListener() = default;
483 :
484 : public:
485 : NS_DECL_ISUPPORTS
486 :
487 1 : explicit ReadCookieDBListener(DBState* dbState)
488 1 : : DBListenerErrorHandler(dbState)
489 1 : , mCanceled(false)
490 : {
491 1 : }
492 :
493 0 : void Cancel() { mCanceled = true; }
494 :
495 0 : NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) override
496 : {
497 0 : nsCOMPtr<mozIStorageRow> row;
498 :
499 : while (true) {
500 0 : DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
501 0 : NS_ASSERT_SUCCESS(rv);
502 :
503 0 : if (!row)
504 0 : break;
505 :
506 0 : CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
507 0 : row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
508 :
509 0 : nsAutoCString suffix;
510 0 : row->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
511 0 : DebugOnly<bool> success = tuple->key.mOriginAttributes.PopulateFromSuffix(suffix);
512 0 : MOZ_ASSERT(success);
513 :
514 : tuple->cookie =
515 0 : gCookieService->GetCookieFromRow(row, tuple->key.mOriginAttributes);
516 0 : }
517 :
518 0 : return NS_OK;
519 : }
520 1 : NS_IMETHOD HandleCompletion(uint16_t aReason) override
521 : {
522 : // Process the completion of the read operation. If we have been canceled,
523 : // we cannot assume that the cookieservice still has an open connection
524 : // or that it even refers to the same database, so we must return early.
525 : // Conversely, the cookieservice guarantees that if we have not been
526 : // canceled, the database connection is still alive and we can safely
527 : // operate on it.
528 :
529 1 : if (mCanceled) {
530 : // We may receive a REASON_FINISHED after being canceled;
531 : // tweak the reason accordingly.
532 0 : aReason = mozIStorageStatementCallback::REASON_CANCELED;
533 : }
534 :
535 1 : switch (aReason) {
536 : case mozIStorageStatementCallback::REASON_FINISHED:
537 1 : gCookieService->AsyncReadComplete();
538 1 : break;
539 : case mozIStorageStatementCallback::REASON_CANCELED:
540 : // Nothing more to do here. The partially read data has already been
541 : // thrown away.
542 0 : COOKIE_LOGSTRING(LogLevel::Debug, ("Read canceled"));
543 0 : break;
544 : case mozIStorageStatementCallback::REASON_ERROR:
545 : // Nothing more to do here. DBListenerErrorHandler::HandleError()
546 : // can handle it.
547 0 : COOKIE_LOGSTRING(LogLevel::Debug, ("Read error"));
548 0 : break;
549 : default:
550 0 : NS_NOTREACHED("invalid reason");
551 : }
552 1 : return NS_OK;
553 : }
554 : };
555 :
556 9 : NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
557 :
558 : /******************************************************************************
559 : * CloseCookieDBListener imp:
560 : * Static mozIStorageCompletionCallback used to notify when the database is
561 : * successfully closed.
562 : ******************************************************************************/
563 : class CloseCookieDBListener final : public mozIStorageCompletionCallback
564 : {
565 0 : ~CloseCookieDBListener() = default;
566 :
567 : public:
568 1 : explicit CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
569 : RefPtr<DBState> mDBState;
570 : NS_DECL_ISUPPORTS
571 :
572 0 : NS_IMETHOD Complete(nsresult, nsISupports*) override
573 : {
574 0 : gCookieService->HandleDBClosed(mDBState);
575 0 : return NS_OK;
576 : }
577 : };
578 :
579 4 : NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
580 :
581 : namespace {
582 :
583 3 : class AppClearDataObserver final : public nsIObserver {
584 :
585 : ~AppClearDataObserver() = default;
586 :
587 : public:
588 : NS_DECL_ISUPPORTS
589 :
590 : // nsIObserver implementation.
591 : NS_IMETHOD
592 0 : Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
593 : {
594 0 : MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA));
595 :
596 0 : MOZ_ASSERT(XRE_IsParentProcess());
597 :
598 : nsCOMPtr<nsICookieManager2> cookieManager
599 0 : = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
600 0 : MOZ_ASSERT(cookieManager);
601 :
602 0 : return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString());
603 : }
604 : };
605 :
606 18 : NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
607 :
608 : } // namespace
609 :
610 : size_t
611 0 : nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
612 : {
613 0 : return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
614 : }
615 :
616 : size_t
617 0 : nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
618 : {
619 0 : size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
620 :
621 0 : amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
622 0 : for (uint32_t i = 0; i < mCookies.Length(); ++i) {
623 0 : amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
624 : }
625 :
626 0 : return amount;
627 : }
628 :
629 : size_t
630 0 : CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
631 : {
632 0 : size_t amount = 0;
633 :
634 0 : amount += key.SizeOfExcludingThis(aMallocSizeOf);
635 0 : amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
636 :
637 0 : return amount;
638 : }
639 :
640 : size_t
641 0 : DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
642 : {
643 0 : size_t amount = 0;
644 :
645 0 : amount += aMallocSizeOf(this);
646 0 : amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
647 0 : amount += hostArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
648 0 : for (uint32_t i = 0; i < hostArray.Length(); ++i) {
649 0 : amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
650 : }
651 0 : amount += readSet.SizeOfExcludingThis(aMallocSizeOf);
652 :
653 0 : return amount;
654 : }
655 :
656 : /******************************************************************************
657 : * nsCookieService impl:
658 : * singleton instance ctor/dtor methods
659 : ******************************************************************************/
660 :
661 : nsICookieService*
662 1 : nsCookieService::GetXPCOMSingleton()
663 : {
664 1 : if (IsNeckoChild())
665 0 : return CookieServiceChild::GetSingleton();
666 :
667 1 : return GetSingleton();
668 : }
669 :
670 : nsCookieService*
671 1 : nsCookieService::GetSingleton()
672 : {
673 1 : NS_ASSERTION(!IsNeckoChild(), "not a parent process");
674 :
675 1 : if (gCookieService) {
676 0 : NS_ADDREF(gCookieService);
677 0 : return gCookieService;
678 : }
679 :
680 : // Create a new singleton nsCookieService.
681 : // We AddRef only once since XPCOM has rules about the ordering of module
682 : // teardowns - by the time our module destructor is called, it's too late to
683 : // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
684 : // cycles have already been completed and would result in serious leaks.
685 : // See bug 209571.
686 1 : gCookieService = new nsCookieService();
687 1 : if (gCookieService) {
688 1 : NS_ADDREF(gCookieService);
689 1 : if (NS_FAILED(gCookieService->Init())) {
690 0 : NS_RELEASE(gCookieService);
691 : }
692 : }
693 :
694 1 : return gCookieService;
695 : }
696 :
697 : /* static */ void
698 3 : nsCookieService::AppClearDataObserverInit()
699 : {
700 6 : nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
701 6 : nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
702 6 : observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA,
703 6 : /* ownsWeak= */ false);
704 3 : }
705 :
706 : /******************************************************************************
707 : * nsCookieService impl:
708 : * public methods
709 : ******************************************************************************/
710 :
711 73 : NS_IMPL_ISUPPORTS(nsCookieService,
712 : nsICookieService,
713 : nsICookieManager,
714 : nsICookieManager2,
715 : nsIObserver,
716 : nsISupportsWeakReference,
717 : nsIMemoryReporter)
718 :
719 1 : nsCookieService::nsCookieService()
720 : : mDBState(nullptr)
721 : , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
722 : , mThirdPartySession(false)
723 : , mLeaveSecureAlone(true)
724 : , mMaxNumberOfCookies(kMaxNumberOfCookies)
725 : , mMaxCookiesPerHost(kMaxCookiesPerHost)
726 1 : , mCookiePurgeAge(kCookiePurgeAge)
727 : {
728 1 : }
729 :
730 : nsresult
731 1 : nsCookieService::Init()
732 : {
733 : nsresult rv;
734 1 : mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
735 1 : NS_ENSURE_SUCCESS(rv, rv);
736 :
737 1 : mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
738 1 : NS_ENSURE_SUCCESS(rv, rv);
739 :
740 1 : mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
741 1 : NS_ENSURE_SUCCESS(rv, rv);
742 :
743 : // init our pref and observer
744 2 : nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
745 1 : if (prefBranch) {
746 1 : prefBranch->AddObserver(kPrefCookieBehavior, this, true);
747 1 : prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
748 1 : prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
749 1 : prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
750 1 : prefBranch->AddObserver(kPrefThirdPartySession, this, true);
751 1 : prefBranch->AddObserver(kCookieLeaveSecurityAlone, this, true);
752 1 : PrefChanged(prefBranch);
753 : }
754 :
755 1 : mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
756 1 : NS_ENSURE_SUCCESS(rv, rv);
757 :
758 : // Init our default, and possibly private DBStates.
759 1 : InitDBStates();
760 :
761 1 : RegisterWeakMemoryReporter(this);
762 :
763 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
764 1 : NS_ENSURE_STATE(os);
765 1 : os->AddObserver(this, "profile-before-change", true);
766 1 : os->AddObserver(this, "profile-do-change", true);
767 1 : os->AddObserver(this, "last-pb-context-exited", true);
768 :
769 1 : mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
770 1 : if (!mPermissionService) {
771 0 : NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
772 0 : COOKIE_LOGSTRING(LogLevel::Warning, ("Init(): nsICookiePermission implementation not available"));
773 : }
774 :
775 1 : return NS_OK;
776 : }
777 :
778 : void
779 1 : nsCookieService::InitDBStates()
780 : {
781 1 : NS_ASSERTION(!mDBState, "already have a DBState");
782 1 : NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
783 1 : NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
784 :
785 : // Create a new default DBState and set our current one.
786 1 : mDefaultDBState = new DBState();
787 1 : mDBState = mDefaultDBState;
788 :
789 1 : mPrivateDBState = new DBState();
790 :
791 : // Get our cookie file.
792 1 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
793 2 : getter_AddRefs(mDefaultDBState->cookieFile));
794 1 : if (NS_FAILED(rv)) {
795 : // We've already set up our DBStates appropriately; nothing more to do.
796 0 : COOKIE_LOGSTRING(LogLevel::Warning,
797 : ("InitDBStates(): couldn't get cookie file"));
798 0 : return;
799 : }
800 1 : mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
801 :
802 : // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
803 : // do so.
804 1 : OpenDBResult result = TryInitDB(false);
805 1 : if (result == RESULT_RETRY) {
806 : // Database may be corrupt. Synchronously close the connection, clean up the
807 : // default DBState, and try again.
808 0 : COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
809 0 : CleanupCachedStatements();
810 0 : CleanupDefaultDBConnection();
811 0 : result = TryInitDB(true);
812 0 : if (result == RESULT_RETRY) {
813 : // We're done. Change the code to failure so we clean up below.
814 0 : result = RESULT_FAILURE;
815 : }
816 : }
817 :
818 1 : if (result == RESULT_FAILURE) {
819 0 : COOKIE_LOGSTRING(LogLevel::Warning,
820 : ("InitDBStates(): TryInitDB() failed, closing connection"));
821 :
822 : // Connection failure is unrecoverable. Clean up our connection. We can run
823 : // fine without persistent storage -- e.g. if there's no profile.
824 0 : CleanupCachedStatements();
825 0 : CleanupDefaultDBConnection();
826 : }
827 : }
828 :
829 : namespace {
830 :
831 0 : class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction
832 : {
833 : ~ConvertAppIdToOriginAttrsSQLFunction() = default;
834 :
835 : NS_DECL_ISUPPORTS
836 : NS_DECL_MOZISTORAGEFUNCTION
837 : };
838 :
839 0 : NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
840 :
841 : NS_IMETHODIMP
842 0 : ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
843 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
844 : {
845 : nsresult rv;
846 : int32_t inIsolatedMozBrowser;
847 :
848 0 : rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
849 0 : NS_ENSURE_SUCCESS(rv, rv);
850 :
851 : // Create an originAttributes object by inIsolatedMozBrowser.
852 : // Then create the originSuffix string from this object.
853 : OriginAttributes attrs(nsIScriptSecurityManager::NO_APP_ID,
854 0 : (inIsolatedMozBrowser ? true : false));
855 0 : nsAutoCString suffix;
856 0 : attrs.CreateSuffix(suffix);
857 :
858 0 : RefPtr<nsVariant> outVar(new nsVariant());
859 0 : rv = outVar->SetAsAUTF8String(suffix);
860 0 : NS_ENSURE_SUCCESS(rv, rv);
861 :
862 0 : outVar.forget(aResult);
863 0 : return NS_OK;
864 : }
865 :
866 0 : class SetAppIdFromOriginAttributesSQLFunction final : public mozIStorageFunction
867 : {
868 : ~SetAppIdFromOriginAttributesSQLFunction() = default;
869 :
870 : NS_DECL_ISUPPORTS
871 : NS_DECL_MOZISTORAGEFUNCTION
872 : };
873 :
874 0 : NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
875 :
876 : NS_IMETHODIMP
877 0 : SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
878 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
879 : {
880 : nsresult rv;
881 0 : nsAutoCString suffix;
882 0 : OriginAttributes attrs;
883 :
884 0 : rv = aFunctionArguments->GetUTF8String(0, suffix);
885 0 : NS_ENSURE_SUCCESS(rv, rv);
886 0 : bool success = attrs.PopulateFromSuffix(suffix);
887 0 : NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
888 :
889 0 : RefPtr<nsVariant> outVar(new nsVariant());
890 0 : rv = outVar->SetAsInt32(attrs.mAppId);
891 0 : NS_ENSURE_SUCCESS(rv, rv);
892 :
893 0 : outVar.forget(aResult);
894 0 : return NS_OK;
895 : }
896 :
897 0 : class SetInBrowserFromOriginAttributesSQLFunction final :
898 : public mozIStorageFunction
899 : {
900 : ~SetInBrowserFromOriginAttributesSQLFunction() = default;
901 :
902 : NS_DECL_ISUPPORTS
903 : NS_DECL_MOZISTORAGEFUNCTION
904 : };
905 :
906 0 : NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
907 : mozIStorageFunction);
908 :
909 : NS_IMETHODIMP
910 0 : SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
911 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
912 : {
913 : nsresult rv;
914 0 : nsAutoCString suffix;
915 0 : OriginAttributes attrs;
916 :
917 0 : rv = aFunctionArguments->GetUTF8String(0, suffix);
918 0 : NS_ENSURE_SUCCESS(rv, rv);
919 0 : bool success = attrs.PopulateFromSuffix(suffix);
920 0 : NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
921 :
922 0 : RefPtr<nsVariant> outVar(new nsVariant());
923 0 : rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
924 0 : NS_ENSURE_SUCCESS(rv, rv);
925 :
926 0 : outVar.forget(aResult);
927 0 : return NS_OK;
928 : }
929 :
930 : } // namespace
931 :
932 : /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
933 : * move the existing database file out of the way and create a new one.
934 : *
935 : * @returns RESULT_OK if opening or creating the database succeeded;
936 : * RESULT_RETRY if the database cannot be opened, is corrupt, or some
937 : * other failure occurred that might be resolved by recreating the
938 : * database; or RESULT_FAILED if there was an unrecoverable error and
939 : * we must run without a database.
940 : *
941 : * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
942 : * cleanup of the default DBState.
943 : */
944 : OpenDBResult
945 1 : nsCookieService::TryInitDB(bool aRecreateDB)
946 : {
947 1 : NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
948 1 : NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
949 1 : NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
950 1 : NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
951 :
952 : // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
953 : // want to delete it outright, since it may be useful for debugging purposes,
954 : // so we move it out of the way.
955 : nsresult rv;
956 1 : if (aRecreateDB) {
957 0 : nsCOMPtr<nsIFile> backupFile;
958 0 : mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
959 0 : rv = backupFile->MoveToNative(nullptr,
960 0 : NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
961 0 : NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
962 : }
963 :
964 : // This block provides scope for the Telemetry AutoTimer
965 : {
966 : Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
967 2 : telemetry;
968 1 : ReadAheadFile(mDefaultDBState->cookieFile);
969 :
970 : // open a connection to the cookie database, and only cache our connection
971 : // and statements upon success. The connection is opened unshared to eliminate
972 : // cache contention between the main and background threads.
973 2 : rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
974 2 : getter_AddRefs(mDefaultDBState->dbConn));
975 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
976 : }
977 :
978 : // Set up our listeners.
979 2 : mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
980 2 : mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
981 2 : mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
982 2 : mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
983 :
984 : // Grow cookie db in 512KB increments
985 1 : mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
986 :
987 1 : bool tableExists = false;
988 4 : mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
989 3 : &tableExists);
990 1 : if (!tableExists) {
991 0 : rv = CreateTable();
992 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
993 :
994 : } else {
995 : // table already exists; check the schema version before reading
996 : int32_t dbSchemaVersion;
997 1 : rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
998 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
999 :
1000 : // Start a transaction for the whole migration block.
1001 2 : mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
1002 :
1003 1 : switch (dbSchemaVersion) {
1004 : // Upgrading.
1005 : // Every time you increment the database schema, you need to implement
1006 : // the upgrading code from the previous version to the new one. If migration
1007 : // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
1008 : // the original database will be saved, in the hopes that we might one day
1009 : // see it and fix it.
1010 : case 1:
1011 : {
1012 : // Add the lastAccessed column to the table.
1013 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1014 0 : "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
1015 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1016 : }
1017 : // Fall through to the next upgrade.
1018 : MOZ_FALLTHROUGH;
1019 :
1020 : case 2:
1021 : {
1022 : // Add the baseDomain column and index to the table.
1023 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1024 0 : "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
1025 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1026 :
1027 : // Compute the baseDomains for the table. This must be done eagerly
1028 : // otherwise we won't be able to synchronously read in individual
1029 : // domains on demand.
1030 0 : const int64_t SCHEMA2_IDX_ID = 0;
1031 0 : const int64_t SCHEMA2_IDX_HOST = 1;
1032 0 : nsCOMPtr<mozIStorageStatement> select;
1033 0 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
1034 0 : "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
1035 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1036 :
1037 0 : nsCOMPtr<mozIStorageStatement> update;
1038 0 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
1039 : "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
1040 0 : getter_AddRefs(update));
1041 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1042 :
1043 0 : nsCString baseDomain, host;
1044 : bool hasResult;
1045 : while (true) {
1046 0 : rv = select->ExecuteStep(&hasResult);
1047 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1048 :
1049 0 : if (!hasResult)
1050 0 : break;
1051 :
1052 0 : int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
1053 0 : select->GetUTF8String(SCHEMA2_IDX_HOST, host);
1054 :
1055 0 : rv = GetBaseDomainFromHost(host, baseDomain);
1056 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1057 :
1058 0 : mozStorageStatementScoper scoper(update);
1059 :
1060 0 : rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
1061 0 : baseDomain);
1062 0 : NS_ASSERT_SUCCESS(rv);
1063 0 : rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1064 0 : id);
1065 0 : NS_ASSERT_SUCCESS(rv);
1066 :
1067 0 : rv = update->ExecuteStep(&hasResult);
1068 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1069 0 : }
1070 :
1071 : // Create an index on baseDomain.
1072 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1073 0 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
1074 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1075 : }
1076 : // Fall through to the next upgrade.
1077 : MOZ_FALLTHROUGH;
1078 :
1079 : case 3:
1080 : {
1081 : // Add the creationTime column to the table, and create a unique index
1082 : // on (name, host, path). Before we do this, we have to purge the table
1083 : // of expired cookies such that we know that the (name, host, path)
1084 : // index is truly unique -- otherwise we can't create the index. Note
1085 : // that we can't just execute a statement to delete all rows where the
1086 : // expiry column is in the past -- doing so would rely on the clock
1087 : // (both now and when previous cookies were set) being monotonic.
1088 :
1089 : // Select the whole table, and order by the fields we're interested in.
1090 : // This means we can simply do a linear traversal of the results and
1091 : // check for duplicates as we go.
1092 0 : const int64_t SCHEMA3_IDX_ID = 0;
1093 0 : const int64_t SCHEMA3_IDX_NAME = 1;
1094 0 : const int64_t SCHEMA3_IDX_HOST = 2;
1095 0 : const int64_t SCHEMA3_IDX_PATH = 3;
1096 0 : nsCOMPtr<mozIStorageStatement> select;
1097 0 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
1098 : "SELECT id, name, host, path FROM moz_cookies "
1099 : "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
1100 0 : getter_AddRefs(select));
1101 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1102 :
1103 0 : nsCOMPtr<mozIStorageStatement> deleteExpired;
1104 0 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
1105 : "DELETE FROM moz_cookies WHERE id = :id"),
1106 0 : getter_AddRefs(deleteExpired));
1107 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1108 :
1109 : // Read the first row.
1110 : bool hasResult;
1111 0 : rv = select->ExecuteStep(&hasResult);
1112 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1113 :
1114 0 : if (hasResult) {
1115 0 : nsCString name1, host1, path1;
1116 0 : int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
1117 0 : select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
1118 0 : select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
1119 0 : select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
1120 :
1121 0 : nsCString name2, host2, path2;
1122 : while (true) {
1123 : // Read the second row.
1124 0 : rv = select->ExecuteStep(&hasResult);
1125 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1126 :
1127 0 : if (!hasResult)
1128 0 : break;
1129 :
1130 0 : int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
1131 0 : select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
1132 0 : select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
1133 0 : select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
1134 :
1135 : // If the two rows match in (name, host, path), we know the earlier
1136 : // row has an earlier expiry time. Delete it.
1137 0 : if (name1 == name2 && host1 == host2 && path1 == path2) {
1138 0 : mozStorageStatementScoper scoper(deleteExpired);
1139 :
1140 0 : rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1141 0 : id1);
1142 0 : NS_ASSERT_SUCCESS(rv);
1143 :
1144 0 : rv = deleteExpired->ExecuteStep(&hasResult);
1145 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1146 : }
1147 :
1148 : // Make the second row the first for the next iteration.
1149 0 : name1 = name2;
1150 0 : host1 = host2;
1151 0 : path1 = path2;
1152 0 : id1 = id2;
1153 0 : }
1154 : }
1155 :
1156 : // Add the creationTime column to the table.
1157 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1158 0 : "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
1159 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1160 :
1161 : // Copy the id of each row into the new creationTime column.
1162 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1163 : "UPDATE moz_cookies SET creationTime = "
1164 0 : "(SELECT id WHERE id = moz_cookies.id)"));
1165 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1166 :
1167 : // Create a unique index on (name, host, path) to allow fast lookup.
1168 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1169 : "CREATE UNIQUE INDEX moz_uniqueid "
1170 0 : "ON moz_cookies (name, host, path)"));
1171 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1172 : }
1173 : // Fall through to the next upgrade.
1174 : MOZ_FALLTHROUGH;
1175 :
1176 : case 4:
1177 : {
1178 : // We need to add appId/inBrowserElement, plus change a constraint on
1179 : // the table (unique entries now include appId/inBrowserElement):
1180 : // this requires creating a new table and copying the data to it. We
1181 : // then rename the new table to the old name.
1182 : //
1183 : // Why we made this change: appId/inBrowserElement allow "cookie jars"
1184 : // for Firefox OS. We create a separate cookie namespace per {appId,
1185 : // inBrowserElement}. When upgrading, we convert existing cookies
1186 : // (which imply we're on desktop/mobile) to use {0, false}, as that is
1187 : // the only namespace used by a non-Firefox-OS implementation.
1188 :
1189 : // Rename existing table
1190 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1191 0 : "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1192 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1193 :
1194 : // Drop existing index (CreateTable will create new one for new table)
1195 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1196 0 : "DROP INDEX moz_basedomain"));
1197 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1198 :
1199 : // Create new table (with new fields and new unique constraint)
1200 0 : rv = CreateTableForSchemaVersion5();
1201 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1202 :
1203 : // Copy data from old table, using appId/inBrowser=0 for existing rows
1204 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1205 : "INSERT INTO moz_cookies "
1206 : "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
1207 : " lastAccessed, creationTime, isSecure, isHttpOnly) "
1208 : "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
1209 : " lastAccessed, creationTime, isSecure, isHttpOnly "
1210 0 : "FROM moz_cookies_old"));
1211 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1212 :
1213 : // Drop old table
1214 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1215 0 : "DROP TABLE moz_cookies_old"));
1216 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1217 :
1218 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1219 : ("Upgraded database to schema version 5"));
1220 : }
1221 : // Fall through to the next upgrade.
1222 : MOZ_FALLTHROUGH;
1223 :
1224 : case 5:
1225 : {
1226 : // Change in the version: Replace the columns |appId| and
1227 : // |inBrowserElement| by a single column |originAttributes|.
1228 : //
1229 : // Why we made this change: FxOS new security model (NSec) encapsulates
1230 : // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to make
1231 : // it easier to modify the contents of this structure in the future.
1232 : //
1233 : // We do the migration in several steps:
1234 : // 1. Rename the old table.
1235 : // 2. Create a new table.
1236 : // 3. Copy data from the old table to the new table; convert appId and
1237 : // inBrowserElement to originAttributes in the meantime.
1238 :
1239 : // Rename existing table.
1240 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1241 0 : "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1242 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1243 :
1244 : // Drop existing index (CreateTable will create new one for new table).
1245 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1246 0 : "DROP INDEX moz_basedomain"));
1247 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1248 :
1249 : // Create new table with new fields and new unique constraint.
1250 0 : rv = CreateTableForSchemaVersion6();
1251 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1252 :
1253 : // Copy data from old table without the two deprecated columns appId and
1254 : // inBrowserElement.
1255 : nsCOMPtr<mozIStorageFunction>
1256 0 : convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction());
1257 0 : NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
1258 :
1259 0 : NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
1260 : "CONVERT_TO_ORIGIN_ATTRIBUTES");
1261 :
1262 0 : rv = mDefaultDBState->dbConn->CreateFunction(convertToOriginAttrsName,
1263 0 : 2, convertToOriginAttrs);
1264 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1265 :
1266 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1267 : "INSERT INTO moz_cookies "
1268 : "(baseDomain, originAttributes, name, value, host, path, expiry,"
1269 : " lastAccessed, creationTime, isSecure, isHttpOnly) "
1270 : "SELECT baseDomain, "
1271 : " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
1272 : " name, value, host, path, expiry, lastAccessed, creationTime, "
1273 : " isSecure, isHttpOnly "
1274 0 : "FROM moz_cookies_old"));
1275 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1276 :
1277 0 : rv = mDefaultDBState->dbConn->RemoveFunction(convertToOriginAttrsName);
1278 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1279 :
1280 : // Drop old table
1281 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1282 0 : "DROP TABLE moz_cookies_old"));
1283 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1284 :
1285 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1286 : ("Upgraded database to schema version 6"));
1287 : }
1288 : MOZ_FALLTHROUGH;
1289 :
1290 : case 6:
1291 : {
1292 : // We made a mistake in schema version 6. We cannot remove expected
1293 : // columns of any version (checked in the default case) from cookie
1294 : // database, because doing this would destroy the possibility of
1295 : // downgrading database.
1296 : //
1297 : // This version simply restores appId and inBrowserElement columns in
1298 : // order to fix downgrading issue even though these two columns are no
1299 : // longer used in the latest schema.
1300 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1301 0 : "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
1302 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1303 :
1304 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1305 0 : "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
1306 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1307 :
1308 : // Compute and populate the values of appId and inBrwoserElement from
1309 : // originAttributes.
1310 : nsCOMPtr<mozIStorageFunction>
1311 0 : setAppId(new SetAppIdFromOriginAttributesSQLFunction());
1312 0 : NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
1313 :
1314 0 : NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
1315 :
1316 0 : rv = mDefaultDBState->dbConn->CreateFunction(setAppIdName, 1, setAppId);
1317 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1318 :
1319 : nsCOMPtr<mozIStorageFunction>
1320 0 : setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
1321 0 : NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
1322 :
1323 0 : NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
1324 :
1325 0 : rv = mDefaultDBState->dbConn->CreateFunction(setInBrowserName, 1,
1326 0 : setInBrowser);
1327 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1328 :
1329 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1330 : "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
1331 : "inBrowserElement = SET_IN_BROWSER(originAttributes);"
1332 0 : ));
1333 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1334 :
1335 0 : rv = mDefaultDBState->dbConn->RemoveFunction(setAppIdName);
1336 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1337 :
1338 0 : rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName);
1339 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1340 :
1341 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1342 : ("Upgraded database to schema version 7"));
1343 : }
1344 : MOZ_FALLTHROUGH;
1345 :
1346 : case 7:
1347 : {
1348 : // Remove the appId field from moz_cookies.
1349 : //
1350 : // Unfortunately sqlite doesn't support dropping columns using ALTER
1351 : // TABLE, so we need to go through the procedure documented in
1352 : // https://www.sqlite.org/lang_altertable.html.
1353 :
1354 : // Drop existing index
1355 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1356 0 : "DROP INDEX moz_basedomain"));
1357 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1358 :
1359 : // Create a new_moz_cookies table without the appId field.
1360 0 : rv = CreateTableWorker("new_moz_cookies");
1361 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1362 :
1363 : // Move the data over.
1364 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1365 : "INSERT INTO new_moz_cookies ("
1366 : "id, "
1367 : "baseDomain, "
1368 : "originAttributes, "
1369 : "name, "
1370 : "value, "
1371 : "host, "
1372 : "path, "
1373 : "expiry, "
1374 : "lastAccessed, "
1375 : "creationTime, "
1376 : "isSecure, "
1377 : "isHttpOnly, "
1378 : "inBrowserElement "
1379 : ") SELECT "
1380 : "id, "
1381 : "baseDomain, "
1382 : "originAttributes, "
1383 : "name, "
1384 : "value, "
1385 : "host, "
1386 : "path, "
1387 : "expiry, "
1388 : "lastAccessed, "
1389 : "creationTime, "
1390 : "isSecure, "
1391 : "isHttpOnly, "
1392 : "inBrowserElement "
1393 0 : "FROM moz_cookies;"));
1394 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1395 :
1396 : // Drop the old table
1397 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1398 0 : "DROP TABLE moz_cookies;"));
1399 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1400 :
1401 : // Rename new_moz_cookies to moz_cookies.
1402 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1403 0 : "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
1404 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1405 :
1406 : // Recreate our index.
1407 0 : rv = CreateIndex();
1408 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1409 :
1410 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1411 : ("Upgraded database to schema version 8"));
1412 : }
1413 :
1414 : // No more upgrades. Update the schema version.
1415 0 : rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1416 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1417 : MOZ_FALLTHROUGH;
1418 :
1419 : case COOKIES_SCHEMA_VERSION:
1420 1 : break;
1421 :
1422 : case 0:
1423 : {
1424 0 : NS_WARNING("couldn't get schema version!");
1425 :
1426 : // the table may be usable; someone might've just clobbered the schema
1427 : // version. we can treat this case like a downgrade using the codepath
1428 : // below, by verifying the columns we care about are all there. for now,
1429 : // re-set the schema version in the db, in case the checks succeed (if
1430 : // they don't, we're dropping the table anyway).
1431 0 : rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1432 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1433 : }
1434 : // fall through to downgrade check
1435 : MOZ_FALLTHROUGH;
1436 :
1437 : // downgrading.
1438 : // if columns have been added to the table, we can still use the ones we
1439 : // understand safely. if columns have been deleted or altered, just
1440 : // blow away the table and start from scratch! if you change the way
1441 : // a column is interpreted, make sure you also change its name so this
1442 : // check will catch it.
1443 : default:
1444 : {
1445 : // check if all the expected columns exist
1446 0 : nsCOMPtr<mozIStorageStatement> stmt;
1447 0 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
1448 : "SELECT "
1449 : "id, "
1450 : "baseDomain, "
1451 : "originAttributes, "
1452 : "name, "
1453 : "value, "
1454 : "host, "
1455 : "path, "
1456 : "expiry, "
1457 : "lastAccessed, "
1458 : "creationTime, "
1459 : "isSecure, "
1460 : "isHttpOnly "
1461 0 : "FROM moz_cookies"), getter_AddRefs(stmt));
1462 0 : if (NS_SUCCEEDED(rv))
1463 0 : break;
1464 :
1465 : // our columns aren't there - drop the table!
1466 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1467 0 : "DROP TABLE moz_cookies"));
1468 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1469 :
1470 0 : rv = CreateTable();
1471 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1472 : }
1473 0 : break;
1474 : }
1475 : }
1476 :
1477 : // make operations on the table asynchronous, for performance
1478 4 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1479 3 : "PRAGMA synchronous = OFF"));
1480 :
1481 : // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1482 : // 16 pages (around 500KB).
1483 4 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1484 3 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
1485 4 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1486 3 : "PRAGMA wal_autocheckpoint = 16"));
1487 :
1488 : // cache frequently used statements (for insertion, deletion, and updating)
1489 4 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1490 : "INSERT INTO moz_cookies ("
1491 : "baseDomain, "
1492 : "originAttributes, "
1493 : "name, "
1494 : "value, "
1495 : "host, "
1496 : "path, "
1497 : "expiry, "
1498 : "lastAccessed, "
1499 : "creationTime, "
1500 : "isSecure, "
1501 : "isHttpOnly"
1502 : ") VALUES ("
1503 : ":baseDomain, "
1504 : ":originAttributes, "
1505 : ":name, "
1506 : ":value, "
1507 : ":host, "
1508 : ":path, "
1509 : ":expiry, "
1510 : ":lastAccessed, "
1511 : ":creationTime, "
1512 : ":isSecure, "
1513 : ":isHttpOnly"
1514 : ")"),
1515 4 : getter_AddRefs(mDefaultDBState->stmtInsert));
1516 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1517 :
1518 4 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1519 : "DELETE FROM moz_cookies "
1520 : "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
1521 4 : getter_AddRefs(mDefaultDBState->stmtDelete));
1522 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1523 :
1524 4 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1525 : "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1526 : "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
1527 4 : getter_AddRefs(mDefaultDBState->stmtUpdate));
1528 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1529 :
1530 : // if we deleted a corrupt db, don't attempt to import - return now
1531 1 : if (aRecreateDB)
1532 0 : return RESULT_OK;
1533 :
1534 : // check whether to import or just read in the db
1535 1 : if (tableExists)
1536 1 : return Read();
1537 :
1538 0 : nsCOMPtr<nsIFile> oldCookieFile;
1539 0 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1540 0 : getter_AddRefs(oldCookieFile));
1541 0 : if (NS_FAILED(rv)) return RESULT_OK;
1542 :
1543 : // Import cookies, and clean up the old file regardless of success or failure.
1544 : // Note that we have to switch out our DBState temporarily, in case we're in
1545 : // private browsing mode; otherwise ImportCookies() won't be happy.
1546 0 : DBState* initialState = mDBState;
1547 0 : mDBState = mDefaultDBState;
1548 0 : oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
1549 0 : ImportCookies(oldCookieFile);
1550 0 : oldCookieFile->Remove(false);
1551 0 : mDBState = initialState;
1552 :
1553 0 : return RESULT_OK;
1554 : }
1555 :
1556 : // Sets the schema version and creates the moz_cookies table.
1557 : nsresult
1558 0 : nsCookieService::CreateTableWorker(const char* aName)
1559 : {
1560 : // Create the table.
1561 : // We default originAttributes to empty string: this is so if users revert to
1562 : // an older Firefox version that doesn't know about this field, any cookies
1563 : // set will still work once they upgrade back.
1564 0 : nsAutoCString command("CREATE TABLE ");
1565 0 : command.Append(aName);
1566 : command.Append(" ("
1567 : "id INTEGER PRIMARY KEY, "
1568 : "baseDomain TEXT, "
1569 : "originAttributes TEXT NOT NULL DEFAULT '', "
1570 : "name TEXT, "
1571 : "value TEXT, "
1572 : "host TEXT, "
1573 : "path TEXT, "
1574 : "expiry INTEGER, "
1575 : "lastAccessed INTEGER, "
1576 : "creationTime INTEGER, "
1577 : "isSecure INTEGER, "
1578 : "isHttpOnly INTEGER, "
1579 : "inBrowserElement INTEGER DEFAULT 0, "
1580 : "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1581 0 : ")");
1582 0 : return mDefaultDBState->dbConn->ExecuteSimpleSQL(command);
1583 : }
1584 :
1585 : // Sets the schema version and creates the moz_cookies table.
1586 : nsresult
1587 0 : nsCookieService::CreateTable()
1588 : {
1589 : // Set the schema version, before creating the table.
1590 0 : nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
1591 0 : COOKIES_SCHEMA_VERSION);
1592 0 : if (NS_FAILED(rv)) return rv;
1593 :
1594 0 : rv = CreateTableWorker("moz_cookies");
1595 0 : if (NS_FAILED(rv)) return rv;
1596 :
1597 0 : return CreateIndex();
1598 : }
1599 :
1600 : nsresult
1601 0 : nsCookieService::CreateIndex()
1602 : {
1603 : // Create an index on baseDomain.
1604 0 : return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1605 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1606 0 : "originAttributes)"));
1607 : }
1608 :
1609 : // Sets the schema version and creates the moz_cookies table.
1610 : nsresult
1611 0 : nsCookieService::CreateTableForSchemaVersion6()
1612 : {
1613 : // Set the schema version, before creating the table.
1614 0 : nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(6);
1615 0 : if (NS_FAILED(rv)) return rv;
1616 :
1617 : // Create the table.
1618 : // We default originAttributes to empty string: this is so if users revert to
1619 : // an older Firefox version that doesn't know about this field, any cookies
1620 : // set will still work once they upgrade back.
1621 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1622 : "CREATE TABLE moz_cookies ("
1623 : "id INTEGER PRIMARY KEY, "
1624 : "baseDomain TEXT, "
1625 : "originAttributes TEXT NOT NULL DEFAULT '', "
1626 : "name TEXT, "
1627 : "value TEXT, "
1628 : "host TEXT, "
1629 : "path TEXT, "
1630 : "expiry INTEGER, "
1631 : "lastAccessed INTEGER, "
1632 : "creationTime INTEGER, "
1633 : "isSecure INTEGER, "
1634 : "isHttpOnly INTEGER, "
1635 : "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1636 0 : ")"));
1637 0 : if (NS_FAILED(rv)) return rv;
1638 :
1639 : // Create an index on baseDomain.
1640 0 : return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1641 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1642 0 : "originAttributes)"));
1643 : }
1644 :
1645 : // Sets the schema version and creates the moz_cookies table.
1646 : nsresult
1647 0 : nsCookieService::CreateTableForSchemaVersion5()
1648 : {
1649 : // Set the schema version, before creating the table.
1650 0 : nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(5);
1651 0 : if (NS_FAILED(rv)) return rv;
1652 :
1653 : // Create the table. We default appId/inBrowserElement to 0: this is so if
1654 : // users revert to an older Firefox version that doesn't know about these
1655 : // fields, any cookies set will still work once they upgrade back.
1656 0 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1657 : "CREATE TABLE moz_cookies ("
1658 : "id INTEGER PRIMARY KEY, "
1659 : "baseDomain TEXT, "
1660 : "appId INTEGER DEFAULT 0, "
1661 : "inBrowserElement INTEGER DEFAULT 0, "
1662 : "name TEXT, "
1663 : "value TEXT, "
1664 : "host TEXT, "
1665 : "path TEXT, "
1666 : "expiry INTEGER, "
1667 : "lastAccessed INTEGER, "
1668 : "creationTime INTEGER, "
1669 : "isSecure INTEGER, "
1670 : "isHttpOnly INTEGER, "
1671 : "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
1672 0 : ")"));
1673 0 : if (NS_FAILED(rv)) return rv;
1674 :
1675 : // Create an index on baseDomain.
1676 0 : return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1677 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1678 : "appId, "
1679 0 : "inBrowserElement)"));
1680 : }
1681 :
1682 : void
1683 0 : nsCookieService::CloseDBStates()
1684 : {
1685 : // Null out our private and pointer DBStates regardless.
1686 0 : mPrivateDBState = nullptr;
1687 0 : mDBState = nullptr;
1688 :
1689 : // If we don't have a default DBState, we're done.
1690 0 : if (!mDefaultDBState)
1691 0 : return;
1692 :
1693 : // Cleanup cached statements before we can close anything.
1694 0 : CleanupCachedStatements();
1695 :
1696 0 : if (mDefaultDBState->dbConn) {
1697 : // Cancel any pending read. No further results will be received by our
1698 : // read listener.
1699 0 : if (mDefaultDBState->pendingRead) {
1700 0 : CancelAsyncRead(true);
1701 : }
1702 :
1703 : // Asynchronously close the connection. We will null it below.
1704 0 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1705 : }
1706 :
1707 0 : CleanupDefaultDBConnection();
1708 :
1709 0 : mDefaultDBState = nullptr;
1710 : }
1711 :
1712 : // Null out the statements.
1713 : // This must be done before closing the connection.
1714 : void
1715 0 : nsCookieService::CleanupCachedStatements()
1716 : {
1717 0 : mDefaultDBState->stmtInsert = nullptr;
1718 0 : mDefaultDBState->stmtDelete = nullptr;
1719 0 : mDefaultDBState->stmtUpdate = nullptr;
1720 0 : }
1721 :
1722 : // Null out the listeners, and the database connection itself. This
1723 : // will not null out the statements, cancel a pending read or
1724 : // asynchronously close the connection -- these must be done
1725 : // beforehand if necessary.
1726 : void
1727 0 : nsCookieService::CleanupDefaultDBConnection()
1728 : {
1729 0 : MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
1730 0 : MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
1731 0 : MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
1732 :
1733 : // Null out the database connections. If 'dbConn' has not been used for any
1734 : // asynchronous operations yet, this will synchronously close it; otherwise,
1735 : // it's expected that the caller has performed an AsyncClose prior.
1736 0 : mDefaultDBState->dbConn = nullptr;
1737 0 : mDefaultDBState->syncConn = nullptr;
1738 :
1739 : // Manually null out our listeners. This is necessary because they hold a
1740 : // strong ref to the DBState itself. They'll stay alive until whatever
1741 : // statements are still executing complete.
1742 0 : mDefaultDBState->readListener = nullptr;
1743 0 : mDefaultDBState->insertListener = nullptr;
1744 0 : mDefaultDBState->updateListener = nullptr;
1745 0 : mDefaultDBState->removeListener = nullptr;
1746 0 : mDefaultDBState->closeListener = nullptr;
1747 0 : }
1748 :
1749 : void
1750 0 : nsCookieService::HandleDBClosed(DBState* aDBState)
1751 : {
1752 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1753 : ("HandleDBClosed(): DBState %p closed", aDBState));
1754 :
1755 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1756 :
1757 0 : switch (aDBState->corruptFlag) {
1758 : case DBState::OK: {
1759 : // Database is healthy. Notify of closure.
1760 0 : if (os) {
1761 0 : os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1762 : }
1763 0 : break;
1764 : }
1765 : case DBState::CLOSING_FOR_REBUILD: {
1766 : // Our close finished. Start the rebuild, and notify of db closure later.
1767 0 : RebuildCorruptDB(aDBState);
1768 0 : break;
1769 : }
1770 : case DBState::REBUILDING: {
1771 : // We encountered an error during rebuild, closed the database, and now
1772 : // here we are. We already have a 'cookies.sqlite.bak' from the original
1773 : // dead database; we don't want to overwrite it, so let's move this one to
1774 : // 'cookies.sqlite.bak-rebuild'.
1775 0 : nsCOMPtr<nsIFile> backupFile;
1776 0 : aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
1777 0 : nsresult rv = backupFile->MoveToNative(nullptr,
1778 0 : NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
1779 :
1780 0 : COOKIE_LOGSTRING(LogLevel::Warning,
1781 : ("HandleDBClosed(): DBState %p encountered error rebuilding db; move to "
1782 : "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
1783 : aDBState, static_cast<uint32_t>(rv)));
1784 0 : if (os) {
1785 0 : os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1786 : }
1787 0 : break;
1788 : }
1789 : }
1790 0 : }
1791 :
1792 : void
1793 0 : nsCookieService::HandleCorruptDB(DBState* aDBState)
1794 : {
1795 0 : if (mDefaultDBState != aDBState) {
1796 : // We've either closed the state or we've switched profiles. It's getting
1797 : // a bit late to rebuild -- bail instead.
1798 0 : COOKIE_LOGSTRING(LogLevel::Warning,
1799 : ("HandleCorruptDB(): DBState %p is already closed, aborting", aDBState));
1800 0 : return;
1801 : }
1802 :
1803 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1804 : ("HandleCorruptDB(): DBState %p has corruptFlag %u", aDBState,
1805 : aDBState->corruptFlag));
1806 :
1807 : // Mark the database corrupt, so the close listener can begin reconstructing
1808 : // it.
1809 0 : switch (mDefaultDBState->corruptFlag) {
1810 : case DBState::OK: {
1811 : // Move to 'closing' state.
1812 0 : mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
1813 :
1814 : // Cancel any pending read and close the database. If we do have an
1815 : // in-flight read we want to throw away all the results so far -- we have no
1816 : // idea how consistent the database is. Note that we may have already
1817 : // canceled the read but not emptied our readSet; do so now.
1818 0 : mDefaultDBState->readSet.Clear();
1819 0 : if (mDefaultDBState->pendingRead) {
1820 0 : CancelAsyncRead(true);
1821 0 : mDefaultDBState->syncConn = nullptr;
1822 : }
1823 :
1824 0 : CleanupCachedStatements();
1825 0 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1826 0 : CleanupDefaultDBConnection();
1827 0 : break;
1828 : }
1829 : case DBState::CLOSING_FOR_REBUILD: {
1830 : // We had an error while waiting for close completion. That's OK, just
1831 : // ignore it -- we're rebuilding anyway.
1832 0 : return;
1833 : }
1834 : case DBState::REBUILDING: {
1835 : // We had an error while rebuilding the DB. Game over. Close the database
1836 : // and let the close handler do nothing; then we'll move it out of the way.
1837 0 : CleanupCachedStatements();
1838 0 : if (mDefaultDBState->dbConn) {
1839 0 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1840 : }
1841 0 : CleanupDefaultDBConnection();
1842 0 : break;
1843 : }
1844 : }
1845 : }
1846 :
1847 : void
1848 0 : nsCookieService::RebuildCorruptDB(DBState* aDBState)
1849 : {
1850 0 : NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
1851 0 : NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
1852 : "should be in CLOSING_FOR_REBUILD state");
1853 :
1854 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1855 :
1856 0 : aDBState->corruptFlag = DBState::REBUILDING;
1857 :
1858 0 : if (mDefaultDBState != aDBState) {
1859 : // We've either closed the state or we've switched profiles. It's getting
1860 : // a bit late to rebuild -- bail instead. In any case, we were waiting
1861 : // on rebuild completion to notify of the db closure, which won't happen --
1862 : // do so now.
1863 0 : COOKIE_LOGSTRING(LogLevel::Warning,
1864 : ("RebuildCorruptDB(): DBState %p is stale, aborting", aDBState));
1865 0 : if (os) {
1866 0 : os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1867 : }
1868 0 : return;
1869 : }
1870 :
1871 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1872 : ("RebuildCorruptDB(): creating new database"));
1873 :
1874 : // The database has been closed, and we're ready to rebuild. Open a
1875 : // connection.
1876 0 : OpenDBResult result = TryInitDB(true);
1877 0 : if (result != RESULT_OK) {
1878 : // We're done. Reset our DB connection and statements, and notify of
1879 : // closure.
1880 0 : COOKIE_LOGSTRING(LogLevel::Warning,
1881 : ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
1882 0 : CleanupCachedStatements();
1883 0 : CleanupDefaultDBConnection();
1884 0 : mDefaultDBState->corruptFlag = DBState::OK;
1885 0 : if (os) {
1886 0 : os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1887 : }
1888 0 : return;
1889 : }
1890 :
1891 : // Notify observers that we're beginning the rebuild.
1892 0 : if (os) {
1893 0 : os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1894 : }
1895 :
1896 : // Enumerate the hash, and add cookies to the params array.
1897 0 : mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
1898 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1899 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1900 0 : for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
1901 0 : nsCookieEntry* entry = iter.Get();
1902 :
1903 0 : const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
1904 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1905 0 : nsCookie* cookie = cookies[i];
1906 :
1907 0 : if (!cookie->IsSession()) {
1908 0 : bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
1909 : }
1910 : }
1911 : }
1912 :
1913 : // Make sure we've got something to write. If we don't, we're done.
1914 : uint32_t length;
1915 0 : paramsArray->GetLength(&length);
1916 0 : if (length == 0) {
1917 0 : COOKIE_LOGSTRING(LogLevel::Debug,
1918 : ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1919 0 : mDefaultDBState->corruptFlag = DBState::OK;
1920 0 : return;
1921 : }
1922 :
1923 : // Execute the statement. If any errors crop up, we won't try again.
1924 0 : DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
1925 0 : NS_ASSERT_SUCCESS(rv);
1926 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
1927 0 : rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
1928 0 : NS_ASSERT_SUCCESS(rv);
1929 : }
1930 :
1931 0 : nsCookieService::~nsCookieService()
1932 : {
1933 0 : CloseDBStates();
1934 :
1935 0 : UnregisterWeakMemoryReporter(this);
1936 :
1937 0 : gCookieService = nullptr;
1938 0 : }
1939 :
1940 : NS_IMETHODIMP
1941 0 : nsCookieService::Observe(nsISupports *aSubject,
1942 : const char *aTopic,
1943 : const char16_t *aData)
1944 : {
1945 : // check the topic
1946 0 : if (!strcmp(aTopic, "profile-before-change")) {
1947 : // The profile is about to change,
1948 : // or is going away because the application is shutting down.
1949 :
1950 : // Close the default DB connection and null out our DBStates before
1951 : // changing.
1952 0 : CloseDBStates();
1953 :
1954 0 : } else if (!strcmp(aTopic, "profile-do-change")) {
1955 0 : NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
1956 0 : NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
1957 :
1958 : // the profile has already changed; init the db from the new location.
1959 : // if we are in the private browsing state, however, we do not want to read
1960 : // data into it - we should instead put it into the default state, so it's
1961 : // ready for us if and when we switch back to it.
1962 0 : InitDBStates();
1963 :
1964 0 : } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1965 0 : nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
1966 0 : if (prefBranch)
1967 0 : PrefChanged(prefBranch);
1968 :
1969 0 : } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1970 : // Flush all the cookies stored by private browsing contexts
1971 0 : mPrivateDBState = new DBState();
1972 : }
1973 :
1974 :
1975 0 : return NS_OK;
1976 : }
1977 :
1978 : NS_IMETHODIMP
1979 0 : nsCookieService::GetCookieString(nsIURI *aHostURI,
1980 : nsIChannel *aChannel,
1981 : char **aCookie)
1982 : {
1983 0 : return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
1984 : }
1985 :
1986 : NS_IMETHODIMP
1987 6 : nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI,
1988 : nsIURI *aFirstURI,
1989 : nsIChannel *aChannel,
1990 : char **aCookie)
1991 : {
1992 6 : return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
1993 : }
1994 :
1995 : nsresult
1996 6 : nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
1997 : nsIChannel *aChannel,
1998 : bool aHttpBound,
1999 : char** aCookie)
2000 : {
2001 6 : NS_ENSURE_ARG(aHostURI);
2002 6 : NS_ENSURE_ARG(aCookie);
2003 :
2004 : // Determine whether the request is foreign. Failure is acceptable.
2005 6 : bool isForeign = true;
2006 6 : mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
2007 :
2008 : // Get originAttributes.
2009 12 : OriginAttributes attrs;
2010 6 : if (aChannel) {
2011 6 : NS_GetOriginAttributes(aChannel, attrs);
2012 : }
2013 :
2014 12 : nsAutoCString result;
2015 6 : GetCookieStringInternal(aHostURI, isForeign, aHttpBound, attrs, result);
2016 6 : *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
2017 6 : return NS_OK;
2018 : }
2019 :
2020 : NS_IMETHODIMP
2021 0 : nsCookieService::SetCookieString(nsIURI *aHostURI,
2022 : nsIPrompt *aPrompt,
2023 : const char *aCookieHeader,
2024 : nsIChannel *aChannel)
2025 : {
2026 : // The aPrompt argument is deprecated and unused. Avoid introducing new
2027 : // code that uses this argument by warning if the value is non-null.
2028 0 : MOZ_ASSERT(!aPrompt);
2029 0 : if (aPrompt) {
2030 : nsCOMPtr<nsIConsoleService> aConsoleService =
2031 0 : do_GetService("@mozilla.org/consoleservice;1");
2032 0 : if (aConsoleService) {
2033 0 : aConsoleService->LogStringMessage(
2034 0 : u"Non-null prompt ignored by nsCookieService.");
2035 : }
2036 : }
2037 : return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
2038 0 : false);
2039 : }
2040 :
2041 : NS_IMETHODIMP
2042 0 : nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
2043 : nsIURI *aFirstURI,
2044 : nsIPrompt *aPrompt,
2045 : const char *aCookieHeader,
2046 : const char *aServerTime,
2047 : nsIChannel *aChannel)
2048 : {
2049 : // The aPrompt argument is deprecated and unused. Avoid introducing new
2050 : // code that uses this argument by warning if the value is non-null.
2051 0 : MOZ_ASSERT(!aPrompt);
2052 0 : if (aPrompt) {
2053 : nsCOMPtr<nsIConsoleService> aConsoleService =
2054 0 : do_GetService("@mozilla.org/consoleservice;1");
2055 0 : if (aConsoleService) {
2056 0 : aConsoleService->LogStringMessage(
2057 0 : u"Non-null prompt ignored by nsCookieService.");
2058 : }
2059 : }
2060 : return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
2061 0 : true);
2062 : }
2063 :
2064 : nsresult
2065 0 : nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
2066 : const char *aCookieHeader,
2067 : const char *aServerTime,
2068 : nsIChannel *aChannel,
2069 : bool aFromHttp)
2070 : {
2071 0 : NS_ENSURE_ARG(aHostURI);
2072 0 : NS_ENSURE_ARG(aCookieHeader);
2073 :
2074 : // Determine whether the request is foreign. Failure is acceptable.
2075 0 : bool isForeign = true;
2076 0 : mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
2077 :
2078 : // Get originAttributes.
2079 0 : OriginAttributes attrs;
2080 0 : if (aChannel) {
2081 0 : NS_GetOriginAttributes(aChannel, attrs);
2082 : }
2083 :
2084 0 : nsDependentCString cookieString(aCookieHeader);
2085 0 : nsDependentCString serverTime(aServerTime ? aServerTime : "");
2086 0 : SetCookieStringInternal(aHostURI, isForeign, cookieString,
2087 0 : serverTime, aFromHttp, attrs, aChannel);
2088 0 : return NS_OK;
2089 : }
2090 :
2091 : void
2092 0 : nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
2093 : bool aIsForeign,
2094 : nsDependentCString &aCookieHeader,
2095 : const nsCString &aServerTime,
2096 : bool aFromHttp,
2097 : const OriginAttributes &aOriginAttrs,
2098 : nsIChannel *aChannel)
2099 : {
2100 0 : NS_ASSERTION(aHostURI, "null host!");
2101 :
2102 0 : if (!mDBState) {
2103 0 : NS_WARNING("No DBState! Profile already closed?");
2104 0 : return;
2105 : }
2106 :
2107 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
2108 0 : mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2109 :
2110 : // get the base domain for the host URI.
2111 : // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2112 : // file:// URI's (i.e. with an empty host) are allowed, but any other
2113 : // scheme must have a non-empty host. A trailing dot in the host
2114 : // is acceptable.
2115 : bool requireHostMatch;
2116 0 : nsAutoCString baseDomain;
2117 0 : nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
2118 0 : if (NS_FAILED(rv)) {
2119 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2120 0 : "couldn't get base domain from URI");
2121 0 : return;
2122 : }
2123 :
2124 0 : nsCookieKey key(baseDomain, aOriginAttrs);
2125 :
2126 : // check default prefs
2127 0 : CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, aCookieHeader.get());
2128 :
2129 : // fire a notification if third party or if cookie was rejected
2130 : // (but not if there was an error)
2131 0 : switch (cookieStatus) {
2132 : case STATUS_REJECTED:
2133 0 : NotifyRejected(aHostURI);
2134 0 : if (aIsForeign) {
2135 0 : NotifyThirdParty(aHostURI, false, aChannel);
2136 : }
2137 0 : return; // Stop here
2138 : case STATUS_REJECTED_WITH_ERROR:
2139 0 : return;
2140 : case STATUS_ACCEPTED: // Fallthrough
2141 : case STATUS_ACCEPT_SESSION:
2142 0 : if (aIsForeign) {
2143 0 : NotifyThirdParty(aHostURI, true, aChannel);
2144 : }
2145 0 : break;
2146 : default:
2147 0 : break;
2148 : }
2149 :
2150 : // parse server local time. this is not just done here for efficiency
2151 : // reasons - if there's an error parsing it, and we need to default it
2152 : // to the current time, we must do it here since the current time in
2153 : // SetCookieInternal() will change for each cookie processed (e.g. if the
2154 : // user is prompted).
2155 : PRTime tempServerTime;
2156 : int64_t serverTime;
2157 0 : PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
2158 0 : &tempServerTime);
2159 0 : if (result == PR_SUCCESS) {
2160 0 : serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
2161 : } else {
2162 0 : serverTime = PR_Now() / PR_USEC_PER_SEC;
2163 : }
2164 :
2165 : // process each cookie in the header
2166 0 : while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
2167 : aCookieHeader, serverTime, aFromHttp, aChannel)) {
2168 : // document.cookie can only set one cookie at a time
2169 0 : if (!aFromHttp)
2170 0 : break;
2171 : }
2172 : }
2173 :
2174 : // notify observers that a cookie was rejected due to the users' prefs.
2175 : void
2176 0 : nsCookieService::NotifyRejected(nsIURI *aHostURI)
2177 : {
2178 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2179 0 : if (os) {
2180 0 : os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
2181 : }
2182 0 : }
2183 :
2184 : // notify observers that a third-party cookie was accepted/rejected
2185 : // if the cookie issuer is unknown, it defaults to "?"
2186 : void
2187 0 : nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
2188 : {
2189 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2190 0 : if (!os) {
2191 0 : return;
2192 : }
2193 :
2194 : const char* topic;
2195 :
2196 0 : if (mDBState != mPrivateDBState) {
2197 : // Regular (non-private) browsing
2198 0 : if (aIsAccepted) {
2199 0 : topic = "third-party-cookie-accepted";
2200 : } else {
2201 0 : topic = "third-party-cookie-rejected";
2202 : }
2203 : } else {
2204 : // Private browsing
2205 0 : if (aIsAccepted) {
2206 0 : topic = "private-third-party-cookie-accepted";
2207 : } else {
2208 0 : topic = "private-third-party-cookie-rejected";
2209 : }
2210 : }
2211 :
2212 : do {
2213 : // Attempt to find the host of aChannel.
2214 0 : if (!aChannel) {
2215 0 : break;
2216 : }
2217 0 : nsCOMPtr<nsIURI> channelURI;
2218 0 : nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
2219 0 : if (NS_FAILED(rv)) {
2220 0 : break;
2221 : }
2222 :
2223 0 : nsAutoCString referringHost;
2224 0 : rv = channelURI->GetHost(referringHost);
2225 0 : if (NS_FAILED(rv)) {
2226 0 : break;
2227 : }
2228 :
2229 0 : nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
2230 0 : os->NotifyObservers(aHostURI, topic, referringHostUTF16.get());
2231 0 : return;
2232 : } while (false);
2233 :
2234 : // This can fail for a number of reasons, in which kind we fallback to "?"
2235 0 : os->NotifyObservers(aHostURI, topic, u"?");
2236 : }
2237 :
2238 : // notify observers that the cookie list changed. there are five possible
2239 : // values for aData:
2240 : // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
2241 : // "added" means a cookie was added. aSubject is the added cookie.
2242 : // "changed" means a cookie was altered. aSubject is the new cookie.
2243 : // "cleared" means the entire cookie list was cleared. aSubject is null.
2244 : // "batch-deleted" means a set of cookies was purged. aSubject is the list of
2245 : // cookies.
2246 : void
2247 0 : nsCookieService::NotifyChanged(nsISupports *aSubject,
2248 : const char16_t *aData,
2249 : bool aOldCookieIsSession)
2250 : {
2251 0 : const char* topic = mDBState == mPrivateDBState ?
2252 0 : "private-cookie-changed" : "cookie-changed";
2253 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2254 0 : if (!os) {
2255 0 : return;
2256 : }
2257 : // Notify for topic "private-cookie-changed" or "cookie-changed"
2258 0 : os->NotifyObservers(aSubject, topic, aData);
2259 :
2260 : // Notify for topic "session-cookie-changed" to update the copy of session
2261 : // cookies in session restore component.
2262 : // Ignore private session cookies since they will not be restored.
2263 0 : if (mDBState == mPrivateDBState) {
2264 0 : return;
2265 : }
2266 : // Filter out notifications for individual non-session cookies.
2267 0 : if (NS_LITERAL_STRING("changed").Equals(aData) ||
2268 0 : NS_LITERAL_STRING("deleted").Equals(aData) ||
2269 0 : NS_LITERAL_STRING("added").Equals(aData)) {
2270 0 : nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
2271 0 : MOZ_ASSERT(xpcCookie);
2272 0 : auto cookie = static_cast<nsCookie*>(xpcCookie.get());
2273 0 : if (!cookie->IsSession() && !aOldCookieIsSession) {
2274 0 : return;
2275 : }
2276 : }
2277 0 : os->NotifyObservers(aSubject, "session-cookie-changed", aData);
2278 : }
2279 :
2280 : already_AddRefed<nsIArray>
2281 0 : nsCookieService::CreatePurgeList(nsICookie2* aCookie)
2282 : {
2283 : nsCOMPtr<nsIMutableArray> removedList =
2284 0 : do_CreateInstance(NS_ARRAY_CONTRACTID);
2285 0 : removedList->AppendElement(aCookie, false);
2286 0 : return removedList.forget();
2287 : }
2288 :
2289 : /******************************************************************************
2290 : * nsCookieService:
2291 : * public transaction helper impl
2292 : ******************************************************************************/
2293 :
2294 : NS_IMETHODIMP
2295 0 : nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback)
2296 : {
2297 0 : NS_ENSURE_ARG(aCallback);
2298 0 : if (NS_WARN_IF(!mDefaultDBState->dbConn)) {
2299 0 : return NS_ERROR_NOT_AVAILABLE;
2300 : }
2301 0 : mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
2302 :
2303 0 : if (NS_FAILED(aCallback->Callback())) {
2304 0 : Unused << transaction.Rollback();
2305 0 : return NS_ERROR_FAILURE;
2306 : }
2307 0 : return NS_OK;
2308 : }
2309 :
2310 : /******************************************************************************
2311 : * nsCookieService:
2312 : * pref observer impl
2313 : ******************************************************************************/
2314 :
2315 : void
2316 1 : nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
2317 : {
2318 : int32_t val;
2319 1 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
2320 1 : mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
2321 :
2322 1 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
2323 0 : mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
2324 :
2325 1 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
2326 0 : mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
2327 :
2328 1 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
2329 0 : mCookiePurgeAge =
2330 0 : int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
2331 : }
2332 :
2333 : bool boolval;
2334 1 : if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
2335 1 : mThirdPartySession = boolval;
2336 :
2337 1 : if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
2338 1 : mLeaveSecureAlone = boolval;
2339 1 : }
2340 :
2341 : /******************************************************************************
2342 : * nsICookieManager impl:
2343 : * nsICookieManager
2344 : ******************************************************************************/
2345 :
2346 : NS_IMETHODIMP
2347 0 : nsCookieService::RemoveAll()
2348 : {
2349 0 : if (!mDBState) {
2350 0 : NS_WARNING("No DBState! Profile already closed?");
2351 0 : return NS_ERROR_NOT_AVAILABLE;
2352 : }
2353 :
2354 0 : RemoveAllFromMemory();
2355 :
2356 : // clear the cookie file
2357 0 : if (mDBState->dbConn) {
2358 0 : NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
2359 :
2360 : // Cancel any pending read. No further results will be received by our
2361 : // read listener.
2362 0 : if (mDefaultDBState->pendingRead) {
2363 0 : CancelAsyncRead(true);
2364 : }
2365 :
2366 0 : nsCOMPtr<mozIStorageAsyncStatement> stmt;
2367 0 : nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
2368 0 : "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
2369 0 : if (NS_SUCCEEDED(rv)) {
2370 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
2371 0 : rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
2372 0 : getter_AddRefs(handle));
2373 0 : NS_ASSERT_SUCCESS(rv);
2374 : } else {
2375 : // Recreate the database.
2376 0 : COOKIE_LOGSTRING(LogLevel::Debug,
2377 : ("RemoveAll(): corruption detected with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
2378 0 : HandleCorruptDB(mDefaultDBState);
2379 : }
2380 : }
2381 :
2382 0 : NotifyChanged(nullptr, u"cleared");
2383 0 : return NS_OK;
2384 : }
2385 :
2386 : NS_IMETHODIMP
2387 0 : nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
2388 : {
2389 0 : if (!mDBState) {
2390 0 : NS_WARNING("No DBState! Profile already closed?");
2391 0 : return NS_ERROR_NOT_AVAILABLE;
2392 : }
2393 :
2394 0 : EnsureReadComplete();
2395 :
2396 0 : nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
2397 0 : for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
2398 0 : const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
2399 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
2400 0 : cookieList.AppendObject(cookies[i]);
2401 : }
2402 : }
2403 :
2404 0 : return NS_NewArrayEnumerator(aEnumerator, cookieList);
2405 : }
2406 :
2407 : NS_IMETHODIMP
2408 0 : nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator)
2409 : {
2410 0 : if (!mDBState) {
2411 0 : NS_WARNING("No DBState! Profile already closed?");
2412 0 : return NS_ERROR_NOT_AVAILABLE;
2413 : }
2414 :
2415 0 : EnsureReadComplete();
2416 :
2417 0 : nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
2418 0 : for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
2419 0 : const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
2420 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
2421 0 : nsCookie* cookie = cookies[i];
2422 : // Filter out non-session cookies.
2423 0 : if (cookie->IsSession()) {
2424 0 : cookieList.AppendObject(cookie);
2425 : }
2426 : }
2427 : }
2428 :
2429 0 : return NS_NewArrayEnumerator(aEnumerator, cookieList);
2430 : }
2431 :
2432 : static nsresult
2433 0 : InitializeOriginAttributes(OriginAttributes* aAttrs,
2434 : JS::HandleValue aOriginAttributes,
2435 : JSContext* aCx,
2436 : uint8_t aArgc,
2437 : const char16_t* aAPI,
2438 : const char16_t* aInterfaceSuffix)
2439 : {
2440 0 : MOZ_ASSERT(aAttrs);
2441 0 : MOZ_ASSERT(aCx);
2442 0 : MOZ_ASSERT(aAPI);
2443 0 : MOZ_ASSERT(aInterfaceSuffix);
2444 :
2445 0 : if (aArgc == 0) {
2446 : const char16_t* params[] = {
2447 : aAPI,
2448 : aInterfaceSuffix
2449 0 : };
2450 :
2451 : // This is supposed to be temporary and in 1 or 2 releases we want to
2452 : // have originAttributes param as mandatory. But for now, we don't want to
2453 : // break existing addons, so we write a console message to inform the addon
2454 : // developers about it.
2455 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
2456 0 : NS_LITERAL_CSTRING("Cookie Manager"),
2457 : nullptr,
2458 : nsContentUtils::eNECKO_PROPERTIES,
2459 : "nsICookieManagerAPIDeprecated",
2460 0 : params, ArrayLength(params));
2461 0 : } else if (aArgc == 1) {
2462 0 : if (!aOriginAttributes.isObject() ||
2463 0 : !aAttrs->Init(aCx, aOriginAttributes)) {
2464 0 : return NS_ERROR_INVALID_ARG;
2465 : }
2466 : }
2467 :
2468 0 : return NS_OK;
2469 : }
2470 :
2471 : NS_IMETHODIMP
2472 0 : nsCookieService::Add(const nsACString &aHost,
2473 : const nsACString &aPath,
2474 : const nsACString &aName,
2475 : const nsACString &aValue,
2476 : bool aIsSecure,
2477 : bool aIsHttpOnly,
2478 : bool aIsSession,
2479 : int64_t aExpiry,
2480 : JS::HandleValue aOriginAttributes,
2481 : JSContext* aCx,
2482 : uint8_t aArgc)
2483 : {
2484 0 : MOZ_ASSERT(aArgc == 0 || aArgc == 1);
2485 :
2486 0 : OriginAttributes attrs;
2487 0 : nsresult rv = InitializeOriginAttributes(&attrs,
2488 : aOriginAttributes,
2489 : aCx,
2490 : aArgc,
2491 : u"nsICookieManager2.add()",
2492 0 : u"2");
2493 0 : NS_ENSURE_SUCCESS(rv, rv);
2494 :
2495 0 : return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
2496 0 : aIsSession, aExpiry, &attrs);
2497 : }
2498 :
2499 : NS_IMETHODIMP_(nsresult)
2500 0 : nsCookieService::AddNative(const nsACString &aHost,
2501 : const nsACString &aPath,
2502 : const nsACString &aName,
2503 : const nsACString &aValue,
2504 : bool aIsSecure,
2505 : bool aIsHttpOnly,
2506 : bool aIsSession,
2507 : int64_t aExpiry,
2508 : OriginAttributes* aOriginAttributes)
2509 : {
2510 0 : if (NS_WARN_IF(!aOriginAttributes)) {
2511 0 : return NS_ERROR_FAILURE;
2512 : }
2513 :
2514 0 : if (!mDBState) {
2515 0 : NS_WARNING("No DBState! Profile already closed?");
2516 0 : return NS_ERROR_NOT_AVAILABLE;
2517 : }
2518 :
2519 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
2520 0 : mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2521 :
2522 : // first, normalize the hostname, and fail if it contains illegal characters.
2523 0 : nsAutoCString host(aHost);
2524 0 : nsresult rv = NormalizeHost(host);
2525 0 : NS_ENSURE_SUCCESS(rv, rv);
2526 :
2527 : // get the base domain for the host URI.
2528 : // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2529 0 : nsAutoCString baseDomain;
2530 0 : rv = GetBaseDomainFromHost(host, baseDomain);
2531 0 : NS_ENSURE_SUCCESS(rv, rv);
2532 :
2533 0 : int64_t currentTimeInUsec = PR_Now();
2534 0 : nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes);
2535 :
2536 : RefPtr<nsCookie> cookie =
2537 : nsCookie::Create(aName, aValue, host, aPath,
2538 : aExpiry,
2539 : currentTimeInUsec,
2540 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
2541 : aIsSession,
2542 : aIsSecure,
2543 : aIsHttpOnly,
2544 0 : key.mOriginAttributes);
2545 0 : if (!cookie) {
2546 0 : return NS_ERROR_OUT_OF_MEMORY;
2547 : }
2548 :
2549 0 : AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
2550 0 : return NS_OK;
2551 : }
2552 :
2553 :
2554 : nsresult
2555 0 : nsCookieService::Remove(const nsACString& aHost, const OriginAttributes& aAttrs,
2556 : const nsACString& aName, const nsACString& aPath,
2557 : bool aBlocked)
2558 : {
2559 0 : if (!mDBState) {
2560 0 : NS_WARNING("No DBState! Profile already closed?");
2561 0 : return NS_ERROR_NOT_AVAILABLE;
2562 : }
2563 :
2564 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
2565 0 : mDBState = (aAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2566 :
2567 : // first, normalize the hostname, and fail if it contains illegal characters.
2568 0 : nsAutoCString host(aHost);
2569 0 : nsresult rv = NormalizeHost(host);
2570 0 : NS_ENSURE_SUCCESS(rv, rv);
2571 :
2572 0 : nsAutoCString baseDomain;
2573 0 : rv = GetBaseDomainFromHost(host, baseDomain);
2574 0 : NS_ENSURE_SUCCESS(rv, rv);
2575 :
2576 0 : nsListIter matchIter;
2577 0 : RefPtr<nsCookie> cookie;
2578 0 : if (FindCookie(nsCookieKey(baseDomain, aAttrs),
2579 : host,
2580 0 : PromiseFlatCString(aName),
2581 0 : PromiseFlatCString(aPath),
2582 : matchIter)) {
2583 0 : cookie = matchIter.Cookie();
2584 0 : RemoveCookieFromList(matchIter);
2585 : }
2586 :
2587 : // check if we need to add the host to the permissions blacklist.
2588 0 : if (aBlocked && mPermissionService) {
2589 : // strip off the domain dot, if necessary
2590 0 : if (!host.IsEmpty() && host.First() == '.')
2591 0 : host.Cut(0, 1);
2592 :
2593 0 : host.Insert(NS_LITERAL_CSTRING("http://"), 0);
2594 :
2595 0 : nsCOMPtr<nsIURI> uri;
2596 0 : NS_NewURI(getter_AddRefs(uri), host);
2597 :
2598 0 : if (uri)
2599 0 : mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
2600 : }
2601 :
2602 0 : if (cookie) {
2603 : // Everything's done. Notify observers.
2604 0 : NotifyChanged(cookie, u"deleted");
2605 : }
2606 :
2607 0 : return NS_OK;
2608 : }
2609 :
2610 : NS_IMETHODIMP
2611 0 : nsCookieService::Remove(const nsACString &aHost,
2612 : const nsACString &aName,
2613 : const nsACString &aPath,
2614 : bool aBlocked,
2615 : JS::HandleValue aOriginAttributes,
2616 : JSContext* aCx,
2617 : uint8_t aArgc)
2618 : {
2619 0 : MOZ_ASSERT(aArgc == 0 || aArgc == 1);
2620 :
2621 0 : OriginAttributes attrs;
2622 0 : nsresult rv = InitializeOriginAttributes(&attrs,
2623 : aOriginAttributes,
2624 : aCx,
2625 : aArgc,
2626 : u"nsICookieManager.remove()",
2627 0 : u"");
2628 0 : NS_ENSURE_SUCCESS(rv, rv);
2629 :
2630 0 : return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
2631 : }
2632 :
2633 : NS_IMETHODIMP_(nsresult)
2634 0 : nsCookieService::RemoveNative(const nsACString &aHost,
2635 : const nsACString &aName,
2636 : const nsACString &aPath,
2637 : bool aBlocked,
2638 : OriginAttributes* aOriginAttributes)
2639 : {
2640 0 : if (NS_WARN_IF(!aOriginAttributes)) {
2641 0 : return NS_ERROR_FAILURE;
2642 : }
2643 :
2644 0 : nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked);
2645 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
2646 0 : return rv;
2647 : }
2648 :
2649 0 : return NS_OK;
2650 : }
2651 :
2652 : /******************************************************************************
2653 : * nsCookieService impl:
2654 : * private file I/O functions
2655 : ******************************************************************************/
2656 :
2657 : // Begin an asynchronous read from the database.
2658 : OpenDBResult
2659 1 : nsCookieService::Read()
2660 : {
2661 : // Set up a statement for the read. Note that our query specifies that
2662 : // 'baseDomain' not be nullptr -- see below for why.
2663 2 : nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
2664 4 : nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
2665 : "SELECT "
2666 : "name, "
2667 : "value, "
2668 : "host, "
2669 : "path, "
2670 : "expiry, "
2671 : "lastAccessed, "
2672 : "creationTime, "
2673 : "isSecure, "
2674 : "isHttpOnly, "
2675 : "baseDomain, "
2676 : "originAttributes "
2677 : "FROM moz_cookies "
2678 4 : "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
2679 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
2680 :
2681 : // Set up a statement to delete any rows with a nullptr 'baseDomain'
2682 : // column. This takes care of any cookies set by browsers that don't
2683 : // understand the 'baseDomain' column, where the database schema version
2684 : // is from one that does. (This would occur when downgrading.)
2685 2 : nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
2686 4 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
2687 : "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
2688 4 : getter_AddRefs(stmtDeleteNull));
2689 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
2690 :
2691 : // Start a new connection for sync reads, to reduce contention with the
2692 : // background thread. We need to do this before we kick off write statements,
2693 : // since they can lock the database and prevent connections from being opened.
2694 2 : rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
2695 2 : getter_AddRefs(mDefaultDBState->syncConn));
2696 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
2697 :
2698 : // Init our readSet hash and execute the statements. Note that, after this
2699 : // point, we cannot fail without altering the cleanup code in InitDBStates()
2700 : // to handle closing of the now-asynchronous connection.
2701 1 : mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
2702 :
2703 2 : mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
2704 2 : rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
2705 2 : getter_AddRefs(mDefaultDBState->pendingRead));
2706 1 : NS_ASSERT_SUCCESS(rv);
2707 :
2708 2 : nsCOMPtr<mozIStoragePendingStatement> handle;
2709 2 : rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
2710 2 : getter_AddRefs(handle));
2711 1 : NS_ASSERT_SUCCESS(rv);
2712 :
2713 1 : return RESULT_OK;
2714 : }
2715 :
2716 : // Extract data from a single result row and create an nsCookie.
2717 : // This is templated since 'T' is different for sync vs async results.
2718 : template<class T> nsCookie*
2719 0 : nsCookieService::GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes)
2720 : {
2721 : // Skip reading 'baseDomain' -- up to the caller.
2722 0 : nsCString name, value, host, path;
2723 0 : DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
2724 0 : NS_ASSERT_SUCCESS(rv);
2725 0 : rv = aRow->GetUTF8String(IDX_VALUE, value);
2726 0 : NS_ASSERT_SUCCESS(rv);
2727 0 : rv = aRow->GetUTF8String(IDX_HOST, host);
2728 0 : NS_ASSERT_SUCCESS(rv);
2729 0 : rv = aRow->GetUTF8String(IDX_PATH, path);
2730 0 : NS_ASSERT_SUCCESS(rv);
2731 :
2732 0 : int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
2733 0 : int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
2734 0 : int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
2735 0 : bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
2736 0 : bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
2737 :
2738 : // Create a new nsCookie and assign the data.
2739 0 : return nsCookie::Create(name, value, host, path,
2740 : expiry,
2741 : lastAccessed,
2742 : creationTime,
2743 : false,
2744 : isSecure,
2745 : isHttpOnly,
2746 0 : aOriginAttributes);
2747 : }
2748 :
2749 : void
2750 1 : nsCookieService::AsyncReadComplete()
2751 : {
2752 : // We may be in the private browsing DB state, with a pending read on the
2753 : // default DB state. (This would occur if we started up in private browsing
2754 : // mode.) As long as we do all our operations on the default state, we're OK.
2755 1 : NS_ASSERTION(mDefaultDBState, "no default DBState");
2756 1 : NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
2757 1 : NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
2758 :
2759 2 : mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
2760 : // Merge the data read on the background thread with the data synchronously
2761 : // read on the main thread. Note that transactions on the cookie table may
2762 : // have occurred on the main thread since, making the background data stale.
2763 1 : for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
2764 0 : const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
2765 :
2766 : // Tiebreak: if the given base domain has already been read in, ignore
2767 : // the background data. Note that readSet may contain domains that were
2768 : // queried but found not to be in the db -- that's harmless.
2769 0 : if (mDefaultDBState->readSet.GetEntry(tuple.key))
2770 0 : continue;
2771 :
2772 0 : AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
2773 : }
2774 2 : DebugOnly<nsresult> rv = transaction.Commit();
2775 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2776 :
2777 1 : mDefaultDBState->stmtReadDomain = nullptr;
2778 1 : mDefaultDBState->pendingRead = nullptr;
2779 1 : mDefaultDBState->readListener = nullptr;
2780 :
2781 : // Close sync connection asynchronously: if we let destructor close, it may
2782 : // cause an expensive fsync operation on the main-thread.
2783 1 : mDefaultDBState->syncConn->AsyncClose(nullptr);
2784 1 : mDefaultDBState->syncConn = nullptr;
2785 1 : mDefaultDBState->hostArray.Clear();
2786 1 : mDefaultDBState->readSet.Clear();
2787 :
2788 1 : COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %" PRIu32 " cookies read",
2789 : mDefaultDBState->cookieCount));
2790 :
2791 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2792 1 : if (os) {
2793 1 : os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
2794 : }
2795 1 : }
2796 :
2797 : void
2798 0 : nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
2799 : {
2800 : // We may be in the private browsing DB state, with a pending read on the
2801 : // default DB state. (This would occur if we started up in private browsing
2802 : // mode.) As long as we do all our operations on the default state, we're OK.
2803 0 : NS_ASSERTION(mDefaultDBState, "no default DBState");
2804 0 : NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
2805 0 : NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
2806 :
2807 : // Cancel the pending read, kill the read listener, and empty the array
2808 : // of data already read in on the background thread.
2809 0 : mDefaultDBState->readListener->Cancel();
2810 0 : DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
2811 0 : NS_ASSERT_SUCCESS(rv);
2812 :
2813 0 : mDefaultDBState->stmtReadDomain = nullptr;
2814 0 : mDefaultDBState->pendingRead = nullptr;
2815 0 : mDefaultDBState->readListener = nullptr;
2816 0 : mDefaultDBState->hostArray.Clear();
2817 :
2818 : // Only clear the 'readSet' table if we no longer need to know what set of
2819 : // data is already accounted for.
2820 0 : if (aPurgeReadSet)
2821 0 : mDefaultDBState->readSet.Clear();
2822 0 : }
2823 :
2824 : void
2825 6 : nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
2826 : {
2827 6 : NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
2828 : "not in default db state");
2829 :
2830 : // Fast path 1: nothing to read, or we've already finished reading.
2831 6 : if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
2832 10 : return;
2833 :
2834 : // Fast path 2: already read in this particular domain.
2835 1 : if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
2836 0 : return;
2837 :
2838 : // Read in the data synchronously.
2839 : // see IDX_NAME, etc. for parameter indexes
2840 : nsresult rv;
2841 1 : if (!mDefaultDBState->stmtReadDomain) {
2842 : // Cache the statement, since it's likely to be used again.
2843 4 : rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
2844 : "SELECT "
2845 : "name, "
2846 : "value, "
2847 : "host, "
2848 : "path, "
2849 : "expiry, "
2850 : "lastAccessed, "
2851 : "creationTime, "
2852 : "isSecure, "
2853 : "isHttpOnly "
2854 : "FROM moz_cookies "
2855 : "WHERE baseDomain = :baseDomain "
2856 : " AND originAttributes = :originAttributes"),
2857 4 : getter_AddRefs(mDefaultDBState->stmtReadDomain));
2858 :
2859 1 : if (NS_FAILED(rv)) {
2860 : // Recreate the database.
2861 0 : COOKIE_LOGSTRING(LogLevel::Debug,
2862 : ("EnsureReadDomain(): corruption detected when creating statement "
2863 : "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
2864 0 : HandleCorruptDB(mDefaultDBState);
2865 0 : return;
2866 : }
2867 : }
2868 :
2869 1 : NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
2870 :
2871 2 : mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
2872 :
2873 3 : rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
2874 3 : NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
2875 1 : NS_ASSERT_SUCCESS(rv);
2876 :
2877 2 : nsAutoCString suffix;
2878 1 : aKey.mOriginAttributes.CreateSuffix(suffix);
2879 2 : rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
2880 2 : NS_LITERAL_CSTRING("originAttributes"), suffix);
2881 1 : NS_ASSERT_SUCCESS(rv);
2882 :
2883 : bool hasResult;
2884 2 : nsCString name, value, host, path;
2885 2 : AutoTArray<RefPtr<nsCookie>, kMaxCookiesPerHost> array;
2886 : while (true) {
2887 1 : rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
2888 1 : if (NS_FAILED(rv)) {
2889 : // Recreate the database.
2890 0 : COOKIE_LOGSTRING(LogLevel::Debug,
2891 : ("EnsureReadDomain(): corruption detected when reading result "
2892 : "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
2893 0 : HandleCorruptDB(mDefaultDBState);
2894 0 : return;
2895 : }
2896 :
2897 1 : if (!hasResult)
2898 1 : break;
2899 :
2900 0 : array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain,
2901 0 : aKey.mOriginAttributes));
2902 : }
2903 :
2904 2 : mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
2905 : // Add the cookies to the table in a single operation. This makes sure that
2906 : // either all the cookies get added, or in the case of corruption, none.
2907 1 : for (uint32_t i = 0; i < array.Length(); ++i) {
2908 0 : AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
2909 : }
2910 1 : rv = transaction.Commit();
2911 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
2912 :
2913 : // Add it to the hashset of read entries, so we don't read it again.
2914 1 : mDefaultDBState->readSet.PutEntry(aKey);
2915 :
2916 1 : COOKIE_LOGSTRING(LogLevel::Debug,
2917 : ("EnsureReadDomain(): %" PRIuSIZE " cookies read for base domain %s, "
2918 : " originAttributes = %s", array.Length(), aKey.mBaseDomain.get(),
2919 : suffix.get()));
2920 : }
2921 :
2922 : void
2923 0 : nsCookieService::EnsureReadComplete()
2924 : {
2925 0 : NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
2926 : "not in default db state");
2927 :
2928 : // Fast path 1: nothing to read, or we've already finished reading.
2929 0 : if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
2930 0 : return;
2931 :
2932 : // Cancel the pending read, so we don't get any more results.
2933 0 : CancelAsyncRead(false);
2934 :
2935 : // Read in the data synchronously.
2936 : // see IDX_NAME, etc. for parameter indexes
2937 0 : nsCOMPtr<mozIStorageStatement> stmt;
2938 0 : nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
2939 : "SELECT "
2940 : "name, "
2941 : "value, "
2942 : "host, "
2943 : "path, "
2944 : "expiry, "
2945 : "lastAccessed, "
2946 : "creationTime, "
2947 : "isSecure, "
2948 : "isHttpOnly, "
2949 : "baseDomain, "
2950 : "originAttributes "
2951 : "FROM moz_cookies "
2952 0 : "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
2953 :
2954 0 : if (NS_FAILED(rv)) {
2955 : // Recreate the database.
2956 0 : COOKIE_LOGSTRING(LogLevel::Debug,
2957 : ("EnsureReadComplete(): corruption detected when creating statement "
2958 : "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
2959 0 : HandleCorruptDB(mDefaultDBState);
2960 0 : return;
2961 : }
2962 :
2963 0 : nsCString baseDomain, name, value, host, path;
2964 : bool hasResult;
2965 0 : nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
2966 : while (true) {
2967 0 : rv = stmt->ExecuteStep(&hasResult);
2968 0 : if (NS_FAILED(rv)) {
2969 : // Recreate the database.
2970 0 : COOKIE_LOGSTRING(LogLevel::Debug,
2971 : ("EnsureReadComplete(): corruption detected when reading result "
2972 : "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
2973 0 : HandleCorruptDB(mDefaultDBState);
2974 0 : return;
2975 : }
2976 :
2977 0 : if (!hasResult)
2978 0 : break;
2979 :
2980 : // Make sure we haven't already read the data.
2981 0 : stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
2982 :
2983 0 : nsAutoCString suffix;
2984 0 : OriginAttributes attrs;
2985 0 : stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
2986 : // If PopulateFromSuffix failed we just ignore the OA attributes
2987 : // that we don't support
2988 0 : Unused << attrs.PopulateFromSuffix(suffix);
2989 :
2990 0 : nsCookieKey key(baseDomain, attrs);
2991 0 : if (mDefaultDBState->readSet.GetEntry(key))
2992 0 : continue;
2993 :
2994 0 : CookieDomainTuple* tuple = array.AppendElement();
2995 0 : tuple->key = key;
2996 0 : tuple->cookie = GetCookieFromRow(stmt, attrs);
2997 0 : }
2998 :
2999 0 : mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
3000 : // Add the cookies to the table in a single operation. This makes sure that
3001 : // either all the cookies get added, or in the case of corruption, none.
3002 0 : for (uint32_t i = 0; i < array.Length(); ++i) {
3003 0 : CookieDomainTuple& tuple = array[i];
3004 0 : AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
3005 0 : false);
3006 : }
3007 0 : rv = transaction.Commit();
3008 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3009 :
3010 0 : mDefaultDBState->syncConn = nullptr;
3011 0 : mDefaultDBState->readSet.Clear();
3012 :
3013 0 : COOKIE_LOGSTRING(LogLevel::Debug,
3014 : ("EnsureReadComplete(): %" PRIuSIZE " cookies read", array.Length()));
3015 : }
3016 :
3017 : NS_IMETHODIMP
3018 0 : nsCookieService::ImportCookies(nsIFile *aCookieFile)
3019 : {
3020 0 : if (!mDBState) {
3021 0 : NS_WARNING("No DBState! Profile already closed?");
3022 0 : return NS_ERROR_NOT_AVAILABLE;
3023 : }
3024 :
3025 : // Make sure we're in the default DB state. We don't want people importing
3026 : // cookies into a private browsing session!
3027 0 : if (mDBState != mDefaultDBState) {
3028 0 : NS_WARNING("Trying to import cookies in a private browsing session!");
3029 0 : return NS_ERROR_NOT_AVAILABLE;
3030 : }
3031 :
3032 : nsresult rv;
3033 0 : nsCOMPtr<nsIInputStream> fileInputStream;
3034 0 : rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
3035 0 : if (NS_FAILED(rv)) return rv;
3036 :
3037 0 : nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
3038 0 : if (NS_FAILED(rv)) return rv;
3039 :
3040 : // First, ensure we've read in everything from the database, if we have one.
3041 0 : EnsureReadComplete();
3042 :
3043 : static const char kTrue[] = "TRUE";
3044 :
3045 0 : nsAutoCString buffer, baseDomain;
3046 0 : bool isMore = true;
3047 : int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
3048 : nsACString::char_iterator iter;
3049 : int32_t numInts;
3050 : int64_t expires;
3051 0 : bool isDomain, isHttpOnly = false;
3052 0 : uint32_t originalCookieCount = mDefaultDBState->cookieCount;
3053 :
3054 0 : int64_t currentTimeInUsec = PR_Now();
3055 0 : int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
3056 : // we use lastAccessedCounter to keep cookies in recently-used order,
3057 : // so we start by initializing to currentTime (somewhat arbitrary)
3058 0 : int64_t lastAccessedCounter = currentTimeInUsec;
3059 :
3060 : /* file format is:
3061 : *
3062 : * host \t isDomain \t path \t secure \t expires \t name \t cookie
3063 : *
3064 : * if this format isn't respected we move onto the next line in the file.
3065 : * isDomain is "TRUE" or "FALSE" (default to "FALSE")
3066 : * isSecure is "TRUE" or "FALSE" (default to "TRUE")
3067 : * expires is a int64_t integer
3068 : * note 1: cookie can contain tabs.
3069 : * note 2: cookies will be stored in order of lastAccessed time:
3070 : * most-recently used come first; least-recently-used come last.
3071 : */
3072 :
3073 : /*
3074 : * ...but due to bug 178933, we hide HttpOnly cookies from older code
3075 : * in a comment, so they don't expose HttpOnly cookies to JS.
3076 : *
3077 : * The format for HttpOnly cookies is
3078 : *
3079 : * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
3080 : *
3081 : */
3082 :
3083 : // We will likely be adding a bunch of cookies to the DB, so we use async
3084 : // batching with storage to make this super fast.
3085 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
3086 0 : if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
3087 0 : mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
3088 : }
3089 :
3090 0 : while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
3091 0 : if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) {
3092 0 : isHttpOnly = true;
3093 0 : hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1;
3094 0 : } else if (buffer.IsEmpty() || buffer.First() == '#') {
3095 0 : continue;
3096 : } else {
3097 0 : isHttpOnly = false;
3098 0 : hostIndex = 0;
3099 : }
3100 :
3101 : // this is a cheap, cheesy way of parsing a tab-delimited line into
3102 : // string indexes, which can be lopped off into substrings. just for
3103 : // purposes of obfuscation, it also checks that each token was found.
3104 : // todo: use iterators?
3105 0 : if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
3106 0 : (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
3107 0 : (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
3108 0 : (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
3109 0 : (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
3110 0 : (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
3111 0 : continue;
3112 : }
3113 :
3114 : // check the expirytime first - if it's expired, ignore
3115 : // nullstomp the trailing tab, to avoid copying the string
3116 0 : buffer.BeginWriting(iter);
3117 0 : *(iter += nameIndex - 1) = char(0);
3118 0 : numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
3119 0 : if (numInts != 1 || expires < currentTime) {
3120 0 : continue;
3121 : }
3122 :
3123 0 : isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
3124 0 : const nsACString& host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
3125 : // check for bad legacy cookies (domain not starting with a dot, or containing a port),
3126 : // and discard
3127 0 : if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
3128 0 : host.Contains(':')) {
3129 0 : continue;
3130 : }
3131 :
3132 : // compute the baseDomain from the host
3133 0 : rv = GetBaseDomainFromHost(host, baseDomain);
3134 0 : if (NS_FAILED(rv))
3135 0 : continue;
3136 :
3137 : // pre-existing cookies have inIsolatedMozBrowser=false set by default
3138 : // constructor of OriginAttributes().
3139 0 : nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
3140 :
3141 : // Create a new nsCookie and assign the data. We don't know the cookie
3142 : // creation time, so just use the current time to generate a unique one.
3143 : RefPtr<nsCookie> newCookie =
3144 0 : nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
3145 0 : Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
3146 : host,
3147 0 : Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
3148 : expires,
3149 : lastAccessedCounter,
3150 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
3151 : false,
3152 0 : Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
3153 : isHttpOnly,
3154 0 : key.mOriginAttributes);
3155 0 : if (!newCookie) {
3156 0 : return NS_ERROR_OUT_OF_MEMORY;
3157 : }
3158 :
3159 : // trick: preserve the most-recently-used cookie ordering,
3160 : // by successively decrementing the lastAccessed time
3161 0 : lastAccessedCounter--;
3162 :
3163 0 : if (originalCookieCount == 0) {
3164 0 : AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
3165 : }
3166 : else {
3167 0 : AddInternal(key, newCookie, currentTimeInUsec,
3168 0 : nullptr, nullptr, true);
3169 : }
3170 : }
3171 :
3172 : // If we need to write to disk, do so now.
3173 0 : if (paramsArray) {
3174 : uint32_t length;
3175 0 : paramsArray->GetLength(&length);
3176 0 : if (length) {
3177 0 : rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
3178 0 : NS_ASSERT_SUCCESS(rv);
3179 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
3180 0 : rv = mDefaultDBState->stmtInsert->ExecuteAsync(
3181 0 : mDefaultDBState->insertListener, getter_AddRefs(handle));
3182 0 : NS_ASSERT_SUCCESS(rv);
3183 : }
3184 : }
3185 :
3186 :
3187 0 : COOKIE_LOGSTRING(LogLevel::Debug, ("ImportCookies(): %" PRIu32 " cookies imported",
3188 : mDefaultDBState->cookieCount));
3189 :
3190 0 : return NS_OK;
3191 : }
3192 :
3193 : /******************************************************************************
3194 : * nsCookieService impl:
3195 : * private GetCookie/SetCookie helpers
3196 : ******************************************************************************/
3197 :
3198 : // helper function for GetCookieList
3199 0 : static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
3200 :
3201 : // Comparator class for sorting cookies before sending to a server.
3202 : class CompareCookiesForSending
3203 : {
3204 : public:
3205 0 : bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
3206 : {
3207 0 : return aCookie1->CreationTime() == aCookie2->CreationTime() &&
3208 0 : aCookie2->Path().Length() == aCookie1->Path().Length();
3209 : }
3210 :
3211 0 : bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
3212 : {
3213 : // compare by cookie path length in accordance with RFC2109
3214 0 : int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
3215 0 : if (result != 0)
3216 0 : return result < 0;
3217 :
3218 : // when path lengths match, older cookies should be listed first. this is
3219 : // required for backwards compatibility since some websites erroneously
3220 : // depend on receiving cookies in the order in which they were sent to the
3221 : // browser! see bug 236772.
3222 0 : return aCookie1->CreationTime() < aCookie2->CreationTime();
3223 : }
3224 : };
3225 :
3226 : static bool
3227 0 : DomainMatches(nsCookie* aCookie, const nsACString& aHost) {
3228 : // first, check for an exact host or domain cookie match, e.g. "google.com"
3229 : // or ".google.com"; second a subdomain match, e.g.
3230 : // host = "mail.google.com", cookie domain = ".google.com".
3231 0 : return aCookie->RawHost() == aHost ||
3232 0 : (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
3233 : }
3234 :
3235 : static bool
3236 0 : PathMatches(nsCookie* aCookie, const nsACString& aPath) {
3237 : // calculate cookie path length, excluding trailing '/'
3238 0 : uint32_t cookiePathLen = aCookie->Path().Length();
3239 0 : if (cookiePathLen > 0 && aCookie->Path().Last() == '/')
3240 0 : --cookiePathLen;
3241 :
3242 : // if the given path is shorter than the cookie path, it doesn't match
3243 : // if the given path doesn't start with the cookie path, it doesn't match.
3244 0 : if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
3245 0 : return false;
3246 :
3247 : // if the given path is longer than the cookie path, and the first char after
3248 : // the cookie path is not a path delimiter, it doesn't match.
3249 0 : if (aPath.Length() > cookiePathLen &&
3250 0 : !ispathdelimiter(aPath.CharAt(cookiePathLen))) {
3251 : /*
3252 : * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
3253 : * '/' is the "standard" case; the '?' test allows a site at host/abc?def
3254 : * to receive a cookie that has a path attribute of abc. this seems
3255 : * strange but at least one major site (citibank, bug 156725) depends
3256 : * on it. The test for # and ; are put in to proactively avoid problems
3257 : * with other sites - these are the only other chars allowed in the path.
3258 : */
3259 0 : return false;
3260 : }
3261 :
3262 : // either the paths match exactly, or the cookie path is a prefix of
3263 : // the given path.
3264 0 : return true;
3265 : }
3266 :
3267 : void
3268 6 : nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
3269 : bool aIsForeign,
3270 : bool aHttpBound,
3271 : const OriginAttributes& aOriginAttrs,
3272 : nsCString &aCookieString)
3273 : {
3274 6 : NS_ASSERTION(aHostURI, "null host!");
3275 :
3276 6 : if (!mDBState) {
3277 0 : NS_WARNING("No DBState! Profile already closed?");
3278 0 : return;
3279 : }
3280 :
3281 6 : AutoRestore<DBState*> savePrevDBState(mDBState);
3282 6 : mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
3283 :
3284 : // get the base domain, host, and path from the URI.
3285 : // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
3286 : // file:// URI's (i.e. with an empty host) are allowed, but any other
3287 : // scheme must have a non-empty host. A trailing dot in the host
3288 : // is acceptable.
3289 : bool requireHostMatch;
3290 6 : nsAutoCString baseDomain, hostFromURI, pathFromURI;
3291 6 : nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
3292 6 : if (NS_SUCCEEDED(rv))
3293 6 : rv = aHostURI->GetAsciiHost(hostFromURI);
3294 6 : if (NS_SUCCEEDED(rv))
3295 6 : rv = aHostURI->GetPath(pathFromURI);
3296 6 : if (NS_FAILED(rv)) {
3297 0 : COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
3298 0 : return;
3299 : }
3300 :
3301 : // check default prefs
3302 6 : CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, nullptr);
3303 :
3304 : // for GetCookie(), we don't fire rejection notifications.
3305 6 : switch (cookieStatus) {
3306 : case STATUS_REJECTED:
3307 : case STATUS_REJECTED_WITH_ERROR:
3308 0 : return;
3309 : default:
3310 6 : break;
3311 : }
3312 :
3313 : // Note: The following permissions logic is mirrored in
3314 : // toolkit/modules/addons/MatchPattern.jsm:MatchPattern.matchesCookie().
3315 : // If it changes, please update that function, or file a bug for someone
3316 : // else to do so.
3317 :
3318 : // check if aHostURI is using an https secure protocol.
3319 : // if it isn't, then we can't send a secure cookie over the connection.
3320 : // if SchemeIs fails, assume an insecure connection, to be on the safe side
3321 : bool isSecure;
3322 6 : if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
3323 0 : isSecure = false;
3324 : }
3325 :
3326 : nsCookie *cookie;
3327 6 : AutoTArray<nsCookie*, 8> foundCookieList;
3328 6 : int64_t currentTimeInUsec = PR_Now();
3329 6 : int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
3330 6 : bool stale = false;
3331 :
3332 6 : nsCookieKey key(baseDomain, aOriginAttrs);
3333 6 : EnsureReadDomain(key);
3334 :
3335 : // perform the hash lookup
3336 6 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
3337 6 : if (!entry)
3338 6 : return;
3339 :
3340 : // iterate the cookies!
3341 0 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
3342 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
3343 0 : cookie = cookies[i];
3344 :
3345 : // check the host, since the base domain lookup is conservative.
3346 0 : if (!DomainMatches(cookie, hostFromURI))
3347 0 : continue;
3348 :
3349 : // if the cookie is secure and the host scheme isn't, we can't send it
3350 0 : if (cookie->IsSecure() && !isSecure)
3351 0 : continue;
3352 :
3353 : // if the cookie is httpOnly and it's not going directly to the HTTP
3354 : // connection, don't send it
3355 0 : if (cookie->IsHttpOnly() && !aHttpBound)
3356 0 : continue;
3357 :
3358 : // if the nsIURI path doesn't match the cookie path, don't send it back
3359 0 : if (!PathMatches(cookie, pathFromURI))
3360 0 : continue;
3361 :
3362 : // check if the cookie has expired
3363 0 : if (cookie->Expiry() <= currentTime) {
3364 0 : continue;
3365 : }
3366 :
3367 : // all checks passed - add to list and check if lastAccessed stamp needs updating
3368 0 : foundCookieList.AppendElement(cookie);
3369 0 : if (cookie->IsStale()) {
3370 0 : stale = true;
3371 : }
3372 : }
3373 :
3374 0 : int32_t count = foundCookieList.Length();
3375 0 : if (count == 0)
3376 0 : return;
3377 :
3378 : // update lastAccessed timestamps. we only do this if the timestamp is stale
3379 : // by a certain amount, to avoid thrashing the db during pageload.
3380 0 : if (stale) {
3381 : // Create an array of parameters to bind to our update statement. Batching
3382 : // is OK here since we're updating cookies with no interleaved operations.
3383 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
3384 0 : mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
3385 0 : if (mDBState->dbConn) {
3386 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
3387 : }
3388 :
3389 0 : for (int32_t i = 0; i < count; ++i) {
3390 0 : cookie = foundCookieList.ElementAt(i);
3391 :
3392 0 : if (cookie->IsStale()) {
3393 0 : UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
3394 : }
3395 : }
3396 : // Update the database now if necessary.
3397 0 : if (paramsArray) {
3398 : uint32_t length;
3399 0 : paramsArray->GetLength(&length);
3400 0 : if (length) {
3401 0 : DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
3402 0 : NS_ASSERT_SUCCESS(rv);
3403 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
3404 0 : rv = stmt->ExecuteAsync(mDBState->updateListener,
3405 0 : getter_AddRefs(handle));
3406 0 : NS_ASSERT_SUCCESS(rv);
3407 : }
3408 : }
3409 : }
3410 :
3411 : // return cookies in order of path length; longest to shortest.
3412 : // this is required per RFC2109. if cookies match in length,
3413 : // then sort by creation time (see bug 236772).
3414 0 : foundCookieList.Sort(CompareCookiesForSending());
3415 :
3416 0 : for (int32_t i = 0; i < count; ++i) {
3417 0 : cookie = foundCookieList.ElementAt(i);
3418 :
3419 : // check if we have anything to write
3420 0 : if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
3421 : // if we've already added a cookie to the return list, append a "; " so
3422 : // that subsequent cookies are delimited in the final list.
3423 0 : if (!aCookieString.IsEmpty()) {
3424 0 : aCookieString.AppendLiteral("; ");
3425 : }
3426 :
3427 0 : if (!cookie->Name().IsEmpty()) {
3428 : // we have a name and value - write both
3429 0 : aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
3430 : } else {
3431 : // just write value
3432 0 : aCookieString += cookie->Value();
3433 : }
3434 : }
3435 : }
3436 :
3437 0 : if (!aCookieString.IsEmpty())
3438 0 : COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
3439 : }
3440 :
3441 : // processes a single cookie, and returns true if there are more cookies
3442 : // to be processed
3443 : bool
3444 0 : nsCookieService::SetCookieInternal(nsIURI *aHostURI,
3445 : const nsCookieKey &aKey,
3446 : bool aRequireHostMatch,
3447 : CookieStatus aStatus,
3448 : nsDependentCString &aCookieHeader,
3449 : int64_t aServerTime,
3450 : bool aFromHttp,
3451 : nsIChannel *aChannel)
3452 : {
3453 0 : NS_ASSERTION(aHostURI, "null host!");
3454 :
3455 : // create a stack-based nsCookieAttributes, to store all the
3456 : // attributes parsed from the cookie
3457 0 : nsCookieAttributes cookieAttributes;
3458 :
3459 : // init expiryTime such that session cookies won't prematurely expire
3460 0 : cookieAttributes.expiryTime = INT64_MAX;
3461 :
3462 : // aCookieHeader is an in/out param to point to the next cookie, if
3463 : // there is one. Save the present value for logging purposes
3464 0 : nsDependentCString savedCookieHeader(aCookieHeader);
3465 :
3466 : // newCookie says whether there are multiple cookies in the header;
3467 : // so we can handle them separately.
3468 0 : bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
3469 :
3470 : // Collect telemetry on how often secure cookies are set from non-secure
3471 : // origins, and vice-versa.
3472 : //
3473 : // 0 = nonsecure and "http:"
3474 : // 1 = nonsecure and "https:"
3475 : // 2 = secure and "http:"
3476 : // 3 = secure and "https:"
3477 : bool isHTTPS;
3478 0 : nsresult rv = aHostURI->SchemeIs("https", &isHTTPS);
3479 0 : if (NS_SUCCEEDED(rv)) {
3480 0 : Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY,
3481 0 : ((cookieAttributes.isSecure)? 0x02 : 0x00) |
3482 0 : ((isHTTPS)? 0x01 : 0x00));
3483 : }
3484 :
3485 0 : int64_t currentTimeInUsec = PR_Now();
3486 :
3487 : // calculate expiry time of cookie.
3488 0 : cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
3489 : currentTimeInUsec / PR_USEC_PER_SEC);
3490 0 : if (aStatus == STATUS_ACCEPT_SESSION) {
3491 : // force lifetime to session. note that the expiration time, if set above,
3492 : // will still apply.
3493 0 : cookieAttributes.isSession = true;
3494 : }
3495 :
3496 : // reject cookie if it's over the size limit, per RFC2109
3497 0 : if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
3498 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
3499 0 : return newCookie;
3500 : }
3501 :
3502 : const char illegalNameCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
3503 : 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
3504 : 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
3505 : 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
3506 : 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
3507 0 : 0x1F, 0x00 };
3508 0 : if (cookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) {
3509 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
3510 0 : return newCookie;
3511 : }
3512 :
3513 : // domain & path checks
3514 0 : if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
3515 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
3516 0 : return newCookie;
3517 : }
3518 0 : if (!CheckPath(cookieAttributes, aHostURI)) {
3519 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
3520 0 : return newCookie;
3521 : }
3522 : // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
3523 0 : if (!CheckPrefixes(cookieAttributes, isHTTPS)) {
3524 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the prefix tests");
3525 0 : return newCookie;
3526 : }
3527 :
3528 : // reject cookie if value contains an RFC 6265 disallowed character - see
3529 : // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
3530 : // NOTE: this is not the full set of characters disallowed by 6265 - notably
3531 : // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is
3532 : // for parity with Chrome. This only applies to cookies set via the Set-Cookie
3533 : // header, as document.cookie is defined to be UTF-8. Hooray for
3534 : // symmetry!</sarcasm>
3535 : const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
3536 : 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
3537 : 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
3538 : 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
3539 0 : 0x1E, 0x1F, 0x3B, 0x00 };
3540 0 : if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
3541 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character");
3542 0 : return newCookie;
3543 : }
3544 :
3545 : // create a new nsCookie and copy attributes
3546 : RefPtr<nsCookie> cookie =
3547 : nsCookie::Create(cookieAttributes.name,
3548 : cookieAttributes.value,
3549 : cookieAttributes.host,
3550 : cookieAttributes.path,
3551 : cookieAttributes.expiryTime,
3552 : currentTimeInUsec,
3553 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
3554 0 : cookieAttributes.isSession,
3555 0 : cookieAttributes.isSecure,
3556 0 : cookieAttributes.isHttpOnly,
3557 0 : aKey.mOriginAttributes);
3558 0 : if (!cookie)
3559 0 : return newCookie;
3560 :
3561 : // check permissions from site permission list, or ask the user,
3562 : // to determine if we can set the cookie
3563 0 : if (mPermissionService) {
3564 : bool permission;
3565 0 : mPermissionService->CanSetCookie(aHostURI,
3566 : aChannel,
3567 0 : static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
3568 : &cookieAttributes.isSession,
3569 : &cookieAttributes.expiryTime,
3570 0 : &permission);
3571 0 : if (!permission) {
3572 0 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
3573 0 : NotifyRejected(aHostURI);
3574 0 : return newCookie;
3575 : }
3576 :
3577 : // update isSession and expiry attributes, in case they changed
3578 0 : cookie->SetIsSession(cookieAttributes.isSession);
3579 0 : cookie->SetExpiry(cookieAttributes.expiryTime);
3580 : }
3581 :
3582 : // add the cookie to the list. AddInternal() takes care of logging.
3583 : // we get the current time again here, since it may have changed during prompting
3584 0 : AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
3585 0 : aFromHttp);
3586 0 : return newCookie;
3587 : }
3588 :
3589 : // this is a backend function for adding a cookie to the list, via SetCookie.
3590 : // also used in the cookie manager, for profile migration from IE.
3591 : // it either replaces an existing cookie; or adds the cookie to the hashtable,
3592 : // and deletes a cookie (if maximum number of cookies has been
3593 : // reached). also performs list maintenance by removing expired cookies.
3594 : void
3595 0 : nsCookieService::AddInternal(const nsCookieKey &aKey,
3596 : nsCookie *aCookie,
3597 : int64_t aCurrentTimeInUsec,
3598 : nsIURI *aHostURI,
3599 : const char *aCookieHeader,
3600 : bool aFromHttp)
3601 : {
3602 0 : int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
3603 :
3604 : // if the new cookie is httponly, make sure we're not coming from script
3605 0 : if (!aFromHttp && aCookie->IsHttpOnly()) {
3606 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3607 0 : "cookie is httponly; coming from script");
3608 0 : return;
3609 : }
3610 :
3611 0 : bool isSecure = true;
3612 0 : if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
3613 0 : isSecure = false;
3614 : }
3615 :
3616 : // If the new cookie is non-https and wants to set secure flag,
3617 : // browser have to ignore this new cookie.
3618 : // (draft-ietf-httpbis-cookie-alone section 3.1)
3619 0 : if (mLeaveSecureAlone && aCookie->IsSecure() && !isSecure) {
3620 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3621 0 : "non-https cookie can't set secure flag");
3622 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3623 0 : BLOCKED_SECURE_SET_FROM_HTTP);
3624 0 : return;
3625 : }
3626 0 : nsListIter exactIter;
3627 0 : bool foundCookie = false;
3628 0 : foundCookie = FindCookie(aKey, aCookie->Host(),
3629 0 : aCookie->Name(), aCookie->Path(), exactIter);
3630 0 : bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
3631 0 : bool oldCookieIsSession = false;
3632 0 : if (mLeaveSecureAlone) {
3633 : // Step1, call FindSecureCookie(). FindSecureCookie() would
3634 : // find the existing cookie with the security flag and has
3635 : // the same name, host and path of the new cookie, if there is any.
3636 : // Step2, Confirm new cookie's security setting. If any targeted
3637 : // cookie had been found in Step1, then confirm whether the
3638 : // new cookie could modify it. If the new created cookie’s
3639 : // "secure-only-flag" is not set, and the "scheme" component
3640 : // of the "request-uri" does not denote a "secure" protocol,
3641 : // then ignore the new cookie.
3642 : // (draft-ietf-httpbis-cookie-alone section 3.2)
3643 0 : if (!aCookie->IsSecure()
3644 0 : && (foundSecureExact || FindSecureCookie(aKey, aCookie))) {
3645 0 : if (!isSecure) {
3646 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3647 0 : "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
3648 0 : if (foundSecureExact) {
3649 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3650 0 : BLOCKED_DOWNGRADE_SECURE_EXACT);
3651 : } else {
3652 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3653 0 : BLOCKED_DOWNGRADE_SECURE_INEXACT);
3654 : }
3655 0 : return;
3656 : }
3657 : // A secure site is allowed to downgrade a secure cookie
3658 : // but we want to measure anyway.
3659 0 : if (foundSecureExact) {
3660 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3661 0 : DOWNGRADE_SECURE_FROM_SECURE_EXACT);
3662 : } else {
3663 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3664 0 : DOWNGRADE_SECURE_FROM_SECURE_INEXACT);
3665 : }
3666 : }
3667 : }
3668 :
3669 0 : RefPtr<nsCookie> oldCookie;
3670 0 : nsCOMPtr<nsIArray> purgedList;
3671 0 : if (foundCookie) {
3672 0 : oldCookie = exactIter.Cookie();
3673 0 : oldCookieIsSession = oldCookie->IsSession();
3674 :
3675 : // Check if the old cookie is stale (i.e. has already expired). If so, we
3676 : // need to be careful about the semantics of removing it and adding the new
3677 : // cookie: we want the behavior wrt adding the new cookie to be the same as
3678 : // if it didn't exist, but we still want to fire a removal notification.
3679 0 : if (oldCookie->Expiry() <= currentTime) {
3680 0 : if (aCookie->Expiry() <= currentTime) {
3681 : // The new cookie has expired and the old one is stale. Nothing to do.
3682 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3683 0 : "cookie has already expired");
3684 0 : return;
3685 : }
3686 :
3687 : // Remove the stale cookie. We save notification for later, once all list
3688 : // modifications are complete.
3689 0 : RemoveCookieFromList(exactIter);
3690 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3691 0 : "stale cookie was purged");
3692 0 : purgedList = CreatePurgeList(oldCookie);
3693 :
3694 : // We've done all we need to wrt removing and notifying the stale cookie.
3695 : // From here on out, we pretend pretend it didn't exist, so that we
3696 : // preserve expected notification semantics when adding the new cookie.
3697 0 : foundCookie = false;
3698 :
3699 : } else {
3700 : // If the old cookie is httponly, make sure we're not coming from script.
3701 0 : if (!aFromHttp && oldCookie->IsHttpOnly()) {
3702 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3703 0 : "previously stored cookie is httponly; coming from script");
3704 0 : return;
3705 : }
3706 :
3707 : // If the new cookie has the same value, expiry date, and isSecure,
3708 : // isSession, and isHttpOnly flags then we can just keep the old one.
3709 : // Only if any of these differ we would want to override the cookie.
3710 0 : if (oldCookie->Value().Equals(aCookie->Value()) &&
3711 0 : oldCookie->Expiry() == aCookie->Expiry() &&
3712 0 : oldCookie->IsSecure() == aCookie->IsSecure() &&
3713 0 : oldCookie->IsSession() == aCookie->IsSession() &&
3714 0 : oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
3715 : // We don't want to perform this optimization if the cookie is
3716 : // considered stale, since in this case we would need to update the
3717 : // database.
3718 0 : !oldCookie->IsStale()) {
3719 : // Update the last access time on the old cookie.
3720 0 : oldCookie->SetLastAccessed(aCookie->LastAccessed());
3721 0 : UpdateCookieOldestTime(mDBState, oldCookie);
3722 0 : return;
3723 : }
3724 :
3725 : // Remove the old cookie.
3726 0 : RemoveCookieFromList(exactIter);
3727 :
3728 : // If the new cookie has expired -- i.e. the intent was simply to delete
3729 : // the old cookie -- then we're done.
3730 0 : if (aCookie->Expiry() <= currentTime) {
3731 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3732 0 : "previously stored cookie was deleted");
3733 0 : NotifyChanged(oldCookie, u"deleted");
3734 0 : return;
3735 : }
3736 :
3737 : // Preserve creation time of cookie for ordering purposes.
3738 0 : aCookie->SetCreationTime(oldCookie->CreationTime());
3739 : }
3740 :
3741 : } else {
3742 : // check if cookie has already expired
3743 0 : if (aCookie->Expiry() <= currentTime) {
3744 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3745 0 : "cookie has already expired");
3746 0 : return;
3747 : }
3748 :
3749 : // check if we have to delete an old cookie.
3750 0 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
3751 0 : if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
3752 0 : nsListIter iter;
3753 : // Prioritize evicting insecure cookies.
3754 : // (draft-ietf-httpbis-cookie-alone section 3.3)
3755 0 : mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
3756 0 : int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
3757 0 : if (iter.entry == nullptr) {
3758 0 : if (aCookie->IsSecure()) {
3759 : // It's valid to evict a secure cookie for another secure cookie.
3760 0 : oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
3761 : } else {
3762 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3763 0 : EVICTING_SECURE_BLOCKED);
3764 0 : COOKIE_LOGEVICTED(aCookie,
3765 : "Too many cookies for this domain and the new cookie is not a secure cookie");
3766 0 : return;
3767 : }
3768 : }
3769 :
3770 0 : MOZ_ASSERT(iter.entry);
3771 :
3772 0 : oldCookie = iter.Cookie();
3773 0 : if (oldestCookieTime > 0 && mLeaveSecureAlone) {
3774 0 : TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
3775 : }
3776 :
3777 : // remove the oldest cookie from the domain
3778 0 : RemoveCookieFromList(iter);
3779 0 : COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
3780 0 : purgedList = CreatePurgeList(oldCookie);
3781 0 : } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
3782 0 : int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
3783 0 : int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
3784 0 : if (maxAge >= purgeAge) {
3785 : // we're over both size and age limits by 10%; time to purge the table!
3786 : // do this by:
3787 : // 1) removing expired cookies;
3788 : // 2) evicting the balance of old cookies until we reach the size limit.
3789 : // note that the cookieOldestTime indicator can be pessimistic - if it's
3790 : // older than the actual oldest cookie, we'll just purge more eagerly.
3791 0 : purgedList = PurgeCookies(aCurrentTimeInUsec);
3792 : }
3793 : }
3794 : }
3795 :
3796 : // Add the cookie to the db. We do not supply a params array for batching
3797 : // because this might result in removals and additions being out of order.
3798 0 : AddCookieToList(aKey, aCookie, mDBState, nullptr);
3799 0 : COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
3800 :
3801 : // Now that list mutations are complete, notify observers. We do it here
3802 : // because observers may themselves attempt to mutate the list.
3803 0 : if (purgedList) {
3804 0 : NotifyChanged(purgedList, u"batch-deleted");
3805 : }
3806 :
3807 0 : NotifyChanged(aCookie, foundCookie ? u"changed" : u"added", oldCookieIsSession);
3808 : }
3809 :
3810 : /******************************************************************************
3811 : * nsCookieService impl:
3812 : * private cookie header parsing functions
3813 : ******************************************************************************/
3814 :
3815 : // The following comment block elucidates the function of ParseAttributes.
3816 : /******************************************************************************
3817 : ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
3818 : ** please note: this BNF deviates from both specifications, and reflects this
3819 : ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
3820 :
3821 : ** Differences from RFC2109/2616 and explanations:
3822 : 1. implied *LWS
3823 : The grammar described by this specification is word-based. Except
3824 : where noted otherwise, linear white space (<LWS>) can be included
3825 : between any two adjacent words (token or quoted-string), and
3826 : between adjacent words and separators, without changing the
3827 : interpretation of a field.
3828 : <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
3829 :
3830 : 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
3831 : common use inside values.
3832 :
3833 : 3. tokens and values have looser restrictions on allowed characters than
3834 : spec. This is also due to certain characters being in common use inside
3835 : values. We allow only '=' to separate token/value pairs, and ';' to
3836 : terminate tokens or values. <LWS> is allowed within tokens and values
3837 : (see bug 206022).
3838 :
3839 : 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
3840 : reject control chars or non-ASCII chars. This is erring on the loose
3841 : side, since there's probably no good reason to enforce this strictness.
3842 :
3843 : 5. cookie <NAME> is optional, where spec requires it. This is a fairly
3844 : trivial case, but allows the flexibility of setting only a cookie <VALUE>
3845 : with a blank <NAME> and is required by some sites (see bug 169091).
3846 :
3847 : 6. Attribute "HttpOnly", not covered in the RFCs, is supported
3848 : (see bug 178993).
3849 :
3850 : ** Begin BNF:
3851 : token = 1*<any allowed-chars except separators>
3852 : value = 1*<any allowed-chars except value-sep>
3853 : separators = ";" | "="
3854 : value-sep = ";"
3855 : cookie-sep = CR | LF
3856 : allowed-chars = <any OCTET except NUL or cookie-sep>
3857 : OCTET = <any 8-bit sequence of data>
3858 : LWS = SP | HT
3859 : NUL = <US-ASCII NUL, null control character (0)>
3860 : CR = <US-ASCII CR, carriage return (13)>
3861 : LF = <US-ASCII LF, linefeed (10)>
3862 : SP = <US-ASCII SP, space (32)>
3863 : HT = <US-ASCII HT, horizontal-tab (9)>
3864 :
3865 : set-cookie = "Set-Cookie:" cookies
3866 : cookies = cookie *( cookie-sep cookie )
3867 : cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
3868 : NAME = token ; cookie name
3869 : VALUE = value ; cookie value
3870 : cookie-av = token ["=" value]
3871 :
3872 : valid values for cookie-av (checked post-parsing) are:
3873 : cookie-av = "Path" "=" value
3874 : | "Domain" "=" value
3875 : | "Expires" "=" value
3876 : | "Max-Age" "=" value
3877 : | "Comment" "=" value
3878 : | "Version" "=" value
3879 : | "Secure"
3880 : | "HttpOnly"
3881 :
3882 : ******************************************************************************/
3883 :
3884 : // helper functions for GetTokenValue
3885 0 : static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; }
3886 0 : static inline bool isterminator (char c) { return c == '\n' || c == '\r'; }
3887 0 : static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
3888 0 : static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
3889 :
3890 : // Parse a single token/value pair.
3891 : // Returns true if a cookie terminator is found, so caller can parse new cookie.
3892 : bool
3893 0 : nsCookieService::GetTokenValue(nsACString::const_char_iterator &aIter,
3894 : nsACString::const_char_iterator &aEndIter,
3895 : nsDependentCSubstring &aTokenString,
3896 : nsDependentCSubstring &aTokenValue,
3897 : bool &aEqualsFound)
3898 : {
3899 : nsACString::const_char_iterator start, lastSpace;
3900 : // initialize value string to clear garbage
3901 0 : aTokenValue.Rebind(aIter, aIter);
3902 :
3903 : // find <token>, including any <LWS> between the end-of-token and the
3904 : // token separator. we'll remove trailing <LWS> next
3905 0 : while (aIter != aEndIter && iswhitespace(*aIter))
3906 0 : ++aIter;
3907 0 : start = aIter;
3908 0 : while (aIter != aEndIter && !istokenseparator(*aIter))
3909 0 : ++aIter;
3910 :
3911 : // remove trailing <LWS>; first check we're not at the beginning
3912 0 : lastSpace = aIter;
3913 0 : if (lastSpace != start) {
3914 0 : while (--lastSpace != start && iswhitespace(*lastSpace))
3915 0 : continue;
3916 0 : ++lastSpace;
3917 : }
3918 0 : aTokenString.Rebind(start, lastSpace);
3919 :
3920 0 : aEqualsFound = (*aIter == '=');
3921 0 : if (aEqualsFound) {
3922 : // find <value>
3923 0 : while (++aIter != aEndIter && iswhitespace(*aIter))
3924 0 : continue;
3925 :
3926 0 : start = aIter;
3927 :
3928 : // process <token>
3929 : // just look for ';' to terminate ('=' allowed)
3930 0 : while (aIter != aEndIter && !isvalueseparator(*aIter))
3931 0 : ++aIter;
3932 :
3933 : // remove trailing <LWS>; first check we're not at the beginning
3934 0 : if (aIter != start) {
3935 0 : lastSpace = aIter;
3936 0 : while (--lastSpace != start && iswhitespace(*lastSpace))
3937 0 : continue;
3938 0 : aTokenValue.Rebind(start, ++lastSpace);
3939 : }
3940 : }
3941 :
3942 : // aIter is on ';', or terminator, or EOS
3943 0 : if (aIter != aEndIter) {
3944 : // if on terminator, increment past & return true to process new cookie
3945 0 : if (isterminator(*aIter)) {
3946 0 : ++aIter;
3947 0 : return true;
3948 : }
3949 : // fall-through: aIter is on ';', increment and return false
3950 0 : ++aIter;
3951 : }
3952 0 : return false;
3953 : }
3954 :
3955 : // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
3956 : // cookie struct here, because we don't know which one to use until we've parsed the header.
3957 : bool
3958 0 : nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
3959 : nsCookieAttributes &aCookieAttributes)
3960 : {
3961 : static const char kPath[] = "path";
3962 : static const char kDomain[] = "domain";
3963 : static const char kExpires[] = "expires";
3964 : static const char kMaxage[] = "max-age";
3965 : static const char kSecure[] = "secure";
3966 : static const char kHttpOnly[] = "httponly";
3967 :
3968 : nsACString::const_char_iterator tempBegin, tempEnd;
3969 : nsACString::const_char_iterator cookieStart, cookieEnd;
3970 0 : aCookieHeader.BeginReading(cookieStart);
3971 0 : aCookieHeader.EndReading(cookieEnd);
3972 :
3973 0 : aCookieAttributes.isSecure = false;
3974 0 : aCookieAttributes.isHttpOnly = false;
3975 :
3976 0 : nsDependentCSubstring tokenString(cookieStart, cookieStart);
3977 0 : nsDependentCSubstring tokenValue (cookieStart, cookieStart);
3978 : bool newCookie, equalsFound;
3979 :
3980 : // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
3981 : // if we find multiple cookies, return for processing
3982 : // note: if there's no '=', we assume token is <VALUE>. this is required by
3983 : // some sites (see bug 169091).
3984 : // XXX fix the parser to parse according to <VALUE> grammar for this case
3985 0 : newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
3986 0 : if (equalsFound) {
3987 0 : aCookieAttributes.name = tokenString;
3988 0 : aCookieAttributes.value = tokenValue;
3989 : } else {
3990 0 : aCookieAttributes.value = tokenString;
3991 : }
3992 :
3993 : // extract remaining attributes
3994 0 : while (cookieStart != cookieEnd && !newCookie) {
3995 0 : newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
3996 :
3997 0 : if (!tokenValue.IsEmpty()) {
3998 0 : tokenValue.BeginReading(tempBegin);
3999 0 : tokenValue.EndReading(tempEnd);
4000 : }
4001 :
4002 : // decide which attribute we have, and copy the string
4003 0 : if (tokenString.LowerCaseEqualsLiteral(kPath))
4004 0 : aCookieAttributes.path = tokenValue;
4005 :
4006 0 : else if (tokenString.LowerCaseEqualsLiteral(kDomain))
4007 0 : aCookieAttributes.host = tokenValue;
4008 :
4009 0 : else if (tokenString.LowerCaseEqualsLiteral(kExpires))
4010 0 : aCookieAttributes.expires = tokenValue;
4011 :
4012 0 : else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
4013 0 : aCookieAttributes.maxage = tokenValue;
4014 :
4015 : // ignore any tokenValue for isSecure; just set the boolean
4016 0 : else if (tokenString.LowerCaseEqualsLiteral(kSecure))
4017 0 : aCookieAttributes.isSecure = true;
4018 :
4019 : // ignore any tokenValue for isHttpOnly (see bug 178993);
4020 : // just set the boolean
4021 0 : else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
4022 0 : aCookieAttributes.isHttpOnly = true;
4023 : }
4024 :
4025 : // rebind aCookieHeader, in case we need to process another cookie
4026 0 : aCookieHeader.Rebind(cookieStart, cookieEnd);
4027 0 : return newCookie;
4028 : }
4029 :
4030 : /******************************************************************************
4031 : * nsCookieService impl:
4032 : * private domain & permission compliance enforcement functions
4033 : ******************************************************************************/
4034 :
4035 : // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
4036 : // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
4037 : // dot may be present. If aHostURI is an IP address, an alias such as
4038 : // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
4039 : // be the exact host, and aRequireHostMatch will be true to indicate that
4040 : // substring matches should not be performed.
4041 : nsresult
4042 6 : nsCookieService::GetBaseDomain(nsIURI *aHostURI,
4043 : nsCString &aBaseDomain,
4044 : bool &aRequireHostMatch)
4045 : {
4046 : // get the base domain. this will fail if the host contains a leading dot,
4047 : // more than one trailing dot, or is otherwise malformed.
4048 6 : nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
4049 6 : aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
4050 : rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
4051 6 : if (aRequireHostMatch) {
4052 : // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
4053 : // such as 'co.uk', or the empty string. use the host as a key in such
4054 : // cases.
4055 6 : rv = aHostURI->GetAsciiHost(aBaseDomain);
4056 : }
4057 6 : NS_ENSURE_SUCCESS(rv, rv);
4058 :
4059 : // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
4060 6 : if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
4061 0 : return NS_ERROR_INVALID_ARG;
4062 :
4063 : // block any URIs without a host that aren't file:// URIs.
4064 6 : if (aBaseDomain.IsEmpty()) {
4065 0 : bool isFileURI = false;
4066 0 : aHostURI->SchemeIs("file", &isFileURI);
4067 0 : if (!isFileURI)
4068 0 : return NS_ERROR_INVALID_ARG;
4069 : }
4070 :
4071 6 : return NS_OK;
4072 : }
4073 :
4074 : // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
4075 : // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
4076 : // that aHost is already normalized, and it may contain a leading dot
4077 : // (indicating that it represents a domain). A trailing dot may be present.
4078 : // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
4079 : // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
4080 : // leading dot will be treated as an error.
4081 : nsresult
4082 0 : nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
4083 : nsCString &aBaseDomain)
4084 : {
4085 : // aHost must not be the string '.'.
4086 0 : if (aHost.Length() == 1 && aHost.Last() == '.')
4087 0 : return NS_ERROR_INVALID_ARG;
4088 :
4089 : // aHost may contain a leading dot; if so, strip it now.
4090 0 : bool domain = !aHost.IsEmpty() && aHost.First() == '.';
4091 :
4092 : // get the base domain. this will fail if the host contains a leading dot,
4093 : // more than one trailing dot, or is otherwise malformed.
4094 0 : nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
4095 0 : if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
4096 : rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
4097 : // aHost is either an IP address, an alias such as 'localhost', an eTLD
4098 : // such as 'co.uk', or the empty string. use the host as a key in such
4099 : // cases; however, we reject any such hosts with a leading dot, since it
4100 : // doesn't make sense for them to be domain cookies.
4101 0 : if (domain)
4102 0 : return NS_ERROR_INVALID_ARG;
4103 :
4104 0 : aBaseDomain = aHost;
4105 0 : return NS_OK;
4106 : }
4107 0 : return rv;
4108 : }
4109 :
4110 : // Normalizes the given hostname, component by component. ASCII/ACE
4111 : // components are lower-cased, and UTF-8 components are normalized per
4112 : // RFC 3454 and converted to ACE.
4113 : nsresult
4114 0 : nsCookieService::NormalizeHost(nsCString &aHost)
4115 : {
4116 0 : if (!IsASCII(aHost)) {
4117 0 : nsAutoCString host;
4118 0 : nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
4119 0 : if (NS_FAILED(rv))
4120 0 : return rv;
4121 :
4122 0 : aHost = host;
4123 : }
4124 :
4125 0 : ToLowerCase(aHost);
4126 0 : return NS_OK;
4127 : }
4128 :
4129 : // returns true if 'a' is equal to or a subdomain of 'b',
4130 : // assuming no leading dots are present.
4131 0 : static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
4132 : {
4133 0 : if (a == b)
4134 0 : return true;
4135 0 : if (a.Length() > b.Length())
4136 0 : return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
4137 0 : return false;
4138 : }
4139 :
4140 : CookieStatus
4141 6 : nsCookieService::CheckPrefs(nsIURI *aHostURI,
4142 : bool aIsForeign,
4143 : const char *aCookieHeader)
4144 : {
4145 : nsresult rv;
4146 :
4147 : // don't let ftp sites get/set cookies (could be a security issue)
4148 : bool ftp;
4149 6 : if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
4150 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
4151 0 : return STATUS_REJECTED_WITH_ERROR;
4152 : }
4153 :
4154 : // check the permission list first; if we find an entry, it overrides
4155 : // default prefs. see bug 184059.
4156 6 : if (mPermissionService) {
4157 : nsCookieAccess access;
4158 : // Not passing an nsIChannel here is probably OK; our implementation
4159 : // doesn't do anything with it anyway.
4160 6 : rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
4161 :
4162 : // if we found an entry, use it
4163 6 : if (NS_SUCCEEDED(rv)) {
4164 6 : switch (access) {
4165 : case nsICookiePermission::ACCESS_DENY:
4166 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4167 0 : aCookieHeader, "cookies are blocked for this site");
4168 0 : return STATUS_REJECTED;
4169 :
4170 : case nsICookiePermission::ACCESS_ALLOW:
4171 0 : return STATUS_ACCEPTED;
4172 :
4173 : case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
4174 0 : if (aIsForeign) {
4175 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4176 : aCookieHeader, "third party cookies are blocked "
4177 0 : "for this site");
4178 0 : return STATUS_REJECTED;
4179 :
4180 : }
4181 0 : return STATUS_ACCEPTED;
4182 :
4183 : case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
4184 0 : if (!aIsForeign)
4185 0 : return STATUS_ACCEPTED;
4186 0 : uint32_t priorCookieCount = 0;
4187 0 : nsAutoCString hostFromURI;
4188 0 : aHostURI->GetHost(hostFromURI);
4189 0 : CountCookiesFromHost(hostFromURI, &priorCookieCount);
4190 0 : if (priorCookieCount == 0) {
4191 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4192 : aCookieHeader, "third party cookies are blocked "
4193 0 : "for this site");
4194 0 : return STATUS_REJECTED;
4195 : }
4196 0 : return STATUS_ACCEPTED;
4197 : }
4198 : }
4199 : }
4200 :
4201 : // check default prefs
4202 6 : if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
4203 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
4204 0 : return STATUS_REJECTED;
4205 : }
4206 :
4207 : // check if cookie is foreign
4208 6 : if (aIsForeign) {
4209 2 : if (mCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT && mThirdPartySession)
4210 0 : return STATUS_ACCEPT_SESSION;
4211 :
4212 2 : if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
4213 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
4214 0 : return STATUS_REJECTED;
4215 : }
4216 :
4217 2 : if (mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
4218 0 : uint32_t priorCookieCount = 0;
4219 0 : nsAutoCString hostFromURI;
4220 0 : aHostURI->GetHost(hostFromURI);
4221 0 : CountCookiesFromHost(hostFromURI, &priorCookieCount);
4222 0 : if (priorCookieCount == 0) {
4223 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
4224 0 : return STATUS_REJECTED;
4225 : }
4226 0 : if (mThirdPartySession)
4227 0 : return STATUS_ACCEPT_SESSION;
4228 : }
4229 : }
4230 :
4231 : // if nothing has complained, accept cookie
4232 6 : return STATUS_ACCEPTED;
4233 : }
4234 :
4235 : // processes domain attribute, and returns true if host has permission to set for this domain.
4236 : bool
4237 0 : nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
4238 : nsIURI *aHostURI,
4239 : const nsCString &aBaseDomain,
4240 : bool aRequireHostMatch)
4241 : {
4242 : // Note: The logic in this function is mirrored in
4243 : // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
4244 : // If it changes, please update that function, or file a bug for someone
4245 : // else to do so.
4246 :
4247 : // get host from aHostURI
4248 0 : nsAutoCString hostFromURI;
4249 0 : aHostURI->GetAsciiHost(hostFromURI);
4250 :
4251 : // if a domain is given, check the host has permission
4252 0 : if (!aCookieAttributes.host.IsEmpty()) {
4253 : // Tolerate leading '.' characters, but not if it's otherwise an empty host.
4254 0 : if (aCookieAttributes.host.Length() > 1 &&
4255 0 : aCookieAttributes.host.First() == '.') {
4256 0 : aCookieAttributes.host.Cut(0, 1);
4257 : }
4258 :
4259 : // switch to lowercase now, to avoid case-insensitive compares everywhere
4260 0 : ToLowerCase(aCookieAttributes.host);
4261 :
4262 : // check whether the host is either an IP address, an alias such as
4263 : // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
4264 : // cases, require an exact string match for the domain, and leave the cookie
4265 : // as a non-domain one. bug 105917 originally noted the requirement to deal
4266 : // with IP addresses.
4267 0 : if (aRequireHostMatch)
4268 0 : return hostFromURI.Equals(aCookieAttributes.host);
4269 :
4270 : // ensure the proposed domain is derived from the base domain; and also
4271 : // that the host domain is derived from the proposed domain (per RFC2109).
4272 0 : if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
4273 0 : IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
4274 : // prepend a dot to indicate a domain cookie
4275 0 : aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
4276 0 : return true;
4277 : }
4278 :
4279 : /*
4280 : * note: RFC2109 section 4.3.2 requires that we check the following:
4281 : * that the portion of host not in domain does not contain a dot.
4282 : * this prevents hosts of the form x.y.co.nz from setting cookies in the
4283 : * entire .co.nz domain. however, it's only a only a partial solution and
4284 : * it breaks sites (IE doesn't enforce it), so we don't perform this check.
4285 : */
4286 0 : return false;
4287 : }
4288 :
4289 : // no domain specified, use hostFromURI
4290 0 : aCookieAttributes.host = hostFromURI;
4291 0 : return true;
4292 : }
4293 :
4294 : nsCString
4295 0 : GetPathFromURI(nsIURI* aHostURI)
4296 : {
4297 : // strip down everything after the last slash to get the path,
4298 : // ignoring slashes in the query string part.
4299 : // if we can QI to nsIURL, that'll take care of the query string portion.
4300 : // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
4301 0 : nsAutoCString path;
4302 0 : nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
4303 0 : if (hostURL) {
4304 0 : hostURL->GetDirectory(path);
4305 : } else {
4306 0 : aHostURI->GetPath(path);
4307 0 : int32_t slash = path.RFindChar('/');
4308 0 : if (slash != kNotFound) {
4309 0 : path.Truncate(slash + 1);
4310 : }
4311 : }
4312 0 : return path;
4313 : }
4314 :
4315 : bool
4316 0 : nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
4317 : nsIURI *aHostURI)
4318 : {
4319 : // if a path is given, check the host has permission
4320 0 : if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
4321 0 : aCookieAttributes.path = GetPathFromURI(aHostURI);
4322 :
4323 : #if 0
4324 : } else {
4325 : /**
4326 : * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
4327 : * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
4328 : * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
4329 : * been disabled, unless we can evangelize these sites.
4330 : */
4331 : // get path from aHostURI
4332 : nsAutoCString pathFromURI;
4333 : if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
4334 : !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
4335 : return false;
4336 : }
4337 : #endif
4338 : }
4339 :
4340 0 : if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
4341 0 : aCookieAttributes.path.Contains('\t'))
4342 0 : return false;
4343 :
4344 0 : return true;
4345 : }
4346 :
4347 : // CheckPrefixes
4348 : //
4349 : // Reject cookies whose name starts with the magic prefixes from
4350 : // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
4351 : // if they do not meet the criteria required by the prefix.
4352 : //
4353 : // Must not be called until after CheckDomain() and CheckPath() have
4354 : // regularized and validated the nsCookieAttributes values!
4355 : bool
4356 0 : nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes,
4357 : bool aSecureRequest)
4358 : {
4359 : static const char kSecure[] = "__Secure-";
4360 : static const char kHost[] = "__Host-";
4361 : static const int kSecureLen = sizeof( kSecure ) - 1;
4362 : static const int kHostLen = sizeof( kHost ) - 1;
4363 :
4364 0 : bool isSecure = strncmp( aCookieAttributes.name.get(), kSecure, kSecureLen ) == 0;
4365 0 : bool isHost = strncmp( aCookieAttributes.name.get(), kHost, kHostLen ) == 0;
4366 :
4367 0 : if ( !isSecure && !isHost ) {
4368 : // not one of the magic prefixes: carry on
4369 0 : return true;
4370 : }
4371 :
4372 0 : if ( !aSecureRequest || !aCookieAttributes.isSecure ) {
4373 : // the magic prefixes may only be used from a secure request and
4374 : // the secure attribute must be set on the cookie
4375 0 : return false;
4376 : }
4377 :
4378 0 : if ( isHost ) {
4379 : // The host prefix requires that the path is "/" and that the cookie
4380 : // had no domain attribute. CheckDomain() and CheckPath() MUST be run
4381 : // first to make sure invalid attributes are rejected and to regularlize
4382 : // them. In particular all explicit domain attributes result in a host
4383 : // that starts with a dot, and if the host doesn't start with a dot it
4384 : // correctly matches the true host.
4385 0 : if ( aCookieAttributes.host[0] == '.' ||
4386 0 : !aCookieAttributes.path.EqualsLiteral( "/" )) {
4387 0 : return false;
4388 : }
4389 : }
4390 :
4391 0 : return true;
4392 : }
4393 :
4394 : bool
4395 0 : nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
4396 : int64_t aServerTime,
4397 : int64_t aCurrentTime)
4398 : {
4399 : /* Determine when the cookie should expire. This is done by taking the difference between
4400 : * the server time and the time the server wants the cookie to expire, and adding that
4401 : * difference to the client time. This localizes the client time regardless of whether or
4402 : * not the TZ environment variable was set on the client.
4403 : *
4404 : * Note: We need to consider accounting for network lag here, per RFC.
4405 : */
4406 : // check for max-age attribute first; this overrides expires attribute
4407 0 : if (!aCookieAttributes.maxage.IsEmpty()) {
4408 : // obtain numeric value of maxageAttribute
4409 : int64_t maxage;
4410 0 : int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
4411 :
4412 : // default to session cookie if the conversion failed
4413 0 : if (numInts != 1) {
4414 0 : return true;
4415 : }
4416 :
4417 : // if this addition overflows, expiryTime will be less than currentTime
4418 : // and the cookie will be expired - that's okay.
4419 0 : aCookieAttributes.expiryTime = aCurrentTime + maxage;
4420 :
4421 : // check for expires attribute
4422 0 : } else if (!aCookieAttributes.expires.IsEmpty()) {
4423 : PRTime expires;
4424 :
4425 : // parse expiry time
4426 0 : if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
4427 0 : return true;
4428 : }
4429 :
4430 : // If set-cookie used absolute time to set expiration, and it can't use
4431 : // client time to set expiration.
4432 : // Because if current time be set in the future, but the cookie expire
4433 : // time be set less than current time and more than server time.
4434 : // The cookie item have to be used to the expired cookie.
4435 0 : aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC);
4436 :
4437 : // default to session cookie if no attributes found
4438 : } else {
4439 0 : return true;
4440 : }
4441 :
4442 0 : return false;
4443 : }
4444 :
4445 : /******************************************************************************
4446 : * nsCookieService impl:
4447 : * private cookielist management functions
4448 : ******************************************************************************/
4449 :
4450 : void
4451 0 : nsCookieService::RemoveAllFromMemory()
4452 : {
4453 : // clearing the hashtable will call each nsCookieEntry's dtor,
4454 : // which releases all their respective children.
4455 0 : mDBState->hostTable.Clear();
4456 0 : mDBState->cookieCount = 0;
4457 0 : mDBState->cookieOldestTime = INT64_MAX;
4458 0 : }
4459 :
4460 : // comparator class for lastaccessed times of cookies.
4461 : class CompareCookiesByAge {
4462 : public:
4463 0 : bool Equals(const nsListIter &a, const nsListIter &b) const
4464 : {
4465 0 : return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
4466 0 : a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
4467 : }
4468 :
4469 0 : bool LessThan(const nsListIter &a, const nsListIter &b) const
4470 : {
4471 : // compare by lastAccessed time, and tiebreak by creationTime.
4472 0 : int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
4473 0 : if (result != 0)
4474 0 : return result < 0;
4475 :
4476 0 : return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
4477 : }
4478 : };
4479 :
4480 : // comparator class for sorting cookies by entry and index.
4481 : class CompareCookiesByIndex {
4482 : public:
4483 0 : bool Equals(const nsListIter &a, const nsListIter &b) const
4484 : {
4485 0 : NS_ASSERTION(a.entry != b.entry || a.index != b.index,
4486 : "cookie indexes should never be equal");
4487 0 : return false;
4488 : }
4489 :
4490 0 : bool LessThan(const nsListIter &a, const nsListIter &b) const
4491 : {
4492 : // compare by entryclass pointer, then by index.
4493 0 : if (a.entry != b.entry)
4494 0 : return a.entry < b.entry;
4495 :
4496 0 : return a.index < b.index;
4497 : }
4498 : };
4499 :
4500 : // purges expired and old cookies in a batch operation.
4501 : already_AddRefed<nsIArray>
4502 0 : nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
4503 : {
4504 0 : NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
4505 0 : EnsureReadComplete();
4506 :
4507 0 : uint32_t initialCookieCount = mDBState->cookieCount;
4508 0 : COOKIE_LOGSTRING(LogLevel::Debug,
4509 : ("PurgeCookies(): beginning purge with %" PRIu32 " cookies and %" PRId64 " oldest age",
4510 : mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
4511 :
4512 : typedef nsTArray<nsListIter> PurgeList;
4513 0 : PurgeList purgeList(kMaxNumberOfCookies);
4514 :
4515 0 : nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
4516 :
4517 : // Create a params array to batch the removals. This is OK here because
4518 : // all the removals are in order, and there are no interleaved additions.
4519 0 : mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
4520 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
4521 0 : if (mDBState->dbConn) {
4522 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
4523 : }
4524 :
4525 0 : int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
4526 0 : int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
4527 0 : int64_t oldestTime = INT64_MAX;
4528 :
4529 0 : for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4530 0 : nsCookieEntry* entry = iter.Get();
4531 :
4532 0 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4533 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
4534 0 : nsListIter iter(entry, i);
4535 0 : nsCookie* cookie = cookies[i];
4536 :
4537 : // check if the cookie has expired
4538 0 : if (cookie->Expiry() <= currentTime) {
4539 0 : removedList->AppendElement(cookie, false);
4540 0 : COOKIE_LOGEVICTED(cookie, "Cookie expired");
4541 :
4542 : // remove from list; do not increment our iterator
4543 0 : gCookieService->RemoveCookieFromList(iter, paramsArray);
4544 :
4545 : } else {
4546 : // check if the cookie is over the age limit
4547 0 : if (cookie->LastAccessed() <= purgeTime) {
4548 0 : purgeList.AppendElement(iter);
4549 :
4550 0 : } else if (cookie->LastAccessed() < oldestTime) {
4551 : // reset our indicator
4552 0 : oldestTime = cookie->LastAccessed();
4553 : }
4554 :
4555 0 : ++i;
4556 : }
4557 : }
4558 : }
4559 :
4560 0 : uint32_t postExpiryCookieCount = mDBState->cookieCount;
4561 :
4562 : // now we have a list of iterators for cookies over the age limit.
4563 : // sort them by age, and then we'll see how many to remove...
4564 0 : purgeList.Sort(CompareCookiesByAge());
4565 :
4566 : // only remove old cookies until we reach the max cookie limit, no more.
4567 0 : uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
4568 0 : mDBState->cookieCount - mMaxNumberOfCookies : 0;
4569 0 : if (purgeList.Length() > excess) {
4570 : // We're not purging everything in the list, so update our indicator.
4571 0 : oldestTime = purgeList[excess].Cookie()->LastAccessed();
4572 :
4573 0 : purgeList.SetLength(excess);
4574 : }
4575 :
4576 : // sort the list again, this time grouping cookies with a common entryclass
4577 : // together, and with ascending index. this allows us to iterate backwards
4578 : // over the list removing cookies, without having to adjust indexes as we go.
4579 0 : purgeList.Sort(CompareCookiesByIndex());
4580 0 : for (PurgeList::index_type i = purgeList.Length(); i--; ) {
4581 0 : nsCookie *cookie = purgeList[i].Cookie();
4582 0 : removedList->AppendElement(cookie, false);
4583 0 : COOKIE_LOGEVICTED(cookie, "Cookie too old");
4584 :
4585 0 : RemoveCookieFromList(purgeList[i], paramsArray);
4586 : }
4587 :
4588 : // Update the database if we have entries to purge.
4589 0 : if (paramsArray) {
4590 : uint32_t length;
4591 0 : paramsArray->GetLength(&length);
4592 0 : if (length) {
4593 0 : DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
4594 0 : NS_ASSERT_SUCCESS(rv);
4595 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
4596 0 : rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
4597 0 : NS_ASSERT_SUCCESS(rv);
4598 : }
4599 : }
4600 :
4601 : // reset the oldest time indicator
4602 0 : mDBState->cookieOldestTime = oldestTime;
4603 :
4604 0 : COOKIE_LOGSTRING(LogLevel::Debug,
4605 : ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32 " purged; %" PRIu32
4606 : " remain; %" PRId64 " oldest age",
4607 : initialCookieCount - postExpiryCookieCount,
4608 : postExpiryCookieCount - mDBState->cookieCount,
4609 : mDBState->cookieCount,
4610 : aCurrentTimeInUsec - mDBState->cookieOldestTime));
4611 :
4612 0 : return removedList.forget();
4613 : }
4614 :
4615 : // find whether a given cookie has been previously set. this is provided by the
4616 : // nsICookieManager2 interface.
4617 : NS_IMETHODIMP
4618 0 : nsCookieService::CookieExists(nsICookie2* aCookie,
4619 : JS::HandleValue aOriginAttributes,
4620 : JSContext* aCx,
4621 : uint8_t aArgc,
4622 : bool* aFoundCookie)
4623 : {
4624 0 : NS_ENSURE_ARG_POINTER(aCookie);
4625 0 : NS_ENSURE_ARG_POINTER(aCx);
4626 0 : NS_ENSURE_ARG_POINTER(aFoundCookie);
4627 0 : MOZ_ASSERT(aArgc == 0 || aArgc == 1);
4628 :
4629 0 : OriginAttributes attrs;
4630 0 : nsresult rv = InitializeOriginAttributes(&attrs,
4631 : aOriginAttributes,
4632 : aCx,
4633 : aArgc,
4634 : u"nsICookieManager2.cookieExists()",
4635 0 : u"2");
4636 0 : NS_ENSURE_SUCCESS(rv, rv);
4637 :
4638 0 : return CookieExistsNative(aCookie, &attrs, aFoundCookie);
4639 : }
4640 :
4641 : NS_IMETHODIMP_(nsresult)
4642 0 : nsCookieService::CookieExistsNative(nsICookie2* aCookie,
4643 : OriginAttributes* aOriginAttributes,
4644 : bool* aFoundCookie)
4645 : {
4646 0 : NS_ENSURE_ARG_POINTER(aCookie);
4647 0 : NS_ENSURE_ARG_POINTER(aOriginAttributes);
4648 0 : NS_ENSURE_ARG_POINTER(aFoundCookie);
4649 :
4650 0 : if (!mDBState) {
4651 0 : NS_WARNING("No DBState! Profile already closed?");
4652 0 : return NS_ERROR_NOT_AVAILABLE;
4653 : }
4654 :
4655 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
4656 0 : mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
4657 :
4658 0 : nsAutoCString host, name, path;
4659 0 : nsresult rv = aCookie->GetHost(host);
4660 0 : NS_ENSURE_SUCCESS(rv, rv);
4661 0 : rv = aCookie->GetName(name);
4662 0 : NS_ENSURE_SUCCESS(rv, rv);
4663 0 : rv = aCookie->GetPath(path);
4664 0 : NS_ENSURE_SUCCESS(rv, rv);
4665 :
4666 0 : nsAutoCString baseDomain;
4667 0 : rv = GetBaseDomainFromHost(host, baseDomain);
4668 0 : NS_ENSURE_SUCCESS(rv, rv);
4669 :
4670 0 : nsListIter iter;
4671 0 : *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
4672 : host, name, path, iter);
4673 0 : return NS_OK;
4674 : }
4675 :
4676 : // For a given base domain, find either an expired cookie or the oldest cookie
4677 : // by lastAccessed time.
4678 : int64_t
4679 0 : nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
4680 : int64_t aCurrentTime,
4681 : nsIURI* aSource,
4682 : const mozilla::Maybe<bool> &aIsSecure,
4683 : nsListIter &aIter)
4684 : {
4685 0 : aIter.entry = nullptr;
4686 0 : bool requireHostMatch = true;
4687 0 : nsAutoCString baseDomain, sourceHost, sourcePath;
4688 0 : if (aSource) {
4689 0 : GetBaseDomain(aSource, baseDomain, requireHostMatch);
4690 0 : aSource->GetAsciiHost(sourceHost);
4691 0 : sourcePath = GetPathFromURI(aSource);
4692 : }
4693 :
4694 0 : const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
4695 :
4696 0 : int64_t oldestNonMatchingCookieTime = 0;
4697 0 : nsListIter oldestNonMatchingCookie;
4698 0 : oldestNonMatchingCookie.entry = nullptr;
4699 :
4700 0 : int64_t oldestCookieTime = 0;
4701 0 : nsListIter oldestCookie;
4702 0 : oldestCookie.entry = nullptr;
4703 :
4704 0 : int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
4705 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4706 0 : nsCookie *cookie = cookies[i];
4707 :
4708 : // If we found an expired cookie, we're done.
4709 0 : if (cookie->Expiry() <= aCurrentTime) {
4710 0 : aIter.entry = aEntry;
4711 0 : aIter.index = i;
4712 0 : return -1;
4713 : }
4714 :
4715 0 : int64_t lastAccessed = cookie->LastAccessed();
4716 : // Record the age of the oldest cookie that is stored for this host.
4717 : // oldestCookieTime is the age of the oldest cookie with a matching
4718 : // secure flag, which may be more recent than an older cookie with
4719 : // a non-matching secure flag.
4720 0 : if (actualOldestCookieTime > lastAccessed) {
4721 0 : actualOldestCookieTime = lastAccessed;
4722 : }
4723 0 : if (aIsSecure.isSome() && !aIsSecure.value()) {
4724 : // We want to look for the oldest non-secure cookie first time through,
4725 : // then find the oldest secure cookie the second time we are called.
4726 0 : if (cookie->IsSecure()) {
4727 0 : continue;
4728 : }
4729 : }
4730 :
4731 : // This cookie is a candidate for eviction if we have no information about
4732 : // the source request, or if it is not a path or domain match against the
4733 : // source request.
4734 0 : bool isPrimaryEvictionCandidate = true;
4735 0 : if (aSource) {
4736 0 : isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
4737 : }
4738 :
4739 0 : if (isPrimaryEvictionCandidate &&
4740 0 : (!oldestNonMatchingCookie.entry ||
4741 : oldestNonMatchingCookieTime > lastAccessed)) {
4742 0 : oldestNonMatchingCookieTime = lastAccessed;
4743 0 : oldestNonMatchingCookie.entry = aEntry;
4744 0 : oldestNonMatchingCookie.index = i;
4745 : }
4746 :
4747 : // Check if we've found the oldest cookie so far.
4748 0 : if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
4749 0 : oldestCookieTime = lastAccessed;
4750 0 : oldestCookie.entry = aEntry;
4751 0 : oldestCookie.index = i;
4752 : }
4753 : }
4754 :
4755 : // Prefer to evict the oldest cookie with a non-matching path/domain,
4756 : // followed by the oldest matching cookie.
4757 0 : if (oldestNonMatchingCookie.entry) {
4758 0 : aIter = oldestNonMatchingCookie;
4759 : } else {
4760 0 : aIter = oldestCookie;
4761 : }
4762 :
4763 0 : return actualOldestCookieTime;
4764 : }
4765 :
4766 : void
4767 0 : nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted,
4768 : int64_t oldestCookieTime)
4769 : {
4770 : // We need to record the evicting cookie to telemetry.
4771 0 : if (!aEvicted->IsSecure()) {
4772 0 : if (aEvicted->LastAccessed() > oldestCookieTime) {
4773 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4774 0 : EVICTED_NEWER_INSECURE);
4775 : } else {
4776 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4777 0 : EVICTED_OLDEST_COOKIE);
4778 : }
4779 : } else {
4780 : Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4781 0 : EVICTED_PREFERRED_COOKIE);
4782 : }
4783 0 : }
4784 :
4785 : // count the number of cookies stored by a particular host. this is provided by the
4786 : // nsICookieManager2 interface.
4787 : NS_IMETHODIMP
4788 0 : nsCookieService::CountCookiesFromHost(const nsACString &aHost,
4789 : uint32_t *aCountFromHost)
4790 : {
4791 0 : if (!mDBState) {
4792 0 : NS_WARNING("No DBState! Profile already closed?");
4793 0 : return NS_ERROR_NOT_AVAILABLE;
4794 : }
4795 :
4796 : // first, normalize the hostname, and fail if it contains illegal characters.
4797 0 : nsAutoCString host(aHost);
4798 0 : nsresult rv = NormalizeHost(host);
4799 0 : NS_ENSURE_SUCCESS(rv, rv);
4800 :
4801 0 : nsAutoCString baseDomain;
4802 0 : rv = GetBaseDomainFromHost(host, baseDomain);
4803 0 : NS_ENSURE_SUCCESS(rv, rv);
4804 :
4805 0 : nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
4806 0 : EnsureReadDomain(key);
4807 :
4808 : // Return a count of all cookies, including expired.
4809 0 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
4810 0 : *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
4811 0 : return NS_OK;
4812 : }
4813 :
4814 : // get an enumerator of cookies stored by a particular host. this is provided by the
4815 : // nsICookieManager2 interface.
4816 : NS_IMETHODIMP
4817 0 : nsCookieService::GetCookiesFromHost(const nsACString &aHost,
4818 : JS::HandleValue aOriginAttributes,
4819 : JSContext* aCx,
4820 : uint8_t aArgc,
4821 : nsISimpleEnumerator **aEnumerator)
4822 : {
4823 0 : MOZ_ASSERT(aArgc == 0 || aArgc == 1);
4824 :
4825 0 : if (!mDBState) {
4826 0 : NS_WARNING("No DBState! Profile already closed?");
4827 0 : return NS_ERROR_NOT_AVAILABLE;
4828 : }
4829 :
4830 : // first, normalize the hostname, and fail if it contains illegal characters.
4831 0 : nsAutoCString host(aHost);
4832 0 : nsresult rv = NormalizeHost(host);
4833 0 : NS_ENSURE_SUCCESS(rv, rv);
4834 :
4835 0 : nsAutoCString baseDomain;
4836 0 : rv = GetBaseDomainFromHost(host, baseDomain);
4837 0 : NS_ENSURE_SUCCESS(rv, rv);
4838 :
4839 0 : OriginAttributes attrs;
4840 0 : rv = InitializeOriginAttributes(&attrs,
4841 : aOriginAttributes,
4842 : aCx,
4843 : aArgc,
4844 : u"nsICookieManager2.getCookiesFromHost()",
4845 0 : u"2");
4846 0 : NS_ENSURE_SUCCESS(rv, rv);
4847 :
4848 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
4849 0 : mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
4850 :
4851 0 : nsCookieKey key = nsCookieKey(baseDomain, attrs);
4852 0 : EnsureReadDomain(key);
4853 :
4854 0 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
4855 0 : if (!entry)
4856 0 : return NS_NewEmptyEnumerator(aEnumerator);
4857 :
4858 0 : nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
4859 0 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4860 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4861 0 : cookieList.AppendObject(cookies[i]);
4862 : }
4863 :
4864 0 : return NS_NewArrayEnumerator(aEnumerator, cookieList);
4865 : }
4866 :
4867 : NS_IMETHODIMP
4868 0 : nsCookieService::GetCookiesWithOriginAttributes(const nsAString& aPattern,
4869 : const nsACString& aHost,
4870 : nsISimpleEnumerator **aEnumerator)
4871 : {
4872 0 : mozilla::OriginAttributesPattern pattern;
4873 0 : if (!pattern.Init(aPattern)) {
4874 0 : return NS_ERROR_INVALID_ARG;
4875 : }
4876 :
4877 0 : nsAutoCString host(aHost);
4878 0 : nsresult rv = NormalizeHost(host);
4879 0 : NS_ENSURE_SUCCESS(rv, rv);
4880 :
4881 0 : nsAutoCString baseDomain;
4882 0 : rv = GetBaseDomainFromHost(host, baseDomain);
4883 0 : NS_ENSURE_SUCCESS(rv, rv);
4884 :
4885 0 : return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator);
4886 : }
4887 :
4888 : nsresult
4889 0 : nsCookieService::GetCookiesWithOriginAttributes(
4890 : const mozilla::OriginAttributesPattern& aPattern,
4891 : const nsCString& aBaseDomain,
4892 : nsISimpleEnumerator **aEnumerator)
4893 : {
4894 0 : if (!mDBState) {
4895 0 : NS_WARNING("No DBState! Profile already closed?");
4896 0 : return NS_ERROR_NOT_AVAILABLE;
4897 : }
4898 :
4899 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
4900 0 : mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
4901 0 : aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
4902 :
4903 0 : nsCOMArray<nsICookie> cookies;
4904 0 : for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4905 0 : nsCookieEntry* entry = iter.Get();
4906 :
4907 0 : if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
4908 0 : continue;
4909 : }
4910 :
4911 0 : if (!aPattern.Matches(entry->mOriginAttributes)) {
4912 0 : continue;
4913 : }
4914 :
4915 0 : const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies();
4916 :
4917 0 : for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
4918 0 : cookies.AppendObject(entryCookies[i]);
4919 : }
4920 : }
4921 :
4922 0 : return NS_NewArrayEnumerator(aEnumerator, cookies);
4923 : }
4924 :
4925 : NS_IMETHODIMP
4926 0 : nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
4927 : const nsACString& aHost)
4928 : {
4929 0 : MOZ_ASSERT(XRE_IsParentProcess());
4930 :
4931 0 : mozilla::OriginAttributesPattern pattern;
4932 0 : if (!pattern.Init(aPattern)) {
4933 0 : return NS_ERROR_INVALID_ARG;
4934 : }
4935 :
4936 0 : nsAutoCString host(aHost);
4937 0 : nsresult rv = NormalizeHost(host);
4938 0 : NS_ENSURE_SUCCESS(rv, rv);
4939 :
4940 0 : nsAutoCString baseDomain;
4941 0 : rv = GetBaseDomainFromHost(host, baseDomain);
4942 0 : NS_ENSURE_SUCCESS(rv, rv);
4943 :
4944 0 : return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
4945 : }
4946 :
4947 : nsresult
4948 0 : nsCookieService::RemoveCookiesWithOriginAttributes(
4949 : const mozilla::OriginAttributesPattern& aPattern,
4950 : const nsCString& aBaseDomain)
4951 : {
4952 0 : if (!mDBState) {
4953 0 : NS_WARNING("No DBState! Profile already close?");
4954 0 : return NS_ERROR_NOT_AVAILABLE;
4955 : }
4956 :
4957 0 : AutoRestore<DBState*> savePrevDBState(mDBState);
4958 0 : mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
4959 0 : aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
4960 :
4961 0 : mozStorageTransaction transaction(mDBState->dbConn, false);
4962 : // Iterate the hash table of nsCookieEntry.
4963 0 : for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4964 0 : nsCookieEntry* entry = iter.Get();
4965 :
4966 0 : if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
4967 0 : continue;
4968 : }
4969 :
4970 0 : if (!aPattern.Matches(entry->mOriginAttributes)) {
4971 0 : continue;
4972 : }
4973 :
4974 : // Pattern matches. Delete all cookies within this nsCookieEntry.
4975 0 : const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
4976 :
4977 0 : while (!cookies.IsEmpty()) {
4978 0 : nsCookie *cookie = cookies.LastElement();
4979 :
4980 0 : nsAutoCString host;
4981 0 : cookie->GetHost(host);
4982 :
4983 0 : nsAutoCString name;
4984 0 : cookie->GetName(name);
4985 :
4986 0 : nsAutoCString path;
4987 0 : cookie->GetPath(path);
4988 :
4989 : // Remove the cookie.
4990 0 : nsresult rv = Remove(host, entry->mOriginAttributes, name, path, false);
4991 0 : NS_ENSURE_SUCCESS(rv, rv);
4992 : }
4993 : }
4994 0 : DebugOnly<nsresult> rv = transaction.Commit();
4995 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
4996 :
4997 0 : return NS_OK;
4998 : }
4999 :
5000 : // find an secure cookie specified by host and name
5001 : bool
5002 0 : nsCookieService::FindSecureCookie(const nsCookieKey &aKey,
5003 : nsCookie *aCookie)
5004 : {
5005 0 : EnsureReadDomain(aKey);
5006 :
5007 0 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
5008 0 : if (!entry)
5009 0 : return false;
5010 :
5011 0 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
5012 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
5013 0 : nsCookie *cookie = cookies[i];
5014 : // isn't a match if insecure or a different name
5015 0 : if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name()))
5016 0 : continue;
5017 :
5018 : // The host must "domain-match" an existing cookie or vice-versa
5019 0 : if (DomainMatches(cookie, aCookie->Host()) ||
5020 0 : DomainMatches(aCookie, cookie->Host())) {
5021 : // If the path of new cookie and the path of existing cookie
5022 : // aren't "/", then this situation needs to compare paths to
5023 : // ensure only that a newly-created non-secure cookie does not
5024 : // overlay an existing secure cookie.
5025 0 : if (PathMatches(cookie, aCookie->Path())) {
5026 0 : return true;
5027 : }
5028 : }
5029 : }
5030 :
5031 0 : return false;
5032 : }
5033 :
5034 : // find an exact cookie specified by host, name, and path that hasn't expired.
5035 : bool
5036 0 : nsCookieService::FindCookie(const nsCookieKey &aKey,
5037 : const nsCString& aHost,
5038 : const nsCString& aName,
5039 : const nsCString& aPath,
5040 : nsListIter &aIter)
5041 : {
5042 0 : EnsureReadDomain(aKey);
5043 :
5044 0 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
5045 0 : if (!entry)
5046 0 : return false;
5047 :
5048 0 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
5049 0 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
5050 0 : nsCookie *cookie = cookies[i];
5051 :
5052 0 : if (aHost.Equals(cookie->Host()) &&
5053 0 : aPath.Equals(cookie->Path()) &&
5054 0 : aName.Equals(cookie->Name())) {
5055 0 : aIter = nsListIter(entry, i);
5056 0 : return true;
5057 : }
5058 : }
5059 :
5060 0 : return false;
5061 : }
5062 :
5063 : // remove a cookie from the hashtable, and update the iterator state.
5064 : void
5065 0 : nsCookieService::RemoveCookieFromList(const nsListIter &aIter,
5066 : mozIStorageBindingParamsArray *aParamsArray)
5067 : {
5068 : // if it's a non-session cookie, remove it from the db
5069 0 : if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
5070 : // Use the asynchronous binding methods to ensure that we do not acquire
5071 : // the database lock.
5072 0 : mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
5073 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
5074 0 : if (!paramsArray) {
5075 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
5076 : }
5077 :
5078 0 : nsCOMPtr<mozIStorageBindingParams> params;
5079 0 : paramsArray->NewBindingParams(getter_AddRefs(params));
5080 :
5081 : DebugOnly<nsresult> rv =
5082 0 : params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5083 0 : aIter.Cookie()->Name());
5084 0 : NS_ASSERT_SUCCESS(rv);
5085 :
5086 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5087 0 : aIter.Cookie()->Host());
5088 0 : NS_ASSERT_SUCCESS(rv);
5089 :
5090 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5091 0 : aIter.Cookie()->Path());
5092 0 : NS_ASSERT_SUCCESS(rv);
5093 :
5094 0 : nsAutoCString suffix;
5095 0 : aIter.Cookie()->OriginAttributesRef().CreateSuffix(suffix);
5096 0 : rv = params->BindUTF8StringByName(
5097 0 : NS_LITERAL_CSTRING("originAttributes"), suffix);
5098 0 : NS_ASSERT_SUCCESS(rv);
5099 :
5100 0 : rv = paramsArray->AddParams(params);
5101 0 : NS_ASSERT_SUCCESS(rv);
5102 :
5103 : // If we weren't given a params array, we'll need to remove it ourselves.
5104 0 : if (!aParamsArray) {
5105 0 : rv = stmt->BindParameters(paramsArray);
5106 0 : NS_ASSERT_SUCCESS(rv);
5107 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
5108 0 : rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
5109 0 : NS_ASSERT_SUCCESS(rv);
5110 : }
5111 : }
5112 :
5113 0 : if (aIter.entry->GetCookies().Length() == 1) {
5114 : // we're removing the last element in the array - so just remove the entry
5115 : // from the hash. note that the entryclass' dtor will take care of
5116 : // releasing this last element for us!
5117 0 : mDBState->hostTable.RawRemoveEntry(aIter.entry);
5118 :
5119 : } else {
5120 : // just remove the element from the list
5121 0 : aIter.entry->GetCookies().RemoveElementAt(aIter.index);
5122 : }
5123 :
5124 0 : --mDBState->cookieCount;
5125 0 : }
5126 :
5127 : void
5128 0 : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
5129 : const nsCookieKey &aKey,
5130 : const nsCookie *aCookie)
5131 : {
5132 0 : NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
5133 0 : NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
5134 :
5135 : // Use the asynchronous binding methods to ensure that we do not acquire the
5136 : // database lock.
5137 0 : nsCOMPtr<mozIStorageBindingParams> params;
5138 : DebugOnly<nsresult> rv =
5139 0 : aParamsArray->NewBindingParams(getter_AddRefs(params));
5140 0 : NS_ASSERT_SUCCESS(rv);
5141 :
5142 : // Bind our values to params
5143 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
5144 0 : aKey.mBaseDomain);
5145 0 : NS_ASSERT_SUCCESS(rv);
5146 :
5147 0 : nsAutoCString suffix;
5148 0 : aKey.mOriginAttributes.CreateSuffix(suffix);
5149 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
5150 0 : suffix);
5151 0 : NS_ASSERT_SUCCESS(rv);
5152 :
5153 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5154 0 : aCookie->Name());
5155 0 : NS_ASSERT_SUCCESS(rv);
5156 :
5157 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
5158 0 : aCookie->Value());
5159 0 : NS_ASSERT_SUCCESS(rv);
5160 :
5161 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5162 0 : aCookie->Host());
5163 0 : NS_ASSERT_SUCCESS(rv);
5164 :
5165 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5166 0 : aCookie->Path());
5167 0 : NS_ASSERT_SUCCESS(rv);
5168 :
5169 0 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
5170 0 : aCookie->Expiry());
5171 0 : NS_ASSERT_SUCCESS(rv);
5172 :
5173 0 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
5174 0 : aCookie->LastAccessed());
5175 0 : NS_ASSERT_SUCCESS(rv);
5176 :
5177 0 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
5178 0 : aCookie->CreationTime());
5179 0 : NS_ASSERT_SUCCESS(rv);
5180 :
5181 0 : rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
5182 0 : aCookie->IsSecure());
5183 0 : NS_ASSERT_SUCCESS(rv);
5184 :
5185 0 : rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
5186 0 : aCookie->IsHttpOnly());
5187 0 : NS_ASSERT_SUCCESS(rv);
5188 :
5189 : // Bind the params to the array.
5190 0 : rv = aParamsArray->AddParams(params);
5191 0 : NS_ASSERT_SUCCESS(rv);
5192 0 : }
5193 :
5194 : void
5195 0 : nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
5196 : nsCookie* aCookie)
5197 : {
5198 0 : if (aCookie->LastAccessed() < aDBState->cookieOldestTime) {
5199 0 : aDBState->cookieOldestTime = aCookie->LastAccessed();
5200 : }
5201 0 : }
5202 :
5203 : void
5204 0 : nsCookieService::AddCookieToList(const nsCookieKey &aKey,
5205 : nsCookie *aCookie,
5206 : DBState *aDBState,
5207 : mozIStorageBindingParamsArray *aParamsArray,
5208 : bool aWriteToDB)
5209 : {
5210 0 : NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
5211 : "Not writing to the DB but have a params array?");
5212 0 : NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
5213 : "Do not have a DB connection but have a params array?");
5214 :
5215 0 : if (!aCookie) {
5216 0 : NS_WARNING("Attempting to AddCookieToList with null cookie");
5217 0 : return;
5218 : }
5219 :
5220 0 : nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
5221 0 : NS_ASSERTION(entry, "can't insert element into a null entry!");
5222 :
5223 0 : entry->GetCookies().AppendElement(aCookie);
5224 0 : ++aDBState->cookieCount;
5225 :
5226 : // keep track of the oldest cookie, for when it comes time to purge
5227 0 : UpdateCookieOldestTime(aDBState, aCookie);
5228 :
5229 : // if it's a non-session cookie and hasn't just been read from the db, write it out.
5230 0 : if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
5231 0 : mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
5232 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
5233 0 : if (!paramsArray) {
5234 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
5235 : }
5236 0 : bindCookieParameters(paramsArray, aKey, aCookie);
5237 :
5238 : // If we were supplied an array to store parameters, we shouldn't call
5239 : // executeAsync - someone up the stack will do this for us.
5240 0 : if (!aParamsArray) {
5241 0 : DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
5242 0 : NS_ASSERT_SUCCESS(rv);
5243 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
5244 0 : rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
5245 0 : NS_ASSERT_SUCCESS(rv);
5246 : }
5247 : }
5248 : }
5249 :
5250 : void
5251 0 : nsCookieService::UpdateCookieInList(nsCookie *aCookie,
5252 : int64_t aLastAccessed,
5253 : mozIStorageBindingParamsArray *aParamsArray)
5254 : {
5255 0 : NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
5256 :
5257 : // udpate the lastAccessed timestamp
5258 0 : aCookie->SetLastAccessed(aLastAccessed);
5259 :
5260 : // if it's a non-session cookie, update it in the db too
5261 0 : if (!aCookie->IsSession() && aParamsArray) {
5262 : // Create our params holder.
5263 0 : nsCOMPtr<mozIStorageBindingParams> params;
5264 0 : aParamsArray->NewBindingParams(getter_AddRefs(params));
5265 :
5266 : // Bind our parameters.
5267 : DebugOnly<nsresult> rv =
5268 0 : params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
5269 0 : aLastAccessed);
5270 0 : NS_ASSERT_SUCCESS(rv);
5271 :
5272 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5273 0 : aCookie->Name());
5274 0 : NS_ASSERT_SUCCESS(rv);
5275 :
5276 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5277 0 : aCookie->Host());
5278 0 : NS_ASSERT_SUCCESS(rv);
5279 :
5280 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5281 0 : aCookie->Path());
5282 0 : NS_ASSERT_SUCCESS(rv);
5283 :
5284 0 : nsAutoCString suffix;
5285 0 : aCookie->OriginAttributesRef().CreateSuffix(suffix);
5286 0 : rv = params->BindUTF8StringByName(
5287 0 : NS_LITERAL_CSTRING("originAttributes"), suffix);
5288 0 : NS_ASSERT_SUCCESS(rv);
5289 :
5290 : // Add our bound parameters to the array.
5291 0 : rv = aParamsArray->AddParams(params);
5292 0 : NS_ASSERT_SUCCESS(rv);
5293 : }
5294 0 : }
5295 :
5296 : size_t
5297 0 : nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
5298 : {
5299 0 : size_t n = aMallocSizeOf(this);
5300 :
5301 0 : if (mDefaultDBState) {
5302 0 : n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
5303 : }
5304 0 : if (mPrivateDBState) {
5305 0 : n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
5306 : }
5307 :
5308 0 : return n;
5309 : }
5310 :
5311 0 : MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
5312 :
5313 : NS_IMETHODIMP
5314 0 : nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
5315 : nsISupports* aData, bool aAnonymize)
5316 : {
5317 0 : MOZ_COLLECT_REPORT(
5318 : "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
5319 : SizeOfIncludingThis(CookieServiceMallocSizeOf),
5320 0 : "Memory used by the cookie service.");
5321 :
5322 0 : return NS_OK;
5323 : }
|