1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2003
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Daniel Witte (dwitte@stanford.edu)
24 : * Michiel van Leeuwen (mvl@exedo.nl)
25 : * Michael Ventnor <m.ventnor@gmail.com>
26 : * Ehsan Akhgari <ehsan.akhgari@gmail.com>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either the GNU General Public License Version 2 or later (the "GPL"), or
30 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 :
43 : #ifdef MOZ_LOGGING
44 : // this next define has to appear before the include of prlog.h
45 : #define FORCE_PR_LOG // Allow logging in the release build
46 : #endif
47 :
48 : #include "mozilla/net/CookieServiceChild.h"
49 : #include "mozilla/net/NeckoCommon.h"
50 :
51 : #include "nsCookieService.h"
52 : #include "nsIServiceManager.h"
53 :
54 : #include "nsIIOService.h"
55 : #include "nsIPrefBranch.h"
56 : #include "nsIPrefService.h"
57 : #include "nsICookiePermission.h"
58 : #include "nsIURI.h"
59 : #include "nsIURL.h"
60 : #include "nsIChannel.h"
61 : #include "nsIFile.h"
62 : #include "nsIObserverService.h"
63 : #include "nsILineInputStream.h"
64 : #include "nsIEffectiveTLDService.h"
65 : #include "nsIIDNService.h"
66 : #include "mozIThirdPartyUtil.h"
67 :
68 : #include "nsTArray.h"
69 : #include "nsCOMArray.h"
70 : #include "nsIMutableArray.h"
71 : #include "nsArrayEnumerator.h"
72 : #include "nsEnumeratorUtils.h"
73 : #include "nsAutoPtr.h"
74 : #include "nsReadableUtils.h"
75 : #include "nsCRT.h"
76 : #include "prtime.h"
77 : #include "prprf.h"
78 : #include "nsNetUtil.h"
79 : #include "nsNetCID.h"
80 : #include "nsAppDirectoryServiceDefs.h"
81 : #include "nsIPrivateBrowsingService.h"
82 : #include "nsNetCID.h"
83 : #include "mozilla/storage.h"
84 : #include "mozilla/FunctionTimer.h"
85 : #include "mozilla/Util.h" // for DebugOnly
86 :
87 : using namespace mozilla::net;
88 :
89 : /******************************************************************************
90 : * nsCookieService impl:
91 : * useful types & constants
92 : ******************************************************************************/
93 :
94 : static nsCookieService *gCookieService;
95 :
96 : // XXX_hack. See bug 178993.
97 : // This is a hack to hide HttpOnly cookies from older browsers
98 : static const char kHttpOnlyPrefix[] = "#HttpOnly_";
99 :
100 : #define COOKIES_FILE "cookies.sqlite"
101 : #define COOKIES_SCHEMA_VERSION 4
102 :
103 : static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
104 : static const PRInt64 kCookiePurgeAge =
105 : PRInt64(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
106 :
107 : static const char kOldCookieFileName[] = "cookies.txt";
108 :
109 : #undef LIMIT
110 : #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
111 :
112 : #undef ADD_TEN_PERCENT
113 : #define ADD_TEN_PERCENT(i) ((i) + (i)/10)
114 :
115 : // default limits for the cookie list. these can be tuned by the
116 : // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
117 : static const PRUint32 kMaxNumberOfCookies = 3000;
118 : static const PRUint32 kMaxCookiesPerHost = 150;
119 : static const PRUint32 kMaxBytesPerCookie = 4096;
120 : static const PRUint32 kMaxBytesPerPath = 1024;
121 :
122 : // behavior pref constants
123 : static const PRUint32 BEHAVIOR_ACCEPT = 0;
124 : static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1;
125 : static const PRUint32 BEHAVIOR_REJECT = 2;
126 :
127 : // pref string constants
128 : static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior";
129 : static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
130 : static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
131 : static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
132 : static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly";
133 :
134 : static void
135 : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
136 : const nsCString &aBaseDomain,
137 : const nsCookie *aCookie);
138 :
139 : // struct for temporarily storing cookie attributes during header parsing
140 : struct nsCookieAttributes
141 30466 : {
142 : nsCAutoString name;
143 : nsCAutoString value;
144 : nsCAutoString host;
145 : nsCAutoString path;
146 : nsCAutoString expires;
147 : nsCAutoString maxage;
148 : PRInt64 expiryTime;
149 : bool isSession;
150 : bool isSecure;
151 : bool isHttpOnly;
152 : };
153 :
154 : // stores the nsCookieEntry entryclass and an index into the cookie array
155 : // within that entryclass, for purposes of storing an iteration state that
156 : // points to a certain cookie.
157 : struct nsListIter
158 155 : {
159 : // default (non-initializing) constructor.
160 16540 : nsListIter()
161 : {
162 16540 : }
163 :
164 : // explicit constructor to a given iterator state with entryclass 'aEntry'
165 : // and index 'aIndex'.
166 : explicit
167 704 : nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
168 : : entry(aEntry)
169 704 : , index(aIndex)
170 : {
171 704 : }
172 :
173 : // get the nsCookie * the iterator currently points to.
174 4495 : nsCookie * Cookie() const
175 : {
176 4495 : 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 NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
193 : // set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
194 : // set NSPR_LOG_FILE=cookie.log
195 : //
196 : #include "prlog.h"
197 : #endif
198 :
199 : // define logging macros for convenience
200 : #define SET_COOKIE true
201 : #define GET_COOKIE false
202 :
203 : #ifdef PR_LOGGING
204 1464 : static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie");
205 :
206 : #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
207 : #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
208 :
209 : #define COOKIE_LOGEVICTED(a, details) \
210 : PR_BEGIN_MACRO \
211 : if (PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) \
212 : LogEvicted(a, details); \
213 : PR_END_MACRO
214 :
215 : #define COOKIE_LOGSTRING(lvl, fmt) \
216 : PR_BEGIN_MACRO \
217 : PR_LOG(sCookieLog, lvl, fmt); \
218 : PR_LOG(sCookieLog, lvl, ("\n")); \
219 : PR_END_MACRO
220 :
221 : static void
222 69 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
223 : {
224 : // if logging isn't enabled, return now to save cycles
225 69 : if (!PR_LOG_TEST(sCookieLog, PR_LOG_WARNING))
226 69 : return;
227 :
228 0 : nsCAutoString spec;
229 0 : if (aHostURI)
230 0 : aHostURI->GetAsciiSpec(spec);
231 :
232 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,
233 : ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
234 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,("request URL: %s\n", spec.get()));
235 0 : if (aSetCookie)
236 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,("cookie string: %s\n", aCookieString));
237 :
238 : PRExplodedTime explodedTime;
239 0 : PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
240 : char timeString[40];
241 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
242 :
243 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,("current time: %s", timeString));
244 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,("rejected because %s\n", aReason));
245 0 : PR_LOG(sCookieLog, PR_LOG_WARNING,("\n"));
246 : }
247 :
248 : static void
249 0 : LogCookie(nsCookie *aCookie)
250 : {
251 : PRExplodedTime explodedTime;
252 0 : PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
253 : char timeString[40];
254 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
255 :
256 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("current time: %s", timeString));
257 :
258 0 : if (aCookie) {
259 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("----------------\n"));
260 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get()));
261 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get()));
262 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
263 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get()));
264 :
265 0 : PR_ExplodeTime(aCookie->Expiry() * PRInt64(PR_USEC_PER_SEC),
266 0 : PR_GMTParameters, &explodedTime);
267 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
268 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,
269 : ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
270 :
271 0 : PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
272 0 : PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
273 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("created: %s", timeString));
274 :
275 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
276 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
277 : }
278 0 : }
279 :
280 : static void
281 16256 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
282 : {
283 : // if logging isn't enabled, return now to save cycles
284 16256 : if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
285 16256 : return;
286 : }
287 :
288 0 : nsCAutoString spec;
289 0 : if (aHostURI)
290 0 : aHostURI->GetAsciiSpec(spec);
291 :
292 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,
293 : ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
294 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
295 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
296 0 : if (aSetCookie)
297 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
298 :
299 0 : LogCookie(aCookie);
300 :
301 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
302 : }
303 :
304 : static void
305 0 : LogEvicted(nsCookie *aCookie, const char* details)
306 : {
307 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
308 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s\n", details));
309 :
310 0 : LogCookie(aCookie);
311 :
312 0 : PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
313 0 : }
314 :
315 : // inline wrappers to make passing in nsAFlatCStrings easier
316 : static inline void
317 21 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
318 : {
319 21 : LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
320 21 : }
321 :
322 : static inline void
323 57 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
324 : {
325 57 : LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
326 57 : }
327 :
328 : #else
329 : #define COOKIE_LOGFAILURE(a, b, c, d) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
330 : #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
331 : #define COOKIE_LOGEVICTED(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
332 : #define COOKIE_LOGSTRING(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
333 : #endif
334 :
335 : #ifdef DEBUG
336 : #define NS_ASSERT_SUCCESS(res) \
337 : PR_BEGIN_MACRO \
338 : nsresult __rv = res; /* Do not evaluate |res| more than once! */ \
339 : if (NS_FAILED(__rv)) { \
340 : char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
341 : #res, __rv); \
342 : NS_ASSERTION(NS_SUCCEEDED(__rv), msg); \
343 : PR_smprintf_free(msg); \
344 : } \
345 : PR_END_MACRO
346 : #else
347 : #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
348 : #endif
349 :
350 : /******************************************************************************
351 : * DBListenerErrorHandler impl:
352 : * Parent class for our async storage listeners that handles the logging of
353 : * errors.
354 : ******************************************************************************/
355 : class DBListenerErrorHandler : public mozIStorageStatementCallback
356 790 : {
357 : protected:
358 790 : DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
359 : nsRefPtr<DBState> mDBState;
360 : virtual const char *GetOpType() = 0;
361 :
362 : public:
363 : NS_DECL_ISUPPORTS
364 :
365 7 : NS_IMETHOD HandleError(mozIStorageError* aError)
366 : {
367 7 : PRInt32 result = -1;
368 7 : aError->GetResult(&result);
369 :
370 : #ifdef PR_LOGGING
371 14 : nsCAutoString message;
372 7 : aError->GetMessage(message);
373 7 : COOKIE_LOGSTRING(PR_LOG_WARNING,
374 : ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
375 : "performing operation '%s' with message '%s'; rebuilding database.",
376 : result, GetOpType(), message.get()));
377 : #endif
378 :
379 : // Rebuild the database.
380 7 : gCookieService->HandleCorruptDB(mDBState);
381 :
382 7 : return NS_OK;
383 : }
384 : };
385 :
386 38834 : NS_IMPL_ISUPPORTS1(DBListenerErrorHandler, mozIStorageStatementCallback)
387 :
388 : /******************************************************************************
389 : * InsertCookieDBListener impl:
390 : * mozIStorageStatementCallback used to track asynchronous insertion operations.
391 : ******************************************************************************/
392 : class InsertCookieDBListener : public DBListenerErrorHandler
393 : {
394 : protected:
395 0 : virtual const char *GetOpType() { return "INSERT"; }
396 :
397 : public:
398 250 : InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
399 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*)
400 : {
401 0 : NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
402 0 : return NS_OK;
403 : }
404 15701 : NS_IMETHOD HandleCompletion(PRUint16 aReason)
405 : {
406 : // If we were rebuilding the db and we succeeded, make our corruptFlag say
407 : // so.
408 15701 : if (mDBState->corruptFlag == DBState::REBUILDING &&
409 : aReason == mozIStorageStatementCallback::REASON_FINISHED) {
410 4 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
411 : ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
412 4 : mDBState->corruptFlag = DBState::OK;
413 : }
414 15701 : return NS_OK;
415 : }
416 : };
417 :
418 : /******************************************************************************
419 : * UpdateCookieDBListener impl:
420 : * mozIStorageStatementCallback used to track asynchronous update operations.
421 : ******************************************************************************/
422 : class UpdateCookieDBListener : public DBListenerErrorHandler
423 : {
424 : protected:
425 0 : virtual const char *GetOpType() { return "UPDATE"; }
426 :
427 : public:
428 250 : UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
429 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*)
430 : {
431 0 : NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
432 0 : return NS_OK;
433 : }
434 0 : NS_IMETHOD HandleCompletion(PRUint16 aReason)
435 : {
436 0 : return NS_OK;
437 : }
438 : };
439 :
440 : /******************************************************************************
441 : * RemoveCookieDBListener impl:
442 : * mozIStorageStatementCallback used to track asynchronous removal operations.
443 : ******************************************************************************/
444 : class RemoveCookieDBListener : public DBListenerErrorHandler
445 : {
446 : protected:
447 0 : virtual const char *GetOpType() { return "REMOVE"; }
448 :
449 : public:
450 250 : RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
451 0 : NS_IMETHOD HandleResult(mozIStorageResultSet*)
452 : {
453 0 : NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
454 0 : return NS_OK;
455 : }
456 257 : NS_IMETHOD HandleCompletion(PRUint16 aReason)
457 : {
458 257 : return NS_OK;
459 : }
460 : };
461 :
462 : /******************************************************************************
463 : * ReadCookieDBListener impl:
464 : * mozIStorageStatementCallback used to track asynchronous removal operations.
465 : ******************************************************************************/
466 : class ReadCookieDBListener : public DBListenerErrorHandler
467 : {
468 : protected:
469 0 : virtual const char *GetOpType() { return "READ"; }
470 : bool mCanceled;
471 :
472 : public:
473 40 : ReadCookieDBListener(DBState* dbState)
474 : : DBListenerErrorHandler(dbState)
475 40 : , mCanceled(false)
476 : {
477 40 : }
478 :
479 32 : void Cancel() { mCanceled = true; }
480 :
481 759 : NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
482 : {
483 : nsresult rv;
484 1518 : nsCOMPtr<mozIStorageRow> row;
485 :
486 11319 : while (1) {
487 12078 : rv = aResult->GetNextRow(getter_AddRefs(row));
488 12078 : NS_ASSERT_SUCCESS(rv);
489 :
490 12078 : if (!row)
491 : break;
492 :
493 11319 : CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
494 11319 : row->GetUTF8String(9, tuple->baseDomain);
495 11319 : tuple->cookie = gCookieService->GetCookieFromRow(row);
496 : }
497 :
498 759 : return NS_OK;
499 : }
500 40 : NS_IMETHOD HandleCompletion(PRUint16 aReason)
501 : {
502 : // Process the completion of the read operation. If we have been canceled,
503 : // we cannot assume that the cookieservice still has an open connection
504 : // or that it even refers to the same database, so we must return early.
505 : // Conversely, the cookieservice guarantees that if we have not been
506 : // canceled, the database connection is still alive and we can safely
507 : // operate on it.
508 :
509 40 : if (mCanceled) {
510 : // We may receive a REASON_FINISHED after being canceled;
511 : // tweak the reason accordingly.
512 32 : aReason = mozIStorageStatementCallback::REASON_CANCELED;
513 : }
514 :
515 40 : switch (aReason) {
516 : case mozIStorageStatementCallback::REASON_FINISHED:
517 8 : gCookieService->AsyncReadComplete();
518 8 : break;
519 : case mozIStorageStatementCallback::REASON_CANCELED:
520 : // Nothing more to do here. The partially read data has already been
521 : // thrown away.
522 32 : COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled"));
523 32 : break;
524 : case mozIStorageStatementCallback::REASON_ERROR:
525 : // Nothing more to do here. DBListenerErrorHandler::HandleError()
526 : // can handle it.
527 0 : COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error"));
528 0 : break;
529 : default:
530 0 : NS_NOTREACHED("invalid reason");
531 : }
532 40 : return NS_OK;
533 : }
534 : };
535 :
536 : /******************************************************************************
537 : * CloseCookieDBListener imp:
538 : * Static mozIStorageCompletionCallback used to notify when the database is
539 : * successfully closed.
540 : ******************************************************************************/
541 : class CloseCookieDBListener : public mozIStorageCompletionCallback
542 250 : {
543 : public:
544 250 : CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
545 : nsRefPtr<DBState> mDBState;
546 : NS_DECL_ISUPPORTS
547 :
548 245 : NS_IMETHOD Complete()
549 : {
550 245 : gCookieService->HandleDBClosed(mDBState);
551 245 : return NS_OK;
552 : }
553 : };
554 :
555 2725 : NS_IMPL_ISUPPORTS1(CloseCookieDBListener, mozIStorageCompletionCallback)
556 :
557 : /******************************************************************************
558 : * nsCookieService impl:
559 : * singleton instance ctor/dtor methods
560 : ******************************************************************************/
561 :
562 : nsICookieService*
563 310 : nsCookieService::GetXPCOMSingleton()
564 : {
565 310 : if (IsNeckoChild())
566 0 : return CookieServiceChild::GetSingleton();
567 :
568 310 : return GetSingleton();
569 : }
570 :
571 : nsCookieService*
572 310 : nsCookieService::GetSingleton()
573 : {
574 310 : NS_ASSERTION(!IsNeckoChild(), "not a parent process");
575 :
576 310 : if (gCookieService) {
577 16 : NS_ADDREF(gCookieService);
578 16 : return gCookieService;
579 : }
580 :
581 : // Create a new singleton nsCookieService.
582 : // We AddRef only once since XPCOM has rules about the ordering of module
583 : // teardowns - by the time our module destructor is called, it's too late to
584 : // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
585 : // cycles have already been completed and would result in serious leaks.
586 : // See bug 209571.
587 294 : gCookieService = new nsCookieService();
588 294 : if (gCookieService) {
589 294 : NS_ADDREF(gCookieService);
590 294 : if (NS_FAILED(gCookieService->Init())) {
591 0 : NS_RELEASE(gCookieService);
592 : }
593 : }
594 :
595 294 : return gCookieService;
596 : }
597 :
598 : /******************************************************************************
599 : * nsCookieService impl:
600 : * public methods
601 : ******************************************************************************/
602 :
603 22485 : NS_IMPL_ISUPPORTS5(nsCookieService,
604 : nsICookieService,
605 : nsICookieManager,
606 : nsICookieManager2,
607 : nsIObserver,
608 : nsISupportsWeakReference)
609 :
610 294 : nsCookieService::nsCookieService()
611 : : mDBState(NULL)
612 : , mCookieBehavior(BEHAVIOR_ACCEPT)
613 : , mThirdPartySession(false)
614 : , mMaxNumberOfCookies(kMaxNumberOfCookies)
615 : , mMaxCookiesPerHost(kMaxCookiesPerHost)
616 294 : , mCookiePurgeAge(kCookiePurgeAge)
617 : {
618 294 : }
619 :
620 : nsresult
621 294 : nsCookieService::Init()
622 : {
623 : NS_TIME_FUNCTION;
624 :
625 : nsresult rv;
626 294 : mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
627 294 : NS_ENSURE_SUCCESS(rv, rv);
628 :
629 294 : mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
630 294 : NS_ENSURE_SUCCESS(rv, rv);
631 :
632 : // init our pref and observer
633 588 : nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
634 294 : if (prefBranch) {
635 294 : prefBranch->AddObserver(kPrefCookieBehavior, this, true);
636 294 : prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
637 294 : prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
638 294 : prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
639 294 : prefBranch->AddObserver(kPrefThirdPartySession, this, true);
640 294 : PrefChanged(prefBranch);
641 : }
642 :
643 294 : mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
644 294 : NS_ENSURE_SUCCESS(rv, rv);
645 :
646 : // Init our default, and possibly private DBStates.
647 294 : InitDBStates();
648 :
649 294 : mObserverService = mozilla::services::GetObserverService();
650 294 : NS_ENSURE_STATE(mObserverService);
651 294 : mObserverService->AddObserver(this, "profile-before-change", true);
652 294 : mObserverService->AddObserver(this, "profile-do-change", true);
653 294 : mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true);
654 :
655 294 : mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
656 294 : if (!mPermissionService) {
657 0 : NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
658 0 : COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
659 : }
660 :
661 294 : return NS_OK;
662 : }
663 :
664 : void
665 343 : nsCookieService::InitDBStates()
666 : {
667 343 : NS_ASSERTION(!mDBState, "already have a DBState");
668 343 : NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
669 343 : NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
670 :
671 : // Create a new default DBState and set our current one.
672 343 : mDefaultDBState = new DBState();
673 343 : mDBState = mDefaultDBState;
674 :
675 : // If we're in private browsing mode, create a private DBState.
676 : nsCOMPtr<nsIPrivateBrowsingService> pbs =
677 686 : do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
678 343 : if (pbs) {
679 343 : bool inPrivateBrowsing = false;
680 343 : pbs->GetPrivateBrowsingEnabled(&inPrivateBrowsing);
681 343 : if (inPrivateBrowsing) {
682 3 : mPrivateDBState = new DBState();
683 3 : mDBState = mPrivateDBState;
684 : }
685 : }
686 :
687 : // Get our cookie file.
688 : nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
689 343 : getter_AddRefs(mDefaultDBState->cookieFile));
690 343 : if (NS_FAILED(rv)) {
691 : // We've already set up our DBStates appropriately; nothing more to do.
692 104 : COOKIE_LOGSTRING(PR_LOG_WARNING,
693 : ("InitDBStates(): couldn't get cookie file"));
694 : return;
695 : }
696 239 : mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
697 :
698 : // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
699 : // do so.
700 239 : OpenDBResult result = TryInitDB(false);
701 239 : if (result == RESULT_RETRY) {
702 : // Database may be corrupt. Synchronously close the connection, clean up the
703 : // default DBState, and try again.
704 6 : COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()"));
705 :
706 6 : CloseDefaultDBConnection();
707 6 : result = TryInitDB(true);
708 6 : if (result == RESULT_RETRY) {
709 : // We're done. Change the code to failure so we clean up below.
710 0 : result = RESULT_FAILURE;
711 : }
712 : }
713 :
714 239 : if (result == RESULT_FAILURE) {
715 0 : COOKIE_LOGSTRING(PR_LOG_WARNING,
716 : ("InitDBStates(): TryInitDB() failed, closing connection"));
717 :
718 : // Connection failure is unrecoverable. Clean up our connection. We can run
719 : // fine without persistent storage -- e.g. if there's no profile.
720 0 : CloseDefaultDBConnection();
721 : }
722 : }
723 :
724 : /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
725 : * move the existing database file out of the way and create a new one.
726 : *
727 : * @returns RESULT_OK if opening or creating the database succeeded;
728 : * RESULT_RETRY if the database cannot be opened, is corrupt, or some
729 : * other failure occurred that might be resolved by recreating the
730 : * database; or RESULT_FAILED if there was an unrecoverable error and
731 : * we must run without a database.
732 : *
733 : * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
734 : * cleanup of the default DBState.
735 : */
736 : OpenDBResult
737 251 : nsCookieService::TryInitDB(bool aRecreateDB)
738 : {
739 251 : NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
740 251 : NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
741 251 : NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
742 251 : NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
743 :
744 : // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
745 : // want to delete it outright, since it may be useful for debugging purposes,
746 : // so we move it out of the way.
747 : nsresult rv;
748 251 : if (aRecreateDB) {
749 24 : nsCOMPtr<nsIFile> backupFile;
750 12 : mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
751 12 : rv = backupFile->MoveToNative(NULL,
752 12 : NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
753 12 : NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
754 : }
755 :
756 : // open a connection to the cookie database, and only cache our connection
757 : // and statements upon success. The connection is opened unshared to eliminate
758 : // cache contention between the main and background threads.
759 502 : rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
760 502 : getter_AddRefs(mDefaultDBState->dbConn));
761 251 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
762 :
763 : // Set up our listeners.
764 500 : mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
765 500 : mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
766 500 : mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
767 500 : mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
768 :
769 : // Grow cookie db in 512KB increments
770 250 : mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
771 :
772 250 : bool tableExists = false;
773 500 : mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
774 250 : &tableExists);
775 250 : if (!tableExists) {
776 205 : rv = CreateTable();
777 205 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
778 :
779 : } else {
780 : // table already exists; check the schema version before reading
781 : PRInt32 dbSchemaVersion;
782 45 : rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
783 45 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
784 :
785 : // Start a transaction for the whole migration block.
786 90 : mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
787 :
788 45 : switch (dbSchemaVersion) {
789 : // Upgrading.
790 : // Every time you increment the database schema, you need to implement
791 : // the upgrading code from the previous version to the new one. If migration
792 : // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
793 : // the original database will be saved, in the hopes that we might one day
794 : // see it and fix it.
795 : case 1:
796 : {
797 : // Add the lastAccessed column to the table.
798 2 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
799 1 : "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
800 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
801 : }
802 : // Fall through to the next upgrade.
803 :
804 : case 2:
805 : {
806 : // Add the baseDomain column and index to the table.
807 6 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
808 3 : "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
809 3 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
810 :
811 : // Compute the baseDomains for the table. This must be done eagerly
812 : // otherwise we won't be able to synchronously read in individual
813 : // domains on demand.
814 4 : nsCOMPtr<mozIStorageStatement> select;
815 4 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
816 4 : "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
817 2 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
818 :
819 4 : nsCOMPtr<mozIStorageStatement> update;
820 4 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
821 : "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
822 4 : getter_AddRefs(update));
823 2 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
824 :
825 4 : nsCString baseDomain, host;
826 : bool hasResult;
827 60 : while (1) {
828 62 : rv = select->ExecuteStep(&hasResult);
829 62 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
830 :
831 62 : if (!hasResult)
832 : break;
833 :
834 61 : PRInt64 id = select->AsInt64(0);
835 61 : select->GetUTF8String(1, host);
836 :
837 61 : rv = GetBaseDomainFromHost(host, baseDomain);
838 61 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
839 :
840 120 : mozStorageStatementScoper scoper(update);
841 :
842 120 : rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
843 60 : baseDomain);
844 60 : NS_ASSERT_SUCCESS(rv);
845 120 : rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
846 60 : id);
847 60 : NS_ASSERT_SUCCESS(rv);
848 :
849 60 : rv = update->ExecuteStep(&hasResult);
850 60 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
851 : }
852 :
853 : // Create an index on baseDomain.
854 2 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
855 1 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
856 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
857 : }
858 : // Fall through to the next upgrade.
859 :
860 : case 3:
861 : {
862 : // Add the creationTime column to the table, and create a unique index
863 : // on (name, host, path). Before we do this, we have to purge the table
864 : // of expired cookies such that we know that the (name, host, path)
865 : // index is truly unique -- otherwise we can't create the index. Note
866 : // that we can't just execute a statement to delete all rows where the
867 : // expiry column is in the past -- doing so would rely on the clock
868 : // (both now and when previous cookies were set) being monotonic.
869 :
870 : // Select the whole table, and order by the fields we're interested in.
871 : // This means we can simply do a linear traversal of the results and
872 : // check for duplicates as we go.
873 10 : nsCOMPtr<mozIStorageStatement> select;
874 10 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
875 : "SELECT id, name, host, path FROM moz_cookies "
876 : "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
877 10 : getter_AddRefs(select));
878 5 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
879 :
880 10 : nsCOMPtr<mozIStorageStatement> deleteExpired;
881 10 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
882 : "DELETE FROM moz_cookies WHERE id = :id"),
883 10 : getter_AddRefs(deleteExpired));
884 5 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
885 :
886 : // Read the first row.
887 : bool hasResult;
888 5 : rv = select->ExecuteStep(&hasResult);
889 5 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
890 :
891 5 : if (hasResult) {
892 10 : nsCString name1, host1, path1;
893 5 : PRInt64 id1 = select->AsInt64(0);
894 5 : select->GetUTF8String(1, name1);
895 5 : select->GetUTF8String(2, host1);
896 5 : select->GetUTF8String(3, path1);
897 :
898 10 : nsCString name2, host2, path2;
899 118 : while (1) {
900 : // Read the second row.
901 123 : rv = select->ExecuteStep(&hasResult);
902 123 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
903 :
904 123 : if (!hasResult)
905 : break;
906 :
907 118 : PRInt64 id2 = select->AsInt64(0);
908 118 : select->GetUTF8String(1, name2);
909 118 : select->GetUTF8String(2, host2);
910 118 : select->GetUTF8String(3, path2);
911 :
912 : // If the two rows match in (name, host, path), we know the earlier
913 : // row has an earlier expiry time. Delete it.
914 118 : if (name1 == name2 && host1 == host2 && path1 == path2) {
915 76 : mozStorageStatementScoper scoper(deleteExpired);
916 :
917 76 : rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
918 38 : id1);
919 38 : NS_ASSERT_SUCCESS(rv);
920 :
921 38 : rv = deleteExpired->ExecuteStep(&hasResult);
922 38 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
923 : }
924 :
925 : // Make the second row the first for the next iteration.
926 118 : name1 = name2;
927 118 : host1 = host2;
928 118 : path1 = path2;
929 118 : id1 = id2;
930 : }
931 : }
932 :
933 : // Add the creationTime column to the table.
934 10 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
935 5 : "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
936 5 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
937 :
938 : // Copy the id of each row into the new creationTime column.
939 8 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
940 : "UPDATE moz_cookies SET creationTime = "
941 4 : "(SELECT id WHERE id = moz_cookies.id)"));
942 4 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
943 :
944 : // Create a unique index on (name, host, path) to allow fast lookup.
945 8 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
946 : "CREATE UNIQUE INDEX moz_uniqueid "
947 4 : "ON moz_cookies (name, host, path)"));
948 4 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
949 : }
950 : // Fall through to the next upgrade.
951 :
952 : // No more upgrades. Update the schema version.
953 3 : rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
954 3 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
955 :
956 : case COOKIES_SCHEMA_VERSION:
957 39 : break;
958 :
959 : case 0:
960 : {
961 0 : NS_WARNING("couldn't get schema version!");
962 :
963 : // the table may be usable; someone might've just clobbered the schema
964 : // version. we can treat this case like a downgrade using the codepath
965 : // below, by verifying the columns we care about are all there. for now,
966 : // re-set the schema version in the db, in case the checks succeed (if
967 : // they don't, we're dropping the table anyway).
968 0 : rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
969 0 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
970 : }
971 : // fall through to downgrade check
972 :
973 : // downgrading.
974 : // if columns have been added to the table, we can still use the ones we
975 : // understand safely. if columns have been deleted or altered, just
976 : // blow away the table and start from scratch! if you change the way
977 : // a column is interpreted, make sure you also change its name so this
978 : // check will catch it.
979 : default:
980 : {
981 : // check if all the expected columns exist
982 2 : nsCOMPtr<mozIStorageStatement> stmt;
983 2 : rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
984 : "SELECT "
985 : "id, "
986 : "baseDomain, "
987 : "name, "
988 : "value, "
989 : "host, "
990 : "path, "
991 : "expiry, "
992 : "lastAccessed, "
993 : "creationTime, "
994 : "isSecure, "
995 : "isHttpOnly "
996 2 : "FROM moz_cookies"), getter_AddRefs(stmt));
997 1 : if (NS_SUCCEEDED(rv))
998 : break;
999 :
1000 : // our columns aren't there - drop the table!
1001 2 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1002 1 : "DROP TABLE moz_cookies"));
1003 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1004 :
1005 1 : rv = CreateTable();
1006 1 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1007 : }
1008 1 : break;
1009 : }
1010 : }
1011 :
1012 : // make operations on the table asynchronous, for performance
1013 490 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1014 245 : "PRAGMA synchronous = OFF"));
1015 :
1016 : // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1017 : // 16 pages (around 500KB).
1018 490 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1019 245 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
1020 490 : mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1021 245 : "PRAGMA wal_autocheckpoint = 16"));
1022 :
1023 : // cache frequently used statements (for insertion, deletion, and updating)
1024 490 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1025 : "INSERT INTO moz_cookies ("
1026 : "baseDomain, "
1027 : "name, "
1028 : "value, "
1029 : "host, "
1030 : "path, "
1031 : "expiry, "
1032 : "lastAccessed, "
1033 : "creationTime, "
1034 : "isSecure, "
1035 : "isHttpOnly"
1036 : ") VALUES ("
1037 : ":baseDomain, "
1038 : ":name, "
1039 : ":value, "
1040 : ":host, "
1041 : ":path, "
1042 : ":expiry, "
1043 : ":lastAccessed, "
1044 : ":creationTime, "
1045 : ":isSecure, "
1046 : ":isHttpOnly"
1047 : ")"),
1048 490 : getter_AddRefs(mDefaultDBState->stmtInsert));
1049 245 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1050 :
1051 490 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1052 : "DELETE FROM moz_cookies "
1053 : "WHERE name = :name AND host = :host AND path = :path"),
1054 490 : getter_AddRefs(mDefaultDBState->stmtDelete));
1055 245 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1056 :
1057 490 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1058 : "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1059 : "WHERE name = :name AND host = :host AND path = :path"),
1060 490 : getter_AddRefs(mDefaultDBState->stmtUpdate));
1061 245 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1062 :
1063 : // if we deleted a corrupt db, don't attempt to import - return now
1064 245 : if (aRecreateDB)
1065 12 : return RESULT_OK;
1066 :
1067 : // check whether to import or just read in the db
1068 233 : if (tableExists)
1069 40 : return Read();
1070 :
1071 386 : nsCOMPtr<nsIFile> oldCookieFile;
1072 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1073 193 : getter_AddRefs(oldCookieFile));
1074 193 : if (NS_FAILED(rv)) return RESULT_OK;
1075 :
1076 : // Import cookies, and clean up the old file regardless of success or failure.
1077 : // Note that we have to switch out our DBState temporarily, in case we're in
1078 : // private browsing mode; otherwise ImportCookies() won't be happy.
1079 193 : DBState* initialState = mDBState;
1080 193 : mDBState = mDefaultDBState;
1081 193 : oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
1082 193 : ImportCookies(oldCookieFile);
1083 193 : oldCookieFile->Remove(false);
1084 193 : mDBState = initialState;
1085 :
1086 193 : return RESULT_OK;
1087 : }
1088 :
1089 : // Sets the schema version and creates the moz_cookies table.
1090 : nsresult
1091 206 : nsCookieService::CreateTable()
1092 : {
1093 : // Set the schema version, before creating the table.
1094 206 : nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
1095 206 : COOKIES_SCHEMA_VERSION);
1096 206 : if (NS_FAILED(rv)) return rv;
1097 :
1098 : // Create the table.
1099 412 : rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1100 : "CREATE TABLE moz_cookies ("
1101 : "id INTEGER PRIMARY KEY, "
1102 : "baseDomain TEXT, "
1103 : "name TEXT, "
1104 : "value TEXT, "
1105 : "host TEXT, "
1106 : "path TEXT, "
1107 : "expiry INTEGER, "
1108 : "lastAccessed INTEGER, "
1109 : "creationTime INTEGER, "
1110 : "isSecure INTEGER, "
1111 : "isHttpOnly INTEGER, "
1112 : "CONSTRAINT moz_uniqueid UNIQUE (name, host, path)"
1113 206 : ")"));
1114 206 : if (NS_FAILED(rv)) return rv;
1115 :
1116 : // Create an index on baseDomain.
1117 412 : return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1118 206 : "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
1119 : }
1120 :
1121 : void
1122 536 : nsCookieService::CloseDBStates()
1123 : {
1124 : // Null out our private and pointer DBStates regardless.
1125 536 : mPrivateDBState = NULL;
1126 536 : mDBState = NULL;
1127 :
1128 : // If we don't have a default DBState, we're done.
1129 536 : if (!mDefaultDBState)
1130 193 : return;
1131 :
1132 343 : if (mDefaultDBState->dbConn) {
1133 : // Cancel any pending read. No further results will be received by our
1134 : // read listener.
1135 236 : if (mDefaultDBState->pendingRead) {
1136 9 : CancelAsyncRead(true);
1137 : }
1138 :
1139 : // Asynchronously close the connection. We will null it below.
1140 236 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1141 : }
1142 :
1143 343 : CloseDefaultDBConnection();
1144 :
1145 343 : mDefaultDBState = NULL;
1146 : }
1147 :
1148 : // Close the default connection by nulling out statements, listeners, and the
1149 : // connection itself. This will not cancel a pending read or asynchronously
1150 : // close the connection -- these must be done beforehand if necessary.
1151 : void
1152 358 : nsCookieService::CloseDefaultDBConnection()
1153 : {
1154 : // Destroy our statements before we close the db.
1155 358 : mDefaultDBState->stmtInsert = NULL;
1156 358 : mDefaultDBState->stmtDelete = NULL;
1157 358 : mDefaultDBState->stmtUpdate = NULL;
1158 :
1159 : // Null out the database connections. If 'dbConn' has not been used for any
1160 : // asynchronous operations yet, this will synchronously close it; otherwise,
1161 : // it's expected that the caller has performed an AsyncClose prior.
1162 358 : mDefaultDBState->dbConn = NULL;
1163 358 : mDefaultDBState->syncConn = NULL;
1164 :
1165 : // Manually null out our listeners. This is necessary because they hold a
1166 : // strong ref to the DBState itself. They'll stay alive until whatever
1167 : // statements are still executing complete.
1168 358 : mDefaultDBState->readListener = NULL;
1169 358 : mDefaultDBState->insertListener = NULL;
1170 358 : mDefaultDBState->updateListener = NULL;
1171 358 : mDefaultDBState->removeListener = NULL;
1172 358 : mDefaultDBState->closeListener = NULL;
1173 358 : }
1174 :
1175 : void
1176 245 : nsCookieService::HandleDBClosed(DBState* aDBState)
1177 : {
1178 245 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1179 : ("HandleDBClosed(): DBState %x closed", aDBState));
1180 :
1181 245 : switch (aDBState->corruptFlag) {
1182 : case DBState::OK: {
1183 : // Database is healthy. Notify of closure.
1184 236 : mObserverService->NotifyObservers(nsnull, "cookie-db-closed", nsnull);
1185 236 : break;
1186 : }
1187 : case DBState::CLOSING_FOR_REBUILD: {
1188 : // Our close finished. Start the rebuild, and notify of db closure later.
1189 8 : RebuildCorruptDB(aDBState);
1190 8 : break;
1191 : }
1192 : case DBState::REBUILDING: {
1193 : // We encountered an error during rebuild, closed the database, and now
1194 : // here we are. We already have a 'cookies.sqlite.bak' from the original
1195 : // dead database; we don't want to overwrite it, so let's move this one to
1196 : // 'cookies.sqlite.bak-rebuild'.
1197 2 : nsCOMPtr<nsIFile> backupFile;
1198 1 : aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
1199 1 : nsresult rv = backupFile->MoveToNative(NULL,
1200 1 : NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
1201 :
1202 1 : COOKIE_LOGSTRING(PR_LOG_WARNING,
1203 : ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
1204 : "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
1205 1 : mObserverService->NotifyObservers(nsnull, "cookie-db-closed", nsnull);
1206 : break;
1207 : }
1208 : }
1209 245 : }
1210 :
1211 : void
1212 11 : nsCookieService::HandleCorruptDB(DBState* aDBState)
1213 : {
1214 11 : if (mDefaultDBState != aDBState) {
1215 : // We've either closed the state or we've switched profiles. It's getting
1216 : // a bit late to rebuild -- bail instead.
1217 2 : COOKIE_LOGSTRING(PR_LOG_WARNING,
1218 : ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
1219 2 : return;
1220 : }
1221 :
1222 9 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1223 : ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
1224 : aDBState->corruptFlag));
1225 :
1226 : // Mark the database corrupt, so the close listener can begin reconstructing
1227 : // it.
1228 9 : switch (mDefaultDBState->corruptFlag) {
1229 : case DBState::OK: {
1230 : // Move to 'closing' state.
1231 8 : mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
1232 :
1233 : // Cancel any pending read and close the database. If we do have an
1234 : // in-flight read we want to throw away all the results so far -- we have no
1235 : // idea how consistent the database is. Note that we may have already
1236 : // canceled the read but not emptied our readSet; do so now.
1237 8 : mDefaultDBState->readSet.Clear();
1238 8 : if (mDefaultDBState->pendingRead) {
1239 4 : CancelAsyncRead(true);
1240 4 : mDefaultDBState->syncConn = nsnull;
1241 : }
1242 :
1243 8 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1244 8 : CloseDefaultDBConnection();
1245 8 : break;
1246 : }
1247 : case DBState::CLOSING_FOR_REBUILD: {
1248 : // We had an error while waiting for close completion. That's OK, just
1249 : // ignore it -- we're rebuilding anyway.
1250 0 : return;
1251 : }
1252 : case DBState::REBUILDING: {
1253 : // We had an error while rebuilding the DB. Game over. Close the database
1254 : // and let the close handler do nothing; then we'll move it out of the way.
1255 1 : if (mDefaultDBState->dbConn) {
1256 1 : mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1257 : }
1258 1 : CloseDefaultDBConnection();
1259 1 : break;
1260 : }
1261 : }
1262 : }
1263 :
1264 : static PLDHashOperator
1265 7 : RebuildDBCallback(nsCookieEntry *aEntry,
1266 : void *aArg)
1267 : {
1268 : mozIStorageBindingParamsArray* paramsArray =
1269 7 : static_cast<mozIStorageBindingParamsArray*>(aArg);
1270 :
1271 7 : const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
1272 24 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1273 17 : nsCookie* cookie = cookies[i];
1274 :
1275 17 : if (!cookie->IsSession()) {
1276 17 : bindCookieParameters(paramsArray, aEntry->GetKey(), cookie);
1277 : }
1278 : }
1279 :
1280 7 : return PL_DHASH_NEXT;
1281 : }
1282 :
1283 : void
1284 8 : nsCookieService::RebuildCorruptDB(DBState* aDBState)
1285 : {
1286 8 : NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
1287 8 : NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
1288 : "should be in CLOSING_FOR_REBUILD state");
1289 :
1290 8 : aDBState->corruptFlag = DBState::REBUILDING;
1291 :
1292 8 : if (mDefaultDBState != aDBState) {
1293 : // We've either closed the state or we've switched profiles. It's getting
1294 : // a bit late to rebuild -- bail instead. In any case, we were waiting
1295 : // on rebuild completion to notify of the db closure, which won't happen --
1296 : // do so now.
1297 2 : COOKIE_LOGSTRING(PR_LOG_WARNING,
1298 : ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
1299 2 : mObserverService->NotifyObservers(nsnull, "cookie-db-closed", nsnull);
1300 2 : return;
1301 : }
1302 :
1303 6 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1304 : ("RebuildCorruptDB(): creating new database"));
1305 :
1306 : // The database has been closed, and we're ready to rebuild. Open a
1307 : // connection.
1308 6 : OpenDBResult result = TryInitDB(true);
1309 6 : if (result != RESULT_OK) {
1310 : // We're done. Reset our DB connection and statements, and notify of
1311 : // closure.
1312 0 : COOKIE_LOGSTRING(PR_LOG_WARNING,
1313 : ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
1314 0 : CloseDefaultDBConnection();
1315 0 : mDefaultDBState->corruptFlag = DBState::OK;
1316 0 : mObserverService->NotifyObservers(nsnull, "cookie-db-closed", nsnull);
1317 0 : return;
1318 : }
1319 :
1320 : // Notify observers that we're beginning the rebuild.
1321 6 : mObserverService->NotifyObservers(nsnull, "cookie-db-rebuilding", nsnull);
1322 :
1323 : // Enumerate the hash, and add cookies to the params array.
1324 6 : mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
1325 12 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1326 6 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1327 6 : aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get());
1328 :
1329 : // Make sure we've got something to write. If we don't, we're done.
1330 : PRUint32 length;
1331 6 : paramsArray->GetLength(&length);
1332 6 : if (length == 0) {
1333 1 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1334 : ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1335 1 : mDefaultDBState->corruptFlag = DBState::OK;
1336 : return;
1337 : }
1338 :
1339 : // Execute the statement. If any errors crop up, we won't try again.
1340 5 : nsresult rv = stmt->BindParameters(paramsArray);
1341 5 : NS_ASSERT_SUCCESS(rv);
1342 10 : nsCOMPtr<mozIStoragePendingStatement> handle;
1343 5 : rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
1344 5 : NS_ASSERT_SUCCESS(rv);
1345 : }
1346 :
1347 876 : nsCookieService::~nsCookieService()
1348 : {
1349 292 : CloseDBStates();
1350 :
1351 292 : gCookieService = nsnull;
1352 1168 : }
1353 :
1354 : NS_IMETHODIMP
1355 314 : nsCookieService::Observe(nsISupports *aSubject,
1356 : const char *aTopic,
1357 : const PRUnichar *aData)
1358 : {
1359 : // check the topic
1360 314 : if (!strcmp(aTopic, "profile-before-change")) {
1361 : // The profile is about to change,
1362 : // or is going away because the application is shutting down.
1363 956 : if (mDBState && mDBState->dbConn &&
1364 712 : !nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
1365 : // Clear the cookie db if we're in the default DBState.
1366 2 : RemoveAll();
1367 : }
1368 :
1369 : // Close the default DB connection and null out our DBStates before
1370 : // changing.
1371 244 : CloseDBStates();
1372 :
1373 70 : } else if (!strcmp(aTopic, "profile-do-change")) {
1374 49 : NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
1375 49 : NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
1376 :
1377 : // the profile has already changed; init the db from the new location.
1378 : // if we are in the private browsing state, however, we do not want to read
1379 : // data into it - we should instead put it into the default state, so it's
1380 : // ready for us if and when we switch back to it.
1381 49 : InitDBStates();
1382 :
1383 21 : } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1384 12 : nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
1385 6 : if (prefBranch)
1386 6 : PrefChanged(prefBranch);
1387 :
1388 15 : } else if (!strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC)) {
1389 15 : if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
1390 8 : NS_ASSERTION(mDefaultDBState, "don't have a default state");
1391 8 : NS_ASSERTION(mDBState == mDefaultDBState, "not in default state");
1392 8 : NS_ASSERTION(!mPrivateDBState, "already have a private state");
1393 :
1394 : // Create a new DBState, and swap it in.
1395 8 : mPrivateDBState = new DBState();
1396 8 : mDBState = mPrivateDBState;
1397 :
1398 7 : } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
1399 7 : NS_ASSERTION(mDefaultDBState, "don't have a default state");
1400 7 : NS_ASSERTION(mDBState == mPrivateDBState, "not in private state");
1401 7 : NS_ASSERTION(!mPrivateDBState->dbConn, "private DB connection not null");
1402 :
1403 : // Clear the private DBState, and restore the default one.
1404 7 : mPrivateDBState = NULL;
1405 7 : mDBState = mDefaultDBState;
1406 : }
1407 :
1408 15 : NotifyChanged(nsnull, NS_LITERAL_STRING("reload").get());
1409 : }
1410 :
1411 314 : return NS_OK;
1412 : }
1413 :
1414 : NS_IMETHODIMP
1415 16 : nsCookieService::GetCookieString(nsIURI *aHostURI,
1416 : nsIChannel *aChannel,
1417 : char **aCookie)
1418 : {
1419 16 : return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
1420 : }
1421 :
1422 : NS_IMETHODIMP
1423 3586 : nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI,
1424 : nsIURI *aFirstURI,
1425 : nsIChannel *aChannel,
1426 : char **aCookie)
1427 : {
1428 3586 : return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
1429 : }
1430 :
1431 : nsresult
1432 3602 : nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
1433 : nsIChannel *aChannel,
1434 : bool aHttpBound,
1435 : char** aCookie)
1436 : {
1437 3602 : NS_ENSURE_ARG(aHostURI);
1438 3602 : NS_ENSURE_ARG(aCookie);
1439 :
1440 : // Determine whether the request is foreign. Failure is acceptable.
1441 3602 : bool isForeign = true;
1442 3602 : if (RequireThirdPartyCheck())
1443 2 : mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
1444 :
1445 7204 : nsCAutoString result;
1446 3602 : GetCookieStringInternal(aHostURI, isForeign, aHttpBound, result);
1447 3602 : *aCookie = result.IsEmpty() ? nsnull : ToNewCString(result);
1448 3602 : return NS_OK;
1449 : }
1450 :
1451 : NS_IMETHODIMP
1452 15075 : nsCookieService::SetCookieString(nsIURI *aHostURI,
1453 : nsIPrompt *aPrompt,
1454 : const char *aCookieHeader,
1455 : nsIChannel *aChannel)
1456 : {
1457 15075 : return SetCookieStringCommon(aHostURI, aCookieHeader, NULL, aChannel, false);
1458 : }
1459 :
1460 : NS_IMETHODIMP
1461 163 : nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
1462 : nsIURI *aFirstURI,
1463 : nsIPrompt *aPrompt,
1464 : const char *aCookieHeader,
1465 : const char *aServerTime,
1466 : nsIChannel *aChannel)
1467 : {
1468 : return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
1469 163 : true);
1470 : }
1471 :
1472 : nsresult
1473 15238 : nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
1474 : const char *aCookieHeader,
1475 : const char *aServerTime,
1476 : nsIChannel *aChannel,
1477 : bool aFromHttp)
1478 : {
1479 15238 : NS_ENSURE_ARG(aHostURI);
1480 15238 : NS_ENSURE_ARG(aCookieHeader);
1481 :
1482 : // Determine whether the request is foreign. Failure is acceptable.
1483 15238 : bool isForeign = true;
1484 15238 : if (RequireThirdPartyCheck())
1485 25 : mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
1486 :
1487 30476 : nsDependentCString cookieString(aCookieHeader);
1488 30476 : nsDependentCString serverTime(aServerTime ? aServerTime : "");
1489 : SetCookieStringInternal(aHostURI, isForeign, cookieString,
1490 15238 : serverTime, aFromHttp);
1491 15238 : return NS_OK;
1492 : }
1493 :
1494 : void
1495 15238 : nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
1496 : bool aIsForeign,
1497 : nsDependentCString &aCookieHeader,
1498 : const nsCString &aServerTime,
1499 : bool aFromHttp)
1500 : {
1501 15238 : NS_ASSERTION(aHostURI, "null host!");
1502 :
1503 15238 : if (!mDBState) {
1504 2 : NS_WARNING("No DBState! Profile already closed?");
1505 2 : return;
1506 : }
1507 :
1508 : // get the base domain for the host URI.
1509 : // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
1510 : // file:// URI's (i.e. with an empty host) are allowed, but any other
1511 : // scheme must have a non-empty host. A trailing dot in the host
1512 : // is acceptable.
1513 : bool requireHostMatch;
1514 30472 : nsCAutoString baseDomain;
1515 15236 : nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
1516 15236 : if (NS_FAILED(rv)) {
1517 2 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
1518 2 : "couldn't get base domain from URI");
1519 : return;
1520 : }
1521 :
1522 : // check default prefs
1523 : CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, baseDomain,
1524 15234 : requireHostMatch, aCookieHeader.get());
1525 : // fire a notification if cookie was rejected (but not if there was an error)
1526 15234 : switch (cookieStatus) {
1527 : case STATUS_REJECTED:
1528 14 : NotifyRejected(aHostURI);
1529 : return;
1530 : case STATUS_REJECTED_WITH_ERROR:
1531 : return;
1532 : default:
1533 : break;
1534 : }
1535 :
1536 : // parse server local time. this is not just done here for efficiency
1537 : // reasons - if there's an error parsing it, and we need to default it
1538 : // to the current time, we must do it here since the current time in
1539 : // SetCookieInternal() will change for each cookie processed (e.g. if the
1540 : // user is prompted).
1541 : PRTime tempServerTime;
1542 : PRInt64 serverTime;
1543 : PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
1544 15220 : &tempServerTime);
1545 15220 : if (result == PR_SUCCESS) {
1546 2 : serverTime = tempServerTime / PRInt64(PR_USEC_PER_SEC);
1547 : } else {
1548 15218 : serverTime = PR_Now() / PR_USEC_PER_SEC;
1549 : }
1550 :
1551 : // process each cookie in the header
1552 30453 : while (SetCookieInternal(aHostURI, baseDomain, requireHostMatch, cookieStatus,
1553 15233 : aCookieHeader, serverTime, aFromHttp)) {
1554 : // document.cookie can only set one cookie at a time
1555 14 : if (!aFromHttp)
1556 1 : break;
1557 : }
1558 : }
1559 :
1560 : // notify observers that a cookie was rejected due to the users' prefs.
1561 : void
1562 15 : nsCookieService::NotifyRejected(nsIURI *aHostURI)
1563 : {
1564 15 : if (mObserverService)
1565 15 : mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nsnull);
1566 15 : }
1567 :
1568 : // notify observers that the cookie list changed. there are five possible
1569 : // values for aData:
1570 : // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
1571 : // "added" means a cookie was added. aSubject is the added cookie.
1572 : // "changed" means a cookie was altered. aSubject is the new cookie.
1573 : // "cleared" means the entire cookie list was cleared. aSubject is null.
1574 : // "batch-deleted" means a set of cookies was purged. aSubject is the list of
1575 : // cookies.
1576 : void
1577 16563 : nsCookieService::NotifyChanged(nsISupports *aSubject,
1578 : const PRUnichar *aData)
1579 : {
1580 16563 : if (mObserverService)
1581 16563 : mObserverService->NotifyObservers(aSubject, "cookie-changed", aData);
1582 16563 : }
1583 :
1584 : already_AddRefed<nsIArray>
1585 71 : nsCookieService::CreatePurgeList(nsICookie2* aCookie)
1586 : {
1587 : nsCOMPtr<nsIMutableArray> removedList =
1588 142 : do_CreateInstance(NS_ARRAY_CONTRACTID);
1589 71 : removedList->AppendElement(aCookie, false);
1590 71 : return removedList.forget();
1591 : }
1592 :
1593 : /******************************************************************************
1594 : * nsCookieService:
1595 : * pref observer impl
1596 : ******************************************************************************/
1597 :
1598 : void
1599 300 : nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
1600 : {
1601 : PRInt32 val;
1602 300 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
1603 300 : mCookieBehavior = (PRUint8) LIMIT(val, 0, 2, 0);
1604 :
1605 300 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
1606 1 : mMaxNumberOfCookies = (PRUint16) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
1607 :
1608 300 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
1609 3 : mMaxCookiesPerHost = (PRUint16) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
1610 :
1611 300 : if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
1612 : mCookiePurgeAge =
1613 1 : PRInt64(LIMIT(val, 0, PR_INT32_MAX, PR_INT32_MAX)) * PR_USEC_PER_SEC;
1614 : }
1615 :
1616 : bool boolval;
1617 300 : if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
1618 300 : mThirdPartySession = boolval;
1619 :
1620 : // Lazily instantiate the third party service if necessary.
1621 300 : if (!mThirdPartyUtil && RequireThirdPartyCheck()) {
1622 3 : mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
1623 3 : NS_ABORT_IF_FALSE(mThirdPartyUtil, "require ThirdPartyUtil service");
1624 : }
1625 300 : }
1626 :
1627 : /******************************************************************************
1628 : * nsICookieManager impl:
1629 : * nsICookieManager
1630 : ******************************************************************************/
1631 :
1632 : NS_IMETHODIMP
1633 45 : nsCookieService::RemoveAll()
1634 : {
1635 45 : if (!mDBState) {
1636 1 : NS_WARNING("No DBState! Profile already closed?");
1637 1 : return NS_ERROR_NOT_AVAILABLE;
1638 : }
1639 :
1640 44 : RemoveAllFromMemory();
1641 :
1642 : // clear the cookie file
1643 44 : if (mDBState->dbConn) {
1644 9 : NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
1645 :
1646 : // Cancel any pending read. No further results will be received by our
1647 : // read listener.
1648 9 : if (mDefaultDBState->pendingRead) {
1649 2 : CancelAsyncRead(true);
1650 : }
1651 :
1652 18 : nsCOMPtr<mozIStorageAsyncStatement> stmt;
1653 18 : nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1654 18 : "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
1655 9 : if (NS_SUCCEEDED(rv)) {
1656 18 : nsCOMPtr<mozIStoragePendingStatement> handle;
1657 18 : rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
1658 18 : getter_AddRefs(handle));
1659 9 : NS_ASSERT_SUCCESS(rv);
1660 : } else {
1661 : // Recreate the database.
1662 0 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1663 : ("RemoveAll(): corruption detected with rv 0x%x", rv));
1664 0 : HandleCorruptDB(mDefaultDBState);
1665 : }
1666 : }
1667 :
1668 44 : NotifyChanged(nsnull, NS_LITERAL_STRING("cleared").get());
1669 44 : return NS_OK;
1670 : }
1671 :
1672 : static PLDHashOperator
1673 15082 : COMArrayCallback(nsCookieEntry *aEntry,
1674 : void *aArg)
1675 : {
1676 15082 : nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
1677 :
1678 15082 : const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
1679 31423 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1680 16341 : data->AppendObject(cookies[i]);
1681 : }
1682 :
1683 15082 : return PL_DHASH_NEXT;
1684 : }
1685 :
1686 : NS_IMETHODIMP
1687 57 : nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
1688 : {
1689 57 : if (!mDBState) {
1690 1 : NS_WARNING("No DBState! Profile already closed?");
1691 1 : return NS_ERROR_NOT_AVAILABLE;
1692 : }
1693 :
1694 56 : EnsureReadComplete();
1695 :
1696 112 : nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
1697 56 : mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
1698 :
1699 56 : return NS_NewArrayEnumerator(aEnumerator, cookieList);
1700 : }
1701 :
1702 : NS_IMETHODIMP
1703 1019 : nsCookieService::Add(const nsACString &aHost,
1704 : const nsACString &aPath,
1705 : const nsACString &aName,
1706 : const nsACString &aValue,
1707 : bool aIsSecure,
1708 : bool aIsHttpOnly,
1709 : bool aIsSession,
1710 : PRInt64 aExpiry)
1711 : {
1712 1019 : if (!mDBState) {
1713 1 : NS_WARNING("No DBState! Profile already closed?");
1714 1 : return NS_ERROR_NOT_AVAILABLE;
1715 : }
1716 :
1717 : // first, normalize the hostname, and fail if it contains illegal characters.
1718 2036 : nsCAutoString host(aHost);
1719 1018 : nsresult rv = NormalizeHost(host);
1720 1018 : NS_ENSURE_SUCCESS(rv, rv);
1721 :
1722 : // get the base domain for the host URI.
1723 : // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
1724 2036 : nsCAutoString baseDomain;
1725 1018 : rv = GetBaseDomainFromHost(host, baseDomain);
1726 1018 : NS_ENSURE_SUCCESS(rv, rv);
1727 :
1728 1017 : PRInt64 currentTimeInUsec = PR_Now();
1729 :
1730 : nsRefPtr<nsCookie> cookie =
1731 : nsCookie::Create(aName, aValue, host, aPath,
1732 : aExpiry,
1733 : currentTimeInUsec,
1734 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
1735 : aIsSession,
1736 : aIsSecure,
1737 2034 : aIsHttpOnly);
1738 1017 : if (!cookie) {
1739 0 : return NS_ERROR_OUT_OF_MEMORY;
1740 : }
1741 :
1742 1017 : AddInternal(baseDomain, cookie, currentTimeInUsec, nsnull, nsnull, true);
1743 1017 : return NS_OK;
1744 : }
1745 :
1746 : NS_IMETHODIMP
1747 212 : nsCookieService::Remove(const nsACString &aHost,
1748 : const nsACString &aName,
1749 : const nsACString &aPath,
1750 : bool aBlocked)
1751 : {
1752 212 : if (!mDBState) {
1753 1 : NS_WARNING("No DBState! Profile already closed?");
1754 1 : return NS_ERROR_NOT_AVAILABLE;
1755 : }
1756 :
1757 : // first, normalize the hostname, and fail if it contains illegal characters.
1758 422 : nsCAutoString host(aHost);
1759 211 : nsresult rv = NormalizeHost(host);
1760 211 : NS_ENSURE_SUCCESS(rv, rv);
1761 :
1762 422 : nsCAutoString baseDomain;
1763 211 : rv = GetBaseDomainFromHost(host, baseDomain);
1764 211 : NS_ENSURE_SUCCESS(rv, rv);
1765 :
1766 210 : nsListIter matchIter;
1767 420 : nsRefPtr<nsCookie> cookie;
1768 420 : if (FindCookie(baseDomain,
1769 : host,
1770 210 : PromiseFlatCString(aName),
1771 210 : PromiseFlatCString(aPath),
1772 210 : matchIter)) {
1773 208 : cookie = matchIter.Cookie();
1774 208 : RemoveCookieFromList(matchIter);
1775 : }
1776 :
1777 : // check if we need to add the host to the permissions blacklist.
1778 210 : if (aBlocked && mPermissionService) {
1779 : // strip off the domain dot, if necessary
1780 1 : if (!host.IsEmpty() && host.First() == '.')
1781 0 : host.Cut(0, 1);
1782 :
1783 1 : host.Insert(NS_LITERAL_CSTRING("http://"), 0);
1784 :
1785 2 : nsCOMPtr<nsIURI> uri;
1786 1 : NS_NewURI(getter_AddRefs(uri), host);
1787 :
1788 1 : if (uri)
1789 1 : mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
1790 : }
1791 :
1792 210 : if (cookie) {
1793 : // Everything's done. Notify observers.
1794 208 : NotifyChanged(cookie, NS_LITERAL_STRING("deleted").get());
1795 : }
1796 :
1797 210 : return NS_OK;
1798 : }
1799 :
1800 : /******************************************************************************
1801 : * nsCookieService impl:
1802 : * private file I/O functions
1803 : ******************************************************************************/
1804 :
1805 : // Begin an asynchronous read from the database.
1806 : OpenDBResult
1807 40 : nsCookieService::Read()
1808 : {
1809 : // Set up a statement for the read. Note that our query specifies that
1810 : // 'baseDomain' not be NULL -- see below for why.
1811 80 : nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
1812 80 : nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1813 : "SELECT "
1814 : "name, "
1815 : "value, "
1816 : "host, "
1817 : "path, "
1818 : "expiry, "
1819 : "lastAccessed, "
1820 : "creationTime, "
1821 : "isSecure, "
1822 : "isHttpOnly, "
1823 : "baseDomain "
1824 : "FROM moz_cookies "
1825 80 : "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
1826 40 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1827 :
1828 : // Set up a statement to delete any rows with a NULL 'baseDomain'
1829 : // column. This takes care of any cookies set by browsers that don't
1830 : // understand the 'baseDomain' column, where the database schema version
1831 : // is from one that does. (This would occur when downgrading.)
1832 80 : nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
1833 80 : rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1834 : "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
1835 80 : getter_AddRefs(stmtDeleteNull));
1836 40 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1837 :
1838 : // Start a new connection for sync reads, to reduce contention with the
1839 : // background thread. We need to do this before we kick off write statements,
1840 : // since they can lock the database and prevent connections from being opened.
1841 80 : rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
1842 80 : getter_AddRefs(mDefaultDBState->syncConn));
1843 40 : NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1844 :
1845 : // Init our readSet hash and execute the statements. Note that, after this
1846 : // point, we cannot fail without altering the cleanup code in InitDBStates()
1847 : // to handle closing of the now-asynchronous connection.
1848 40 : mDefaultDBState->readSet.Init();
1849 40 : mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
1850 :
1851 80 : mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
1852 80 : rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
1853 80 : getter_AddRefs(mDefaultDBState->pendingRead));
1854 40 : NS_ASSERT_SUCCESS(rv);
1855 :
1856 80 : nsCOMPtr<mozIStoragePendingStatement> handle;
1857 80 : rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
1858 80 : getter_AddRefs(handle));
1859 40 : NS_ASSERT_SUCCESS(rv);
1860 :
1861 40 : return RESULT_OK;
1862 : }
1863 :
1864 : // Extract data from a single result row and create an nsCookie.
1865 : // This is templated since 'T' is different for sync vs async results.
1866 : template<class T> nsCookie*
1867 14414 : nsCookieService::GetCookieFromRow(T &aRow)
1868 : {
1869 : // Skip reading 'baseDomain' -- up to the caller.
1870 51466 : nsCString name, value, host, path;
1871 25733 : nsresult rv = aRow->GetUTF8String(0, name);
1872 25733 : NS_ASSERT_SUCCESS(rv);
1873 25733 : rv = aRow->GetUTF8String(1, value);
1874 25733 : NS_ASSERT_SUCCESS(rv);
1875 25733 : rv = aRow->GetUTF8String(2, host);
1876 25733 : NS_ASSERT_SUCCESS(rv);
1877 25733 : rv = aRow->GetUTF8String(3, path);
1878 25733 : NS_ASSERT_SUCCESS(rv);
1879 :
1880 25733 : PRInt64 expiry = aRow->AsInt64(4);
1881 25733 : PRInt64 lastAccessed = aRow->AsInt64(5);
1882 25733 : PRInt64 creationTime = aRow->AsInt64(6);
1883 25733 : bool isSecure = 0 != aRow->AsInt32(7);
1884 25733 : bool isHttpOnly = 0 != aRow->AsInt32(8);
1885 :
1886 : // Create a new nsCookie and assign the data.
1887 : return nsCookie::Create(name, value, host, path,
1888 : expiry,
1889 : lastAccessed,
1890 : creationTime,
1891 : false,
1892 : isSecure,
1893 25733 : isHttpOnly);
1894 : }
1895 :
1896 : void
1897 8 : nsCookieService::AsyncReadComplete()
1898 : {
1899 : // We may be in the private browsing DB state, with a pending read on the
1900 : // default DB state. (This would occur if we started up in private browsing
1901 : // mode.) As long as we do all our operations on the default state, we're OK.
1902 8 : NS_ASSERTION(mDefaultDBState, "no default DBState");
1903 8 : NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
1904 8 : NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
1905 :
1906 : // Merge the data read on the background thread with the data synchronously
1907 : // read on the main thread. Note that transactions on the cookie table may
1908 : // have occurred on the main thread since, making the background data stale.
1909 2912 : for (PRUint32 i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
1910 2904 : const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
1911 :
1912 : // Tiebreak: if the given base domain has already been read in, ignore
1913 : // the background data. Note that readSet may contain domains that were
1914 : // queried but found not to be in the db -- that's harmless.
1915 2904 : if (mDefaultDBState->readSet.GetEntry(tuple.baseDomain))
1916 21 : continue;
1917 :
1918 : AddCookieToList(tuple.baseDomain, tuple.cookie, mDefaultDBState, NULL,
1919 2883 : false);
1920 : }
1921 :
1922 8 : mDefaultDBState->stmtReadDomain = nsnull;
1923 8 : mDefaultDBState->pendingRead = nsnull;
1924 8 : mDefaultDBState->readListener = nsnull;
1925 8 : mDefaultDBState->syncConn = nsnull;
1926 8 : mDefaultDBState->hostArray.Clear();
1927 8 : mDefaultDBState->readSet.Clear();
1928 :
1929 8 : COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
1930 : mDefaultDBState->cookieCount));
1931 :
1932 8 : mObserverService->NotifyObservers(nsnull, "cookie-db-read", nsnull);
1933 8 : }
1934 :
1935 : void
1936 32 : nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
1937 : {
1938 : // We may be in the private browsing DB state, with a pending read on the
1939 : // default DB state. (This would occur if we started up in private browsing
1940 : // mode.) As long as we do all our operations on the default state, we're OK.
1941 32 : NS_ASSERTION(mDefaultDBState, "no default DBState");
1942 32 : NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
1943 32 : NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
1944 :
1945 : // Cancel the pending read, kill the read listener, and empty the array
1946 : // of data already read in on the background thread.
1947 32 : mDefaultDBState->readListener->Cancel();
1948 64 : mozilla::DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
1949 32 : NS_ASSERT_SUCCESS(rv);
1950 :
1951 32 : mDefaultDBState->stmtReadDomain = nsnull;
1952 32 : mDefaultDBState->pendingRead = nsnull;
1953 32 : mDefaultDBState->readListener = nsnull;
1954 32 : mDefaultDBState->hostArray.Clear();
1955 :
1956 : // Only clear the 'readSet' table if we no longer need to know what set of
1957 : // data is already accounted for.
1958 32 : if (aPurgeReadSet)
1959 15 : mDefaultDBState->readSet.Clear();
1960 32 : }
1961 :
1962 : void
1963 26161 : nsCookieService::EnsureReadDomain(const nsCString &aBaseDomain)
1964 : {
1965 26161 : NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
1966 : "not in default db state");
1967 :
1968 : // Fast path 1: nothing to read, or we've already finished reading.
1969 26161 : if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
1970 25880 : return;
1971 :
1972 : // Fast path 2: already read in this particular domain.
1973 281 : if (NS_LIKELY(mDefaultDBState->readSet.GetEntry(aBaseDomain)))
1974 36 : return;
1975 :
1976 : // Read in the data synchronously.
1977 : nsresult rv;
1978 245 : if (!mDefaultDBState->stmtReadDomain) {
1979 : // Cache the statement, since it's likely to be used again.
1980 46 : rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
1981 : "SELECT "
1982 : "name, "
1983 : "value, "
1984 : "host, "
1985 : "path, "
1986 : "expiry, "
1987 : "lastAccessed, "
1988 : "creationTime, "
1989 : "isSecure, "
1990 : "isHttpOnly "
1991 : "FROM moz_cookies "
1992 : "WHERE baseDomain = :baseDomain"),
1993 46 : getter_AddRefs(mDefaultDBState->stmtReadDomain));
1994 :
1995 23 : if (NS_FAILED(rv)) {
1996 : // Recreate the database.
1997 0 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
1998 : ("EnsureReadDomain(): corruption detected when creating statement "
1999 : "with rv 0x%x", rv));
2000 0 : HandleCorruptDB(mDefaultDBState);
2001 0 : return;
2002 : }
2003 : }
2004 :
2005 245 : NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
2006 :
2007 490 : mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
2008 :
2009 245 : rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
2010 245 : NS_LITERAL_CSTRING("baseDomain"), aBaseDomain);
2011 245 : NS_ASSERT_SUCCESS(rv);
2012 :
2013 : bool hasResult;
2014 490 : nsCString name, value, host, path;
2015 490 : nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
2016 2758 : while (1) {
2017 3003 : rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
2018 3003 : if (NS_FAILED(rv)) {
2019 : // Recreate the database.
2020 1 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
2021 : ("EnsureReadDomain(): corruption detected when reading result "
2022 : "with rv 0x%x", rv));
2023 1 : HandleCorruptDB(mDefaultDBState);
2024 : return;
2025 : }
2026 :
2027 3002 : if (!hasResult)
2028 : break;
2029 :
2030 2758 : array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
2031 : }
2032 :
2033 : // Add the cookies to the table in a single operation. This makes sure that
2034 : // either all the cookies get added, or in the case of corruption, none.
2035 623 : for (PRUint32 i = 0; i < array.Length(); ++i) {
2036 379 : AddCookieToList(aBaseDomain, array[i], mDefaultDBState, NULL, false);
2037 : }
2038 :
2039 : // Add it to the hashset of read entries, so we don't read it again.
2040 244 : mDefaultDBState->readSet.PutEntry(aBaseDomain);
2041 :
2042 244 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
2043 : ("EnsureReadDomain(): %ld cookies read for base domain %s",
2044 : array.Length(), aBaseDomain.get()));
2045 : }
2046 :
2047 : void
2048 60 : nsCookieService::EnsureReadComplete()
2049 : {
2050 60 : NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
2051 : "not in default db state");
2052 :
2053 : // Fast path 1: nothing to read, or we've already finished reading.
2054 60 : if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
2055 43 : return;
2056 :
2057 : // Cancel the pending read, so we don't get any more results.
2058 17 : CancelAsyncRead(false);
2059 :
2060 : // Read in the data synchronously.
2061 34 : nsCOMPtr<mozIStorageStatement> stmt;
2062 34 : nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
2063 : "SELECT "
2064 : "name, "
2065 : "value, "
2066 : "host, "
2067 : "path, "
2068 : "expiry, "
2069 : "lastAccessed, "
2070 : "creationTime, "
2071 : "isSecure, "
2072 : "isHttpOnly, "
2073 : "baseDomain "
2074 : "FROM moz_cookies "
2075 34 : "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
2076 :
2077 17 : if (NS_FAILED(rv)) {
2078 : // Recreate the database.
2079 2 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
2080 : ("EnsureReadComplete(): corruption detected when creating statement "
2081 : "with rv 0x%x", rv));
2082 2 : HandleCorruptDB(mDefaultDBState);
2083 : return;
2084 : }
2085 :
2086 30 : nsCString baseDomain, name, value, host, path;
2087 : bool hasResult;
2088 30 : nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
2089 11862 : while (1) {
2090 11877 : rv = stmt->ExecuteStep(&hasResult);
2091 11877 : if (NS_FAILED(rv)) {
2092 : // Recreate the database.
2093 1 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
2094 : ("EnsureReadComplete(): corruption detected when reading result "
2095 : "with rv 0x%x", rv));
2096 1 : HandleCorruptDB(mDefaultDBState);
2097 : return;
2098 : }
2099 :
2100 11876 : if (!hasResult)
2101 : break;
2102 :
2103 : // Make sure we haven't already read the data.
2104 11862 : stmt->GetUTF8String(9, baseDomain);
2105 11862 : if (mDefaultDBState->readSet.GetEntry(baseDomain))
2106 206 : continue;
2107 :
2108 11656 : CookieDomainTuple* tuple = array.AppendElement();
2109 11656 : tuple->baseDomain = baseDomain;
2110 11656 : tuple->cookie = GetCookieFromRow(stmt);
2111 : }
2112 :
2113 : // Add the cookies to the table in a single operation. This makes sure that
2114 : // either all the cookies get added, or in the case of corruption, none.
2115 9281 : for (PRUint32 i = 0; i < array.Length(); ++i) {
2116 9267 : CookieDomainTuple& tuple = array[i];
2117 : AddCookieToList(tuple.baseDomain, tuple.cookie, mDefaultDBState, NULL,
2118 9267 : false);
2119 : }
2120 :
2121 14 : mDefaultDBState->syncConn = nsnull;
2122 14 : mDefaultDBState->readSet.Clear();
2123 :
2124 14 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
2125 : ("EnsureReadComplete(): %ld cookies read", array.Length()));
2126 : }
2127 :
2128 : NS_IMETHODIMP
2129 195 : nsCookieService::ImportCookies(nsIFile *aCookieFile)
2130 : {
2131 195 : if (!mDBState) {
2132 1 : NS_WARNING("No DBState! Profile already closed?");
2133 1 : return NS_ERROR_NOT_AVAILABLE;
2134 : }
2135 :
2136 : // Make sure we're in the default DB state. We don't want people importing
2137 : // cookies into a private browsing session!
2138 194 : if (mDBState != mDefaultDBState) {
2139 1 : NS_WARNING("Trying to import cookies in a private browsing session!");
2140 1 : return NS_ERROR_NOT_AVAILABLE;
2141 : }
2142 :
2143 : nsresult rv;
2144 386 : nsCOMPtr<nsIInputStream> fileInputStream;
2145 193 : rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
2146 193 : if (NS_FAILED(rv)) return rv;
2147 :
2148 0 : nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
2149 0 : if (NS_FAILED(rv)) return rv;
2150 :
2151 : // First, ensure we've read in everything from the database, if we have one.
2152 0 : EnsureReadComplete();
2153 :
2154 : static const char kTrue[] = "TRUE";
2155 :
2156 0 : nsCAutoString buffer, baseDomain;
2157 0 : bool isMore = true;
2158 : PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
2159 : nsASingleFragmentCString::char_iterator iter;
2160 : PRInt32 numInts;
2161 : PRInt64 expires;
2162 0 : bool isDomain, isHttpOnly = false;
2163 0 : PRUint32 originalCookieCount = mDefaultDBState->cookieCount;
2164 :
2165 0 : PRInt64 currentTimeInUsec = PR_Now();
2166 0 : PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
2167 : // we use lastAccessedCounter to keep cookies in recently-used order,
2168 : // so we start by initializing to currentTime (somewhat arbitrary)
2169 0 : PRInt64 lastAccessedCounter = currentTimeInUsec;
2170 :
2171 : /* file format is:
2172 : *
2173 : * host \t isDomain \t path \t secure \t expires \t name \t cookie
2174 : *
2175 : * if this format isn't respected we move onto the next line in the file.
2176 : * isDomain is "TRUE" or "FALSE" (default to "FALSE")
2177 : * isSecure is "TRUE" or "FALSE" (default to "TRUE")
2178 : * expires is a PRInt64 integer
2179 : * note 1: cookie can contain tabs.
2180 : * note 2: cookies will be stored in order of lastAccessed time:
2181 : * most-recently used come first; least-recently-used come last.
2182 : */
2183 :
2184 : /*
2185 : * ...but due to bug 178933, we hide HttpOnly cookies from older code
2186 : * in a comment, so they don't expose HttpOnly cookies to JS.
2187 : *
2188 : * The format for HttpOnly cookies is
2189 : *
2190 : * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
2191 : *
2192 : */
2193 :
2194 : // We will likely be adding a bunch of cookies to the DB, so we use async
2195 : // batching with storage to make this super fast.
2196 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
2197 0 : if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
2198 0 : mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
2199 : }
2200 :
2201 0 : while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
2202 0 : if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
2203 0 : isHttpOnly = true;
2204 0 : hostIndex = sizeof(kHttpOnlyPrefix) - 1;
2205 0 : } else if (buffer.IsEmpty() || buffer.First() == '#') {
2206 0 : continue;
2207 : } else {
2208 0 : isHttpOnly = false;
2209 0 : hostIndex = 0;
2210 : }
2211 :
2212 : // this is a cheap, cheesy way of parsing a tab-delimited line into
2213 : // string indexes, which can be lopped off into substrings. just for
2214 : // purposes of obfuscation, it also checks that each token was found.
2215 : // todo: use iterators?
2216 0 : if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
2217 0 : (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
2218 0 : (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
2219 0 : (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
2220 0 : (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
2221 0 : (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
2222 0 : continue;
2223 : }
2224 :
2225 : // check the expirytime first - if it's expired, ignore
2226 : // nullstomp the trailing tab, to avoid copying the string
2227 0 : buffer.BeginWriting(iter);
2228 0 : *(iter += nameIndex - 1) = char(0);
2229 0 : numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
2230 0 : if (numInts != 1 || expires < currentTime) {
2231 0 : continue;
2232 : }
2233 :
2234 0 : isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
2235 0 : const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
2236 : // check for bad legacy cookies (domain not starting with a dot, or containing a port),
2237 : // and discard
2238 0 : if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
2239 0 : host.FindChar(':') != kNotFound) {
2240 0 : continue;
2241 : }
2242 :
2243 : // compute the baseDomain from the host
2244 0 : rv = GetBaseDomainFromHost(host, baseDomain);
2245 0 : if (NS_FAILED(rv))
2246 0 : continue;
2247 :
2248 : // Create a new nsCookie and assign the data. We don't know the cookie
2249 : // creation time, so just use the current time to generate a unique one.
2250 : nsRefPtr<nsCookie> newCookie =
2251 0 : nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
2252 0 : Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
2253 : host,
2254 0 : Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
2255 : expires,
2256 : lastAccessedCounter,
2257 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
2258 : false,
2259 0 : Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
2260 0 : isHttpOnly);
2261 0 : if (!newCookie) {
2262 0 : return NS_ERROR_OUT_OF_MEMORY;
2263 : }
2264 :
2265 : // trick: preserve the most-recently-used cookie ordering,
2266 : // by successively decrementing the lastAccessed time
2267 0 : lastAccessedCounter--;
2268 :
2269 0 : if (originalCookieCount == 0) {
2270 0 : AddCookieToList(baseDomain, newCookie, mDefaultDBState, paramsArray);
2271 : }
2272 : else {
2273 0 : AddInternal(baseDomain, newCookie, currentTimeInUsec, NULL, NULL, true);
2274 : }
2275 : }
2276 :
2277 : // If we need to write to disk, do so now.
2278 0 : if (paramsArray) {
2279 : PRUint32 length;
2280 0 : paramsArray->GetLength(&length);
2281 0 : if (length) {
2282 0 : rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
2283 0 : NS_ASSERT_SUCCESS(rv);
2284 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
2285 0 : rv = mDefaultDBState->stmtInsert->ExecuteAsync(
2286 0 : mDefaultDBState->insertListener, getter_AddRefs(handle));
2287 0 : NS_ASSERT_SUCCESS(rv);
2288 : }
2289 : }
2290 :
2291 :
2292 0 : COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
2293 : mDefaultDBState->cookieCount));
2294 :
2295 0 : return NS_OK;
2296 : }
2297 :
2298 : /******************************************************************************
2299 : * nsCookieService impl:
2300 : * private GetCookie/SetCookie helpers
2301 : ******************************************************************************/
2302 :
2303 : // helper function for GetCookieList
2304 123 : static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
2305 :
2306 : // Comparator class for sorting cookies before sending to a server.
2307 : class CompareCookiesForSending
2308 : {
2309 : public:
2310 82 : bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
2311 : {
2312 82 : return aCookie1->CreationTime() == aCookie2->CreationTime() &&
2313 164 : aCookie2->Path().Length() == aCookie1->Path().Length();
2314 : }
2315 :
2316 181 : bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
2317 : {
2318 : // compare by cookie path length in accordance with RFC2109
2319 181 : PRInt32 result = aCookie2->Path().Length() - aCookie1->Path().Length();
2320 181 : if (result != 0)
2321 17 : return result < 0;
2322 :
2323 : // when path lengths match, older cookies should be listed first. this is
2324 : // required for backwards compatibility since some websites erroneously
2325 : // depend on receiving cookies in the order in which they were sent to the
2326 : // browser! see bug 236772.
2327 164 : return aCookie1->CreationTime() < aCookie2->CreationTime();
2328 : }
2329 : };
2330 :
2331 : void
2332 3602 : nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
2333 : bool aIsForeign,
2334 : bool aHttpBound,
2335 : nsCString &aCookieString)
2336 : {
2337 3602 : NS_ASSERTION(aHostURI, "null host!");
2338 :
2339 3602 : if (!mDBState) {
2340 3 : NS_WARNING("No DBState! Profile already closed?");
2341 3 : return;
2342 : }
2343 :
2344 : // get the base domain, host, and path from the URI.
2345 : // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
2346 : // file:// URI's (i.e. with an empty host) are allowed, but any other
2347 : // scheme must have a non-empty host. A trailing dot in the host
2348 : // is acceptable.
2349 : bool requireHostMatch;
2350 7198 : nsCAutoString baseDomain, hostFromURI, pathFromURI;
2351 3599 : nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
2352 3599 : if (NS_SUCCEEDED(rv))
2353 3597 : rv = aHostURI->GetAsciiHost(hostFromURI);
2354 3599 : if (NS_SUCCEEDED(rv))
2355 3597 : rv = aHostURI->GetPath(pathFromURI);
2356 3599 : if (NS_FAILED(rv)) {
2357 2 : COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nsnull, "invalid host/path from URI");
2358 : return;
2359 : }
2360 :
2361 : // check default prefs
2362 : CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, baseDomain,
2363 3597 : requireHostMatch, nsnull);
2364 : // for GetCookie(), we don't fire rejection notifications.
2365 3597 : switch (cookieStatus) {
2366 : case STATUS_REJECTED:
2367 : case STATUS_REJECTED_WITH_ERROR:
2368 : return;
2369 : default:
2370 : break;
2371 : }
2372 :
2373 : // check if aHostURI is using an https secure protocol.
2374 : // if it isn't, then we can't send a secure cookie over the connection.
2375 : // if SchemeIs fails, assume an insecure connection, to be on the safe side
2376 : bool isSecure;
2377 3597 : if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
2378 0 : isSecure = false;
2379 : }
2380 :
2381 : nsCookie *cookie;
2382 7194 : nsAutoTArray<nsCookie*, 8> foundCookieList;
2383 3597 : PRInt64 currentTimeInUsec = PR_Now();
2384 3597 : PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
2385 3597 : bool stale = false;
2386 :
2387 3597 : EnsureReadDomain(baseDomain);
2388 :
2389 : // perform the hash lookup
2390 3597 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
2391 3597 : if (!entry)
2392 : return;
2393 :
2394 : // iterate the cookies!
2395 63 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
2396 193 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
2397 130 : cookie = cookies[i];
2398 :
2399 : // check the host, since the base domain lookup is conservative.
2400 : // first, check for an exact host or domain cookie match, e.g. "google.com"
2401 : // or ".google.com"; second a subdomain match, e.g.
2402 : // host = "mail.google.com", cookie domain = ".google.com".
2403 264 : if (cookie->RawHost() != hostFromURI &&
2404 138 : !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
2405 0 : continue;
2406 :
2407 : // if the cookie is secure and the host scheme isn't, we can't send it
2408 130 : if (cookie->IsSecure() && !isSecure)
2409 0 : continue;
2410 :
2411 : // if the cookie is httpOnly and it's not going directly to the HTTP
2412 : // connection, don't send it
2413 130 : if (cookie->IsHttpOnly() && !aHttpBound)
2414 3 : continue;
2415 :
2416 : // calculate cookie path length, excluding trailing '/'
2417 127 : PRUint32 cookiePathLen = cookie->Path().Length();
2418 127 : if (cookiePathLen > 0 && cookie->Path().Last() == '/')
2419 114 : --cookiePathLen;
2420 :
2421 : // if the nsIURI path is shorter than the cookie path, don't send it back
2422 127 : if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
2423 1 : continue;
2424 :
2425 249 : if (pathFromURI.Length() > cookiePathLen &&
2426 123 : !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
2427 : /*
2428 : * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
2429 : * '/' is the "standard" case; the '?' test allows a site at host/abc?def
2430 : * to receive a cookie that has a path attribute of abc. this seems
2431 : * strange but at least one major site (citibank, bug 156725) depends
2432 : * on it. The test for # and ; are put in to proactively avoid problems
2433 : * with other sites - these are the only other chars allowed in the path.
2434 : */
2435 2 : continue;
2436 : }
2437 :
2438 : // check if the cookie has expired
2439 124 : if (cookie->Expiry() <= currentTime) {
2440 0 : continue;
2441 : }
2442 :
2443 : // all checks passed - add to list and check if lastAccessed stamp needs updating
2444 124 : foundCookieList.AppendElement(cookie);
2445 124 : if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
2446 0 : stale = true;
2447 : }
2448 :
2449 63 : PRInt32 count = foundCookieList.Length();
2450 63 : if (count == 0)
2451 : return;
2452 :
2453 : // update lastAccessed timestamps. we only do this if the timestamp is stale
2454 : // by a certain amount, to avoid thrashing the db during pageload.
2455 58 : if (stale) {
2456 : // Create an array of parameters to bind to our update statement. Batching
2457 : // is OK here since we're updating cookies with no interleaved operations.
2458 0 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
2459 0 : mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
2460 0 : if (mDBState->dbConn) {
2461 0 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
2462 : }
2463 :
2464 0 : for (PRInt32 i = 0; i < count; ++i) {
2465 0 : cookie = foundCookieList.ElementAt(i);
2466 :
2467 0 : if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
2468 0 : UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
2469 : }
2470 : // Update the database now if necessary.
2471 0 : if (paramsArray) {
2472 : PRUint32 length;
2473 0 : paramsArray->GetLength(&length);
2474 0 : if (length) {
2475 0 : nsresult rv = stmt->BindParameters(paramsArray);
2476 0 : NS_ASSERT_SUCCESS(rv);
2477 0 : nsCOMPtr<mozIStoragePendingStatement> handle;
2478 : rv = stmt->ExecuteAsync(mDBState->updateListener,
2479 0 : getter_AddRefs(handle));
2480 0 : NS_ASSERT_SUCCESS(rv);
2481 : }
2482 : }
2483 : }
2484 :
2485 : // return cookies in order of path length; longest to shortest.
2486 : // this is required per RFC2109. if cookies match in length,
2487 : // then sort by creation time (see bug 236772).
2488 58 : foundCookieList.Sort(CompareCookiesForSending());
2489 :
2490 182 : for (PRInt32 i = 0; i < count; ++i) {
2491 124 : cookie = foundCookieList.ElementAt(i);
2492 :
2493 : // check if we have anything to write
2494 124 : if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
2495 : // if we've already added a cookie to the return list, append a "; " so
2496 : // that subsequent cookies are delimited in the final list.
2497 122 : if (!aCookieString.IsEmpty()) {
2498 65 : aCookieString.AppendLiteral("; ");
2499 : }
2500 :
2501 122 : if (!cookie->Name().IsEmpty()) {
2502 : // we have a name and value - write both
2503 117 : aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
2504 : } else {
2505 : // just write value
2506 5 : aCookieString += cookie->Value();
2507 : }
2508 : }
2509 : }
2510 :
2511 58 : if (!aCookieString.IsEmpty())
2512 57 : COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nsnull, nsnull);
2513 : }
2514 :
2515 : // processes a single cookie, and returns true if there are more cookies
2516 : // to be processed
2517 : bool
2518 15233 : nsCookieService::SetCookieInternal(nsIURI *aHostURI,
2519 : const nsCString &aBaseDomain,
2520 : bool aRequireHostMatch,
2521 : CookieStatus aStatus,
2522 : nsDependentCString &aCookieHeader,
2523 : PRInt64 aServerTime,
2524 : bool aFromHttp)
2525 : {
2526 15233 : NS_ASSERTION(aHostURI, "null host!");
2527 :
2528 : // create a stack-based nsCookieAttributes, to store all the
2529 : // attributes parsed from the cookie
2530 30466 : nsCookieAttributes cookieAttributes;
2531 :
2532 : // init expiryTime such that session cookies won't prematurely expire
2533 15233 : cookieAttributes.expiryTime = LL_MAXINT;
2534 :
2535 : // aCookieHeader is an in/out param to point to the next cookie, if
2536 : // there is one. Save the present value for logging purposes
2537 30466 : nsDependentCString savedCookieHeader(aCookieHeader);
2538 :
2539 : // newCookie says whether there are multiple cookies in the header;
2540 : // so we can handle them separately.
2541 15233 : bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
2542 :
2543 15233 : PRInt64 currentTimeInUsec = PR_Now();
2544 :
2545 : // calculate expiry time of cookie.
2546 : cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
2547 15233 : currentTimeInUsec / PR_USEC_PER_SEC);
2548 15233 : if (aStatus == STATUS_ACCEPT_SESSION) {
2549 : // force lifetime to session. note that the expiration time, if set above,
2550 : // will still apply.
2551 8 : cookieAttributes.isSession = true;
2552 : }
2553 :
2554 : // reject cookie if it's over the size limit, per RFC2109
2555 15233 : if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
2556 1 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
2557 1 : return newCookie;
2558 : }
2559 :
2560 15232 : if (cookieAttributes.name.FindChar('\t') != kNotFound) {
2561 1 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
2562 1 : return newCookie;
2563 : }
2564 :
2565 : // domain & path checks
2566 15231 : if (!CheckDomain(cookieAttributes, aHostURI, aBaseDomain, aRequireHostMatch)) {
2567 13 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
2568 13 : return newCookie;
2569 : }
2570 15218 : if (!CheckPath(cookieAttributes, aHostURI)) {
2571 3 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
2572 3 : return newCookie;
2573 : }
2574 :
2575 : // create a new nsCookie and copy attributes
2576 : nsRefPtr<nsCookie> cookie =
2577 : nsCookie::Create(cookieAttributes.name,
2578 : cookieAttributes.value,
2579 : cookieAttributes.host,
2580 : cookieAttributes.path,
2581 : cookieAttributes.expiryTime,
2582 : currentTimeInUsec,
2583 : nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
2584 : cookieAttributes.isSession,
2585 : cookieAttributes.isSecure,
2586 30430 : cookieAttributes.isHttpOnly);
2587 15215 : if (!cookie)
2588 0 : return newCookie;
2589 :
2590 : // check permissions from site permission list, or ask the user,
2591 : // to determine if we can set the cookie
2592 15215 : if (mPermissionService) {
2593 : bool permission;
2594 : // Not passing an nsIChannel here means CanSetCookie will use the currently
2595 : // active window to display the prompt. This isn't exactly ideal, but this
2596 : // code is going away. See bug 546746.
2597 15215 : mPermissionService->CanSetCookie(aHostURI,
2598 : nsnull,
2599 15215 : static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
2600 : &cookieAttributes.isSession,
2601 : &cookieAttributes.expiryTime,
2602 30430 : &permission);
2603 15215 : if (!permission) {
2604 1 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
2605 1 : NotifyRejected(aHostURI);
2606 1 : return newCookie;
2607 : }
2608 :
2609 : // update isSession and expiry attributes, in case they changed
2610 15214 : cookie->SetIsSession(cookieAttributes.isSession);
2611 15214 : cookie->SetExpiry(cookieAttributes.expiryTime);
2612 : }
2613 :
2614 : // add the cookie to the list. AddInternal() takes care of logging.
2615 : // we get the current time again here, since it may have changed during prompting
2616 : AddInternal(aBaseDomain, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
2617 15214 : aFromHttp);
2618 15214 : return newCookie;
2619 : }
2620 :
2621 : // this is a backend function for adding a cookie to the list, via SetCookie.
2622 : // also used in the cookie manager, for profile migration from IE.
2623 : // it either replaces an existing cookie; or adds the cookie to the hashtable,
2624 : // and deletes a cookie (if maximum number of cookies has been
2625 : // reached). also performs list maintenance by removing expired cookies.
2626 : void
2627 16231 : nsCookieService::AddInternal(const nsCString &aBaseDomain,
2628 : nsCookie *aCookie,
2629 : PRInt64 aCurrentTimeInUsec,
2630 : nsIURI *aHostURI,
2631 : const char *aCookieHeader,
2632 : bool aFromHttp)
2633 : {
2634 16231 : PRInt64 currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
2635 :
2636 : // if the new cookie is httponly, make sure we're not coming from script
2637 16231 : if (!aFromHttp && aCookie->IsHttpOnly()) {
2638 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2639 2 : "cookie is httponly; coming from script");
2640 2 : return;
2641 : }
2642 :
2643 16229 : nsListIter matchIter;
2644 16229 : bool foundCookie = FindCookie(aBaseDomain, aCookie->Host(),
2645 32458 : aCookie->Name(), aCookie->Path(), matchIter);
2646 :
2647 32458 : nsRefPtr<nsCookie> oldCookie;
2648 32458 : nsCOMPtr<nsIArray> purgedList;
2649 16229 : if (foundCookie) {
2650 42 : oldCookie = matchIter.Cookie();
2651 :
2652 : // Check if the old cookie is stale (i.e. has already expired). If so, we
2653 : // need to be careful about the semantics of removing it and adding the new
2654 : // cookie: we want the behavior wrt adding the new cookie to be the same as
2655 : // if it didn't exist, but we still want to fire a removal notification.
2656 42 : if (oldCookie->Expiry() <= currentTime) {
2657 0 : if (aCookie->Expiry() <= currentTime) {
2658 : // The new cookie has expired and the old one is stale. Nothing to do.
2659 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2660 0 : "cookie has already expired");
2661 : return;
2662 : }
2663 :
2664 : // Remove the stale cookie. We save notification for later, once all list
2665 : // modifications are complete.
2666 0 : RemoveCookieFromList(matchIter);
2667 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2668 0 : "stale cookie was purged");
2669 0 : purgedList = CreatePurgeList(oldCookie);
2670 :
2671 : // We've done all we need to wrt removing and notifying the stale cookie.
2672 : // From here on out, we pretend pretend it didn't exist, so that we
2673 : // preserve expected notification semantics when adding the new cookie.
2674 0 : foundCookie = false;
2675 :
2676 : } else {
2677 : // If the old cookie is httponly, make sure we're not coming from script.
2678 42 : if (!aFromHttp && oldCookie->IsHttpOnly()) {
2679 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2680 2 : "previously stored cookie is httponly; coming from script");
2681 : return;
2682 : }
2683 :
2684 : // Remove the old cookie.
2685 40 : RemoveCookieFromList(matchIter);
2686 :
2687 : // If the new cookie has expired -- i.e. the intent was simply to delete
2688 : // the old cookie -- then we're done.
2689 40 : if (aCookie->Expiry() <= currentTime) {
2690 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2691 22 : "previously stored cookie was deleted");
2692 22 : NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
2693 : return;
2694 : }
2695 :
2696 : // Preserve creation time of cookie for ordering purposes.
2697 18 : aCookie->SetCreationTime(oldCookie->CreationTime());
2698 : }
2699 :
2700 : } else {
2701 : // check if cookie has already expired
2702 16187 : if (aCookie->Expiry() <= currentTime) {
2703 : COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2704 6 : "cookie has already expired");
2705 : return;
2706 : }
2707 :
2708 : // check if we have to delete an old cookie.
2709 16181 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
2710 16181 : if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
2711 71 : nsListIter iter;
2712 71 : FindStaleCookie(entry, currentTime, iter);
2713 71 : oldCookie = iter.Cookie();
2714 :
2715 : // remove the oldest cookie from the domain
2716 71 : RemoveCookieFromList(iter);
2717 71 : COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
2718 71 : purgedList = CreatePurgeList(oldCookie);
2719 :
2720 16110 : } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
2721 14 : PRInt64 maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
2722 14 : PRInt64 purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
2723 14 : if (maxAge >= purgeAge) {
2724 : // we're over both size and age limits by 10%; time to purge the table!
2725 : // do this by:
2726 : // 1) removing expired cookies;
2727 : // 2) evicting the balance of old cookies until we reach the size limit.
2728 : // note that the cookieOldestTime indicator can be pessimistic - if it's
2729 : // older than the actual oldest cookie, we'll just purge more eagerly.
2730 4 : purgedList = PurgeCookies(aCurrentTimeInUsec);
2731 : }
2732 : }
2733 : }
2734 :
2735 : // Add the cookie to the db. We do not supply a params array for batching
2736 : // because this might result in removals and additions being out of order.
2737 16199 : AddCookieToList(aBaseDomain, aCookie, mDBState, NULL);
2738 16199 : COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
2739 :
2740 : // Now that list mutations are complete, notify observers. We do it here
2741 : // because observers may themselves attempt to mutate the list.
2742 16199 : if (purgedList) {
2743 75 : NotifyChanged(purgedList, NS_LITERAL_STRING("batch-deleted").get());
2744 : }
2745 :
2746 16217 : NotifyChanged(aCookie, foundCookie ? NS_LITERAL_STRING("changed").get()
2747 16217 : : NS_LITERAL_STRING("added").get());
2748 : }
2749 :
2750 : /******************************************************************************
2751 : * nsCookieService impl:
2752 : * private cookie header parsing functions
2753 : ******************************************************************************/
2754 :
2755 : // The following comment block elucidates the function of ParseAttributes.
2756 : /******************************************************************************
2757 : ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
2758 : ** please note: this BNF deviates from both specifications, and reflects this
2759 : ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
2760 :
2761 : ** Differences from RFC2109/2616 and explanations:
2762 : 1. implied *LWS
2763 : The grammar described by this specification is word-based. Except
2764 : where noted otherwise, linear white space (<LWS>) can be included
2765 : between any two adjacent words (token or quoted-string), and
2766 : between adjacent words and separators, without changing the
2767 : interpretation of a field.
2768 : <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
2769 :
2770 : 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
2771 : common use inside values.
2772 :
2773 : 3. tokens and values have looser restrictions on allowed characters than
2774 : spec. This is also due to certain characters being in common use inside
2775 : values. We allow only '=' to separate token/value pairs, and ';' to
2776 : terminate tokens or values. <LWS> is allowed within tokens and values
2777 : (see bug 206022).
2778 :
2779 : 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
2780 : reject control chars or non-ASCII chars. This is erring on the loose
2781 : side, since there's probably no good reason to enforce this strictness.
2782 :
2783 : 5. cookie <NAME> is optional, where spec requires it. This is a fairly
2784 : trivial case, but allows the flexibility of setting only a cookie <VALUE>
2785 : with a blank <NAME> and is required by some sites (see bug 169091).
2786 :
2787 : 6. Attribute "HttpOnly", not covered in the RFCs, is supported
2788 : (see bug 178993).
2789 :
2790 : ** Begin BNF:
2791 : token = 1*<any allowed-chars except separators>
2792 : value = 1*<any allowed-chars except value-sep>
2793 : separators = ";" | "="
2794 : value-sep = ";"
2795 : cookie-sep = CR | LF
2796 : allowed-chars = <any OCTET except NUL or cookie-sep>
2797 : OCTET = <any 8-bit sequence of data>
2798 : LWS = SP | HT
2799 : NUL = <US-ASCII NUL, null control character (0)>
2800 : CR = <US-ASCII CR, carriage return (13)>
2801 : LF = <US-ASCII LF, linefeed (10)>
2802 : SP = <US-ASCII SP, space (32)>
2803 : HT = <US-ASCII HT, horizontal-tab (9)>
2804 :
2805 : set-cookie = "Set-Cookie:" cookies
2806 : cookies = cookie *( cookie-sep cookie )
2807 : cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
2808 : NAME = token ; cookie name
2809 : VALUE = value ; cookie value
2810 : cookie-av = token ["=" value]
2811 :
2812 : valid values for cookie-av (checked post-parsing) are:
2813 : cookie-av = "Path" "=" value
2814 : | "Domain" "=" value
2815 : | "Expires" "=" value
2816 : | "Max-Age" "=" value
2817 : | "Comment" "=" value
2818 : | "Version" "=" value
2819 : | "Secure"
2820 : | "HttpOnly"
2821 :
2822 : ******************************************************************************/
2823 :
2824 : // helper functions for GetTokenValue
2825 136549 : static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; }
2826 426471 : static inline bool isterminator (char c) { return c == '\n' || c == '\r'; }
2827 411319 : static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
2828 178068 : static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
2829 :
2830 : // Parse a single token/value pair.
2831 : // Returns true if a cookie terminator is found, so caller can parse new cookie.
2832 : bool
2833 30368 : nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
2834 : nsASingleFragmentCString::const_char_iterator &aEndIter,
2835 : nsDependentCSubstring &aTokenString,
2836 : nsDependentCSubstring &aTokenValue,
2837 : bool &aEqualsFound)
2838 : {
2839 : nsASingleFragmentCString::const_char_iterator start, lastSpace;
2840 : // initialize value string to clear garbage
2841 30368 : aTokenValue.Rebind(aIter, aIter);
2842 :
2843 : // find <token>, including any <LWS> between the end-of-token and the
2844 : // token separator. we'll remove trailing <LWS> next
2845 75874 : while (aIter != aEndIter && iswhitespace(*aIter))
2846 15138 : ++aIter;
2847 30368 : start = aIter;
2848 208445 : while (aIter != aEndIter && !istokenseparator(*aIter))
2849 147709 : ++aIter;
2850 :
2851 : // remove trailing <LWS>; first check we're not at the beginning
2852 30368 : lastSpace = aIter;
2853 30368 : if (lastSpace != start) {
2854 30359 : while (--lastSpace != start && iswhitespace(*lastSpace));
2855 30359 : ++lastSpace;
2856 : }
2857 30368 : aTokenString.Rebind(start, lastSpace);
2858 :
2859 30368 : aEqualsFound = (*aIter == '=');
2860 30368 : if (aEqualsFound) {
2861 : // find <value>
2862 30349 : while (++aIter != aEndIter && iswhitespace(*aIter));
2863 :
2864 30349 : start = aIter;
2865 :
2866 : // process <token>
2867 : // just look for ';' to terminate ('=' allowed)
2868 278807 : while (aIter != aEndIter && !isvalueseparator(*aIter))
2869 218109 : ++aIter;
2870 :
2871 : // remove trailing <LWS>; first check we're not at the beginning
2872 30349 : if (aIter != start) {
2873 30345 : lastSpace = aIter;
2874 30345 : while (--lastSpace != start && iswhitespace(*lastSpace));
2875 30345 : aTokenValue.Rebind(start, ++lastSpace);
2876 : }
2877 : }
2878 :
2879 : // aIter is on ';', or terminator, or EOS
2880 30368 : if (aIter != aEndIter) {
2881 : // if on terminator, increment past & return true to process new cookie
2882 15152 : if (isterminator(*aIter)) {
2883 14 : ++aIter;
2884 14 : return true;
2885 : }
2886 : // fall-through: aIter is on ';', increment and return false
2887 15138 : ++aIter;
2888 : }
2889 30354 : return false;
2890 : }
2891 :
2892 : // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
2893 : // cookie struct here, because we don't know which one to use until we've parsed the header.
2894 : bool
2895 15233 : nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
2896 : nsCookieAttributes &aCookieAttributes)
2897 : {
2898 : static const char kPath[] = "path";
2899 : static const char kDomain[] = "domain";
2900 : static const char kExpires[] = "expires";
2901 : static const char kMaxage[] = "max-age";
2902 : static const char kSecure[] = "secure";
2903 : static const char kHttpOnly[] = "httponly";
2904 :
2905 : nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
2906 : nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
2907 15233 : aCookieHeader.BeginReading(cookieStart);
2908 15233 : aCookieHeader.EndReading(cookieEnd);
2909 :
2910 15233 : aCookieAttributes.isSecure = false;
2911 15233 : aCookieAttributes.isHttpOnly = false;
2912 :
2913 30466 : nsDependentCSubstring tokenString(cookieStart, cookieStart);
2914 30466 : nsDependentCSubstring tokenValue (cookieStart, cookieStart);
2915 : bool newCookie, equalsFound;
2916 :
2917 : // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
2918 : // if we find multiple cookies, return for processing
2919 : // note: if there's no '=', we assume token is <VALUE>. this is required by
2920 : // some sites (see bug 169091).
2921 : // XXX fix the parser to parse according to <VALUE> grammar for this case
2922 15233 : newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
2923 15233 : if (equalsFound) {
2924 15227 : aCookieAttributes.name = tokenString;
2925 15227 : aCookieAttributes.value = tokenValue;
2926 : } else {
2927 6 : aCookieAttributes.value = tokenString;
2928 : }
2929 :
2930 : // extract remaining attributes
2931 45601 : while (cookieStart != cookieEnd && !newCookie) {
2932 15135 : newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
2933 :
2934 15135 : if (!tokenValue.IsEmpty()) {
2935 15118 : tokenValue.BeginReading(tempBegin);
2936 15118 : tokenValue.EndReading(tempEnd);
2937 : }
2938 :
2939 : // decide which attribute we have, and copy the string
2940 15135 : if (tokenString.LowerCaseEqualsLiteral(kPath))
2941 20 : aCookieAttributes.path = tokenValue;
2942 :
2943 15115 : else if (tokenString.LowerCaseEqualsLiteral(kDomain))
2944 35 : aCookieAttributes.host = tokenValue;
2945 :
2946 15080 : else if (tokenString.LowerCaseEqualsLiteral(kExpires))
2947 6 : aCookieAttributes.expires = tokenValue;
2948 :
2949 15074 : else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
2950 15057 : aCookieAttributes.maxage = tokenValue;
2951 :
2952 : // ignore any tokenValue for isSecure; just set the boolean
2953 17 : else if (tokenString.LowerCaseEqualsLiteral(kSecure))
2954 0 : aCookieAttributes.isSecure = true;
2955 :
2956 : // ignore any tokenValue for isHttpOnly (see bug 178993);
2957 : // just set the boolean
2958 17 : else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
2959 7 : aCookieAttributes.isHttpOnly = true;
2960 : }
2961 :
2962 : // rebind aCookieHeader, in case we need to process another cookie
2963 15233 : aCookieHeader.Rebind(cookieStart, cookieEnd);
2964 15233 : return newCookie;
2965 : }
2966 :
2967 : /******************************************************************************
2968 : * nsCookieService impl:
2969 : * private domain & permission compliance enforcement functions
2970 : ******************************************************************************/
2971 :
2972 : // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
2973 : // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
2974 : // dot may be present. If aHostURI is an IP address, an alias such as
2975 : // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
2976 : // be the exact host, and aRequireHostMatch will be true to indicate that
2977 : // substring matches should not be performed.
2978 : nsresult
2979 18835 : nsCookieService::GetBaseDomain(nsIURI *aHostURI,
2980 : nsCString &aBaseDomain,
2981 : bool &aRequireHostMatch)
2982 : {
2983 : // get the base domain. this will fail if the host contains a leading dot,
2984 : // more than one trailing dot, or is otherwise malformed.
2985 18835 : nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
2986 : aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
2987 18835 : rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
2988 18835 : if (aRequireHostMatch) {
2989 : // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
2990 : // such as 'co.uk', or the empty string. use the host as a key in such
2991 : // cases.
2992 3246 : rv = aHostURI->GetAsciiHost(aBaseDomain);
2993 : }
2994 18835 : NS_ENSURE_SUCCESS(rv, rv);
2995 :
2996 : // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
2997 18833 : if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
2998 0 : return NS_ERROR_INVALID_ARG;
2999 :
3000 : // block any URIs without a host that aren't file:// URIs.
3001 18833 : if (aBaseDomain.IsEmpty()) {
3002 7 : bool isFileURI = false;
3003 7 : aHostURI->SchemeIs("file", &isFileURI);
3004 7 : if (!isFileURI)
3005 2 : return NS_ERROR_INVALID_ARG;
3006 : }
3007 :
3008 18831 : return NS_OK;
3009 : }
3010 :
3011 : // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
3012 : // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
3013 : // that aHost is already normalized, and it may contain a leading dot
3014 : // (indicating that it represents a domain). A trailing dot may be present.
3015 : // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
3016 : // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
3017 : // leading dot will be treated as an error.
3018 : nsresult
3019 7432 : nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
3020 : nsCString &aBaseDomain)
3021 : {
3022 : // aHost must not be the string '.'.
3023 7432 : if (aHost.Length() == 1 && aHost.Last() == '.')
3024 8 : return NS_ERROR_INVALID_ARG;
3025 :
3026 : // aHost may contain a leading dot; if so, strip it now.
3027 7424 : bool domain = !aHost.IsEmpty() && aHost.First() == '.';
3028 :
3029 : // get the base domain. this will fail if the host contains a leading dot,
3030 : // more than one trailing dot, or is otherwise malformed.
3031 7424 : nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
3032 7424 : if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
3033 : rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
3034 : // aHost is either an IP address, an alias such as 'localhost', an eTLD
3035 : // such as 'co.uk', or the empty string. use the host as a key in such
3036 : // cases; however, we reject any such hosts with a leading dot, since it
3037 : // doesn't make sense for them to be domain cookies.
3038 38 : if (domain)
3039 6 : return NS_ERROR_INVALID_ARG;
3040 :
3041 32 : aBaseDomain = aHost;
3042 32 : return NS_OK;
3043 : }
3044 7386 : return rv;
3045 : }
3046 :
3047 : // Normalizes the given hostname, component by component. ASCII/ACE
3048 : // components are lower-cased, and UTF-8 components are normalized per
3049 : // RFC 3454 and converted to ACE.
3050 : nsresult
3051 7341 : nsCookieService::NormalizeHost(nsCString &aHost)
3052 : {
3053 7341 : if (!IsASCII(aHost)) {
3054 0 : nsCAutoString host;
3055 0 : nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
3056 0 : if (NS_FAILED(rv))
3057 0 : return rv;
3058 :
3059 0 : aHost = host;
3060 : }
3061 :
3062 7341 : ToLowerCase(aHost);
3063 7341 : return NS_OK;
3064 : }
3065 :
3066 : // returns true if 'a' is equal to or a subdomain of 'b',
3067 : // assuming no leading dots are present.
3068 39 : static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
3069 : {
3070 39 : if (a == b)
3071 24 : return true;
3072 15 : if (a.Length() > b.Length())
3073 12 : return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
3074 3 : return false;
3075 : }
3076 :
3077 : bool
3078 37969 : nsCookieService::RequireThirdPartyCheck()
3079 : {
3080 : // 'true' iff we need to perform a third party test.
3081 37969 : return mCookieBehavior == BEHAVIOR_REJECTFOREIGN || mThirdPartySession;
3082 : }
3083 :
3084 : CookieStatus
3085 18831 : nsCookieService::CheckPrefs(nsIURI *aHostURI,
3086 : bool aIsForeign,
3087 : const nsCString &aBaseDomain,
3088 : bool aRequireHostMatch,
3089 : const char *aCookieHeader)
3090 : {
3091 : nsresult rv;
3092 :
3093 : // don't let ftp sites get/set cookies (could be a security issue)
3094 : bool ftp;
3095 18831 : if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
3096 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
3097 0 : return STATUS_REJECTED_WITH_ERROR;
3098 : }
3099 :
3100 : // check the permission list first; if we find an entry, it overrides
3101 : // default prefs. see bug 184059.
3102 18831 : if (mPermissionService) {
3103 : nsCookieAccess access;
3104 : // Not passing an nsIChannel here is probably OK; our implementation
3105 : // doesn't do anything with it anyway.
3106 18831 : rv = mPermissionService->CanAccess(aHostURI, nsnull, &access);
3107 :
3108 : // if we found an entry, use it
3109 18831 : if (NS_SUCCEEDED(rv)) {
3110 18831 : switch (access) {
3111 : case nsICookiePermission::ACCESS_DENY:
3112 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are blocked for this site");
3113 0 : return STATUS_REJECTED;
3114 :
3115 : case nsICookiePermission::ACCESS_ALLOW:
3116 0 : return STATUS_ACCEPTED;
3117 : }
3118 : }
3119 : }
3120 :
3121 : // check default prefs
3122 18831 : if (mCookieBehavior == BEHAVIOR_REJECT) {
3123 0 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
3124 0 : return STATUS_REJECTED;
3125 : }
3126 :
3127 18831 : if (RequireThirdPartyCheck() && aIsForeign) {
3128 : // check if cookie is foreign
3129 22 : if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
3130 8 : return STATUS_ACCEPT_SESSION;
3131 :
3132 14 : COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
3133 14 : return STATUS_REJECTED;
3134 : }
3135 :
3136 : // if nothing has complained, accept cookie
3137 18809 : return STATUS_ACCEPTED;
3138 : }
3139 :
3140 : // processes domain attribute, and returns true if host has permission to set for this domain.
3141 : bool
3142 15231 : nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
3143 : nsIURI *aHostURI,
3144 : const nsCString &aBaseDomain,
3145 : bool aRequireHostMatch)
3146 : {
3147 : // get host from aHostURI
3148 30462 : nsCAutoString hostFromURI;
3149 15231 : aHostURI->GetAsciiHost(hostFromURI);
3150 :
3151 : // if a domain is given, check the host has permission
3152 15231 : if (!aCookieAttributes.host.IsEmpty()) {
3153 : // Tolerate leading '.' characters, but not if it's otherwise an empty host.
3154 67 : if (aCookieAttributes.host.Length() > 1 &&
3155 33 : aCookieAttributes.host.First() == '.') {
3156 17 : aCookieAttributes.host.Cut(0, 1);
3157 : }
3158 :
3159 : // switch to lowercase now, to avoid case-insensitive compares everywhere
3160 34 : ToLowerCase(aCookieAttributes.host);
3161 :
3162 : // check whether the host is either an IP address, an alias such as
3163 : // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
3164 : // cases, require an exact string match for the domain, and leave the cookie
3165 : // as a non-domain one. bug 105917 originally noted the requirement to deal
3166 : // with IP addresses.
3167 34 : if (aRequireHostMatch)
3168 12 : return hostFromURI.Equals(aCookieAttributes.host);
3169 :
3170 : // ensure the proposed domain is derived from the base domain; and also
3171 : // that the host domain is derived from the proposed domain (per RFC2109).
3172 39 : if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
3173 17 : IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
3174 : // prepend a dot to indicate a domain cookie
3175 15 : aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
3176 15 : return true;
3177 : }
3178 :
3179 : /*
3180 : * note: RFC2109 section 4.3.2 requires that we check the following:
3181 : * that the portion of host not in domain does not contain a dot.
3182 : * this prevents hosts of the form x.y.co.nz from setting cookies in the
3183 : * entire .co.nz domain. however, it's only a only a partial solution and
3184 : * it breaks sites (IE doesn't enforce it), so we don't perform this check.
3185 : */
3186 7 : return false;
3187 : }
3188 :
3189 : // no domain specified, use hostFromURI
3190 15197 : aCookieAttributes.host = hostFromURI;
3191 15197 : return true;
3192 : }
3193 :
3194 : bool
3195 15218 : nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
3196 : nsIURI *aHostURI)
3197 : {
3198 : // if a path is given, check the host has permission
3199 15218 : if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
3200 : // strip down everything after the last slash to get the path,
3201 : // ignoring slashes in the query string part.
3202 : // if we can QI to nsIURL, that'll take care of the query string portion.
3203 : // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
3204 30400 : nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
3205 15200 : if (hostURL) {
3206 15200 : hostURL->GetDirectory(aCookieAttributes.path);
3207 : } else {
3208 0 : aHostURI->GetPath(aCookieAttributes.path);
3209 0 : PRInt32 slash = aCookieAttributes.path.RFindChar('/');
3210 0 : if (slash != kNotFound) {
3211 0 : aCookieAttributes.path.Truncate(slash + 1);
3212 : }
3213 : }
3214 :
3215 : #if 0
3216 : } else {
3217 : /**
3218 : * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
3219 : * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
3220 : * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
3221 : * been disabled, unless we can evangelize these sites.
3222 : */
3223 : // get path from aHostURI
3224 : nsCAutoString pathFromURI;
3225 : if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
3226 : !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
3227 : return false;
3228 : }
3229 : #endif
3230 : }
3231 :
3232 30434 : if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
3233 15216 : aCookieAttributes.path.FindChar('\t') != kNotFound )
3234 3 : return false;
3235 :
3236 15215 : return true;
3237 : }
3238 :
3239 : bool
3240 15233 : nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
3241 : PRInt64 aServerTime,
3242 : PRInt64 aCurrentTime)
3243 : {
3244 : /* Determine when the cookie should expire. This is done by taking the difference between
3245 : * the server time and the time the server wants the cookie to expire, and adding that
3246 : * difference to the client time. This localizes the client time regardless of whether or
3247 : * not the TZ environment variable was set on the client.
3248 : *
3249 : * Note: We need to consider accounting for network lag here, per RFC.
3250 : */
3251 : PRInt64 delta;
3252 :
3253 : // check for max-age attribute first; this overrides expires attribute
3254 15233 : if (!aCookieAttributes.maxage.IsEmpty()) {
3255 : // obtain numeric value of maxageAttribute
3256 : PRInt64 maxage;
3257 15057 : PRInt32 numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
3258 :
3259 : // default to session cookie if the conversion failed
3260 15057 : if (numInts != 1) {
3261 0 : return true;
3262 : }
3263 :
3264 15057 : delta = maxage;
3265 :
3266 : // check for expires attribute
3267 176 : } else if (!aCookieAttributes.expires.IsEmpty()) {
3268 : PRTime expires;
3269 :
3270 : // parse expiry time
3271 6 : if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
3272 1 : return true;
3273 : }
3274 :
3275 5 : delta = expires / PRInt64(PR_USEC_PER_SEC) - aServerTime;
3276 :
3277 : // default to session cookie if no attributes found
3278 : } else {
3279 170 : return true;
3280 : }
3281 :
3282 : // if this addition overflows, expiryTime will be less than currentTime
3283 : // and the cookie will be expired - that's okay.
3284 15062 : aCookieAttributes.expiryTime = aCurrentTime + delta;
3285 :
3286 15062 : return false;
3287 : }
3288 :
3289 : /******************************************************************************
3290 : * nsCookieService impl:
3291 : * private cookielist management functions
3292 : ******************************************************************************/
3293 :
3294 : void
3295 44 : nsCookieService::RemoveAllFromMemory()
3296 : {
3297 : // clearing the hashtable will call each nsCookieEntry's dtor,
3298 : // which releases all their respective children.
3299 44 : mDBState->hostTable.Clear();
3300 44 : mDBState->cookieCount = 0;
3301 44 : mDBState->cookieOldestTime = LL_MAXINT;
3302 44 : }
3303 :
3304 : // stores temporary data for enumerating over the hash entries,
3305 : // since enumeration is done using callback functions
3306 : struct nsPurgeData
3307 : {
3308 : typedef nsTArray<nsListIter> ArrayType;
3309 :
3310 4 : nsPurgeData(PRInt64 aCurrentTime,
3311 : PRInt64 aPurgeTime,
3312 : ArrayType &aPurgeList,
3313 : nsIMutableArray *aRemovedList,
3314 : mozIStorageBindingParamsArray *aParamsArray)
3315 : : currentTime(aCurrentTime)
3316 : , purgeTime(aPurgeTime)
3317 : , oldestTime(LL_MAXINT)
3318 : , purgeList(aPurgeList)
3319 : , removedList(aRemovedList)
3320 4 : , paramsArray(aParamsArray)
3321 : {
3322 4 : }
3323 :
3324 : // the current time, in seconds
3325 : PRInt64 currentTime;
3326 :
3327 : // lastAccessed time older than which cookies are eligible for purge
3328 : PRInt64 purgeTime;
3329 :
3330 : // lastAccessed time of the oldest cookie found during purge, to update our indicator
3331 : PRInt64 oldestTime;
3332 :
3333 : // list of cookies over the age limit, for purging
3334 : ArrayType &purgeList;
3335 :
3336 : // list of all cookies we've removed, for notification
3337 : nsIMutableArray *removedList;
3338 :
3339 : // The array of parameters to be bound to the statement for deletion later.
3340 : mozIStorageBindingParamsArray *paramsArray;
3341 : };
3342 :
3343 : // comparator class for lastaccessed times of cookies.
3344 : class CompareCookiesByAge {
3345 : public:
3346 422 : bool Equals(const nsListIter &a, const nsListIter &b) const
3347 : {
3348 422 : return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
3349 422 : a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
3350 : }
3351 :
3352 899 : bool LessThan(const nsListIter &a, const nsListIter &b) const
3353 : {
3354 : // compare by lastAccessed time, and tiebreak by creationTime.
3355 899 : PRInt64 result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
3356 899 : if (result != 0)
3357 773 : return result < 0;
3358 :
3359 126 : return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
3360 : }
3361 : };
3362 :
3363 : // comparator class for sorting cookies by entry and index.
3364 : class CompareCookiesByIndex {
3365 : public:
3366 27 : bool Equals(const nsListIter &a, const nsListIter &b) const
3367 : {
3368 27 : NS_ASSERTION(a.entry != b.entry || a.index != b.index,
3369 : "cookie indexes should never be equal");
3370 27 : return false;
3371 : }
3372 :
3373 54 : bool LessThan(const nsListIter &a, const nsListIter &b) const
3374 : {
3375 : // compare by entryclass pointer, then by index.
3376 54 : if (a.entry != b.entry)
3377 54 : return a.entry < b.entry;
3378 :
3379 0 : return a.index < b.index;
3380 : }
3381 : };
3382 :
3383 : PLDHashOperator
3384 440 : purgeCookiesCallback(nsCookieEntry *aEntry,
3385 : void *aArg)
3386 : {
3387 440 : nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
3388 :
3389 440 : const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
3390 440 : mozIStorageBindingParamsArray *array = data.paramsArray;
3391 1320 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
3392 440 : nsListIter iter(aEntry, i);
3393 440 : nsCookie *cookie = cookies[i];
3394 :
3395 : // check if the cookie has expired
3396 440 : if (cookie->Expiry() <= data.currentTime) {
3397 20 : data.removedList->AppendElement(cookie, false);
3398 20 : COOKIE_LOGEVICTED(cookie, "Cookie expired");
3399 :
3400 : // remove from list; do not increment our iterator
3401 20 : gCookieService->RemoveCookieFromList(iter, array);
3402 :
3403 : } else {
3404 : // check if the cookie is over the age limit
3405 420 : if (cookie->LastAccessed() <= data.purgeTime) {
3406 155 : data.purgeList.AppendElement(iter);
3407 :
3408 265 : } else if (cookie->LastAccessed() < data.oldestTime) {
3409 : // reset our indicator
3410 16 : data.oldestTime = cookie->LastAccessed();
3411 : }
3412 :
3413 420 : ++i;
3414 : }
3415 : }
3416 440 : return PL_DHASH_NEXT;
3417 : }
3418 :
3419 : // purges expired and old cookies in a batch operation.
3420 : already_AddRefed<nsIArray>
3421 4 : nsCookieService::PurgeCookies(PRInt64 aCurrentTimeInUsec)
3422 : {
3423 4 : NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
3424 4 : EnsureReadComplete();
3425 :
3426 : #ifdef PR_LOGGING
3427 4 : PRUint32 initialCookieCount = mDBState->cookieCount;
3428 4 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
3429 : ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
3430 : mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
3431 : #endif
3432 :
3433 8 : nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
3434 :
3435 8 : nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
3436 :
3437 : // Create a params array to batch the removals. This is OK here because
3438 : // all the removals are in order, and there are no interleaved additions.
3439 4 : mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
3440 8 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
3441 4 : if (mDBState->dbConn) {
3442 4 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
3443 : }
3444 :
3445 : nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
3446 4 : aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
3447 4 : mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
3448 :
3449 : #ifdef PR_LOGGING
3450 4 : PRUint32 postExpiryCookieCount = mDBState->cookieCount;
3451 : #endif
3452 :
3453 : // now we have a list of iterators for cookies over the age limit.
3454 : // sort them by age, and then we'll see how many to remove...
3455 4 : purgeList.Sort(CompareCookiesByAge());
3456 :
3457 : // only remove old cookies until we reach the max cookie limit, no more.
3458 : PRUint32 excess = mDBState->cookieCount > mMaxNumberOfCookies ?
3459 4 : mDBState->cookieCount - mMaxNumberOfCookies : 0;
3460 4 : if (purgeList.Length() > excess) {
3461 : // We're not purging everything in the list, so update our indicator.
3462 2 : data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
3463 :
3464 2 : purgeList.SetLength(excess);
3465 : }
3466 :
3467 : // sort the list again, this time grouping cookies with a common entryclass
3468 : // together, and with ascending index. this allows us to iterate backwards
3469 : // over the list removing cookies, without having to adjust indexes as we go.
3470 4 : purgeList.Sort(CompareCookiesByIndex());
3471 33 : for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
3472 25 : nsCookie *cookie = purgeList[i].Cookie();
3473 25 : removedList->AppendElement(cookie, false);
3474 25 : COOKIE_LOGEVICTED(cookie, "Cookie too old");
3475 :
3476 25 : RemoveCookieFromList(purgeList[i], paramsArray);
3477 : }
3478 :
3479 : // Update the database if we have entries to purge.
3480 4 : if (paramsArray) {
3481 : PRUint32 length;
3482 4 : paramsArray->GetLength(&length);
3483 4 : if (length) {
3484 4 : nsresult rv = stmt->BindParameters(paramsArray);
3485 4 : NS_ASSERT_SUCCESS(rv);
3486 8 : nsCOMPtr<mozIStoragePendingStatement> handle;
3487 4 : rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
3488 4 : NS_ASSERT_SUCCESS(rv);
3489 : }
3490 : }
3491 :
3492 : // reset the oldest time indicator
3493 4 : mDBState->cookieOldestTime = data.oldestTime;
3494 :
3495 4 : COOKIE_LOGSTRING(PR_LOG_DEBUG,
3496 : ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
3497 : initialCookieCount - postExpiryCookieCount,
3498 : postExpiryCookieCount - mDBState->cookieCount,
3499 : mDBState->cookieCount,
3500 : aCurrentTimeInUsec - mDBState->cookieOldestTime));
3501 :
3502 4 : return removedList.forget();
3503 : }
3504 :
3505 : // find whether a given cookie has been previously set. this is provided by the
3506 : // nsICookieManager2 interface.
3507 : NS_IMETHODIMP
3508 31 : nsCookieService::CookieExists(nsICookie2 *aCookie,
3509 : bool *aFoundCookie)
3510 : {
3511 31 : NS_ENSURE_ARG_POINTER(aCookie);
3512 :
3513 31 : if (!mDBState) {
3514 1 : NS_WARNING("No DBState! Profile already closed?");
3515 1 : return NS_ERROR_NOT_AVAILABLE;
3516 : }
3517 :
3518 60 : nsCAutoString host, name, path;
3519 30 : nsresult rv = aCookie->GetHost(host);
3520 30 : NS_ENSURE_SUCCESS(rv, rv);
3521 30 : rv = aCookie->GetName(name);
3522 30 : NS_ENSURE_SUCCESS(rv, rv);
3523 30 : rv = aCookie->GetPath(path);
3524 30 : NS_ENSURE_SUCCESS(rv, rv);
3525 :
3526 60 : nsCAutoString baseDomain;
3527 30 : rv = GetBaseDomainFromHost(host, baseDomain);
3528 30 : NS_ENSURE_SUCCESS(rv, rv);
3529 :
3530 30 : nsListIter iter;
3531 30 : *aFoundCookie = FindCookie(baseDomain, host, name, path, iter);
3532 30 : return NS_OK;
3533 : }
3534 :
3535 : // For a given base domain, find either an expired cookie or the oldest cookie
3536 : // by lastAccessed time.
3537 : void
3538 71 : nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
3539 : PRInt64 aCurrentTime,
3540 : nsListIter &aIter)
3541 : {
3542 71 : aIter.entry = NULL;
3543 :
3544 : PRInt64 oldestTime;
3545 71 : const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
3546 3620 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
3547 3550 : nsCookie *cookie = cookies[i];
3548 :
3549 : // If we found an expired cookie, we're done.
3550 3550 : if (cookie->Expiry() <= aCurrentTime) {
3551 1 : aIter.entry = aEntry;
3552 1 : aIter.index = i;
3553 1 : return;
3554 : }
3555 :
3556 : // Check if we've found the oldest cookie so far.
3557 3549 : if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
3558 71 : oldestTime = cookie->LastAccessed();
3559 71 : aIter.entry = aEntry;
3560 71 : aIter.index = i;
3561 : }
3562 : }
3563 : }
3564 :
3565 : // count the number of cookies stored by a particular host. this is provided by the
3566 : // nsICookieManager2 interface.
3567 : NS_IMETHODIMP
3568 6014 : nsCookieService::CountCookiesFromHost(const nsACString &aHost,
3569 : PRUint32 *aCountFromHost)
3570 : {
3571 6014 : if (!mDBState) {
3572 1 : NS_WARNING("No DBState! Profile already closed?");
3573 1 : return NS_ERROR_NOT_AVAILABLE;
3574 : }
3575 :
3576 : // first, normalize the hostname, and fail if it contains illegal characters.
3577 12026 : nsCAutoString host(aHost);
3578 6013 : nsresult rv = NormalizeHost(host);
3579 6013 : NS_ENSURE_SUCCESS(rv, rv);
3580 :
3581 12026 : nsCAutoString baseDomain;
3582 6013 : rv = GetBaseDomainFromHost(host, baseDomain);
3583 6013 : NS_ENSURE_SUCCESS(rv, rv);
3584 :
3585 6001 : EnsureReadDomain(baseDomain);
3586 :
3587 : // Return a count of all cookies, including expired.
3588 6001 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
3589 6001 : *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
3590 6001 : return NS_OK;
3591 : }
3592 :
3593 : // get an enumerator of cookies stored by a particular host. this is provided by the
3594 : // nsICookieManager2 interface.
3595 : NS_IMETHODIMP
3596 100 : nsCookieService::GetCookiesFromHost(const nsACString &aHost,
3597 : nsISimpleEnumerator **aEnumerator)
3598 : {
3599 100 : if (!mDBState) {
3600 1 : NS_WARNING("No DBState! Profile already closed?");
3601 1 : return NS_ERROR_NOT_AVAILABLE;
3602 : }
3603 :
3604 : // first, normalize the hostname, and fail if it contains illegal characters.
3605 198 : nsCAutoString host(aHost);
3606 99 : nsresult rv = NormalizeHost(host);
3607 99 : NS_ENSURE_SUCCESS(rv, rv);
3608 :
3609 198 : nsCAutoString baseDomain;
3610 99 : rv = GetBaseDomainFromHost(host, baseDomain);
3611 99 : NS_ENSURE_SUCCESS(rv, rv);
3612 :
3613 94 : EnsureReadDomain(baseDomain);
3614 :
3615 94 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
3616 94 : if (!entry)
3617 47 : return NS_NewEmptyEnumerator(aEnumerator);
3618 :
3619 94 : nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
3620 47 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
3621 447 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
3622 400 : cookieList.AppendObject(cookies[i]);
3623 : }
3624 :
3625 47 : return NS_NewArrayEnumerator(aEnumerator, cookieList);
3626 : }
3627 :
3628 : // find an exact cookie specified by host, name, and path that hasn't expired.
3629 : bool
3630 16469 : nsCookieService::FindCookie(const nsCString &aBaseDomain,
3631 : const nsAFlatCString &aHost,
3632 : const nsAFlatCString &aName,
3633 : const nsAFlatCString &aPath,
3634 : nsListIter &aIter)
3635 : {
3636 16469 : EnsureReadDomain(aBaseDomain);
3637 :
3638 16469 : nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
3639 16469 : if (!entry)
3640 12784 : return false;
3641 :
3642 3685 : const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
3643 4483308 : for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
3644 4479887 : nsCookie *cookie = cookies[i];
3645 :
3646 26873818 : if (aHost.Equals(cookie->Host()) &&
3647 13436937 : aPath.Equals(cookie->Path()) &&
3648 13436881 : aName.Equals(cookie->Name())) {
3649 264 : aIter = nsListIter(entry, i);
3650 264 : return true;
3651 : }
3652 : }
3653 :
3654 3421 : return false;
3655 : }
3656 :
3657 : // remove a cookie from the hashtable, and update the iterator state.
3658 : void
3659 364 : nsCookieService::RemoveCookieFromList(const nsListIter &aIter,
3660 : mozIStorageBindingParamsArray *aParamsArray)
3661 : {
3662 : // if it's a non-session cookie, remove it from the db
3663 364 : if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
3664 : // Use the asynchronous binding methods to ensure that we do not acquire
3665 : // the database lock.
3666 249 : mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
3667 498 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
3668 249 : if (!paramsArray) {
3669 204 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
3670 : }
3671 :
3672 498 : nsCOMPtr<mozIStorageBindingParams> params;
3673 249 : paramsArray->NewBindingParams(getter_AddRefs(params));
3674 :
3675 498 : nsresult rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
3676 498 : aIter.Cookie()->Name());
3677 249 : NS_ASSERT_SUCCESS(rv);
3678 :
3679 498 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
3680 498 : aIter.Cookie()->Host());
3681 249 : NS_ASSERT_SUCCESS(rv);
3682 :
3683 498 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
3684 498 : aIter.Cookie()->Path());
3685 249 : NS_ASSERT_SUCCESS(rv);
3686 :
3687 249 : rv = paramsArray->AddParams(params);
3688 249 : NS_ASSERT_SUCCESS(rv);
3689 :
3690 : // If we weren't given a params array, we'll need to remove it ourselves.
3691 249 : if (!aParamsArray) {
3692 204 : rv = stmt->BindParameters(paramsArray);
3693 204 : NS_ASSERT_SUCCESS(rv);
3694 408 : nsCOMPtr<mozIStoragePendingStatement> handle;
3695 204 : rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
3696 204 : NS_ASSERT_SUCCESS(rv);
3697 : }
3698 : }
3699 :
3700 364 : if (aIter.entry->GetCookies().Length() == 1) {
3701 : // we're removing the last element in the array - so just remove the entry
3702 : // from the hash. note that the entryclass' dtor will take care of
3703 : // releasing this last element for us!
3704 275 : mDBState->hostTable.RawRemoveEntry(aIter.entry);
3705 :
3706 : } else {
3707 : // just remove the element from the list
3708 89 : aIter.entry->GetCookies().RemoveElementAt(aIter.index);
3709 : }
3710 :
3711 364 : --mDBState->cookieCount;
3712 364 : }
3713 :
3714 : void
3715 15713 : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
3716 : const nsCString &aBaseDomain,
3717 : const nsCookie *aCookie)
3718 : {
3719 15713 : NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
3720 15713 : NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
3721 : nsresult rv;
3722 :
3723 : // Use the asynchronous binding methods to ensure that we do not acquire the
3724 : // database lock.
3725 31426 : nsCOMPtr<mozIStorageBindingParams> params;
3726 15713 : rv = aParamsArray->NewBindingParams(getter_AddRefs(params));
3727 15713 : NS_ASSERT_SUCCESS(rv);
3728 :
3729 : // Bind our values to params
3730 31426 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
3731 15713 : aBaseDomain);
3732 15713 : NS_ASSERT_SUCCESS(rv);
3733 :
3734 31426 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
3735 31426 : aCookie->Name());
3736 15713 : NS_ASSERT_SUCCESS(rv);
3737 :
3738 31426 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
3739 31426 : aCookie->Value());
3740 15713 : NS_ASSERT_SUCCESS(rv);
3741 :
3742 31426 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
3743 31426 : aCookie->Host());
3744 15713 : NS_ASSERT_SUCCESS(rv);
3745 :
3746 31426 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
3747 31426 : aCookie->Path());
3748 15713 : NS_ASSERT_SUCCESS(rv);
3749 :
3750 31426 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
3751 15713 : aCookie->Expiry());
3752 15713 : NS_ASSERT_SUCCESS(rv);
3753 :
3754 31426 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
3755 15713 : aCookie->LastAccessed());
3756 15713 : NS_ASSERT_SUCCESS(rv);
3757 :
3758 31426 : rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
3759 15713 : aCookie->CreationTime());
3760 15713 : NS_ASSERT_SUCCESS(rv);
3761 :
3762 31426 : rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
3763 15713 : aCookie->IsSecure());
3764 15713 : NS_ASSERT_SUCCESS(rv);
3765 :
3766 31426 : rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
3767 15713 : aCookie->IsHttpOnly());
3768 15713 : NS_ASSERT_SUCCESS(rv);
3769 :
3770 : // Bind the params to the array.
3771 15713 : rv = aParamsArray->AddParams(params);
3772 15713 : NS_ASSERT_SUCCESS(rv);
3773 15713 : }
3774 :
3775 : void
3776 28728 : nsCookieService::AddCookieToList(const nsCString &aBaseDomain,
3777 : nsCookie *aCookie,
3778 : DBState *aDBState,
3779 : mozIStorageBindingParamsArray *aParamsArray,
3780 : bool aWriteToDB)
3781 : {
3782 28728 : NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
3783 : "Not writing to the DB but have a params array?");
3784 28728 : NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
3785 : "Do not have a DB connection but have a params array?");
3786 :
3787 28728 : nsCookieEntry *entry = aDBState->hostTable.PutEntry(aBaseDomain);
3788 28728 : NS_ASSERTION(entry, "can't insert element into a null entry!");
3789 :
3790 28728 : entry->GetCookies().AppendElement(aCookie);
3791 28728 : ++aDBState->cookieCount;
3792 :
3793 : // keep track of the oldest cookie, for when it comes time to purge
3794 28728 : if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
3795 191 : aDBState->cookieOldestTime = aCookie->LastAccessed();
3796 :
3797 : // if it's a non-session cookie and hasn't just been read from the db, write it out.
3798 28728 : if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
3799 15696 : mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
3800 31392 : nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
3801 15696 : if (!paramsArray) {
3802 15696 : stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
3803 : }
3804 15696 : bindCookieParameters(paramsArray, aBaseDomain, aCookie);
3805 :
3806 : // If we were supplied an array to store parameters, we shouldn't call
3807 : // executeAsync - someone up the stack will do this for us.
3808 15696 : if (!aParamsArray) {
3809 15696 : nsresult rv = stmt->BindParameters(paramsArray);
3810 15696 : NS_ASSERT_SUCCESS(rv);
3811 31392 : nsCOMPtr<mozIStoragePendingStatement> handle;
3812 15696 : rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
3813 15696 : NS_ASSERT_SUCCESS(rv);
3814 : }
3815 : }
3816 28728 : }
3817 :
3818 : void
3819 0 : nsCookieService::UpdateCookieInList(nsCookie *aCookie,
3820 : PRInt64 aLastAccessed,
3821 : mozIStorageBindingParamsArray *aParamsArray)
3822 : {
3823 0 : NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
3824 :
3825 : // udpate the lastAccessed timestamp
3826 0 : aCookie->SetLastAccessed(aLastAccessed);
3827 :
3828 : // if it's a non-session cookie, update it in the db too
3829 0 : if (!aCookie->IsSession() && aParamsArray) {
3830 : // Create our params holder.
3831 0 : nsCOMPtr<mozIStorageBindingParams> params;
3832 0 : aParamsArray->NewBindingParams(getter_AddRefs(params));
3833 :
3834 : // Bind our parameters.
3835 0 : nsresult rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
3836 0 : aLastAccessed);
3837 0 : NS_ASSERT_SUCCESS(rv);
3838 :
3839 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
3840 0 : aCookie->Name());
3841 0 : NS_ASSERT_SUCCESS(rv);
3842 :
3843 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
3844 0 : aCookie->Host());
3845 0 : NS_ASSERT_SUCCESS(rv);
3846 :
3847 0 : rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
3848 0 : aCookie->Path());
3849 0 : NS_ASSERT_SUCCESS(rv);
3850 :
3851 : // Add our bound parameters to the array.
3852 0 : rv = aParamsArray->AddParams(params);
3853 0 : NS_ASSERT_SUCCESS(rv);
3854 : }
3855 4392 : }
3856 :
|