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
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.
23 * Daniel Witte (dwitte@stanford.edu)
24 * Michiel van Leeuwen (mvl@exedo.nl)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsCookieService.h"
41 #include "nsIServiceManager.h"
43 #include "nsIIOService.h"
44 #include "nsIPrefBranch.h"
45 #include "nsIPrefBranch2.h"
46 #include "nsIPrefService.h"
47 #include "nsICookiePermission.h"
50 #include "nsIChannel.h"
52 #include "nsIObserverService.h"
53 #include "nsILineInputStream.h"
54 #include "nsIEffectiveTLDService.h"
56 #include "nsCOMArray.h"
57 #include "nsArrayEnumerator.h"
58 #include "nsAutoPtr.h"
59 #include "nsReadableUtils.h"
63 #include "nsNetUtil.h"
65 #include "nsAppDirectoryServiceDefs.h"
66 #include "mozIStorageService.h"
67 #include "mozIStorageStatement.h"
68 #include "mozIStorageConnection.h"
69 #include "mozStorageHelper.h"
71 /******************************************************************************
72 * nsCookieService impl:
73 * useful types & constants
74 ******************************************************************************/
76 // XXX_hack. See bug 178993.
77 // This is a hack to hide HttpOnly cookies from older browsers
79 static const char kHttpOnlyPrefix
[] = "#HttpOnly_";
81 static const char kCookieFileName
[] = "cookies.sqlite";
82 #define COOKIES_SCHEMA_VERSION 2
84 static const PRInt64 kCookieStaleThreshold
= 60 * PR_USEC_PER_SEC
; // 1 minute in microseconds
86 static const char kOldCookieFileName
[] = "cookies.txt";
89 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
91 // default limits for the cookie list. these can be tuned by the
92 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
93 static const PRUint32 kMaxNumberOfCookies
= 1000;
94 static const PRUint32 kMaxCookiesPerHost
= 50;
95 static const PRUint32 kMaxBytesPerCookie
= 4096;
96 static const PRUint32 kMaxBytesPerPath
= 1024;
98 // these constants represent a decision about a cookie based on user prefs.
99 static const PRUint32 STATUS_ACCEPTED
= 0;
100 static const PRUint32 STATUS_REJECTED
= 1;
101 // STATUS_REJECTED_WITH_ERROR indicates the cookie should be rejected because
102 // of an error (rather than something the user can control). this is used for
103 // notification purposes, since we only want to notify of rejections where
104 // the user can do something about it (e.g. whitelist the site).
105 static const PRUint32 STATUS_REJECTED_WITH_ERROR
= 2;
107 // behavior pref constants
108 static const PRUint32 BEHAVIOR_ACCEPT
= 0;
109 static const PRUint32 BEHAVIOR_REJECTFOREIGN
= 1;
110 static const PRUint32 BEHAVIOR_REJECT
= 2;
112 // pref string constants
113 static const char kPrefCookiesPermissions
[] = "network.cookie.cookieBehavior";
114 static const char kPrefMaxNumberOfCookies
[] = "network.cookie.maxNumber";
115 static const char kPrefMaxCookiesPerHost
[] = "network.cookie.maxPerHost";
117 // struct for temporarily storing cookie attributes during header parsing
118 struct nsCookieAttributes
124 nsCAutoString expires
;
125 nsCAutoString maxage
;
132 // stores linked list iteration state, and provides a rudimentary
133 // list traversal method
138 nsListIter(nsCookieEntry
*aEntry
)
141 , current(aEntry
? aEntry
->Head() : nsnull
) {}
143 nsListIter(nsCookieEntry
*aEntry
,
148 , current(aCurrent
) {}
150 nsListIter
& operator++() { prev
= current
; current
= current
->Next(); return *this; }
152 nsCookieEntry
*entry
;
157 // stores temporary data for enumerating over the hash entries,
158 // since enumeration is done using callback functions
159 struct nsEnumerationData
161 nsEnumerationData(PRInt64 aCurrentTime
,
163 : currentTime(aCurrentTime
)
164 , oldestTime(aOldestTime
)
165 , iter(nsnull
, nsnull
, nsnull
) {}
167 // the current time, in seconds
170 // oldest lastAccessed time in the cookie list. use aOldestTime = LL_MAXINT
171 // to enable this search, LL_MININT to disable it.
174 // an iterator object that points to the desired cookie
178 /******************************************************************************
179 * Cookie logging handlers
180 * used for logging in nsCookieService
181 ******************************************************************************/
185 // in order to do logging, the following environment variables need to be set:
187 // set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
188 // set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
189 // set NSPR_LOG_FILE=cookie.log
191 // this next define has to appear before the include of prlog.h
192 #define FORCE_PR_LOG // Allow logging in the release build
196 // define logging macros for convenience
197 #define SET_COOKIE PR_TRUE
198 #define GET_COOKIE PR_FALSE
201 static PRLogModuleInfo
*sCookieLog
= PR_NewLogModule("cookie");
203 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
204 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
205 #define COOKIE_LOGEVICTED(a) LogEvicted(a)
206 #define COOKIE_LOGSTRING(lvl, fmt) \
208 PR_LOG(sCookieLog, lvl, fmt); \
209 PR_LOG(sCookieLog, lvl, ("\n")); \
213 LogFailure(PRBool aSetCookie
, nsIURI
*aHostURI
, const char *aCookieString
, const char *aReason
)
215 // if logging isn't enabled, return now to save cycles
216 if (!PR_LOG_TEST(sCookieLog
, PR_LOG_WARNING
))
221 aHostURI
->GetAsciiSpec(spec
);
223 PR_LOG(sCookieLog
, PR_LOG_WARNING
,
224 ("===== %s =====\n", aSetCookie
? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
225 PR_LOG(sCookieLog
, PR_LOG_WARNING
,("request URL: %s\n", spec
.get()));
227 PR_LOG(sCookieLog
, PR_LOG_WARNING
,("cookie string: %s\n", aCookieString
));
229 PRExplodedTime explodedTime
;
230 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &explodedTime
);
232 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
234 PR_LOG(sCookieLog
, PR_LOG_WARNING
,("current time: %s", timeString
));
235 PR_LOG(sCookieLog
, PR_LOG_WARNING
,("rejected because %s\n", aReason
));
236 PR_LOG(sCookieLog
, PR_LOG_WARNING
,("\n"));
240 LogCookie(nsCookie
*aCookie
)
242 PRExplodedTime explodedTime
;
243 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &explodedTime
);
245 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
247 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("current time: %s", timeString
));
250 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("----------------\n"));
251 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("name: %s\n", aCookie
->Name().get()));
252 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("value: %s\n", aCookie
->Value().get()));
253 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("%s: %s\n", aCookie
->IsDomain() ? "domain" : "host", aCookie
->Host().get()));
254 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("path: %s\n", aCookie
->Path().get()));
256 PR_ExplodeTime(aCookie
->Expiry() * PR_USEC_PER_SEC
, PR_GMTParameters
, &explodedTime
);
257 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
258 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,
259 ("expires: %s%s", timeString
, aCookie
->IsSession() ? " (at end of session)" : ""));
261 PR_ExplodeTime(aCookie
->CreationID(), PR_GMTParameters
, &explodedTime
);
262 PR_FormatTimeUSEnglish(timeString
, 40, "%c GMT", &explodedTime
);
263 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,
264 ("created: %s (id %lld)", timeString
, aCookie
->CreationID()));
266 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("is secure: %s\n", aCookie
->IsSecure() ? "true" : "false"));
267 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("is httpOnly: %s\n", aCookie
->IsHttpOnly() ? "true" : "false"));
272 LogSuccess(PRBool aSetCookie
, nsIURI
*aHostURI
, const char *aCookieString
, nsCookie
*aCookie
, PRBool aReplacing
)
274 // if logging isn't enabled, return now to save cycles
275 if (!PR_LOG_TEST(sCookieLog
, PR_LOG_DEBUG
)) {
281 aHostURI
->GetAsciiSpec(spec
);
283 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,
284 ("===== %s =====\n", aSetCookie
? "COOKIE ACCEPTED" : "COOKIE SENT"));
285 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("request URL: %s\n", spec
.get()));
286 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("cookie string: %s\n", aCookieString
));
288 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("replaces existing cookie: %s\n", aReplacing
? "true" : "false"));
292 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("\n"));
296 LogEvicted(nsCookie
*aCookie
)
298 // if logging isn't enabled, return now to save cycles
299 if (!PR_LOG_TEST(sCookieLog
, PR_LOG_DEBUG
)) {
303 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("===== COOKIE EVICTED =====\n"));
307 PR_LOG(sCookieLog
, PR_LOG_DEBUG
,("\n"));
310 // inline wrappers to make passing in nsAFlatCStrings easier
312 LogFailure(PRBool aSetCookie
, nsIURI
*aHostURI
, const nsAFlatCString
&aCookieString
, const char *aReason
)
314 LogFailure(aSetCookie
, aHostURI
, aCookieString
.get(), aReason
);
318 LogSuccess(PRBool aSetCookie
, nsIURI
*aHostURI
, const nsAFlatCString
&aCookieString
, nsCookie
*aCookie
, PRBool aReplacing
)
320 LogSuccess(aSetCookie
, aHostURI
, aCookieString
.get(), aCookie
, aReplacing
);
324 #define COOKIE_LOGFAILURE(a, b, c, d) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
325 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
326 #define COOKIE_LOGEVICTED(a) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
327 #define COOKIE_LOGSTRING(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
330 /******************************************************************************
331 * nsCookieService impl:
332 * private list sorting callbacks
334 * these functions return:
335 * < 0 if the first element should come before the second element,
336 * 0 if the first element may come before or after the second element,
337 * > 0 if the first element should come after the second element.
338 ******************************************************************************/
340 // comparison function for sorting cookies before sending to a server.
342 compareCookiesForSending(const void *aElement1
,
343 const void *aElement2
,
346 const nsCookie
*cookie1
= static_cast<const nsCookie
*>(aElement1
);
347 const nsCookie
*cookie2
= static_cast<const nsCookie
*>(aElement2
);
349 // compare by cookie path length in accordance with RFC2109
350 int rv
= cookie2
->Path().Length() - cookie1
->Path().Length();
352 // when path lengths match, older cookies should be listed first. this is
353 // required for backwards compatibility since some websites erroneously
354 // depend on receiving cookies in the order in which they were sent to the
355 // browser! see bug 236772.
356 // note: CreationID is unique, so two id's can never be equal.
357 // we may have overflow problems returning the result directly, so we need branches
358 rv
= (cookie1
->CreationID() > cookie2
->CreationID() ? 1 : -1);
363 /******************************************************************************
364 * nsCookieService impl:
365 * singleton instance ctor/dtor methods
366 ******************************************************************************/
368 nsCookieService
*nsCookieService::gCookieService
= nsnull
;
371 nsCookieService::GetSingleton()
373 if (gCookieService
) {
374 NS_ADDREF(gCookieService
);
375 return gCookieService
;
378 // Create a new singleton nsCookieService.
379 // We AddRef only once since XPCOM has rules about the ordering of module
380 // teardowns - by the time our module destructor is called, it's too late to
381 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
382 // cycles have already been completed and would result in serious leaks.
384 gCookieService
= new nsCookieService();
385 if (gCookieService
) {
386 NS_ADDREF(gCookieService
);
387 if (NS_FAILED(gCookieService
->Init())) {
388 NS_RELEASE(gCookieService
);
392 return gCookieService
;
395 /******************************************************************************
396 * nsCookieService impl:
398 ******************************************************************************/
400 NS_IMPL_ISUPPORTS5(nsCookieService
,
405 nsISupportsWeakReference
)
407 nsCookieService::nsCookieService()
409 , mCookiesPermissions(BEHAVIOR_ACCEPT
)
410 , mMaxNumberOfCookies(kMaxNumberOfCookies
)
411 , mMaxCookiesPerHost(kMaxCookiesPerHost
)
416 nsCookieService::Init()
418 if (!mHostTable
.Init()) {
419 return NS_ERROR_OUT_OF_MEMORY
;
423 mTLDService
= do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
, &rv
);
424 NS_ENSURE_SUCCESS(rv
, rv
);
426 // init our pref and observer
427 nsCOMPtr
<nsIPrefBranch2
> prefBranch
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
429 prefBranch
->AddObserver(kPrefCookiesPermissions
, this, PR_TRUE
);
430 prefBranch
->AddObserver(kPrefMaxNumberOfCookies
, this, PR_TRUE
);
431 prefBranch
->AddObserver(kPrefMaxCookiesPerHost
, this, PR_TRUE
);
432 PrefChanged(prefBranch
);
435 // ignore failure here, since it's non-fatal (we can run fine without
436 // persistent storage - e.g. if there's no profile)
439 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("Init(): InitDB() gave error %x", rv
));
441 mObserverService
= do_GetService("@mozilla.org/observer-service;1");
442 if (mObserverService
) {
443 mObserverService
->AddObserver(this, "profile-before-change", PR_TRUE
);
444 mObserverService
->AddObserver(this, "profile-do-change", PR_TRUE
);
447 mPermissionService
= do_GetService(NS_COOKIEPERMISSION_CONTRACTID
);
448 if (!mPermissionService
) {
449 NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
450 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("Init(): nsICookiePermission implementation not available"));
457 nsCookieService::InitDB()
459 nsCOMPtr
<nsIFile
> cookieFile
;
460 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(cookieFile
));
461 if (NS_FAILED(rv
)) return rv
;
463 cookieFile
->AppendNative(NS_LITERAL_CSTRING(kCookieFileName
));
465 nsCOMPtr
<mozIStorageService
> storage
= do_GetService("@mozilla.org/storage/service;1");
467 return NS_ERROR_UNEXPECTED
;
469 // cache a connection to the cookie database
470 rv
= storage
->OpenUnsharedDatabase(cookieFile
, getter_AddRefs(mDBConn
));
471 if (rv
== NS_ERROR_FILE_CORRUPTED
) {
472 // delete and try again
473 rv
= cookieFile
->Remove(PR_FALSE
);
474 NS_ENSURE_SUCCESS(rv
, rv
);
476 rv
= storage
->OpenUnsharedDatabase(cookieFile
, getter_AddRefs(mDBConn
));
478 NS_ENSURE_SUCCESS(rv
, rv
);
480 PRBool tableExists
= PR_FALSE
;
481 mDBConn
->TableExists(NS_LITERAL_CSTRING("moz_cookies"), &tableExists
);
484 NS_ENSURE_SUCCESS(rv
, rv
);
487 // table already exists; check the schema version before reading
488 PRInt32 dbSchemaVersion
;
489 rv
= mDBConn
->GetSchemaVersion(&dbSchemaVersion
);
490 NS_ENSURE_SUCCESS(rv
, rv
);
492 switch (dbSchemaVersion
) {
494 // every time you increment the database schema, you need to implement
495 // the upgrading code from the previous version to the new one.
498 // add the lastAccessed column to the table
499 rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
500 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
501 NS_ENSURE_SUCCESS(rv
, rv
);
503 // update the schema version
504 rv
= mDBConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
505 NS_ENSURE_SUCCESS(rv
, rv
);
507 // fall through to the next upgrade
509 case COOKIES_SCHEMA_VERSION
:
514 NS_WARNING("couldn't get schema version!");
516 // the table may be usable; someone might've just clobbered the schema
517 // version. we can treat this case like a downgrade using the codepath
518 // below, by verifying the columns we care about are all there. for now,
519 // re-set the schema version in the db, in case the checks succeed (if
520 // they don't, we're dropping the table anyway).
521 rv
= mDBConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
522 NS_ENSURE_SUCCESS(rv
, rv
);
524 // fall through to downgrade check
527 // if columns have been added to the table, we can still use the ones we
528 // understand safely. if columns have been deleted or altered, just
529 // blow away the table and start from scratch! if you change the way
530 // a column is interpreted, make sure you also change its name so this
531 // check will catch it.
534 // check if all the expected columns exist
535 nsCOMPtr
<mozIStorageStatement
> stmt
;
536 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
537 "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly "
538 "FROM moz_cookies"), getter_AddRefs(stmt
));
539 if (NS_SUCCEEDED(rv
))
542 // our columns aren't there - drop the table!
543 rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
544 NS_ENSURE_SUCCESS(rv
, rv
);
547 NS_ENSURE_SUCCESS(rv
, rv
);
553 // make operations on the table asynchronous, for performance
554 mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
556 // open in exclusive mode for performance
557 mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA locking_mode = EXCLUSIVE"));
559 // cache frequently used statements (for insertion, deletion, and updating)
560 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
561 "INSERT INTO moz_cookies "
562 "(id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly) "
563 "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert
));
564 NS_ENSURE_SUCCESS(rv
, rv
);
566 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
567 "DELETE FROM moz_cookies WHERE id = ?1"), getter_AddRefs(mStmtDelete
));
568 NS_ENSURE_SUCCESS(rv
, rv
);
570 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
571 "UPDATE moz_cookies SET lastAccessed = ?1 WHERE id = ?2"), getter_AddRefs(mStmtUpdate
));
572 NS_ENSURE_SUCCESS(rv
, rv
);
574 // check whether to import or just read in the db
578 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(cookieFile
));
579 if (NS_FAILED(rv
)) return rv
;
581 cookieFile
->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName
));
582 rv
= ImportCookies(cookieFile
);
583 if (NS_FAILED(rv
)) return rv
;
585 // we're done importing - delete the old cookie file
586 cookieFile
->Remove(PR_FALSE
);
590 // sets the schema version and creates the moz_cookies table.
592 nsCookieService::CreateTable()
594 // set the schema version, before creating the table
595 nsresult rv
= mDBConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
596 if (NS_FAILED(rv
)) return rv
;
599 return mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
600 "CREATE TABLE moz_cookies ("
601 "id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,"
602 "expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"));
605 nsCookieService::~nsCookieService()
607 gCookieService
= nsnull
;
611 nsCookieService::Observe(nsISupports
*aSubject
,
613 const PRUnichar
*aData
)
616 if (!strcmp(aTopic
, "profile-before-change")) {
617 // The profile is about to change,
618 // or is going away because the application is shutting down.
619 RemoveAllFromMemory();
622 if (!nsCRT::strcmp(aData
, NS_LITERAL_STRING("shutdown-cleanse").get())) {
623 // clear the cookie file
624 nsresult rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
626 NS_WARNING("db delete failed");
629 // Close the DB connection before changing
634 } else if (!strcmp(aTopic
, "profile-do-change")) {
635 // the profile has already changed; init the db from the new location
638 } else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
639 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
641 PrefChanged(prefBranch
);
648 nsCookieService::GetCookieString(nsIURI
*aHostURI
,
649 nsIChannel
*aChannel
,
652 GetCookieInternal(aHostURI
, aChannel
, PR_FALSE
, aCookie
);
658 nsCookieService::GetCookieStringFromHttp(nsIURI
*aHostURI
,
660 nsIChannel
*aChannel
,
663 GetCookieInternal(aHostURI
, aChannel
, PR_TRUE
, aCookie
);
669 nsCookieService::SetCookieString(nsIURI
*aHostURI
,
671 const char *aCookieHeader
,
672 nsIChannel
*aChannel
)
674 return SetCookieStringInternal(aHostURI
, aPrompt
, aCookieHeader
, nsnull
, aChannel
, PR_FALSE
);
678 nsCookieService::SetCookieStringFromHttp(nsIURI
*aHostURI
,
681 const char *aCookieHeader
,
682 const char *aServerTime
,
683 nsIChannel
*aChannel
)
685 return SetCookieStringInternal(aHostURI
, aPrompt
, aCookieHeader
, aServerTime
, aChannel
, PR_TRUE
);
689 nsCookieService::SetCookieStringInternal(nsIURI
*aHostURI
,
691 const char *aCookieHeader
,
692 const char *aServerTime
,
693 nsIChannel
*aChannel
,
697 COOKIE_LOGFAILURE(SET_COOKIE
, nsnull
, aCookieHeader
, "host URI is null");
701 // check default prefs
702 PRUint32 cookieStatus
= CheckPrefs(aHostURI
, aChannel
, aCookieHeader
);
703 // fire a notification if cookie was rejected (but not if there was an error)
704 switch (cookieStatus
) {
705 case STATUS_REJECTED
:
706 NotifyRejected(aHostURI
);
707 case STATUS_REJECTED_WITH_ERROR
:
711 // parse server local time. this is not just done here for efficiency
712 // reasons - if there's an error parsing it, and we need to default it
713 // to the current time, we must do it here since the current time in
714 // SetCookieInternal() will change for each cookie processed (e.g. if the
715 // user is prompted).
716 PRTime tempServerTime
;
718 if (aServerTime
&& PR_ParseTimeString(aServerTime
, PR_TRUE
, &tempServerTime
) == PR_SUCCESS
) {
719 serverTime
= tempServerTime
/ PR_USEC_PER_SEC
;
721 serverTime
= PR_Now() / PR_USEC_PER_SEC
;
724 // start a transaction on the storage db, to optimize insertions.
725 // transaction will automically commit on completion
726 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
728 // switch to a nice string type now, and process each cookie in the header
729 nsDependentCString
cookieHeader(aCookieHeader
);
730 while (SetCookieInternal(aHostURI
, aChannel
, cookieHeader
, serverTime
, aFromHttp
));
735 // notify observers that a cookie was rejected due to the users' prefs.
737 nsCookieService::NotifyRejected(nsIURI
*aHostURI
)
739 if (mObserverService
)
740 mObserverService
->NotifyObservers(aHostURI
, "cookie-rejected", nsnull
);
743 // notify observers that the cookie list changed. there are four possible
745 // "deleted" means a cookie was deleted. aCookie is the deleted cookie.
746 // "added" means a cookie was added. aCookie is the added cookie.
747 // "changed" means a cookie was altered. aCookie is the new cookie.
748 // "cleared" means the entire cookie list was cleared. aCookie is null.
750 nsCookieService::NotifyChanged(nsICookie2
*aCookie
,
751 const PRUnichar
*aData
)
753 if (mObserverService
)
754 mObserverService
->NotifyObservers(aCookie
, "cookie-changed", aData
);
757 /******************************************************************************
760 ******************************************************************************/
763 nsCookieService::PrefChanged(nsIPrefBranch
*aPrefBranch
)
766 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookiesPermissions
, &val
)))
767 mCookiesPermissions
= (PRUint8
) LIMIT(val
, 0, 2, 0);
769 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxNumberOfCookies
, &val
)))
770 mMaxNumberOfCookies
= (PRUint16
) LIMIT(val
, 0, 0xFFFF, 0xFFFF);
772 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxCookiesPerHost
, &val
)))
773 mMaxCookiesPerHost
= (PRUint16
) LIMIT(val
, 0, 0xFFFF, 0xFFFF);
776 /******************************************************************************
777 * nsICookieManager impl:
779 ******************************************************************************/
782 nsCookieService::RemoveAll()
784 RemoveAllFromMemory();
786 // clear the cookie file
788 nsresult rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
790 // Database must be corrupted, so remove it completely.
791 nsCOMPtr
<nsIFile
> dbFile
;
792 mDBConn
->GetDatabaseFile(getter_AddRefs(dbFile
));
794 dbFile
->Remove(PR_FALSE
);
799 NotifyChanged(nsnull
, NS_LITERAL_STRING("cleared").get());
803 // helper struct for passing arguments into hash enumeration callback.
804 struct nsGetEnumeratorData
806 nsGetEnumeratorData(nsCOMArray
<nsICookie
> *aArray
, PRInt64 aTime
)
808 , currentTime(aTime
) {}
810 nsCOMArray
<nsICookie
> *array
;
814 static PLDHashOperator
815 COMArrayCallback(nsCookieEntry
*aEntry
,
818 nsGetEnumeratorData
*data
= static_cast<nsGetEnumeratorData
*>(aArg
);
820 for (nsCookie
*cookie
= aEntry
->Head(); cookie
; cookie
= cookie
->Next()) {
821 // only append non-expired cookies
822 if (cookie
->Expiry() > data
->currentTime
)
823 data
->array
->AppendObject(cookie
);
825 return PL_DHASH_NEXT
;
829 nsCookieService::GetEnumerator(nsISimpleEnumerator
**aEnumerator
)
831 nsCOMArray
<nsICookie
> cookieList(mCookieCount
);
832 nsGetEnumeratorData
data(&cookieList
, PR_Now() / PR_USEC_PER_SEC
);
834 mHostTable
.EnumerateEntries(COMArrayCallback
, &data
);
836 return NS_NewArrayEnumerator(aEnumerator
, cookieList
);
840 nsCookieService::Add(const nsACString
&aDomain
,
841 const nsACString
&aPath
,
842 const nsACString
&aName
,
843 const nsACString
&aValue
,
849 PRInt64 currentTimeInUsec
= PR_Now();
851 nsRefPtr
<nsCookie
> cookie
=
852 nsCookie::Create(aName
, aValue
, aDomain
, aPath
,
860 return NS_ERROR_OUT_OF_MEMORY
;
863 AddInternal(cookie
, currentTimeInUsec
/ PR_USEC_PER_SEC
, nsnull
, nsnull
, PR_TRUE
);
868 nsCookieService::Remove(const nsACString
&aHost
,
869 const nsACString
&aName
,
870 const nsACString
&aPath
,
873 nsListIter matchIter
;
874 if (FindCookie(PromiseFlatCString(aHost
),
875 PromiseFlatCString(aName
),
876 PromiseFlatCString(aPath
),
878 PR_Now() / PR_USEC_PER_SEC
)) {
879 nsRefPtr
<nsCookie
> cookie
= matchIter
.current
;
880 RemoveCookieFromList(matchIter
);
881 NotifyChanged(cookie
, NS_LITERAL_STRING("deleted").get());
884 // check if we need to add the host to the permissions blacklist.
885 if (aBlocked
&& mPermissionService
) {
886 nsCAutoString
host(NS_LITERAL_CSTRING("http://"));
888 // strip off the domain dot, if necessary
889 if (!aHost
.IsEmpty() && aHost
.First() == '.')
890 host
.Append(Substring(aHost
, 1, aHost
.Length() - 1));
894 nsCOMPtr
<nsIURI
> uri
;
895 NS_NewURI(getter_AddRefs(uri
), host
);
898 mPermissionService
->SetAccess(uri
, nsICookiePermission::ACCESS_DENY
);
904 /******************************************************************************
905 * nsCookieService impl:
906 * private file I/O functions
907 ******************************************************************************/
910 nsCookieService::Read()
914 // delete expired cookies, before we read in the db
916 // scope the deletion, so the write lock is released when finished
917 nsCOMPtr
<mozIStorageStatement
> stmtDeleteExpired
;
918 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE expiry <= ?1"),
919 getter_AddRefs(stmtDeleteExpired
));
920 NS_ENSURE_SUCCESS(rv
, rv
);
922 rv
= stmtDeleteExpired
->BindInt64Parameter(0, PR_Now() / PR_USEC_PER_SEC
);
923 NS_ENSURE_SUCCESS(rv
, rv
);
926 rv
= stmtDeleteExpired
->ExecuteStep(&hasResult
);
927 NS_ENSURE_SUCCESS(rv
, rv
);
930 // let the reading begin!
931 nsCOMPtr
<mozIStorageStatement
> stmt
;
932 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
933 "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly "
934 "FROM moz_cookies"), getter_AddRefs(stmt
));
935 NS_ENSURE_SUCCESS(rv
, rv
);
937 nsCAutoString name
, value
, host
, path
;
939 while (NS_SUCCEEDED(stmt
->ExecuteStep(&hasResult
)) && hasResult
) {
940 PRInt64 creationID
= stmt
->AsInt64(0);
942 stmt
->GetUTF8String(1, name
);
943 stmt
->GetUTF8String(2, value
);
944 stmt
->GetUTF8String(3, host
);
945 stmt
->GetUTF8String(4, path
);
947 PRInt64 expiry
= stmt
->AsInt64(5);
948 PRInt64 lastAccessed
= stmt
->AsInt64(6);
949 PRBool isSecure
= 0 != stmt
->AsInt32(7);
950 PRBool isHttpOnly
= 0 != stmt
->AsInt32(8);
952 // create a new nsCookie and assign the data.
953 nsCookie
* newCookie
=
954 nsCookie::Create(name
, value
, host
, path
,
962 return NS_ERROR_OUT_OF_MEMORY
;
964 if (!AddCookieToList(newCookie
, PR_FALSE
))
965 // It is purpose that created us; purpose that connects us;
966 // purpose that pulls us; that guides us; that drives us.
967 // It is purpose that defines us; purpose that binds us.
968 // When a cookie no longer has purpose, it has a choice:
969 // it can return to the source to be deleted, or it can go
970 // into exile, and stay hidden inside the Matrix.
971 // Let's choose deletion.
975 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("Read(): %ld cookies read", mCookieCount
));
981 nsCookieService::ImportCookies(nsIFile
*aCookieFile
)
984 nsCOMPtr
<nsIInputStream
> fileInputStream
;
985 rv
= NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream
), aCookieFile
);
986 if (NS_FAILED(rv
)) return rv
;
988 nsCOMPtr
<nsILineInputStream
> lineInputStream
= do_QueryInterface(fileInputStream
, &rv
);
989 if (NS_FAILED(rv
)) return rv
;
991 // start a transaction on the storage db, to optimize insertions.
992 // transaction will automically commit on completion
993 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
995 static const char kTrue
[] = "TRUE";
997 nsCAutoString buffer
;
998 PRBool isMore
= PR_TRUE
;
999 PRInt32 hostIndex
, isDomainIndex
, pathIndex
, secureIndex
, expiresIndex
, nameIndex
, cookieIndex
;
1000 nsASingleFragmentCString::char_iterator iter
;
1003 PRBool isDomain
, isHttpOnly
= PR_FALSE
;
1004 PRUint32 originalCookieCount
= mCookieCount
;
1006 PRInt64 currentTimeInUsec
= PR_Now();
1007 PRInt64 currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
1008 // we use lastAccessedCounter to keep cookies in recently-used order,
1009 // so we start by initializing to currentTime (somewhat arbitrary)
1010 PRInt64 lastAccessedCounter
= currentTimeInUsec
;
1014 * host \t isDomain \t path \t secure \t expires \t name \t cookie
1016 * if this format isn't respected we move onto the next line in the file.
1017 * isDomain is "TRUE" or "FALSE" (default to "FALSE")
1018 * isSecure is "TRUE" or "FALSE" (default to "TRUE")
1019 * expires is a PRInt64 integer
1020 * note 1: cookie can contain tabs.
1021 * note 2: cookies will be stored in order of lastAccessed time:
1022 * most-recently used come first; least-recently-used come last.
1026 * ...but due to bug 178933, we hide HttpOnly cookies from older code
1027 * in a comment, so they don't expose HttpOnly cookies to JS.
1029 * The format for HttpOnly cookies is
1031 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
1035 while (isMore
&& NS_SUCCEEDED(lineInputStream
->ReadLine(buffer
, &isMore
))) {
1036 if (StringBeginsWith(buffer
, NS_LITERAL_CSTRING(kHttpOnlyPrefix
))) {
1037 isHttpOnly
= PR_TRUE
;
1038 hostIndex
= sizeof(kHttpOnlyPrefix
) - 1;
1039 } else if (buffer
.IsEmpty() || buffer
.First() == '#') {
1042 isHttpOnly
= PR_FALSE
;
1046 // this is a cheap, cheesy way of parsing a tab-delimited line into
1047 // string indexes, which can be lopped off into substrings. just for
1048 // purposes of obfuscation, it also checks that each token was found.
1049 // todo: use iterators?
1050 if ((isDomainIndex
= buffer
.FindChar('\t', hostIndex
) + 1) == 0 ||
1051 (pathIndex
= buffer
.FindChar('\t', isDomainIndex
) + 1) == 0 ||
1052 (secureIndex
= buffer
.FindChar('\t', pathIndex
) + 1) == 0 ||
1053 (expiresIndex
= buffer
.FindChar('\t', secureIndex
) + 1) == 0 ||
1054 (nameIndex
= buffer
.FindChar('\t', expiresIndex
) + 1) == 0 ||
1055 (cookieIndex
= buffer
.FindChar('\t', nameIndex
) + 1) == 0) {
1059 // check the expirytime first - if it's expired, ignore
1060 // nullstomp the trailing tab, to avoid copying the string
1061 buffer
.BeginWriting(iter
);
1062 *(iter
+= nameIndex
- 1) = char(0);
1063 numInts
= PR_sscanf(buffer
.get() + expiresIndex
, "%lld", &expires
);
1064 if (numInts
!= 1 || expires
< currentTime
) {
1068 isDomain
= Substring(buffer
, isDomainIndex
, pathIndex
- isDomainIndex
- 1).EqualsLiteral(kTrue
);
1069 const nsASingleFragmentCString
&host
= Substring(buffer
, hostIndex
, isDomainIndex
- hostIndex
- 1);
1070 // check for bad legacy cookies (domain not starting with a dot, or containing a port),
1072 if ((isDomain
&& !host
.IsEmpty() && host
.First() != '.') ||
1073 host
.FindChar(':') != kNotFound
) {
1077 // create a new nsCookie and assign the data.
1078 // we don't know the cookie creation time, so just use the current time;
1079 // this is okay, since nsCookie::Create() will make sure the creation id
1080 // ends up monotonically increasing.
1081 nsRefPtr
<nsCookie
> newCookie
=
1082 nsCookie::Create(Substring(buffer
, nameIndex
, cookieIndex
- nameIndex
- 1),
1083 Substring(buffer
, cookieIndex
, buffer
.Length() - cookieIndex
),
1085 Substring(buffer
, pathIndex
, secureIndex
- pathIndex
- 1),
1087 lastAccessedCounter
,
1090 Substring(buffer
, secureIndex
, expiresIndex
- secureIndex
- 1).EqualsLiteral(kTrue
),
1093 return NS_ERROR_OUT_OF_MEMORY
;
1096 // trick: preserve the most-recently-used cookie ordering,
1097 // by successively decrementing the lastAccessed time
1098 lastAccessedCounter
--;
1100 if (originalCookieCount
== 0)
1101 AddCookieToList(newCookie
);
1103 AddInternal(newCookie
, currentTime
, nsnull
, nsnull
, PR_TRUE
);
1106 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("ImportCookies(): %ld cookies imported", mCookieCount
));
1111 /******************************************************************************
1112 * nsCookieService impl:
1113 * private GetCookie/SetCookie helpers
1114 ******************************************************************************/
1116 // helper function for GetCookieList
1117 static inline PRBool
ispathdelimiter(char c
) { return c
== '/' || c
== '?' || c
== '#' || c
== ';'; }
1120 nsCookieService::GetCookieInternal(nsIURI
*aHostURI
,
1121 nsIChannel
*aChannel
,
1128 COOKIE_LOGFAILURE(GET_COOKIE
, nsnull
, nsnull
, "host URI is null");
1132 // check default prefs
1133 PRUint32 cookieStatus
= CheckPrefs(aHostURI
, aChannel
, nsnull
);
1134 // for GetCookie(), we don't fire rejection notifications.
1135 switch (cookieStatus
) {
1136 case STATUS_REJECTED
:
1137 case STATUS_REJECTED_WITH_ERROR
:
1141 // get host and path from the nsIURI
1142 // note: there was a "check if host has embedded whitespace" here.
1143 // it was removed since this check was added into the nsIURI impl (bug 146094).
1144 nsCAutoString hostFromURI
, pathFromURI
;
1145 if (NS_FAILED(aHostURI
->GetAsciiHost(hostFromURI
)) ||
1146 NS_FAILED(aHostURI
->GetPath(pathFromURI
))) {
1147 COOKIE_LOGFAILURE(GET_COOKIE
, aHostURI
, nsnull
, "couldn't get host/path from URI");
1150 // trim trailing dots
1151 hostFromURI
.Trim(".");
1152 // insert a leading dot, so we begin the hash lookup with the
1153 // equivalent domain cookie host
1154 hostFromURI
.Insert(NS_LITERAL_CSTRING("."), 0);
1156 // check if aHostURI is using an https secure protocol.
1157 // if it isn't, then we can't send a secure cookie over the connection.
1158 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1160 if (NS_FAILED(aHostURI
->SchemeIs("https", &isSecure
))) {
1161 isSecure
= PR_FALSE
;
1165 nsAutoVoidArray foundCookieList
;
1166 PRInt64 currentTimeInUsec
= PR_Now();
1167 PRInt64 currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
1168 const char *currentDot
= hostFromURI
.get();
1169 const char *nextDot
= currentDot
+ 1;
1170 PRBool stale
= PR_FALSE
;
1172 // begin hash lookup, walking up the subdomain levels.
1173 // we use nextDot to force a lookup of the original host (without leading dot).
1175 nsCookieEntry
*entry
= mHostTable
.GetEntry(currentDot
);
1176 cookie
= entry
? entry
->Head() : nsnull
;
1177 for (; cookie
; cookie
= cookie
->Next()) {
1178 // if the cookie is secure and the host scheme isn't, we can't send it
1179 if (cookie
->IsSecure() && !isSecure
) {
1183 // if the cookie is httpOnly and it's not going directly to the HTTP
1184 // connection, don't send it
1185 if (cookie
->IsHttpOnly() && !aHttpBound
) {
1189 // calculate cookie path length, excluding trailing '/'
1190 PRUint32 cookiePathLen
= cookie
->Path().Length();
1191 if (cookiePathLen
> 0 && cookie
->Path().Last() == '/') {
1195 // if the nsIURI path is shorter than the cookie path, don't send it back
1196 if (!StringBeginsWith(pathFromURI
, Substring(cookie
->Path(), 0, cookiePathLen
))) {
1200 if (pathFromURI
.Length() > cookiePathLen
&&
1201 !ispathdelimiter(pathFromURI
.CharAt(cookiePathLen
))) {
1203 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
1204 * '/' is the "standard" case; the '?' test allows a site at host/abc?def
1205 * to receive a cookie that has a path attribute of abc. this seems
1206 * strange but at least one major site (citibank, bug 156725) depends
1207 * on it. The test for # and ; are put in to proactively avoid problems
1208 * with other sites - these are the only other chars allowed in the path.
1213 // check if the cookie has expired
1214 if (cookie
->Expiry() <= currentTime
) {
1218 // all checks passed - add to list and check if lastAccessed stamp needs updating
1219 foundCookieList
.AppendElement(cookie
);
1220 if (currentTimeInUsec
- cookie
->LastAccessed() > kCookieStaleThreshold
)
1224 currentDot
= nextDot
;
1226 nextDot
= strchr(currentDot
+ 1, '.');
1228 } while (currentDot
);
1230 PRInt32 count
= foundCookieList
.Count();
1234 // update lastAccessed timestamps. we only do this if the timestamp is stale
1235 // by a certain amount, to avoid thrashing the db during pageload.
1237 // start a transaction on the storage db, to optimize updates.
1238 // transaction will automically commit on completion.
1239 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
1241 for (PRInt32 i
= 0; i
< count
; ++i
) {
1242 cookie
= static_cast<nsCookie
*>(foundCookieList
.ElementAt(i
));
1244 if (currentTimeInUsec
- cookie
->LastAccessed() > kCookieStaleThreshold
)
1245 UpdateCookieInList(cookie
, currentTimeInUsec
);
1249 // return cookies in order of path length; longest to shortest.
1250 // this is required per RFC2109. if cookies match in length,
1251 // then sort by creation time (see bug 236772).
1252 foundCookieList
.Sort(compareCookiesForSending
, nsnull
);
1254 nsCAutoString cookieData
;
1255 for (PRInt32 i
= 0; i
< count
; ++i
) {
1256 cookie
= static_cast<nsCookie
*>(foundCookieList
.ElementAt(i
));
1258 // check if we have anything to write
1259 if (!cookie
->Name().IsEmpty() || !cookie
->Value().IsEmpty()) {
1260 // if we've already added a cookie to the return list, append a "; " so
1261 // that subsequent cookies are delimited in the final list.
1262 if (!cookieData
.IsEmpty()) {
1263 cookieData
.AppendLiteral("; ");
1266 if (!cookie
->Name().IsEmpty()) {
1267 // we have a name and value - write both
1268 cookieData
+= cookie
->Name() + NS_LITERAL_CSTRING("=") + cookie
->Value();
1271 cookieData
+= cookie
->Value();
1276 // it's wasteful to alloc a new string; but we have no other choice, until we
1277 // fix the callers to use nsACStrings.
1278 if (!cookieData
.IsEmpty()) {
1279 COOKIE_LOGSUCCESS(GET_COOKIE
, aHostURI
, cookieData
, nsnull
, nsnull
);
1280 *aCookie
= ToNewCString(cookieData
);
1284 // processes a single cookie, and returns PR_TRUE if there are more cookies
1287 nsCookieService::SetCookieInternal(nsIURI
*aHostURI
,
1288 nsIChannel
*aChannel
,
1289 nsDependentCString
&aCookieHeader
,
1290 PRInt64 aServerTime
,
1293 // create a stack-based nsCookieAttributes, to store all the
1294 // attributes parsed from the cookie
1295 nsCookieAttributes cookieAttributes
;
1297 // init expiryTime such that session cookies won't prematurely expire
1298 cookieAttributes
.expiryTime
= LL_MAXINT
;
1300 // aCookieHeader is an in/out param to point to the next cookie, if
1301 // there is one. Save the present value for logging purposes
1302 nsDependentCString
savedCookieHeader(aCookieHeader
);
1304 // newCookie says whether there are multiple cookies in the header;
1305 // so we can handle them separately.
1306 PRBool newCookie
= ParseAttributes(aCookieHeader
, cookieAttributes
);
1308 PRInt64 currentTimeInUsec
= PR_Now();
1310 // calculate expiry time of cookie.
1311 cookieAttributes
.isSession
= GetExpiry(cookieAttributes
, aServerTime
,
1312 currentTimeInUsec
/ PR_USEC_PER_SEC
);
1314 // reject cookie if it's over the size limit, per RFC2109
1315 if ((cookieAttributes
.name
.Length() + cookieAttributes
.value
.Length()) > kMaxBytesPerCookie
) {
1316 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie too big (> 4kb)");
1320 if (cookieAttributes
.name
.FindChar('\t') != kNotFound
) {
1321 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "invalid name character");
1325 // domain & path checks
1326 if (!CheckDomain(cookieAttributes
, aHostURI
)) {
1327 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the domain tests");
1330 if (!CheckPath(cookieAttributes
, aHostURI
)) {
1331 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the path tests");
1335 // create a new nsCookie and copy attributes
1336 nsRefPtr
<nsCookie
> cookie
=
1337 nsCookie::Create(cookieAttributes
.name
,
1338 cookieAttributes
.value
,
1339 cookieAttributes
.host
,
1340 cookieAttributes
.path
,
1341 cookieAttributes
.expiryTime
,
1344 cookieAttributes
.isSession
,
1345 cookieAttributes
.isSecure
,
1346 cookieAttributes
.isHttpOnly
);
1350 // check permissions from site permission list, or ask the user,
1351 // to determine if we can set the cookie
1352 if (mPermissionService
) {
1354 // we need to think about prompters/parent windows here - TestPermission
1355 // needs one to prompt, so right now it has to fend for itself to get one
1356 mPermissionService
->CanSetCookie(aHostURI
,
1358 static_cast<nsICookie2
*>(static_cast<nsCookie
*>(cookie
)),
1359 &cookieAttributes
.isSession
,
1360 &cookieAttributes
.expiryTime
,
1363 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie rejected by permission manager");
1364 NotifyRejected(aHostURI
);
1368 // update isSession and expiry attributes, in case they changed
1369 cookie
->SetIsSession(cookieAttributes
.isSession
);
1370 cookie
->SetExpiry(cookieAttributes
.expiryTime
);
1373 // add the cookie to the list. AddInternal() takes care of logging.
1374 // we get the current time again here, since it may have changed during prompting
1375 AddInternal(cookie
, PR_Now() / PR_USEC_PER_SEC
, aHostURI
, savedCookieHeader
.get(), aFromHttp
);
1379 // this is a backend function for adding a cookie to the list, via SetCookie.
1380 // also used in the cookie manager, for profile migration from IE.
1381 // it either replaces an existing cookie; or adds the cookie to the hashtable,
1382 // and deletes a cookie (if maximum number of cookies has been
1383 // reached). also performs list maintenance by removing expired cookies.
1385 nsCookieService::AddInternal(nsCookie
*aCookie
,
1386 PRInt64 aCurrentTime
,
1388 const char *aCookieHeader
,
1391 // if the new cookie is httponly, make sure we're not coming from script
1392 if (!aFromHttp
&& aCookie
->IsHttpOnly()) {
1393 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "cookie is httponly; coming from script");
1397 // start a transaction on the storage db, to optimize deletions/insertions.
1398 // transaction will automically commit on completion. if we already have a
1399 // transaction (e.g. from SetCookie*()), this will have no effect.
1400 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
1402 nsListIter matchIter
;
1403 PRBool foundCookie
= FindCookie(aCookie
->Host(), aCookie
->Name(), aCookie
->Path(),
1404 matchIter
, aCurrentTime
);
1406 nsRefPtr
<nsCookie
> oldCookie
;
1408 oldCookie
= matchIter
.current
;
1410 // if the old cookie is httponly, make sure we're not coming from script
1411 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
1412 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "previously stored cookie is httponly; coming from script");
1416 RemoveCookieFromList(matchIter
);
1418 // check if the cookie has expired
1419 if (aCookie
->Expiry() <= aCurrentTime
) {
1420 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "previously stored cookie was deleted");
1421 NotifyChanged(oldCookie
, NS_LITERAL_STRING("deleted").get());
1425 // preserve creation time of cookie
1427 aCookie
->SetCreationID(oldCookie
->CreationID());
1430 // check if cookie has already expired
1431 if (aCookie
->Expiry() <= aCurrentTime
) {
1432 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "cookie has already expired");
1436 // check if we have to delete an old cookie.
1437 nsEnumerationData
data(aCurrentTime
, LL_MAXINT
);
1438 if (CountCookiesFromHostInternal(aCookie
->RawHost(), data
) >= mMaxCookiesPerHost
) {
1439 // remove the oldest cookie from host
1440 oldCookie
= data
.iter
.current
;
1441 RemoveCookieFromList(data
.iter
);
1443 } else if (mCookieCount
>= mMaxNumberOfCookies
) {
1444 // try to make room, by removing expired cookies
1445 RemoveExpiredCookies(aCurrentTime
);
1447 // check if we still have to get rid of something
1448 if (mCookieCount
>= mMaxNumberOfCookies
) {
1449 // find the position of the oldest cookie, and remove it
1450 data
.oldestTime
= LL_MAXINT
;
1451 FindOldestCookie(data
);
1452 oldCookie
= data
.iter
.current
;
1453 RemoveCookieFromList(data
.iter
);
1457 // if we deleted an old cookie, notify consumers
1459 COOKIE_LOGEVICTED(oldCookie
);
1460 NotifyChanged(oldCookie
, NS_LITERAL_STRING("deleted").get());
1464 // add the cookie to head of list
1465 AddCookieToList(aCookie
);
1466 NotifyChanged(aCookie
, foundCookie
? NS_LITERAL_STRING("changed").get()
1467 : NS_LITERAL_STRING("added").get());
1469 COOKIE_LOGSUCCESS(SET_COOKIE
, aHostURI
, aCookieHeader
, aCookie
, foundCookie
!= nsnull
);
1472 /******************************************************************************
1473 * nsCookieService impl:
1474 * private cookie header parsing functions
1475 ******************************************************************************/
1477 // The following comment block elucidates the function of ParseAttributes.
1478 /******************************************************************************
1479 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1480 ** please note: this BNF deviates from both specifications, and reflects this
1481 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1483 ** Differences from RFC2109/2616 and explanations:
1485 The grammar described by this specification is word-based. Except
1486 where noted otherwise, linear white space (<LWS>) can be included
1487 between any two adjacent words (token or quoted-string), and
1488 between adjacent words and separators, without changing the
1489 interpretation of a field.
1490 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1492 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1493 common use inside values.
1495 3. tokens and values have looser restrictions on allowed characters than
1496 spec. This is also due to certain characters being in common use inside
1497 values. We allow only '=' to separate token/value pairs, and ';' to
1498 terminate tokens or values. <LWS> is allowed within tokens and values
1501 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1502 reject control chars or non-ASCII chars. This is erring on the loose
1503 side, since there's probably no good reason to enforce this strictness.
1505 5. cookie <NAME> is optional, where spec requires it. This is a fairly
1506 trivial case, but allows the flexibility of setting only a cookie <VALUE>
1507 with a blank <NAME> and is required by some sites (see bug 169091).
1509 6. Attribute "HttpOnly", not covered in the RFCs, is supported
1513 token = 1*<any allowed-chars except separators>
1514 value = token-value | quoted-string
1515 token-value = 1*<any allowed-chars except value-sep>
1516 quoted-string = ( <"> *( qdtext | quoted-pair ) <"> )
1517 qdtext = <any allowed-chars except <">> ; CR | LF removed by necko
1518 quoted-pair = "\" <any OCTET except NUL or cookie-sep> ; CR | LF removed by necko
1519 separators = ";" | "="
1521 cookie-sep = CR | LF
1522 allowed-chars = <any OCTET except NUL or cookie-sep>
1523 OCTET = <any 8-bit sequence of data>
1525 NUL = <US-ASCII NUL, null control character (0)>
1526 CR = <US-ASCII CR, carriage return (13)>
1527 LF = <US-ASCII LF, linefeed (10)>
1528 SP = <US-ASCII SP, space (32)>
1529 HT = <US-ASCII HT, horizontal-tab (9)>
1531 set-cookie = "Set-Cookie:" cookies
1532 cookies = cookie *( cookie-sep cookie )
1533 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1534 NAME = token ; cookie name
1535 VALUE = value ; cookie value
1536 cookie-av = token ["=" value]
1538 valid values for cookie-av (checked post-parsing) are:
1539 cookie-av = "Path" "=" value
1540 | "Domain" "=" value
1541 | "Expires" "=" value
1542 | "Max-Age" "=" value
1543 | "Comment" "=" value
1544 | "Version" "=" value
1548 ******************************************************************************/
1550 // helper functions for GetTokenValue
1551 static inline PRBool
iswhitespace (char c
) { return c
== ' ' || c
== '\t'; }
1552 static inline PRBool
isterminator (char c
) { return c
== '\n' || c
== '\r'; }
1553 static inline PRBool
isquoteterminator(char c
) { return isterminator(c
) || c
== '"'; }
1554 static inline PRBool
isvalueseparator (char c
) { return isterminator(c
) || c
== ';'; }
1555 static inline PRBool
istokenseparator (char c
) { return isvalueseparator(c
) || c
== '='; }
1557 // Parse a single token/value pair.
1558 // Returns PR_TRUE if a cookie terminator is found, so caller can parse new cookie.
1560 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator
&aIter
,
1561 nsASingleFragmentCString::const_char_iterator
&aEndIter
,
1562 nsDependentCSubstring
&aTokenString
,
1563 nsDependentCSubstring
&aTokenValue
,
1564 PRBool
&aEqualsFound
)
1566 nsASingleFragmentCString::const_char_iterator start
, lastSpace
;
1567 // initialize value string to clear garbage
1568 aTokenValue
.Rebind(aIter
, aIter
);
1570 // find <token>, including any <LWS> between the end-of-token and the
1571 // token separator. we'll remove trailing <LWS> next
1572 while (aIter
!= aEndIter
&& iswhitespace(*aIter
))
1575 while (aIter
!= aEndIter
&& !istokenseparator(*aIter
))
1578 // remove trailing <LWS>; first check we're not at the beginning
1580 if (lastSpace
!= start
) {
1581 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
));
1584 aTokenString
.Rebind(start
, lastSpace
);
1586 aEqualsFound
= (*aIter
== '=');
1589 while (++aIter
!= aEndIter
&& iswhitespace(*aIter
));
1593 if (*aIter
== '"') {
1594 // process <quoted-string>
1595 // (note: cookie terminators, CR | LF, can't happen:
1596 // they're removed by necko before the header gets here)
1597 // assume value mangled if no terminating '"', return
1598 while (++aIter
!= aEndIter
&& !isquoteterminator(*aIter
)) {
1599 // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
1600 // we increment once over the backwhack, nullcheck, then continue to the 'while',
1601 // which increments over the backwhacked char. one exception - we don't allow
1602 // CR | LF here either (see above about necko)
1603 if (*aIter
== '\\' && (++aIter
== aEndIter
|| isterminator(*aIter
)))
1607 if (aIter
!= aEndIter
&& !isterminator(*aIter
)) {
1608 // include terminating quote in attribute string
1609 aTokenValue
.Rebind(start
, ++aIter
);
1611 while (aIter
!= aEndIter
&& !isvalueseparator(*aIter
))
1615 // process <token-value>
1616 // just look for ';' to terminate ('=' allowed)
1617 while (aIter
!= aEndIter
&& !isvalueseparator(*aIter
))
1620 // remove trailing <LWS>; first check we're not at the beginning
1621 if (aIter
!= start
) {
1623 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
));
1624 aTokenValue
.Rebind(start
, ++lastSpace
);
1629 // aIter is on ';', or terminator, or EOS
1630 if (aIter
!= aEndIter
) {
1631 // if on terminator, increment past & return PR_TRUE to process new cookie
1632 if (isterminator(*aIter
)) {
1636 // fall-through: aIter is on ';', increment and return PR_FALSE
1642 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
1643 // cookie struct here, because we don't know which one to use until we've parsed the header.
1645 nsCookieService::ParseAttributes(nsDependentCString
&aCookieHeader
,
1646 nsCookieAttributes
&aCookieAttributes
)
1648 static const char kPath
[] = "path";
1649 static const char kDomain
[] = "domain";
1650 static const char kExpires
[] = "expires";
1651 static const char kMaxage
[] = "max-age";
1652 static const char kSecure
[] = "secure";
1653 static const char kHttpOnly
[] = "httponly";
1655 nsASingleFragmentCString::const_char_iterator tempBegin
, tempEnd
;
1656 nsASingleFragmentCString::const_char_iterator cookieStart
, cookieEnd
;
1657 aCookieHeader
.BeginReading(cookieStart
);
1658 aCookieHeader
.EndReading(cookieEnd
);
1660 aCookieAttributes
.isSecure
= PR_FALSE
;
1661 aCookieAttributes
.isHttpOnly
= PR_FALSE
;
1663 nsDependentCSubstring
tokenString(cookieStart
, cookieStart
);
1664 nsDependentCSubstring
tokenValue (cookieStart
, cookieStart
);
1665 PRBool newCookie
, equalsFound
;
1667 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1668 // if we find multiple cookies, return for processing
1669 // note: if there's no '=', we assume token is <VALUE>. this is required by
1670 // some sites (see bug 169091).
1671 // XXX fix the parser to parse according to <VALUE> grammar for this case
1672 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
1674 aCookieAttributes
.name
= tokenString
;
1675 aCookieAttributes
.value
= tokenValue
;
1677 aCookieAttributes
.value
= tokenString
;
1680 // extract remaining attributes
1681 while (cookieStart
!= cookieEnd
&& !newCookie
) {
1682 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
1684 if (!tokenValue
.IsEmpty()) {
1685 tokenValue
.BeginReading(tempBegin
);
1686 tokenValue
.EndReading(tempEnd
);
1687 if (*tempBegin
== '"' && *--tempEnd
== '"') {
1688 // our parameter is a quoted-string; remove quotes for later parsing
1689 tokenValue
.Rebind(++tempBegin
, tempEnd
);
1693 // decide which attribute we have, and copy the string
1694 if (tokenString
.LowerCaseEqualsLiteral(kPath
))
1695 aCookieAttributes
.path
= tokenValue
;
1697 else if (tokenString
.LowerCaseEqualsLiteral(kDomain
))
1698 aCookieAttributes
.host
= tokenValue
;
1700 else if (tokenString
.LowerCaseEqualsLiteral(kExpires
))
1701 aCookieAttributes
.expires
= tokenValue
;
1703 else if (tokenString
.LowerCaseEqualsLiteral(kMaxage
))
1704 aCookieAttributes
.maxage
= tokenValue
;
1706 // ignore any tokenValue for isSecure; just set the boolean
1707 else if (tokenString
.LowerCaseEqualsLiteral(kSecure
))
1708 aCookieAttributes
.isSecure
= PR_TRUE
;
1710 // ignore any tokenValue for isHttpOnly (see bug 178993);
1711 // just set the boolean
1712 else if (tokenString
.LowerCaseEqualsLiteral(kHttpOnly
))
1713 aCookieAttributes
.isHttpOnly
= PR_TRUE
;
1716 // rebind aCookieHeader, in case we need to process another cookie
1717 aCookieHeader
.Rebind(cookieStart
, cookieEnd
);
1721 /******************************************************************************
1722 * nsCookieService impl:
1723 * private domain & permission compliance enforcement functions
1724 ******************************************************************************/
1727 nsCookieService::IsForeign(nsIURI
*aHostURI
,
1731 nsCAutoString currentHost
, firstHost
;
1732 if (NS_FAILED(aHostURI
->GetAsciiHost(currentHost
)) ||
1733 NS_FAILED(aFirstURI
->GetAsciiHost(firstHost
))) {
1737 // trim trailing dots
1738 currentHost
.Trim(".");
1739 firstHost
.Trim(".");
1741 // fast path: check if the two hosts are identical.
1742 // this also covers two special cases:
1743 // 1) if we're dealing with IP addresses, require an exact match. this
1744 // eliminates any chance of IP address funkiness (e.g. the alias 127.1
1745 // domain-matching 99.54.127.1). bug 105917 originally noted the requirement
1746 // to deal with IP addresses. note that GetBaseDomain() below will return an
1747 // error if the URI is an IP address.
1748 // 2) we also need this for the (rare) case where the site is actually an eTLD,
1749 // e.g. http://co.tv; GetBaseDomain() will throw an error and we might
1750 // erroneously think currentHost is foreign. so we consider this case non-
1751 // foreign only if the hosts exactly match.
1752 if (firstHost
.Equals(currentHost
))
1755 // get the base domain for the originating URI.
1756 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
1757 nsCAutoString baseDomain
;
1758 nsresult rv
= mTLDService
->GetBaseDomain(aFirstURI
, 0, baseDomain
);
1759 if (NS_FAILED(rv
)) {
1760 // URI is an IP, eTLD, or something else went wrong - assume foreign
1763 baseDomain
.Trim(".");
1765 // ensure the host domain is derived from the base domain.
1766 // we prepend dots before the comparison to ensure e.g.
1767 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1768 currentHost
.Insert(NS_LITERAL_CSTRING("."), 0);
1769 baseDomain
.Insert(NS_LITERAL_CSTRING("."), 0);
1770 return !StringEndsWith(currentHost
, baseDomain
);
1774 nsCookieService::CheckPrefs(nsIURI
*aHostURI
,
1775 nsIChannel
*aChannel
,
1776 const char *aCookieHeader
)
1780 // don't let ftp sites get/set cookies (could be a security issue)
1782 if (NS_SUCCEEDED(aHostURI
->SchemeIs("ftp", &ftp
)) && ftp
) {
1783 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "ftp sites cannot read cookies");
1784 return STATUS_REJECTED_WITH_ERROR
;
1787 // check the permission list first; if we find an entry, it overrides
1788 // default prefs. see bug 184059.
1789 if (mPermissionService
) {
1790 nsCookieAccess access
;
1791 rv
= mPermissionService
->CanAccess(aHostURI
, aChannel
, &access
);
1793 // if we found an entry, use it
1794 if (NS_SUCCEEDED(rv
)) {
1796 case nsICookiePermission::ACCESS_DENY
:
1797 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are blocked for this site");
1798 return STATUS_REJECTED
;
1800 case nsICookiePermission::ACCESS_ALLOW
:
1801 return STATUS_ACCEPTED
;
1806 // check default prefs
1807 if (mCookiesPermissions
== BEHAVIOR_REJECT
) {
1808 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are disabled");
1809 return STATUS_REJECTED
;
1811 } else if (mCookiesPermissions
== BEHAVIOR_REJECTFOREIGN
) {
1812 // check if cookie is foreign
1813 if (!mPermissionService
) {
1814 NS_WARNING("Foreign cookie blocking enabled, but nsICookiePermission unavailable! Rejecting cookie");
1815 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("CheckPrefs(): foreign blocking enabled, but nsICookiePermission unavailable! Rejecting cookie"));
1816 return STATUS_REJECTED
;
1819 nsCOMPtr
<nsIURI
> firstURI
;
1820 rv
= mPermissionService
->GetOriginatingURI(aChannel
, getter_AddRefs(firstURI
));
1822 if (NS_FAILED(rv
) || IsForeign(aHostURI
, firstURI
)) {
1823 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "originating server test failed");
1824 return STATUS_REJECTED
;
1828 // if nothing has complained, accept cookie
1829 return STATUS_ACCEPTED
;
1832 // processes domain attribute, and returns PR_TRUE if host has permission to set for this domain.
1834 nsCookieService::CheckDomain(nsCookieAttributes
&aCookieAttributes
,
1839 // get host from aHostURI
1840 nsCAutoString hostFromURI
;
1841 if (NS_FAILED(aHostURI
->GetAsciiHost(hostFromURI
))) {
1844 // trim trailing dots
1845 hostFromURI
.Trim(".");
1847 // if a domain is given, check the host has permission
1848 if (!aCookieAttributes
.host
.IsEmpty()) {
1849 aCookieAttributes
.host
.Trim(".");
1850 // switch to lowercase now, to avoid case-insensitive compares everywhere
1851 ToLowerCase(aCookieAttributes
.host
);
1853 // get the base domain for the host URI.
1854 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk", which
1855 // represents the lowest level domain a cookie can be set for.
1856 nsCAutoString baseDomain
;
1857 rv
= mTLDService
->GetBaseDomain(aHostURI
, 0, baseDomain
);
1858 baseDomain
.Trim(".");
1859 if (NS_FAILED(rv
)) {
1860 // check whether the host is an IP address, and leave the cookie as
1861 // a non-domain one. this will require an exact host match for the cookie,
1862 // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1
1863 // domain-matching 99.54.127.1). bug 105917 originally noted the
1864 // requirement to deal with IP addresses.
1865 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
)
1866 return hostFromURI
.Equals(aCookieAttributes
.host
);
1871 // ensure the proposed domain is derived from the base domain; and also
1872 // that the host domain is derived from the proposed domain (per RFC2109).
1873 // we prepend a dot before the comparison to ensure e.g.
1874 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1875 hostFromURI
.Insert(NS_LITERAL_CSTRING("."), 0);
1876 aCookieAttributes
.host
.Insert(NS_LITERAL_CSTRING("."), 0);
1877 baseDomain
.Insert(NS_LITERAL_CSTRING("."), 0);
1878 return StringEndsWith(aCookieAttributes
.host
, baseDomain
) &&
1879 StringEndsWith(hostFromURI
, aCookieAttributes
.host
);
1882 * note: RFC2109 section 4.3.2 requires that we check the following:
1883 * that the portion of host not in domain does not contain a dot.
1884 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1885 * entire .co.nz domain. however, it's only a only a partial solution and
1886 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1890 // block any URIs without a host that aren't file:/// URIs
1891 if (hostFromURI
.IsEmpty()) {
1892 PRBool isFileURI
= PR_FALSE
;
1893 aHostURI
->SchemeIs("file", &isFileURI
);
1898 // no domain specified, use hostFromURI
1899 aCookieAttributes
.host
= hostFromURI
;
1905 nsCookieService::CheckPath(nsCookieAttributes
&aCookieAttributes
,
1908 // if a path is given, check the host has permission
1909 if (aCookieAttributes
.path
.IsEmpty()) {
1910 // strip down everything after the last slash to get the path,
1911 // ignoring slashes in the query string part.
1912 // if we can QI to nsIURL, that'll take care of the query string portion.
1913 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
1914 nsCOMPtr
<nsIURL
> hostURL
= do_QueryInterface(aHostURI
);
1916 hostURL
->GetDirectory(aCookieAttributes
.path
);
1918 aHostURI
->GetPath(aCookieAttributes
.path
);
1919 PRInt32 slash
= aCookieAttributes
.path
.RFindChar('/');
1920 if (slash
!= kNotFound
) {
1921 aCookieAttributes
.path
.Truncate(slash
+ 1);
1928 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
1929 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
1930 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
1931 * been disabled, unless we can evangelize these sites.
1933 // get path from aHostURI
1934 nsCAutoString pathFromURI
;
1935 if (NS_FAILED(aHostURI
->GetPath(pathFromURI
)) ||
1936 !StringBeginsWith(pathFromURI
, aCookieAttributes
.path
)) {
1942 if (aCookieAttributes
.path
.Length() > kMaxBytesPerPath
||
1943 aCookieAttributes
.path
.FindChar('\t') != kNotFound
)
1950 nsCookieService::GetExpiry(nsCookieAttributes
&aCookieAttributes
,
1951 PRInt64 aServerTime
,
1952 PRInt64 aCurrentTime
)
1954 /* Determine when the cookie should expire. This is done by taking the difference between
1955 * the server time and the time the server wants the cookie to expire, and adding that
1956 * difference to the client time. This localizes the client time regardless of whether or
1957 * not the TZ environment variable was set on the client.
1959 * Note: We need to consider accounting for network lag here, per RFC.
1963 // check for max-age attribute first; this overrides expires attribute
1964 if (!aCookieAttributes
.maxage
.IsEmpty()) {
1965 // obtain numeric value of maxageAttribute
1967 PRInt32 numInts
= PR_sscanf(aCookieAttributes
.maxage
.get(), "%lld", &maxage
);
1969 // default to session cookie if the conversion failed
1976 // check for expires attribute
1977 } else if (!aCookieAttributes
.expires
.IsEmpty()) {
1981 // parse expiry time
1982 if (PR_ParseTimeString(aCookieAttributes
.expires
.get(), PR_TRUE
, &tempExpires
) == PR_SUCCESS
) {
1983 expires
= tempExpires
/ PR_USEC_PER_SEC
;
1988 delta
= expires
- aServerTime
;
1990 // default to session cookie if no attributes found
1995 // if this addition overflows, expiryTime will be less than currentTime
1996 // and the cookie will be expired - that's okay.
1997 aCookieAttributes
.expiryTime
= aCurrentTime
+ delta
;
2002 /******************************************************************************
2003 * nsCookieService impl:
2004 * private cookielist management functions
2005 ******************************************************************************/
2008 nsCookieService::RemoveAllFromMemory()
2010 // clearing the hashtable will call each nsCookieEntry's dtor,
2011 // which releases all their respective children.
2017 removeExpiredCallback(nsCookieEntry
*aEntry
,
2020 const PRInt64
¤tTime
= *static_cast<PRInt64
*>(aArg
);
2021 for (nsListIter
iter(aEntry
, nsnull
, aEntry
->Head()); iter
.current
; ) {
2022 if (iter
.current
->Expiry() <= currentTime
)
2023 // remove from list. this takes care of updating the iterator for us
2024 nsCookieService::gCookieService
->RemoveCookieFromList(iter
);
2028 return PL_DHASH_NEXT
;
2031 // removes any expired cookies from memory
2033 nsCookieService::RemoveExpiredCookies(PRInt64 aCurrentTime
)
2036 PRUint32 initialCookieCount
= mCookieCount
;
2038 mHostTable
.EnumerateEntries(removeExpiredCallback
, &aCurrentTime
);
2039 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("RemoveExpiredCookies(): %ld purged; %ld remain", initialCookieCount
- mCookieCount
, mCookieCount
));
2042 // find whether a given cookie has been previously set. this is provided by the
2043 // nsICookieManager2 interface.
2045 nsCookieService::CookieExists(nsICookie2
*aCookie
,
2046 PRBool
*aFoundCookie
)
2048 NS_ENSURE_ARG_POINTER(aCookie
);
2050 // just a placeholder
2052 nsCAutoString host
, name
, path
;
2053 nsresult rv
= aCookie
->GetHost(host
);
2054 NS_ENSURE_SUCCESS(rv
, rv
);
2055 rv
= aCookie
->GetName(name
);
2056 NS_ENSURE_SUCCESS(rv
, rv
);
2057 rv
= aCookie
->GetPath(path
);
2058 NS_ENSURE_SUCCESS(rv
, rv
);
2060 *aFoundCookie
= FindCookie(host
, name
, path
, iter
,
2061 PR_Now() / PR_USEC_PER_SEC
);
2065 // count the number of cookies from a given host, and simultaneously find the
2066 // oldest cookie from the host.
2068 nsCookieService::CountCookiesFromHostInternal(const nsACString
&aHost
,
2069 nsEnumerationData
&aData
)
2071 PRUint32 countFromHost
= 0;
2073 nsCAutoString
hostWithDot(NS_LITERAL_CSTRING(".") + aHost
);
2075 const char *currentDot
= hostWithDot
.get();
2076 const char *nextDot
= currentDot
+ 1;
2078 nsCookieEntry
*entry
= mHostTable
.GetEntry(currentDot
);
2079 for (nsListIter
iter(entry
); iter
.current
; ++iter
) {
2080 // only count non-expired cookies
2081 if (iter
.current
->Expiry() > aData
.currentTime
) {
2084 // check if we've found the oldest cookie so far
2085 if (aData
.oldestTime
> iter
.current
->LastAccessed()) {
2086 aData
.oldestTime
= iter
.current
->LastAccessed();
2092 currentDot
= nextDot
;
2094 nextDot
= strchr(currentDot
+ 1, '.');
2096 } while (currentDot
);
2098 return countFromHost
;
2101 // count the number of cookies stored by a particular host. this is provided by the
2102 // nsICookieManager2 interface.
2104 nsCookieService::CountCookiesFromHost(const nsACString
&aHost
,
2105 PRUint32
*aCountFromHost
)
2107 // we don't care about finding the oldest cookie here, so disable the search
2108 nsEnumerationData
data(PR_Now() / PR_USEC_PER_SEC
, LL_MININT
);
2110 *aCountFromHost
= CountCookiesFromHostInternal(aHost
, data
);
2114 // find an exact cookie specified by host, name, and path that hasn't expired.
2116 nsCookieService::FindCookie(const nsAFlatCString
&aHost
,
2117 const nsAFlatCString
&aName
,
2118 const nsAFlatCString
&aPath
,
2120 PRInt64 aCurrentTime
)
2122 nsCookieEntry
*entry
= mHostTable
.GetEntry(aHost
.get());
2123 for (aIter
= nsListIter(entry
); aIter
.current
; ++aIter
) {
2124 if (aIter
.current
->Expiry() > aCurrentTime
&&
2125 aPath
.Equals(aIter
.current
->Path()) &&
2126 aName
.Equals(aIter
.current
->Name())) {
2134 // removes a cookie from the hashtable, and update the iterator state.
2136 nsCookieService::RemoveCookieFromList(nsListIter
&aIter
)
2138 // if it's a non-session cookie, remove it from the db
2139 if (!aIter
.current
->IsSession() && mStmtDelete
) {
2140 // use our cached sqlite "delete" statement
2141 mozStorageStatementScoper
scoper(mStmtDelete
);
2143 nsresult rv
= mStmtDelete
->BindInt64Parameter(0, aIter
.current
->CreationID());
2144 if (NS_SUCCEEDED(rv
)) {
2146 rv
= mStmtDelete
->ExecuteStep(&hasResult
);
2149 if (NS_FAILED(rv
)) {
2150 NS_WARNING("db remove failed!");
2151 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("RemoveCookieFromList(): removing from db gave error %x", rv
));
2155 if (!aIter
.prev
&& !aIter
.current
->Next()) {
2156 // we're removing the last element in the list - so just remove the entry
2157 // from the hash. note that the entryclass' dtor will take care of
2158 // releasing this last element for us!
2159 mHostTable
.RawRemoveEntry(aIter
.entry
);
2160 aIter
.current
= nsnull
;
2163 // just remove the element from the list, and increment the iterator
2164 nsCookie
*next
= aIter
.current
->Next();
2165 NS_RELEASE(aIter
.current
);
2167 // element to remove is not the head
2168 aIter
.current
= aIter
.prev
->Next() = next
;
2170 // element to remove is the head
2171 aIter
.current
= aIter
.entry
->Head() = next
;
2179 bindCookieParameters(mozIStorageStatement
* aStmt
, const nsCookie
* aCookie
)
2183 rv
= aStmt
->BindInt64Parameter(0, aCookie
->CreationID());
2184 if (NS_FAILED(rv
)) return rv
;
2186 rv
= aStmt
->BindUTF8StringParameter(1, aCookie
->Name());
2187 if (NS_FAILED(rv
)) return rv
;
2189 rv
= aStmt
->BindUTF8StringParameter(2, aCookie
->Value());
2190 if (NS_FAILED(rv
)) return rv
;
2192 rv
= aStmt
->BindUTF8StringParameter(3, aCookie
->Host());
2193 if (NS_FAILED(rv
)) return rv
;
2195 rv
= aStmt
->BindUTF8StringParameter(4, aCookie
->Path());
2196 if (NS_FAILED(rv
)) return rv
;
2198 rv
= aStmt
->BindInt64Parameter(5, aCookie
->Expiry());
2199 if (NS_FAILED(rv
)) return rv
;
2201 rv
= aStmt
->BindInt64Parameter(6, aCookie
->LastAccessed());
2202 if (NS_FAILED(rv
)) return rv
;
2204 rv
= aStmt
->BindInt32Parameter(7, aCookie
->IsSecure());
2205 if (NS_FAILED(rv
)) return rv
;
2207 rv
= aStmt
->BindInt32Parameter(8, aCookie
->IsHttpOnly());
2212 nsCookieService::AddCookieToList(nsCookie
*aCookie
, PRBool aWriteToDB
)
2214 nsCookieEntry
*entry
= mHostTable
.PutEntry(aCookie
->Host().get());
2217 NS_ERROR("can't insert element into a null entry!");
2223 aCookie
->Next() = entry
->Head();
2224 entry
->Head() = aCookie
;
2227 // if it's a non-session cookie and hasn't just been read from the db, write it out.
2228 if (aWriteToDB
&& !aCookie
->IsSession() && mStmtInsert
) {
2229 // use our cached sqlite "insert" statement
2230 mozStorageStatementScoper
scoper(mStmtInsert
);
2232 nsresult rv
= bindCookieParameters(mStmtInsert
, aCookie
);
2233 if (NS_SUCCEEDED(rv
)) {
2235 rv
= mStmtInsert
->ExecuteStep(&hasResult
);
2238 if (NS_FAILED(rv
)) {
2239 NS_WARNING("db insert failed!");
2240 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("AddCookieToList(): adding to db gave error %x", rv
));
2248 nsCookieService::UpdateCookieInList(nsCookie
*aCookie
, PRInt64 aLastAccessed
)
2250 // update the lastAccessed timestamp
2251 aCookie
->SetLastAccessed(aLastAccessed
);
2253 // if it's a non-session cookie, update it in the db too
2254 if (!aCookie
->IsSession() && mStmtUpdate
) {
2255 // use our cached sqlite "update" statement
2256 mozStorageStatementScoper
scoper(mStmtUpdate
);
2258 nsresult rv
= mStmtUpdate
->BindInt64Parameter(0, aLastAccessed
);
2259 if (NS_SUCCEEDED(rv
)) {
2260 rv
= mStmtUpdate
->BindInt64Parameter(1, aCookie
->CreationID());
2261 if (NS_SUCCEEDED(rv
)) {
2263 rv
= mStmtUpdate
->ExecuteStep(&hasResult
);
2267 if (NS_FAILED(rv
)) {
2268 NS_WARNING("db update failed!");
2269 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("UpdateCookieInList(): updating db gave error %x", rv
));
2274 static PLDHashOperator
2275 findOldestCallback(nsCookieEntry
*aEntry
,
2278 nsEnumerationData
*data
= static_cast<nsEnumerationData
*>(aArg
);
2279 for (nsListIter
iter(aEntry
, nsnull
, aEntry
->Head()); iter
.current
; ++iter
) {
2280 // check if we've found the oldest cookie so far
2281 if (data
->oldestTime
> iter
.current
->LastAccessed()) {
2282 data
->oldestTime
= iter
.current
->LastAccessed();
2286 return PL_DHASH_NEXT
;
2290 nsCookieService::FindOldestCookie(nsEnumerationData
&aData
)
2292 mHostTable
.EnumerateEntries(findOldestCallback
, &aData
);