Investigating leaks in bug 463263, backout bug 453403.
[wine-gecko.git] / netwerk / cookie / src / nsCookieService.cpp
blob3b880c49a98ba595677c038cb54a484d436ceeb0
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
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/
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.
15 * The Original Code is mozilla.org code.
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.
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>
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.
40 * ***** END LICENSE BLOCK ***** */
42 #include "nsCookieService.h"
43 #include "nsIServiceManager.h"
45 #include "nsIIOService.h"
46 #include "nsIPrefBranch.h"
47 #include "nsIPrefBranch2.h"
48 #include "nsIPrefService.h"
49 #include "nsICookiePermission.h"
50 #include "nsIURI.h"
51 #include "nsIURL.h"
52 #include "nsIChannel.h"
53 #include "nsIFile.h"
54 #include "nsIObserverService.h"
55 #include "nsILineInputStream.h"
56 #include "nsIEffectiveTLDService.h"
58 #include "nsCOMArray.h"
59 #include "nsArrayEnumerator.h"
60 #include "nsAutoPtr.h"
61 #include "nsReadableUtils.h"
62 #include "nsCRT.h"
63 #include "prtime.h"
64 #include "prprf.h"
65 #include "nsNetUtil.h"
66 #include "nsNetCID.h"
67 #include "nsAppDirectoryServiceDefs.h"
68 #include "mozIStorageService.h"
69 #include "mozIStorageStatement.h"
70 #include "mozIStorageConnection.h"
71 #include "mozStorageHelper.h"
72 #include "nsIPrivateBrowsingService.h"
73 #include "nsNetCID.h"
75 /******************************************************************************
76 * nsCookieService impl:
77 * useful types & constants
78 ******************************************************************************/
80 // XXX_hack. See bug 178993.
81 // This is a hack to hide HttpOnly cookies from older browsers
83 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
85 static const char kCookieFileName[] = "cookies.sqlite";
86 #define COOKIES_SCHEMA_VERSION 2
88 static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
90 static const char kOldCookieFileName[] = "cookies.txt";
92 #undef LIMIT
93 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
95 // default limits for the cookie list. these can be tuned by the
96 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
97 static const PRUint32 kMaxNumberOfCookies = 1000;
98 static const PRUint32 kMaxCookiesPerHost = 50;
99 static const PRUint32 kMaxBytesPerCookie = 4096;
100 static const PRUint32 kMaxBytesPerPath = 1024;
102 // these constants represent a decision about a cookie based on user prefs.
103 static const PRUint32 STATUS_ACCEPTED = 0;
104 static const PRUint32 STATUS_REJECTED = 1;
105 // STATUS_REJECTED_WITH_ERROR indicates the cookie should be rejected because
106 // of an error (rather than something the user can control). this is used for
107 // notification purposes, since we only want to notify of rejections where
108 // the user can do something about it (e.g. whitelist the site).
109 static const PRUint32 STATUS_REJECTED_WITH_ERROR = 2;
111 // behavior pref constants
112 static const PRUint32 BEHAVIOR_ACCEPT = 0;
113 static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1;
114 static const PRUint32 BEHAVIOR_REJECT = 2;
116 // pref string constants
117 static const char kPrefCookiesPermissions[] = "network.cookie.cookieBehavior";
118 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
119 static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
121 // struct for temporarily storing cookie attributes during header parsing
122 struct nsCookieAttributes
124 nsCAutoString name;
125 nsCAutoString value;
126 nsCAutoString host;
127 nsCAutoString path;
128 nsCAutoString expires;
129 nsCAutoString maxage;
130 PRInt64 expiryTime;
131 PRBool isSession;
132 PRBool isSecure;
133 PRBool isHttpOnly;
136 // stores linked list iteration state, and provides a rudimentary
137 // list traversal method
138 struct nsListIter
140 nsListIter() {}
142 nsListIter(nsCookieEntry *aEntry)
143 : entry(aEntry)
144 , prev(nsnull)
145 , current(aEntry ? aEntry->Head() : nsnull) {}
147 nsListIter(nsCookieEntry *aEntry,
148 nsCookie *aPrev,
149 nsCookie *aCurrent)
150 : entry(aEntry)
151 , prev(aPrev)
152 , current(aCurrent) {}
154 nsListIter& operator++() { prev = current; current = current->Next(); return *this; }
156 nsCookieEntry *entry;
157 nsCookie *prev;
158 nsCookie *current;
161 // stores temporary data for enumerating over the hash entries,
162 // since enumeration is done using callback functions
163 struct nsEnumerationData
165 nsEnumerationData(PRInt64 aCurrentTime,
166 PRInt64 aOldestTime)
167 : currentTime(aCurrentTime)
168 , oldestTime(aOldestTime)
169 , iter(nsnull, nsnull, nsnull) {}
171 // the current time, in seconds
172 PRInt64 currentTime;
174 // oldest lastAccessed time in the cookie list. use aOldestTime = LL_MAXINT
175 // to enable this search, LL_MININT to disable it.
176 PRInt64 oldestTime;
178 // an iterator object that points to the desired cookie
179 nsListIter iter;
182 /******************************************************************************
183 * Cookie logging handlers
184 * used for logging in nsCookieService
185 ******************************************************************************/
187 // logging handlers
188 #ifdef MOZ_LOGGING
189 // in order to do logging, the following environment variables need to be set:
191 // set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
192 // set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
193 // set NSPR_LOG_FILE=cookie.log
195 // this next define has to appear before the include of prlog.h
196 #define FORCE_PR_LOG // Allow logging in the release build
197 #include "prlog.h"
198 #endif
200 // define logging macros for convenience
201 #define SET_COOKIE PR_TRUE
202 #define GET_COOKIE PR_FALSE
204 #ifdef PR_LOGGING
205 static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie");
207 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
208 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
209 #define COOKIE_LOGEVICTED(a) LogEvicted(a)
210 #define COOKIE_LOGSTRING(lvl, fmt) \
211 PR_BEGIN_MACRO \
212 PR_LOG(sCookieLog, lvl, fmt); \
213 PR_LOG(sCookieLog, lvl, ("\n")); \
214 PR_END_MACRO
216 static void
217 LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
219 // if logging isn't enabled, return now to save cycles
220 if (!PR_LOG_TEST(sCookieLog, PR_LOG_WARNING))
221 return;
223 nsCAutoString spec;
224 if (aHostURI)
225 aHostURI->GetAsciiSpec(spec);
227 PR_LOG(sCookieLog, PR_LOG_WARNING,
228 ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
229 PR_LOG(sCookieLog, PR_LOG_WARNING,("request URL: %s\n", spec.get()));
230 if (aSetCookie)
231 PR_LOG(sCookieLog, PR_LOG_WARNING,("cookie string: %s\n", aCookieString));
233 PRExplodedTime explodedTime;
234 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
235 char timeString[40];
236 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
238 PR_LOG(sCookieLog, PR_LOG_WARNING,("current time: %s", timeString));
239 PR_LOG(sCookieLog, PR_LOG_WARNING,("rejected because %s\n", aReason));
240 PR_LOG(sCookieLog, PR_LOG_WARNING,("\n"));
243 static void
244 LogCookie(nsCookie *aCookie)
246 PRExplodedTime explodedTime;
247 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
248 char timeString[40];
249 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
251 PR_LOG(sCookieLog, PR_LOG_DEBUG,("current time: %s", timeString));
253 if (aCookie) {
254 PR_LOG(sCookieLog, PR_LOG_DEBUG,("----------------\n"));
255 PR_LOG(sCookieLog, PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get()));
256 PR_LOG(sCookieLog, PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get()));
257 PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
258 PR_LOG(sCookieLog, PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get()));
260 PR_ExplodeTime(aCookie->Expiry() * PR_USEC_PER_SEC, PR_GMTParameters, &explodedTime);
261 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
262 PR_LOG(sCookieLog, PR_LOG_DEBUG,
263 ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
265 PR_ExplodeTime(aCookie->CreationID(), PR_GMTParameters, &explodedTime);
266 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
267 PR_LOG(sCookieLog, PR_LOG_DEBUG,
268 ("created: %s (id %lld)", timeString, aCookie->CreationID()));
270 PR_LOG(sCookieLog, PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
271 PR_LOG(sCookieLog, PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
275 static void
276 LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, PRBool aReplacing)
278 // if logging isn't enabled, return now to save cycles
279 if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
280 return;
283 nsCAutoString spec;
284 if (aHostURI)
285 aHostURI->GetAsciiSpec(spec);
287 PR_LOG(sCookieLog, PR_LOG_DEBUG,
288 ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
289 PR_LOG(sCookieLog, PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
290 PR_LOG(sCookieLog, PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
291 if (aSetCookie)
292 PR_LOG(sCookieLog, PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
294 LogCookie(aCookie);
296 PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
299 static void
300 LogEvicted(nsCookie *aCookie)
302 // if logging isn't enabled, return now to save cycles
303 if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
304 return;
307 PR_LOG(sCookieLog, PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
309 LogCookie(aCookie);
311 PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
314 // inline wrappers to make passing in nsAFlatCStrings easier
315 static inline void
316 LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
318 LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
321 static inline void
322 LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, PRBool aReplacing)
324 LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
327 #else
328 #define COOKIE_LOGFAILURE(a, b, c, d) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
329 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
330 #define COOKIE_LOGEVICTED(a) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
331 #define COOKIE_LOGSTRING(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
332 #endif
334 /******************************************************************************
335 * nsCookieService impl:
336 * private list sorting callbacks
338 * these functions return:
339 * < 0 if the first element should come before the second element,
340 * 0 if the first element may come before or after the second element,
341 * > 0 if the first element should come after the second element.
342 ******************************************************************************/
344 // comparison function for sorting cookies before sending to a server.
345 static int
346 compareCookiesForSending(const void *aElement1,
347 const void *aElement2,
348 void *aData)
350 const nsCookie *cookie1 = static_cast<const nsCookie*>(aElement1);
351 const nsCookie *cookie2 = static_cast<const nsCookie*>(aElement2);
353 // compare by cookie path length in accordance with RFC2109
354 int rv = cookie2->Path().Length() - cookie1->Path().Length();
355 if (rv == 0) {
356 // when path lengths match, older cookies should be listed first. this is
357 // required for backwards compatibility since some websites erroneously
358 // depend on receiving cookies in the order in which they were sent to the
359 // browser! see bug 236772.
360 // note: CreationID is unique, so two id's can never be equal.
361 // we may have overflow problems returning the result directly, so we need branches
362 rv = (cookie1->CreationID() > cookie2->CreationID() ? 1 : -1);
364 return rv;
367 /******************************************************************************
368 * nsCookieService impl:
369 * singleton instance ctor/dtor methods
370 ******************************************************************************/
372 nsCookieService *nsCookieService::gCookieService = nsnull;
374 nsCookieService*
375 nsCookieService::GetSingleton()
377 if (gCookieService) {
378 NS_ADDREF(gCookieService);
379 return gCookieService;
382 // Create a new singleton nsCookieService.
383 // We AddRef only once since XPCOM has rules about the ordering of module
384 // teardowns - by the time our module destructor is called, it's too late to
385 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
386 // cycles have already been completed and would result in serious leaks.
387 // See bug 209571.
388 gCookieService = new nsCookieService();
389 if (gCookieService) {
390 NS_ADDREF(gCookieService);
391 if (NS_FAILED(gCookieService->Init())) {
392 NS_RELEASE(gCookieService);
396 return gCookieService;
399 /******************************************************************************
400 * nsCookieService impl:
401 * public methods
402 ******************************************************************************/
404 NS_IMPL_ISUPPORTS5(nsCookieService,
405 nsICookieService,
406 nsICookieManager,
407 nsICookieManager2,
408 nsIObserver,
409 nsISupportsWeakReference)
411 nsCookieService::nsCookieService()
412 : mCookieCount(0)
413 , mCookiesPermissions(BEHAVIOR_ACCEPT)
414 , mMaxNumberOfCookies(kMaxNumberOfCookies)
415 , mMaxCookiesPerHost(kMaxCookiesPerHost)
416 , mHostTable(&mDefaultHostTable)
420 nsresult
421 nsCookieService::Init()
423 if (!mHostTable->Init()) {
424 return NS_ERROR_OUT_OF_MEMORY;
427 nsresult rv;
428 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
429 NS_ENSURE_SUCCESS(rv, rv);
431 // init our pref and observer
432 nsCOMPtr<nsIPrefBranch2> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
433 if (prefBranch) {
434 prefBranch->AddObserver(kPrefCookiesPermissions, this, PR_TRUE);
435 prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, PR_TRUE);
436 prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, PR_TRUE);
437 PrefChanged(prefBranch);
440 // ignore failure here, since it's non-fatal (we can run fine without
441 // persistent storage - e.g. if there's no profile)
442 rv = InitDB();
443 if (NS_FAILED(rv))
444 COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): InitDB() gave error %x", rv));
446 mObserverService = do_GetService("@mozilla.org/observer-service;1");
447 if (mObserverService) {
448 mObserverService->AddObserver(this, "profile-before-change", PR_TRUE);
449 mObserverService->AddObserver(this, "profile-do-change", PR_TRUE);
450 mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, PR_TRUE);
452 nsCOMPtr<nsIPrivateBrowsingService> pbs =
453 do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
454 if (pbs) {
455 PRBool inPrivateBrowsing = PR_FALSE;
456 pbs->GetPrivateBrowsingEnabled(&inPrivateBrowsing);
457 if (inPrivateBrowsing) {
458 Observe(nsnull, NS_PRIVATE_BROWSING_SWITCH_TOPIC, NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).get());
463 mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
464 if (!mPermissionService) {
465 NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
466 COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
469 return NS_OK;
472 nsresult
473 nsCookieService::InitDB()
475 nsCOMPtr<nsIFile> cookieFile;
476 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(cookieFile));
477 if (NS_FAILED(rv)) return rv;
479 cookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName));
481 nsCOMPtr<mozIStorageService> storage = do_GetService("@mozilla.org/storage/service;1");
482 if (!storage)
483 return NS_ERROR_UNEXPECTED;
485 // cache a connection to the cookie database
486 rv = storage->OpenUnsharedDatabase(cookieFile, getter_AddRefs(mDBConn));
487 if (rv == NS_ERROR_FILE_CORRUPTED) {
488 // delete and try again
489 rv = cookieFile->Remove(PR_FALSE);
490 NS_ENSURE_SUCCESS(rv, rv);
492 rv = storage->OpenUnsharedDatabase(cookieFile, getter_AddRefs(mDBConn));
494 NS_ENSURE_SUCCESS(rv, rv);
496 PRBool tableExists = PR_FALSE;
497 mDBConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"), &tableExists);
498 if (!tableExists) {
499 rv = CreateTable();
500 NS_ENSURE_SUCCESS(rv, rv);
502 } else {
503 // table already exists; check the schema version before reading
504 PRInt32 dbSchemaVersion;
505 rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
506 NS_ENSURE_SUCCESS(rv, rv);
508 switch (dbSchemaVersion) {
509 // upgrading.
510 // every time you increment the database schema, you need to implement
511 // the upgrading code from the previous version to the new one.
512 case 1:
514 // add the lastAccessed column to the table
515 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
516 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
517 NS_ENSURE_SUCCESS(rv, rv);
519 // update the schema version
520 rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
521 NS_ENSURE_SUCCESS(rv, rv);
523 // fall through to the next upgrade
525 case COOKIES_SCHEMA_VERSION:
526 break;
528 case 0:
530 NS_WARNING("couldn't get schema version!");
532 // the table may be usable; someone might've just clobbered the schema
533 // version. we can treat this case like a downgrade using the codepath
534 // below, by verifying the columns we care about are all there. for now,
535 // re-set the schema version in the db, in case the checks succeed (if
536 // they don't, we're dropping the table anyway).
537 rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
538 NS_ENSURE_SUCCESS(rv, rv);
540 // fall through to downgrade check
542 // downgrading.
543 // if columns have been added to the table, we can still use the ones we
544 // understand safely. if columns have been deleted or altered, just
545 // blow away the table and start from scratch! if you change the way
546 // a column is interpreted, make sure you also change its name so this
547 // check will catch it.
548 default:
550 // check if all the expected columns exist
551 nsCOMPtr<mozIStorageStatement> stmt;
552 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
553 "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly "
554 "FROM moz_cookies"), getter_AddRefs(stmt));
555 if (NS_SUCCEEDED(rv))
556 break;
558 // our columns aren't there - drop the table!
559 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
560 NS_ENSURE_SUCCESS(rv, rv);
562 rv = CreateTable();
563 NS_ENSURE_SUCCESS(rv, rv);
565 break;
569 // make operations on the table asynchronous, for performance
570 mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
572 // open in exclusive mode for performance
573 mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA locking_mode = EXCLUSIVE"));
575 // cache frequently used statements (for insertion, deletion, and updating)
576 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
577 "INSERT INTO moz_cookies "
578 "(id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly) "
579 "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert));
580 NS_ENSURE_SUCCESS(rv, rv);
582 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
583 "DELETE FROM moz_cookies WHERE id = ?1"), getter_AddRefs(mStmtDelete));
584 NS_ENSURE_SUCCESS(rv, rv);
586 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
587 "UPDATE moz_cookies SET lastAccessed = ?1 WHERE id = ?2"), getter_AddRefs(mStmtUpdate));
588 NS_ENSURE_SUCCESS(rv, rv);
590 // check whether to import or just read in the db
591 if (tableExists)
592 return Read();
594 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(cookieFile));
595 if (NS_FAILED(rv)) return rv;
597 cookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
598 rv = ImportCookies(cookieFile);
599 if (NS_FAILED(rv)) return rv;
601 // we're done importing - delete the old cookie file
602 cookieFile->Remove(PR_FALSE);
603 return NS_OK;
606 // sets the schema version and creates the moz_cookies table.
607 nsresult
608 nsCookieService::CreateTable()
610 // set the schema version, before creating the table
611 nsresult rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
612 if (NS_FAILED(rv)) return rv;
614 // create the table
615 return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
616 "CREATE TABLE moz_cookies ("
617 "id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,"
618 "expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"));
621 nsCookieService::~nsCookieService()
623 gCookieService = nsnull;
626 NS_IMETHODIMP
627 nsCookieService::Observe(nsISupports *aSubject,
628 const char *aTopic,
629 const PRUnichar *aData)
631 // check the topic
632 if (!strcmp(aTopic, "profile-before-change")) {
633 // The profile is about to change,
634 // or is going away because the application is shutting down.
635 RemoveAllFromMemory();
637 if (mDBConn) {
638 if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
639 // clear the cookie file
640 nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
641 if (NS_FAILED(rv))
642 NS_WARNING("db delete failed");
645 // Close the DB connection before changing
646 mDBConn->Close();
647 mDBConn = nsnull;
650 } else if (!strcmp(aTopic, "profile-do-change")) {
651 // the profile has already changed; init the db from the new location
652 InitDB();
654 } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
655 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
656 if (prefBranch)
657 PrefChanged(prefBranch);
658 } else if (!strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC)) {
659 if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
660 // backup the existing in-memory DB
661 if (mPrivateHostTable.IsInitialized() || mPrivateHostTable.Init()) {
662 mHostTable = &mPrivateHostTable;
663 mCookieCount = mHostTable->Count();
664 NotifyChanged(nsnull, NS_LITERAL_STRING("reload").get());
666 // close the connection to the on-disk DB
667 mStmtInsert = nsnull;
668 mStmtDelete = nsnull;
669 mStmtUpdate = nsnull;
670 mDBConn = nsnull;
671 // continue to use the in-memory DB
672 } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
673 // open the connection to the on-disk DB
674 InitDB();
675 // restore the in-memory DB as it was prior to private browsing
676 if (mPrivateHostTable.IsInitialized())
677 mPrivateHostTable.Clear();
678 mHostTable = &mDefaultHostTable;
679 mCookieCount = mHostTable->Count();
680 NotifyChanged(nsnull, NS_LITERAL_STRING("reload").get());
681 // continue to use both on-disk and in-memory DB
685 return NS_OK;
688 NS_IMETHODIMP
689 nsCookieService::GetCookieString(nsIURI *aHostURI,
690 nsIChannel *aChannel,
691 char **aCookie)
693 GetCookieInternal(aHostURI, aChannel, PR_FALSE, aCookie);
695 return NS_OK;
698 NS_IMETHODIMP
699 nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI,
700 nsIURI *aFirstURI,
701 nsIChannel *aChannel,
702 char **aCookie)
704 GetCookieInternal(aHostURI, aChannel, PR_TRUE, aCookie);
706 return NS_OK;
709 NS_IMETHODIMP
710 nsCookieService::SetCookieString(nsIURI *aHostURI,
711 nsIPrompt *aPrompt,
712 const char *aCookieHeader,
713 nsIChannel *aChannel)
715 return SetCookieStringInternal(aHostURI, aPrompt, aCookieHeader, nsnull, aChannel, PR_FALSE);
718 NS_IMETHODIMP
719 nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
720 nsIURI *aFirstURI,
721 nsIPrompt *aPrompt,
722 const char *aCookieHeader,
723 const char *aServerTime,
724 nsIChannel *aChannel)
726 return SetCookieStringInternal(aHostURI, aPrompt, aCookieHeader, aServerTime, aChannel, PR_TRUE);
729 nsresult
730 nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
731 nsIPrompt *aPrompt,
732 const char *aCookieHeader,
733 const char *aServerTime,
734 nsIChannel *aChannel,
735 PRBool aFromHttp)
737 if (!aHostURI) {
738 COOKIE_LOGFAILURE(SET_COOKIE, nsnull, aCookieHeader, "host URI is null");
739 return NS_OK;
742 // check default prefs
743 PRUint32 cookieStatus = CheckPrefs(aHostURI, aChannel, aCookieHeader);
744 // fire a notification if cookie was rejected (but not if there was an error)
745 switch (cookieStatus) {
746 case STATUS_REJECTED:
747 NotifyRejected(aHostURI);
748 case STATUS_REJECTED_WITH_ERROR:
749 return NS_OK;
752 // parse server local time. this is not just done here for efficiency
753 // reasons - if there's an error parsing it, and we need to default it
754 // to the current time, we must do it here since the current time in
755 // SetCookieInternal() will change for each cookie processed (e.g. if the
756 // user is prompted).
757 PRTime tempServerTime;
758 PRInt64 serverTime;
759 if (aServerTime && PR_ParseTimeString(aServerTime, PR_TRUE, &tempServerTime) == PR_SUCCESS) {
760 serverTime = tempServerTime / PR_USEC_PER_SEC;
761 } else {
762 serverTime = PR_Now() / PR_USEC_PER_SEC;
765 // start a transaction on the storage db, to optimize insertions.
766 // transaction will automically commit on completion
767 mozStorageTransaction transaction(mDBConn, PR_TRUE);
769 // switch to a nice string type now, and process each cookie in the header
770 nsDependentCString cookieHeader(aCookieHeader);
771 while (SetCookieInternal(aHostURI, aChannel, cookieHeader, serverTime, aFromHttp));
773 return NS_OK;
776 // notify observers that a cookie was rejected due to the users' prefs.
777 void
778 nsCookieService::NotifyRejected(nsIURI *aHostURI)
780 if (mObserverService)
781 mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nsnull);
784 // notify observers that the cookie list changed. there are four possible
785 // values for aData:
786 // "deleted" means a cookie was deleted. aCookie is the deleted cookie.
787 // "added" means a cookie was added. aCookie is the added cookie.
788 // "changed" means a cookie was altered. aCookie is the new cookie.
789 // "cleared" means the entire cookie list was cleared. aCookie is null.
790 void
791 nsCookieService::NotifyChanged(nsICookie2 *aCookie,
792 const PRUnichar *aData)
794 if (mObserverService)
795 mObserverService->NotifyObservers(aCookie, "cookie-changed", aData);
798 /******************************************************************************
799 * nsCookieService:
800 * pref observer impl
801 ******************************************************************************/
803 void
804 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
806 PRInt32 val;
807 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiesPermissions, &val)))
808 mCookiesPermissions = (PRUint8) LIMIT(val, 0, 2, 0);
810 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
811 mMaxNumberOfCookies = (PRUint16) LIMIT(val, 0, 0xFFFF, 0xFFFF);
813 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
814 mMaxCookiesPerHost = (PRUint16) LIMIT(val, 0, 0xFFFF, 0xFFFF);
817 /******************************************************************************
818 * nsICookieManager impl:
819 * nsICookieManager
820 ******************************************************************************/
822 NS_IMETHODIMP
823 nsCookieService::RemoveAll()
825 RemoveAllFromMemory();
827 // clear the cookie file
828 if (mDBConn) {
829 nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
830 if (NS_FAILED(rv)) {
831 // Database must be corrupted, so remove it completely.
832 nsCOMPtr<nsIFile> dbFile;
833 mDBConn->GetDatabaseFile(getter_AddRefs(dbFile));
834 mDBConn->Close();
835 dbFile->Remove(PR_FALSE);
836 InitDB();
840 NotifyChanged(nsnull, NS_LITERAL_STRING("cleared").get());
841 return NS_OK;
844 // helper struct for passing arguments into hash enumeration callback.
845 struct nsGetEnumeratorData
847 nsGetEnumeratorData(nsCOMArray<nsICookie> *aArray, PRInt64 aTime)
848 : array(aArray)
849 , currentTime(aTime) {}
851 nsCOMArray<nsICookie> *array;
852 PRInt64 currentTime;
855 static PLDHashOperator
856 COMArrayCallback(nsCookieEntry *aEntry,
857 void *aArg)
859 nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(aArg);
861 for (nsCookie *cookie = aEntry->Head(); cookie; cookie = cookie->Next()) {
862 // only append non-expired cookies
863 if (cookie->Expiry() > data->currentTime)
864 data->array->AppendObject(cookie);
866 return PL_DHASH_NEXT;
869 NS_IMETHODIMP
870 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
872 nsCOMArray<nsICookie> cookieList(mCookieCount);
873 nsGetEnumeratorData data(&cookieList, PR_Now() / PR_USEC_PER_SEC);
875 mHostTable->EnumerateEntries(COMArrayCallback, &data);
877 return NS_NewArrayEnumerator(aEnumerator, cookieList);
880 NS_IMETHODIMP
881 nsCookieService::Add(const nsACString &aDomain,
882 const nsACString &aPath,
883 const nsACString &aName,
884 const nsACString &aValue,
885 PRBool aIsSecure,
886 PRBool aIsHttpOnly,
887 PRBool aIsSession,
888 PRInt64 aExpiry)
890 PRInt64 currentTimeInUsec = PR_Now();
892 nsRefPtr<nsCookie> cookie =
893 nsCookie::Create(aName, aValue, aDomain, aPath,
894 aExpiry,
895 currentTimeInUsec,
896 currentTimeInUsec,
897 aIsSession,
898 aIsSecure,
899 aIsHttpOnly);
900 if (!cookie) {
901 return NS_ERROR_OUT_OF_MEMORY;
904 AddInternal(cookie, currentTimeInUsec / PR_USEC_PER_SEC, nsnull, nsnull, PR_TRUE);
905 return NS_OK;
908 NS_IMETHODIMP
909 nsCookieService::Remove(const nsACString &aHost,
910 const nsACString &aName,
911 const nsACString &aPath,
912 PRBool aBlocked)
914 nsListIter matchIter;
915 if (FindCookie(PromiseFlatCString(aHost),
916 PromiseFlatCString(aName),
917 PromiseFlatCString(aPath),
918 matchIter,
919 PR_Now() / PR_USEC_PER_SEC)) {
920 nsRefPtr<nsCookie> cookie = matchIter.current;
921 RemoveCookieFromList(matchIter);
922 NotifyChanged(cookie, NS_LITERAL_STRING("deleted").get());
925 // check if we need to add the host to the permissions blacklist.
926 if (aBlocked && mPermissionService) {
927 nsCAutoString host(NS_LITERAL_CSTRING("http://"));
929 // strip off the domain dot, if necessary
930 if (!aHost.IsEmpty() && aHost.First() == '.')
931 host.Append(Substring(aHost, 1, aHost.Length() - 1));
932 else
933 host.Append(aHost);
935 nsCOMPtr<nsIURI> uri;
936 NS_NewURI(getter_AddRefs(uri), host);
938 if (uri)
939 mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
942 return NS_OK;
945 /******************************************************************************
946 * nsCookieService impl:
947 * private file I/O functions
948 ******************************************************************************/
950 nsresult
951 nsCookieService::Read()
953 nsresult rv;
955 // delete expired cookies, before we read in the db
957 // scope the deletion, so the write lock is released when finished
958 nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
959 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE expiry <= ?1"),
960 getter_AddRefs(stmtDeleteExpired));
961 NS_ENSURE_SUCCESS(rv, rv);
963 rv = stmtDeleteExpired->BindInt64Parameter(0, PR_Now() / PR_USEC_PER_SEC);
964 NS_ENSURE_SUCCESS(rv, rv);
966 PRBool hasResult;
967 rv = stmtDeleteExpired->ExecuteStep(&hasResult);
968 NS_ENSURE_SUCCESS(rv, rv);
971 // let the reading begin!
972 nsCOMPtr<mozIStorageStatement> stmt;
973 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
974 "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly "
975 "FROM moz_cookies"), getter_AddRefs(stmt));
976 NS_ENSURE_SUCCESS(rv, rv);
978 nsCAutoString name, value, host, path;
979 PRBool hasResult;
980 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
981 PRInt64 creationID = stmt->AsInt64(0);
983 stmt->GetUTF8String(1, name);
984 stmt->GetUTF8String(2, value);
985 stmt->GetUTF8String(3, host);
986 stmt->GetUTF8String(4, path);
988 PRInt64 expiry = stmt->AsInt64(5);
989 PRInt64 lastAccessed = stmt->AsInt64(6);
990 PRBool isSecure = 0 != stmt->AsInt32(7);
991 PRBool isHttpOnly = 0 != stmt->AsInt32(8);
993 // create a new nsCookie and assign the data.
994 nsCookie* newCookie =
995 nsCookie::Create(name, value, host, path,
996 expiry,
997 lastAccessed,
998 creationID,
999 PR_FALSE,
1000 isSecure,
1001 isHttpOnly);
1002 if (!newCookie)
1003 return NS_ERROR_OUT_OF_MEMORY;
1005 if (!AddCookieToList(newCookie, PR_FALSE))
1006 // It is purpose that created us; purpose that connects us;
1007 // purpose that pulls us; that guides us; that drives us.
1008 // It is purpose that defines us; purpose that binds us.
1009 // When a cookie no longer has purpose, it has a choice:
1010 // it can return to the source to be deleted, or it can go
1011 // into exile, and stay hidden inside the Matrix.
1012 // Let's choose deletion.
1013 delete newCookie;
1016 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read", mCookieCount));
1018 return NS_OK;
1021 NS_IMETHODIMP
1022 nsCookieService::ImportCookies(nsIFile *aCookieFile)
1024 nsresult rv;
1025 nsCOMPtr<nsIInputStream> fileInputStream;
1026 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
1027 if (NS_FAILED(rv)) return rv;
1029 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
1030 if (NS_FAILED(rv)) return rv;
1032 // start a transaction on the storage db, to optimize insertions.
1033 // transaction will automically commit on completion
1034 mozStorageTransaction transaction(mDBConn, PR_TRUE);
1036 static const char kTrue[] = "TRUE";
1038 nsCAutoString buffer;
1039 PRBool isMore = PR_TRUE;
1040 PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
1041 nsASingleFragmentCString::char_iterator iter;
1042 PRInt32 numInts;
1043 PRInt64 expires;
1044 PRBool isDomain, isHttpOnly = PR_FALSE;
1045 PRUint32 originalCookieCount = mCookieCount;
1047 PRInt64 currentTimeInUsec = PR_Now();
1048 PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
1049 // we use lastAccessedCounter to keep cookies in recently-used order,
1050 // so we start by initializing to currentTime (somewhat arbitrary)
1051 PRInt64 lastAccessedCounter = currentTimeInUsec;
1053 /* file format is:
1055 * host \t isDomain \t path \t secure \t expires \t name \t cookie
1057 * if this format isn't respected we move onto the next line in the file.
1058 * isDomain is "TRUE" or "FALSE" (default to "FALSE")
1059 * isSecure is "TRUE" or "FALSE" (default to "TRUE")
1060 * expires is a PRInt64 integer
1061 * note 1: cookie can contain tabs.
1062 * note 2: cookies will be stored in order of lastAccessed time:
1063 * most-recently used come first; least-recently-used come last.
1067 * ...but due to bug 178933, we hide HttpOnly cookies from older code
1068 * in a comment, so they don't expose HttpOnly cookies to JS.
1070 * The format for HttpOnly cookies is
1072 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
1076 while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
1077 if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
1078 isHttpOnly = PR_TRUE;
1079 hostIndex = sizeof(kHttpOnlyPrefix) - 1;
1080 } else if (buffer.IsEmpty() || buffer.First() == '#') {
1081 continue;
1082 } else {
1083 isHttpOnly = PR_FALSE;
1084 hostIndex = 0;
1087 // this is a cheap, cheesy way of parsing a tab-delimited line into
1088 // string indexes, which can be lopped off into substrings. just for
1089 // purposes of obfuscation, it also checks that each token was found.
1090 // todo: use iterators?
1091 if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
1092 (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
1093 (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
1094 (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
1095 (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
1096 (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
1097 continue;
1100 // check the expirytime first - if it's expired, ignore
1101 // nullstomp the trailing tab, to avoid copying the string
1102 buffer.BeginWriting(iter);
1103 *(iter += nameIndex - 1) = char(0);
1104 numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
1105 if (numInts != 1 || expires < currentTime) {
1106 continue;
1109 isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
1110 const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
1111 // check for bad legacy cookies (domain not starting with a dot, or containing a port),
1112 // and discard
1113 if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
1114 host.FindChar(':') != kNotFound) {
1115 continue;
1118 // create a new nsCookie and assign the data.
1119 // we don't know the cookie creation time, so just use the current time;
1120 // this is okay, since nsCookie::Create() will make sure the creation id
1121 // ends up monotonically increasing.
1122 nsRefPtr<nsCookie> newCookie =
1123 nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
1124 Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
1125 host,
1126 Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
1127 expires,
1128 lastAccessedCounter,
1129 currentTimeInUsec,
1130 PR_FALSE,
1131 Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
1132 isHttpOnly);
1133 if (!newCookie) {
1134 return NS_ERROR_OUT_OF_MEMORY;
1137 // trick: preserve the most-recently-used cookie ordering,
1138 // by successively decrementing the lastAccessed time
1139 lastAccessedCounter--;
1141 if (originalCookieCount == 0)
1142 AddCookieToList(newCookie);
1143 else
1144 AddInternal(newCookie, currentTime, nsnull, nsnull, PR_TRUE);
1147 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", mCookieCount));
1149 return NS_OK;
1152 /******************************************************************************
1153 * nsCookieService impl:
1154 * private GetCookie/SetCookie helpers
1155 ******************************************************************************/
1157 // helper function for GetCookieList
1158 static inline PRBool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
1160 void
1161 nsCookieService::GetCookieInternal(nsIURI *aHostURI,
1162 nsIChannel *aChannel,
1163 PRBool aHttpBound,
1164 char **aCookie)
1166 *aCookie = nsnull;
1168 if (!aHostURI) {
1169 COOKIE_LOGFAILURE(GET_COOKIE, nsnull, nsnull, "host URI is null");
1170 return;
1173 // check default prefs
1174 PRUint32 cookieStatus = CheckPrefs(aHostURI, aChannel, nsnull);
1175 // for GetCookie(), we don't fire rejection notifications.
1176 switch (cookieStatus) {
1177 case STATUS_REJECTED:
1178 case STATUS_REJECTED_WITH_ERROR:
1179 return;
1182 // get host and path from the nsIURI
1183 // note: there was a "check if host has embedded whitespace" here.
1184 // it was removed since this check was added into the nsIURI impl (bug 146094).
1185 nsCAutoString hostFromURI, pathFromURI;
1186 if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI)) ||
1187 NS_FAILED(aHostURI->GetPath(pathFromURI))) {
1188 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nsnull, "couldn't get host/path from URI");
1189 return;
1191 // trim trailing dots
1192 hostFromURI.Trim(".");
1193 // insert a leading dot, so we begin the hash lookup with the
1194 // equivalent domain cookie host
1195 hostFromURI.Insert(NS_LITERAL_CSTRING("."), 0);
1197 // check if aHostURI is using an https secure protocol.
1198 // if it isn't, then we can't send a secure cookie over the connection.
1199 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1200 PRBool isSecure;
1201 if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
1202 isSecure = PR_FALSE;
1205 nsCookie *cookie;
1206 nsAutoVoidArray foundCookieList;
1207 PRInt64 currentTimeInUsec = PR_Now();
1208 PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
1209 const char *currentDot = hostFromURI.get();
1210 const char *nextDot = currentDot + 1;
1211 PRBool stale = PR_FALSE;
1213 // begin hash lookup, walking up the subdomain levels.
1214 // we use nextDot to force a lookup of the original host (without leading dot).
1215 do {
1216 nsCookieEntry *entry = mHostTable->GetEntry(currentDot);
1217 cookie = entry ? entry->Head() : nsnull;
1218 for (; cookie; cookie = cookie->Next()) {
1219 // if the cookie is secure and the host scheme isn't, we can't send it
1220 if (cookie->IsSecure() && !isSecure) {
1221 continue;
1224 // if the cookie is httpOnly and it's not going directly to the HTTP
1225 // connection, don't send it
1226 if (cookie->IsHttpOnly() && !aHttpBound) {
1227 continue;
1230 // calculate cookie path length, excluding trailing '/'
1231 PRUint32 cookiePathLen = cookie->Path().Length();
1232 if (cookiePathLen > 0 && cookie->Path().Last() == '/') {
1233 --cookiePathLen;
1236 // if the nsIURI path is shorter than the cookie path, don't send it back
1237 if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen))) {
1238 continue;
1241 if (pathFromURI.Length() > cookiePathLen &&
1242 !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
1244 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
1245 * '/' is the "standard" case; the '?' test allows a site at host/abc?def
1246 * to receive a cookie that has a path attribute of abc. this seems
1247 * strange but at least one major site (citibank, bug 156725) depends
1248 * on it. The test for # and ; are put in to proactively avoid problems
1249 * with other sites - these are the only other chars allowed in the path.
1251 continue;
1254 // check if the cookie has expired
1255 if (cookie->Expiry() <= currentTime) {
1256 continue;
1259 // all checks passed - add to list and check if lastAccessed stamp needs updating
1260 foundCookieList.AppendElement(cookie);
1261 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
1262 stale = PR_TRUE;
1265 currentDot = nextDot;
1266 if (currentDot)
1267 nextDot = strchr(currentDot + 1, '.');
1269 } while (currentDot);
1271 PRInt32 count = foundCookieList.Count();
1272 if (count == 0)
1273 return;
1275 // update lastAccessed timestamps. we only do this if the timestamp is stale
1276 // by a certain amount, to avoid thrashing the db during pageload.
1277 if (stale) {
1278 // start a transaction on the storage db, to optimize updates.
1279 // transaction will automically commit on completion.
1280 mozStorageTransaction transaction(mDBConn, PR_TRUE);
1282 for (PRInt32 i = 0; i < count; ++i) {
1283 cookie = static_cast<nsCookie*>(foundCookieList.ElementAt(i));
1285 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
1286 UpdateCookieInList(cookie, currentTimeInUsec);
1290 // return cookies in order of path length; longest to shortest.
1291 // this is required per RFC2109. if cookies match in length,
1292 // then sort by creation time (see bug 236772).
1293 foundCookieList.Sort(compareCookiesForSending, nsnull);
1295 nsCAutoString cookieData;
1296 for (PRInt32 i = 0; i < count; ++i) {
1297 cookie = static_cast<nsCookie*>(foundCookieList.ElementAt(i));
1299 // check if we have anything to write
1300 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
1301 // if we've already added a cookie to the return list, append a "; " so
1302 // that subsequent cookies are delimited in the final list.
1303 if (!cookieData.IsEmpty()) {
1304 cookieData.AppendLiteral("; ");
1307 if (!cookie->Name().IsEmpty()) {
1308 // we have a name and value - write both
1309 cookieData += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
1310 } else {
1311 // just write value
1312 cookieData += cookie->Value();
1317 // it's wasteful to alloc a new string; but we have no other choice, until we
1318 // fix the callers to use nsACStrings.
1319 if (!cookieData.IsEmpty()) {
1320 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, cookieData, nsnull, nsnull);
1321 *aCookie = ToNewCString(cookieData);
1325 // processes a single cookie, and returns PR_TRUE if there are more cookies
1326 // to be processed
1327 PRBool
1328 nsCookieService::SetCookieInternal(nsIURI *aHostURI,
1329 nsIChannel *aChannel,
1330 nsDependentCString &aCookieHeader,
1331 PRInt64 aServerTime,
1332 PRBool aFromHttp)
1334 // create a stack-based nsCookieAttributes, to store all the
1335 // attributes parsed from the cookie
1336 nsCookieAttributes cookieAttributes;
1338 // init expiryTime such that session cookies won't prematurely expire
1339 cookieAttributes.expiryTime = LL_MAXINT;
1341 // aCookieHeader is an in/out param to point to the next cookie, if
1342 // there is one. Save the present value for logging purposes
1343 nsDependentCString savedCookieHeader(aCookieHeader);
1345 // newCookie says whether there are multiple cookies in the header;
1346 // so we can handle them separately.
1347 PRBool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
1349 PRInt64 currentTimeInUsec = PR_Now();
1351 // calculate expiry time of cookie.
1352 cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
1353 currentTimeInUsec / PR_USEC_PER_SEC);
1355 // reject cookie if it's over the size limit, per RFC2109
1356 if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
1357 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
1358 return newCookie;
1361 if (cookieAttributes.name.FindChar('\t') != kNotFound) {
1362 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
1363 return newCookie;
1366 // domain & path checks
1367 if (!CheckDomain(cookieAttributes, aHostURI)) {
1368 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
1369 return newCookie;
1371 if (!CheckPath(cookieAttributes, aHostURI)) {
1372 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
1373 return newCookie;
1376 // create a new nsCookie and copy attributes
1377 nsRefPtr<nsCookie> cookie =
1378 nsCookie::Create(cookieAttributes.name,
1379 cookieAttributes.value,
1380 cookieAttributes.host,
1381 cookieAttributes.path,
1382 cookieAttributes.expiryTime,
1383 currentTimeInUsec,
1384 currentTimeInUsec,
1385 cookieAttributes.isSession,
1386 cookieAttributes.isSecure,
1387 cookieAttributes.isHttpOnly);
1388 if (!cookie)
1389 return newCookie;
1391 // check permissions from site permission list, or ask the user,
1392 // to determine if we can set the cookie
1393 if (mPermissionService) {
1394 PRBool permission;
1395 // we need to think about prompters/parent windows here - TestPermission
1396 // needs one to prompt, so right now it has to fend for itself to get one
1397 mPermissionService->CanSetCookie(aHostURI,
1398 aChannel,
1399 static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
1400 &cookieAttributes.isSession,
1401 &cookieAttributes.expiryTime,
1402 &permission);
1403 if (!permission) {
1404 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
1405 NotifyRejected(aHostURI);
1406 return newCookie;
1409 // update isSession and expiry attributes, in case they changed
1410 cookie->SetIsSession(cookieAttributes.isSession);
1411 cookie->SetExpiry(cookieAttributes.expiryTime);
1414 // add the cookie to the list. AddInternal() takes care of logging.
1415 // we get the current time again here, since it may have changed during prompting
1416 AddInternal(cookie, PR_Now() / PR_USEC_PER_SEC, aHostURI, savedCookieHeader.get(), aFromHttp);
1417 return newCookie;
1420 // this is a backend function for adding a cookie to the list, via SetCookie.
1421 // also used in the cookie manager, for profile migration from IE.
1422 // it either replaces an existing cookie; or adds the cookie to the hashtable,
1423 // and deletes a cookie (if maximum number of cookies has been
1424 // reached). also performs list maintenance by removing expired cookies.
1425 void
1426 nsCookieService::AddInternal(nsCookie *aCookie,
1427 PRInt64 aCurrentTime,
1428 nsIURI *aHostURI,
1429 const char *aCookieHeader,
1430 PRBool aFromHttp)
1432 // if the new cookie is httponly, make sure we're not coming from script
1433 if (!aFromHttp && aCookie->IsHttpOnly()) {
1434 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script");
1435 return;
1438 // start a transaction on the storage db, to optimize deletions/insertions.
1439 // transaction will automically commit on completion. if we already have a
1440 // transaction (e.g. from SetCookie*()), this will have no effect.
1441 mozStorageTransaction transaction(mDBConn, PR_TRUE);
1443 nsListIter matchIter;
1444 PRBool foundCookie = FindCookie(aCookie->Host(), aCookie->Name(), aCookie->Path(),
1445 matchIter, aCurrentTime);
1447 nsRefPtr<nsCookie> oldCookie;
1448 if (foundCookie) {
1449 oldCookie = matchIter.current;
1451 // if the old cookie is httponly, make sure we're not coming from script
1452 if (!aFromHttp && oldCookie->IsHttpOnly()) {
1453 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie is httponly; coming from script");
1454 return;
1457 RemoveCookieFromList(matchIter);
1459 // check if the cookie has expired
1460 if (aCookie->Expiry() <= aCurrentTime) {
1461 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie was deleted");
1462 NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
1463 return;
1466 // preserve creation time of cookie
1467 if (oldCookie)
1468 aCookie->SetCreationID(oldCookie->CreationID());
1470 } else {
1471 // check if cookie has already expired
1472 if (aCookie->Expiry() <= aCurrentTime) {
1473 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired");
1474 return;
1477 // check if we have to delete an old cookie.
1478 nsEnumerationData data(aCurrentTime, LL_MAXINT);
1479 if (CountCookiesFromHostInternal(aCookie->RawHost(), data) >= mMaxCookiesPerHost) {
1480 // remove the oldest cookie from host
1481 oldCookie = data.iter.current;
1482 RemoveCookieFromList(data.iter);
1484 } else if (mCookieCount >= mMaxNumberOfCookies) {
1485 // try to make room, by removing expired cookies
1486 RemoveExpiredCookies(aCurrentTime);
1488 // check if we still have to get rid of something
1489 if (mCookieCount >= mMaxNumberOfCookies) {
1490 // find the position of the oldest cookie, and remove it
1491 data.oldestTime = LL_MAXINT;
1492 FindOldestCookie(data);
1493 oldCookie = data.iter.current;
1494 RemoveCookieFromList(data.iter);
1498 // if we deleted an old cookie, notify consumers
1499 if (oldCookie) {
1500 COOKIE_LOGEVICTED(oldCookie);
1501 NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
1505 // add the cookie to head of list
1506 AddCookieToList(aCookie);
1507 NotifyChanged(aCookie, foundCookie ? NS_LITERAL_STRING("changed").get()
1508 : NS_LITERAL_STRING("added").get());
1510 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie != nsnull);
1513 /******************************************************************************
1514 * nsCookieService impl:
1515 * private cookie header parsing functions
1516 ******************************************************************************/
1518 // The following comment block elucidates the function of ParseAttributes.
1519 /******************************************************************************
1520 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1521 ** please note: this BNF deviates from both specifications, and reflects this
1522 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1524 ** Differences from RFC2109/2616 and explanations:
1525 1. implied *LWS
1526 The grammar described by this specification is word-based. Except
1527 where noted otherwise, linear white space (<LWS>) can be included
1528 between any two adjacent words (token or quoted-string), and
1529 between adjacent words and separators, without changing the
1530 interpretation of a field.
1531 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1533 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1534 common use inside values.
1536 3. tokens and values have looser restrictions on allowed characters than
1537 spec. This is also due to certain characters being in common use inside
1538 values. We allow only '=' to separate token/value pairs, and ';' to
1539 terminate tokens or values. <LWS> is allowed within tokens and values
1540 (see bug 206022).
1542 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1543 reject control chars or non-ASCII chars. This is erring on the loose
1544 side, since there's probably no good reason to enforce this strictness.
1546 5. cookie <NAME> is optional, where spec requires it. This is a fairly
1547 trivial case, but allows the flexibility of setting only a cookie <VALUE>
1548 with a blank <NAME> and is required by some sites (see bug 169091).
1550 6. Attribute "HttpOnly", not covered in the RFCs, is supported
1551 (see bug 178993).
1553 ** Begin BNF:
1554 token = 1*<any allowed-chars except separators>
1555 value = token-value | quoted-string
1556 token-value = 1*<any allowed-chars except value-sep>
1557 quoted-string = ( <"> *( qdtext | quoted-pair ) <"> )
1558 qdtext = <any allowed-chars except <">> ; CR | LF removed by necko
1559 quoted-pair = "\" <any OCTET except NUL or cookie-sep> ; CR | LF removed by necko
1560 separators = ";" | "="
1561 value-sep = ";"
1562 cookie-sep = CR | LF
1563 allowed-chars = <any OCTET except NUL or cookie-sep>
1564 OCTET = <any 8-bit sequence of data>
1565 LWS = SP | HT
1566 NUL = <US-ASCII NUL, null control character (0)>
1567 CR = <US-ASCII CR, carriage return (13)>
1568 LF = <US-ASCII LF, linefeed (10)>
1569 SP = <US-ASCII SP, space (32)>
1570 HT = <US-ASCII HT, horizontal-tab (9)>
1572 set-cookie = "Set-Cookie:" cookies
1573 cookies = cookie *( cookie-sep cookie )
1574 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1575 NAME = token ; cookie name
1576 VALUE = value ; cookie value
1577 cookie-av = token ["=" value]
1579 valid values for cookie-av (checked post-parsing) are:
1580 cookie-av = "Path" "=" value
1581 | "Domain" "=" value
1582 | "Expires" "=" value
1583 | "Max-Age" "=" value
1584 | "Comment" "=" value
1585 | "Version" "=" value
1586 | "Secure"
1587 | "HttpOnly"
1589 ******************************************************************************/
1591 // helper functions for GetTokenValue
1592 static inline PRBool iswhitespace (char c) { return c == ' ' || c == '\t'; }
1593 static inline PRBool isterminator (char c) { return c == '\n' || c == '\r'; }
1594 static inline PRBool isquoteterminator(char c) { return isterminator(c) || c == '"'; }
1595 static inline PRBool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
1596 static inline PRBool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
1598 // Parse a single token/value pair.
1599 // Returns PR_TRUE if a cookie terminator is found, so caller can parse new cookie.
1600 PRBool
1601 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
1602 nsASingleFragmentCString::const_char_iterator &aEndIter,
1603 nsDependentCSubstring &aTokenString,
1604 nsDependentCSubstring &aTokenValue,
1605 PRBool &aEqualsFound)
1607 nsASingleFragmentCString::const_char_iterator start, lastSpace;
1608 // initialize value string to clear garbage
1609 aTokenValue.Rebind(aIter, aIter);
1611 // find <token>, including any <LWS> between the end-of-token and the
1612 // token separator. we'll remove trailing <LWS> next
1613 while (aIter != aEndIter && iswhitespace(*aIter))
1614 ++aIter;
1615 start = aIter;
1616 while (aIter != aEndIter && !istokenseparator(*aIter))
1617 ++aIter;
1619 // remove trailing <LWS>; first check we're not at the beginning
1620 lastSpace = aIter;
1621 if (lastSpace != start) {
1622 while (--lastSpace != start && iswhitespace(*lastSpace));
1623 ++lastSpace;
1625 aTokenString.Rebind(start, lastSpace);
1627 aEqualsFound = (*aIter == '=');
1628 if (aEqualsFound) {
1629 // find <value>
1630 while (++aIter != aEndIter && iswhitespace(*aIter));
1632 start = aIter;
1634 if (*aIter == '"') {
1635 // process <quoted-string>
1636 // (note: cookie terminators, CR | LF, can't happen:
1637 // they're removed by necko before the header gets here)
1638 // assume value mangled if no terminating '"', return
1639 while (++aIter != aEndIter && !isquoteterminator(*aIter)) {
1640 // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
1641 // we increment once over the backwhack, nullcheck, then continue to the 'while',
1642 // which increments over the backwhacked char. one exception - we don't allow
1643 // CR | LF here either (see above about necko)
1644 if (*aIter == '\\' && (++aIter == aEndIter || isterminator(*aIter)))
1645 break;
1648 if (aIter != aEndIter && !isterminator(*aIter)) {
1649 // include terminating quote in attribute string
1650 aTokenValue.Rebind(start, ++aIter);
1651 // skip to next ';'
1652 while (aIter != aEndIter && !isvalueseparator(*aIter))
1653 ++aIter;
1655 } else {
1656 // process <token-value>
1657 // just look for ';' to terminate ('=' allowed)
1658 while (aIter != aEndIter && !isvalueseparator(*aIter))
1659 ++aIter;
1661 // remove trailing <LWS>; first check we're not at the beginning
1662 if (aIter != start) {
1663 lastSpace = aIter;
1664 while (--lastSpace != start && iswhitespace(*lastSpace));
1665 aTokenValue.Rebind(start, ++lastSpace);
1670 // aIter is on ';', or terminator, or EOS
1671 if (aIter != aEndIter) {
1672 // if on terminator, increment past & return PR_TRUE to process new cookie
1673 if (isterminator(*aIter)) {
1674 ++aIter;
1675 return PR_TRUE;
1677 // fall-through: aIter is on ';', increment and return PR_FALSE
1678 ++aIter;
1680 return PR_FALSE;
1683 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
1684 // cookie struct here, because we don't know which one to use until we've parsed the header.
1685 PRBool
1686 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
1687 nsCookieAttributes &aCookieAttributes)
1689 static const char kPath[] = "path";
1690 static const char kDomain[] = "domain";
1691 static const char kExpires[] = "expires";
1692 static const char kMaxage[] = "max-age";
1693 static const char kSecure[] = "secure";
1694 static const char kHttpOnly[] = "httponly";
1696 nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
1697 nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
1698 aCookieHeader.BeginReading(cookieStart);
1699 aCookieHeader.EndReading(cookieEnd);
1701 aCookieAttributes.isSecure = PR_FALSE;
1702 aCookieAttributes.isHttpOnly = PR_FALSE;
1704 nsDependentCSubstring tokenString(cookieStart, cookieStart);
1705 nsDependentCSubstring tokenValue (cookieStart, cookieStart);
1706 PRBool newCookie, equalsFound;
1708 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1709 // if we find multiple cookies, return for processing
1710 // note: if there's no '=', we assume token is <VALUE>. this is required by
1711 // some sites (see bug 169091).
1712 // XXX fix the parser to parse according to <VALUE> grammar for this case
1713 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
1714 if (equalsFound) {
1715 aCookieAttributes.name = tokenString;
1716 aCookieAttributes.value = tokenValue;
1717 } else {
1718 aCookieAttributes.value = tokenString;
1721 // extract remaining attributes
1722 while (cookieStart != cookieEnd && !newCookie) {
1723 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
1725 if (!tokenValue.IsEmpty()) {
1726 tokenValue.BeginReading(tempBegin);
1727 tokenValue.EndReading(tempEnd);
1728 if (*tempBegin == '"' && *--tempEnd == '"') {
1729 // our parameter is a quoted-string; remove quotes for later parsing
1730 tokenValue.Rebind(++tempBegin, tempEnd);
1734 // decide which attribute we have, and copy the string
1735 if (tokenString.LowerCaseEqualsLiteral(kPath))
1736 aCookieAttributes.path = tokenValue;
1738 else if (tokenString.LowerCaseEqualsLiteral(kDomain))
1739 aCookieAttributes.host = tokenValue;
1741 else if (tokenString.LowerCaseEqualsLiteral(kExpires))
1742 aCookieAttributes.expires = tokenValue;
1744 else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
1745 aCookieAttributes.maxage = tokenValue;
1747 // ignore any tokenValue for isSecure; just set the boolean
1748 else if (tokenString.LowerCaseEqualsLiteral(kSecure))
1749 aCookieAttributes.isSecure = PR_TRUE;
1751 // ignore any tokenValue for isHttpOnly (see bug 178993);
1752 // just set the boolean
1753 else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
1754 aCookieAttributes.isHttpOnly = PR_TRUE;
1757 // rebind aCookieHeader, in case we need to process another cookie
1758 aCookieHeader.Rebind(cookieStart, cookieEnd);
1759 return newCookie;
1762 /******************************************************************************
1763 * nsCookieService impl:
1764 * private domain & permission compliance enforcement functions
1765 ******************************************************************************/
1767 PRBool
1768 nsCookieService::IsForeign(nsIURI *aHostURI,
1769 nsIURI *aFirstURI)
1771 // Get hosts
1772 nsCAutoString currentHost, firstHost;
1773 if (NS_FAILED(aHostURI->GetAsciiHost(currentHost)) ||
1774 NS_FAILED(aFirstURI->GetAsciiHost(firstHost))) {
1775 // assume foreign
1776 return PR_TRUE;
1778 // trim trailing dots
1779 currentHost.Trim(".");
1780 firstHost.Trim(".");
1782 // fast path: check if the two hosts are identical.
1783 // this also covers two special cases:
1784 // 1) if we're dealing with IP addresses, require an exact match. this
1785 // eliminates any chance of IP address funkiness (e.g. the alias 127.1
1786 // domain-matching 99.54.127.1). bug 105917 originally noted the requirement
1787 // to deal with IP addresses. note that GetBaseDomain() below will return an
1788 // error if the URI is an IP address.
1789 // 2) we also need this for the (rare) case where the site is actually an eTLD,
1790 // e.g. http://co.tv; GetBaseDomain() will throw an error and we might
1791 // erroneously think currentHost is foreign. so we consider this case non-
1792 // foreign only if the hosts exactly match.
1793 if (firstHost.Equals(currentHost))
1794 return PR_FALSE;
1796 // get the base domain for the originating URI.
1797 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
1798 nsCAutoString baseDomain;
1799 nsresult rv = mTLDService->GetBaseDomain(aFirstURI, 0, baseDomain);
1800 if (NS_FAILED(rv)) {
1801 // URI is an IP, eTLD, or something else went wrong - assume foreign
1802 return PR_TRUE;
1804 baseDomain.Trim(".");
1806 // ensure the host domain is derived from the base domain.
1807 // we prepend dots before the comparison to ensure e.g.
1808 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1809 currentHost.Insert(NS_LITERAL_CSTRING("."), 0);
1810 baseDomain.Insert(NS_LITERAL_CSTRING("."), 0);
1811 return !StringEndsWith(currentHost, baseDomain);
1814 PRUint32
1815 nsCookieService::CheckPrefs(nsIURI *aHostURI,
1816 nsIChannel *aChannel,
1817 const char *aCookieHeader)
1819 nsresult rv;
1821 // don't let ftp sites get/set cookies (could be a security issue)
1822 PRBool ftp;
1823 if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
1824 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
1825 return STATUS_REJECTED_WITH_ERROR;
1828 // check the permission list first; if we find an entry, it overrides
1829 // default prefs. see bug 184059.
1830 if (mPermissionService) {
1831 nsCookieAccess access;
1832 rv = mPermissionService->CanAccess(aHostURI, aChannel, &access);
1834 // if we found an entry, use it
1835 if (NS_SUCCEEDED(rv)) {
1836 switch (access) {
1837 case nsICookiePermission::ACCESS_DENY:
1838 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are blocked for this site");
1839 return STATUS_REJECTED;
1841 case nsICookiePermission::ACCESS_ALLOW:
1842 return STATUS_ACCEPTED;
1847 // check default prefs
1848 if (mCookiesPermissions == BEHAVIOR_REJECT) {
1849 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
1850 return STATUS_REJECTED;
1852 } else if (mCookiesPermissions == BEHAVIOR_REJECTFOREIGN) {
1853 // check if cookie is foreign
1854 if (!mPermissionService) {
1855 NS_WARNING("Foreign cookie blocking enabled, but nsICookiePermission unavailable! Rejecting cookie");
1856 COOKIE_LOGSTRING(PR_LOG_WARNING, ("CheckPrefs(): foreign blocking enabled, but nsICookiePermission unavailable! Rejecting cookie"));
1857 return STATUS_REJECTED;
1860 nsCOMPtr<nsIURI> firstURI;
1861 rv = mPermissionService->GetOriginatingURI(aChannel, getter_AddRefs(firstURI));
1863 if (NS_FAILED(rv) || IsForeign(aHostURI, firstURI)) {
1864 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "originating server test failed");
1865 return STATUS_REJECTED;
1869 // if nothing has complained, accept cookie
1870 return STATUS_ACCEPTED;
1873 // processes domain attribute, and returns PR_TRUE if host has permission to set for this domain.
1874 PRBool
1875 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
1876 nsIURI *aHostURI)
1878 nsresult rv;
1880 // get host from aHostURI
1881 nsCAutoString hostFromURI;
1882 if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI))) {
1883 return PR_FALSE;
1885 // trim trailing dots
1886 hostFromURI.Trim(".");
1888 // if a domain is given, check the host has permission
1889 if (!aCookieAttributes.host.IsEmpty()) {
1890 aCookieAttributes.host.Trim(".");
1891 // switch to lowercase now, to avoid case-insensitive compares everywhere
1892 ToLowerCase(aCookieAttributes.host);
1894 // get the base domain for the host URI.
1895 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk", which
1896 // represents the lowest level domain a cookie can be set for.
1897 nsCAutoString baseDomain;
1898 rv = mTLDService->GetBaseDomain(aHostURI, 0, baseDomain);
1899 baseDomain.Trim(".");
1900 if (NS_FAILED(rv)) {
1901 // check whether the host is an IP address, and leave the cookie as
1902 // a non-domain one. this will require an exact host match for the cookie,
1903 // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1
1904 // domain-matching 99.54.127.1). bug 105917 originally noted the
1905 // requirement to deal with IP addresses.
1906 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS)
1907 return hostFromURI.Equals(aCookieAttributes.host);
1909 return PR_FALSE;
1912 // ensure the proposed domain is derived from the base domain; and also
1913 // that the host domain is derived from the proposed domain (per RFC2109).
1914 // we prepend a dot before the comparison to ensure e.g.
1915 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1916 hostFromURI.Insert(NS_LITERAL_CSTRING("."), 0);
1917 aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
1918 baseDomain.Insert(NS_LITERAL_CSTRING("."), 0);
1919 return StringEndsWith(aCookieAttributes.host, baseDomain) &&
1920 StringEndsWith(hostFromURI, aCookieAttributes.host);
1923 * note: RFC2109 section 4.3.2 requires that we check the following:
1924 * that the portion of host not in domain does not contain a dot.
1925 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1926 * entire .co.nz domain. however, it's only a only a partial solution and
1927 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1931 // block any URIs without a host that aren't file:/// URIs
1932 if (hostFromURI.IsEmpty()) {
1933 PRBool isFileURI = PR_FALSE;
1934 aHostURI->SchemeIs("file", &isFileURI);
1935 if (!isFileURI)
1936 return PR_FALSE;
1939 // no domain specified, use hostFromURI
1940 aCookieAttributes.host = hostFromURI;
1942 return PR_TRUE;
1945 PRBool
1946 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
1947 nsIURI *aHostURI)
1949 // if a path is given, check the host has permission
1950 if (aCookieAttributes.path.IsEmpty()) {
1951 // strip down everything after the last slash to get the path,
1952 // ignoring slashes in the query string part.
1953 // if we can QI to nsIURL, that'll take care of the query string portion.
1954 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
1955 nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
1956 if (hostURL) {
1957 hostURL->GetDirectory(aCookieAttributes.path);
1958 } else {
1959 aHostURI->GetPath(aCookieAttributes.path);
1960 PRInt32 slash = aCookieAttributes.path.RFindChar('/');
1961 if (slash != kNotFound) {
1962 aCookieAttributes.path.Truncate(slash + 1);
1966 #if 0
1967 } else {
1969 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
1970 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
1971 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
1972 * been disabled, unless we can evangelize these sites.
1974 // get path from aHostURI
1975 nsCAutoString pathFromURI;
1976 if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
1977 !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
1978 return PR_FALSE;
1980 #endif
1983 if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
1984 aCookieAttributes.path.FindChar('\t') != kNotFound )
1985 return PR_FALSE;
1987 return PR_TRUE;
1990 PRBool
1991 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
1992 PRInt64 aServerTime,
1993 PRInt64 aCurrentTime)
1995 /* Determine when the cookie should expire. This is done by taking the difference between
1996 * the server time and the time the server wants the cookie to expire, and adding that
1997 * difference to the client time. This localizes the client time regardless of whether or
1998 * not the TZ environment variable was set on the client.
2000 * Note: We need to consider accounting for network lag here, per RFC.
2002 PRInt64 delta;
2004 // check for max-age attribute first; this overrides expires attribute
2005 if (!aCookieAttributes.maxage.IsEmpty()) {
2006 // obtain numeric value of maxageAttribute
2007 PRInt64 maxage;
2008 PRInt32 numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
2010 // default to session cookie if the conversion failed
2011 if (numInts != 1) {
2012 return PR_TRUE;
2015 delta = maxage;
2017 // check for expires attribute
2018 } else if (!aCookieAttributes.expires.IsEmpty()) {
2019 PRTime tempExpires;
2020 PRInt64 expires;
2022 // parse expiry time
2023 if (PR_ParseTimeString(aCookieAttributes.expires.get(), PR_TRUE, &tempExpires) == PR_SUCCESS) {
2024 expires = tempExpires / PR_USEC_PER_SEC;
2025 } else {
2026 return PR_TRUE;
2029 delta = expires - aServerTime;
2031 // default to session cookie if no attributes found
2032 } else {
2033 return PR_TRUE;
2036 // if this addition overflows, expiryTime will be less than currentTime
2037 // and the cookie will be expired - that's okay.
2038 aCookieAttributes.expiryTime = aCurrentTime + delta;
2040 return PR_FALSE;
2043 /******************************************************************************
2044 * nsCookieService impl:
2045 * private cookielist management functions
2046 ******************************************************************************/
2048 void
2049 nsCookieService::RemoveAllFromMemory()
2051 // clearing the hashtable will call each nsCookieEntry's dtor,
2052 // which releases all their respective children.
2053 mHostTable->Clear();
2054 mCookieCount = 0;
2057 PLDHashOperator
2058 removeExpiredCallback(nsCookieEntry *aEntry,
2059 void *aArg)
2061 const PRInt64 &currentTime = *static_cast<PRInt64*>(aArg);
2062 for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ) {
2063 if (iter.current->Expiry() <= currentTime)
2064 // remove from list. this takes care of updating the iterator for us
2065 nsCookieService::gCookieService->RemoveCookieFromList(iter);
2066 else
2067 ++iter;
2069 return PL_DHASH_NEXT;
2072 // removes any expired cookies from memory
2073 void
2074 nsCookieService::RemoveExpiredCookies(PRInt64 aCurrentTime)
2076 #ifdef PR_LOGGING
2077 PRUint32 initialCookieCount = mCookieCount;
2078 #endif
2079 mHostTable->EnumerateEntries(removeExpiredCallback, &aCurrentTime);
2080 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("RemoveExpiredCookies(): %ld purged; %ld remain", initialCookieCount - mCookieCount, mCookieCount));
2083 // find whether a given cookie has been previously set. this is provided by the
2084 // nsICookieManager2 interface.
2085 NS_IMETHODIMP
2086 nsCookieService::CookieExists(nsICookie2 *aCookie,
2087 PRBool *aFoundCookie)
2089 NS_ENSURE_ARG_POINTER(aCookie);
2091 // just a placeholder
2092 nsListIter iter;
2093 nsCAutoString host, name, path;
2094 nsresult rv = aCookie->GetHost(host);
2095 NS_ENSURE_SUCCESS(rv, rv);
2096 rv = aCookie->GetName(name);
2097 NS_ENSURE_SUCCESS(rv, rv);
2098 rv = aCookie->GetPath(path);
2099 NS_ENSURE_SUCCESS(rv, rv);
2101 *aFoundCookie = FindCookie(host, name, path, iter,
2102 PR_Now() / PR_USEC_PER_SEC);
2103 return NS_OK;
2106 // count the number of cookies from a given host, and simultaneously find the
2107 // oldest cookie from the host.
2108 PRUint32
2109 nsCookieService::CountCookiesFromHostInternal(const nsACString &aHost,
2110 nsEnumerationData &aData)
2112 PRUint32 countFromHost = 0;
2114 nsCAutoString hostWithDot(NS_LITERAL_CSTRING(".") + aHost);
2116 const char *currentDot = hostWithDot.get();
2117 const char *nextDot = currentDot + 1;
2118 do {
2119 nsCookieEntry *entry = mHostTable->GetEntry(currentDot);
2120 for (nsListIter iter(entry); iter.current; ++iter) {
2121 // only count non-expired cookies
2122 if (iter.current->Expiry() > aData.currentTime) {
2123 ++countFromHost;
2125 // check if we've found the oldest cookie so far
2126 if (aData.oldestTime > iter.current->LastAccessed()) {
2127 aData.oldestTime = iter.current->LastAccessed();
2128 aData.iter = iter;
2133 currentDot = nextDot;
2134 if (currentDot)
2135 nextDot = strchr(currentDot + 1, '.');
2137 } while (currentDot);
2139 return countFromHost;
2142 // count the number of cookies stored by a particular host. this is provided by the
2143 // nsICookieManager2 interface.
2144 NS_IMETHODIMP
2145 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
2146 PRUint32 *aCountFromHost)
2148 // we don't care about finding the oldest cookie here, so disable the search
2149 nsEnumerationData data(PR_Now() / PR_USEC_PER_SEC, LL_MININT);
2151 *aCountFromHost = CountCookiesFromHostInternal(aHost, data);
2152 return NS_OK;
2155 // find an exact cookie specified by host, name, and path that hasn't expired.
2156 PRBool
2157 nsCookieService::FindCookie(const nsAFlatCString &aHost,
2158 const nsAFlatCString &aName,
2159 const nsAFlatCString &aPath,
2160 nsListIter &aIter,
2161 PRInt64 aCurrentTime)
2163 nsCookieEntry *entry = mHostTable->GetEntry(aHost.get());
2164 for (aIter = nsListIter(entry); aIter.current; ++aIter) {
2165 if (aIter.current->Expiry() > aCurrentTime &&
2166 aPath.Equals(aIter.current->Path()) &&
2167 aName.Equals(aIter.current->Name())) {
2168 return PR_TRUE;
2172 return PR_FALSE;
2175 // removes a cookie from the hashtable, and update the iterator state.
2176 void
2177 nsCookieService::RemoveCookieFromList(nsListIter &aIter)
2179 // if it's a non-session cookie, remove it from the db
2180 if (!aIter.current->IsSession() && mStmtDelete) {
2181 // use our cached sqlite "delete" statement
2182 mozStorageStatementScoper scoper(mStmtDelete);
2184 nsresult rv = mStmtDelete->BindInt64Parameter(0, aIter.current->CreationID());
2185 if (NS_SUCCEEDED(rv)) {
2186 PRBool hasResult;
2187 rv = mStmtDelete->ExecuteStep(&hasResult);
2190 if (NS_FAILED(rv)) {
2191 NS_WARNING("db remove failed!");
2192 COOKIE_LOGSTRING(PR_LOG_WARNING, ("RemoveCookieFromList(): removing from db gave error %x", rv));
2196 if (!aIter.prev && !aIter.current->Next()) {
2197 // we're removing the last element in the list - so just remove the entry
2198 // from the hash. note that the entryclass' dtor will take care of
2199 // releasing this last element for us!
2200 mHostTable->RawRemoveEntry(aIter.entry);
2201 aIter.current = nsnull;
2203 } else {
2204 // just remove the element from the list, and increment the iterator
2205 nsCookie *next = aIter.current->Next();
2206 NS_RELEASE(aIter.current);
2207 if (aIter.prev) {
2208 // element to remove is not the head
2209 aIter.current = aIter.prev->Next() = next;
2210 } else {
2211 // element to remove is the head
2212 aIter.current = aIter.entry->Head() = next;
2216 --mCookieCount;
2219 nsresult
2220 bindCookieParameters(mozIStorageStatement* aStmt, const nsCookie* aCookie)
2222 nsresult rv;
2224 rv = aStmt->BindInt64Parameter(0, aCookie->CreationID());
2225 if (NS_FAILED(rv)) return rv;
2227 rv = aStmt->BindUTF8StringParameter(1, aCookie->Name());
2228 if (NS_FAILED(rv)) return rv;
2230 rv = aStmt->BindUTF8StringParameter(2, aCookie->Value());
2231 if (NS_FAILED(rv)) return rv;
2233 rv = aStmt->BindUTF8StringParameter(3, aCookie->Host());
2234 if (NS_FAILED(rv)) return rv;
2236 rv = aStmt->BindUTF8StringParameter(4, aCookie->Path());
2237 if (NS_FAILED(rv)) return rv;
2239 rv = aStmt->BindInt64Parameter(5, aCookie->Expiry());
2240 if (NS_FAILED(rv)) return rv;
2242 rv = aStmt->BindInt64Parameter(6, aCookie->LastAccessed());
2243 if (NS_FAILED(rv)) return rv;
2245 rv = aStmt->BindInt32Parameter(7, aCookie->IsSecure());
2246 if (NS_FAILED(rv)) return rv;
2248 rv = aStmt->BindInt32Parameter(8, aCookie->IsHttpOnly());
2249 return rv;
2252 PRBool
2253 nsCookieService::AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB)
2255 nsCookieEntry *entry = mHostTable->PutEntry(aCookie->Host().get());
2257 if (!entry) {
2258 NS_ERROR("can't insert element into a null entry!");
2259 return PR_FALSE;
2262 NS_ADDREF(aCookie);
2264 aCookie->Next() = entry->Head();
2265 entry->Head() = aCookie;
2266 ++mCookieCount;
2268 // if it's a non-session cookie and hasn't just been read from the db, write it out.
2269 if (aWriteToDB && !aCookie->IsSession() && mStmtInsert) {
2270 // use our cached sqlite "insert" statement
2271 mozStorageStatementScoper scoper(mStmtInsert);
2273 nsresult rv = bindCookieParameters(mStmtInsert, aCookie);
2274 if (NS_SUCCEEDED(rv)) {
2275 PRBool hasResult;
2276 rv = mStmtInsert->ExecuteStep(&hasResult);
2279 if (NS_FAILED(rv)) {
2280 NS_WARNING("db insert failed!");
2281 COOKIE_LOGSTRING(PR_LOG_WARNING, ("AddCookieToList(): adding to db gave error %x", rv));
2285 return PR_TRUE;
2288 void
2289 nsCookieService::UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed)
2291 // update the lastAccessed timestamp
2292 aCookie->SetLastAccessed(aLastAccessed);
2294 // if it's a non-session cookie, update it in the db too
2295 if (!aCookie->IsSession() && mStmtUpdate) {
2296 // use our cached sqlite "update" statement
2297 mozStorageStatementScoper scoper(mStmtUpdate);
2299 nsresult rv = mStmtUpdate->BindInt64Parameter(0, aLastAccessed);
2300 if (NS_SUCCEEDED(rv)) {
2301 rv = mStmtUpdate->BindInt64Parameter(1, aCookie->CreationID());
2302 if (NS_SUCCEEDED(rv)) {
2303 PRBool hasResult;
2304 rv = mStmtUpdate->ExecuteStep(&hasResult);
2308 if (NS_FAILED(rv)) {
2309 NS_WARNING("db update failed!");
2310 COOKIE_LOGSTRING(PR_LOG_WARNING, ("UpdateCookieInList(): updating db gave error %x", rv));
2315 static PLDHashOperator
2316 findOldestCallback(nsCookieEntry *aEntry,
2317 void *aArg)
2319 nsEnumerationData *data = static_cast<nsEnumerationData*>(aArg);
2320 for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ++iter) {
2321 // check if we've found the oldest cookie so far
2322 if (data->oldestTime > iter.current->LastAccessed()) {
2323 data->oldestTime = iter.current->LastAccessed();
2324 data->iter = iter;
2327 return PL_DHASH_NEXT;
2330 void
2331 nsCookieService::FindOldestCookie(nsEnumerationData &aData)
2333 mHostTable->EnumerateEntries(findOldestCallback, &aData);