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.
341 PR_STATIC_CALLBACK(int)
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
->OpenDatabase(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
->OpenDatabase(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 // cache frequently used statements (for insertion, deletion, and updating)
557 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
558 "INSERT INTO moz_cookies "
559 "(id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly) "
560 "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert
));
561 NS_ENSURE_SUCCESS(rv
, rv
);
563 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
564 "DELETE FROM moz_cookies WHERE id = ?1"), getter_AddRefs(mStmtDelete
));
565 NS_ENSURE_SUCCESS(rv
, rv
);
567 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
568 "UPDATE moz_cookies SET lastAccessed = ?1 WHERE id = ?2"), getter_AddRefs(mStmtUpdate
));
569 NS_ENSURE_SUCCESS(rv
, rv
);
571 // check whether to import or just read in the db
575 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(cookieFile
));
576 if (NS_FAILED(rv
)) return rv
;
578 cookieFile
->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName
));
579 rv
= ImportCookies(cookieFile
);
580 if (NS_FAILED(rv
)) return rv
;
582 // we're done importing - delete the old cookie file
583 cookieFile
->Remove(PR_FALSE
);
587 // sets the schema version and creates the moz_cookies table.
589 nsCookieService::CreateTable()
591 // set the schema version, before creating the table
592 nsresult rv
= mDBConn
->SetSchemaVersion(COOKIES_SCHEMA_VERSION
);
593 if (NS_FAILED(rv
)) return rv
;
596 return mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
597 "CREATE TABLE moz_cookies ("
598 "id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,"
599 "expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)"));
602 nsCookieService::~nsCookieService()
604 gCookieService
= nsnull
;
608 nsCookieService::Observe(nsISupports
*aSubject
,
610 const PRUnichar
*aData
)
613 if (!strcmp(aTopic
, "profile-before-change")) {
614 // The profile is about to change,
615 // or is going away because the application is shutting down.
616 RemoveAllFromMemory();
618 if (!nsCRT::strcmp(aData
, NS_LITERAL_STRING("shutdown-cleanse").get()) && mDBConn
) {
619 // clear the cookie file
620 nsresult rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
622 NS_WARNING("db delete failed");
625 } else if (!strcmp(aTopic
, "profile-do-change")) {
626 // the profile has already changed; init the db from the new location
629 } else if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
630 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
632 PrefChanged(prefBranch
);
639 nsCookieService::GetCookieString(nsIURI
*aHostURI
,
640 nsIChannel
*aChannel
,
643 GetCookieInternal(aHostURI
, aChannel
, PR_FALSE
, aCookie
);
649 nsCookieService::GetCookieStringFromHttp(nsIURI
*aHostURI
,
651 nsIChannel
*aChannel
,
654 GetCookieInternal(aHostURI
, aChannel
, PR_TRUE
, aCookie
);
660 nsCookieService::SetCookieString(nsIURI
*aHostURI
,
662 const char *aCookieHeader
,
663 nsIChannel
*aChannel
)
665 return SetCookieStringInternal(aHostURI
, aPrompt
, aCookieHeader
, nsnull
, aChannel
, PR_FALSE
);
669 nsCookieService::SetCookieStringFromHttp(nsIURI
*aHostURI
,
672 const char *aCookieHeader
,
673 const char *aServerTime
,
674 nsIChannel
*aChannel
)
676 return SetCookieStringInternal(aHostURI
, aPrompt
, aCookieHeader
, aServerTime
, aChannel
, PR_TRUE
);
680 nsCookieService::SetCookieStringInternal(nsIURI
*aHostURI
,
682 const char *aCookieHeader
,
683 const char *aServerTime
,
684 nsIChannel
*aChannel
,
688 COOKIE_LOGFAILURE(SET_COOKIE
, nsnull
, aCookieHeader
, "host URI is null");
692 // check default prefs
693 PRUint32 cookieStatus
= CheckPrefs(aHostURI
, aChannel
, aCookieHeader
);
694 // fire a notification if cookie was rejected (but not if there was an error)
695 switch (cookieStatus
) {
696 case STATUS_REJECTED
:
697 NotifyRejected(aHostURI
);
698 case STATUS_REJECTED_WITH_ERROR
:
702 // parse server local time. this is not just done here for efficiency
703 // reasons - if there's an error parsing it, and we need to default it
704 // to the current time, we must do it here since the current time in
705 // SetCookieInternal() will change for each cookie processed (e.g. if the
706 // user is prompted).
707 PRTime tempServerTime
;
709 if (aServerTime
&& PR_ParseTimeString(aServerTime
, PR_TRUE
, &tempServerTime
) == PR_SUCCESS
) {
710 serverTime
= tempServerTime
/ PR_USEC_PER_SEC
;
712 serverTime
= PR_Now() / PR_USEC_PER_SEC
;
715 // start a transaction on the storage db, to optimize insertions.
716 // transaction will automically commit on completion
717 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
719 // switch to a nice string type now, and process each cookie in the header
720 nsDependentCString
cookieHeader(aCookieHeader
);
721 while (SetCookieInternal(aHostURI
, aChannel
, cookieHeader
, serverTime
, aFromHttp
));
726 // notify observers that a cookie was rejected due to the users' prefs.
728 nsCookieService::NotifyRejected(nsIURI
*aHostURI
)
730 if (mObserverService
)
731 mObserverService
->NotifyObservers(aHostURI
, "cookie-rejected", nsnull
);
734 // notify observers that the cookie list changed. there are four possible
736 // "deleted" means a cookie was deleted. aCookie is the deleted cookie.
737 // "added" means a cookie was added. aCookie is the added cookie.
738 // "changed" means a cookie was altered. aCookie is the new cookie.
739 // "cleared" means the entire cookie list was cleared. aCookie is null.
741 nsCookieService::NotifyChanged(nsICookie2
*aCookie
,
742 const PRUnichar
*aData
)
744 if (mObserverService
)
745 mObserverService
->NotifyObservers(aCookie
, "cookie-changed", aData
);
748 /******************************************************************************
751 ******************************************************************************/
754 nsCookieService::PrefChanged(nsIPrefBranch
*aPrefBranch
)
757 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefCookiesPermissions
, &val
)))
758 mCookiesPermissions
= (PRUint8
) LIMIT(val
, 0, 2, 0);
760 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxNumberOfCookies
, &val
)))
761 mMaxNumberOfCookies
= (PRUint16
) LIMIT(val
, 0, 0xFFFF, 0xFFFF);
763 if (NS_SUCCEEDED(aPrefBranch
->GetIntPref(kPrefMaxCookiesPerHost
, &val
)))
764 mMaxCookiesPerHost
= (PRUint16
) LIMIT(val
, 0, 0xFFFF, 0xFFFF);
767 /******************************************************************************
768 * nsICookieManager impl:
770 ******************************************************************************/
773 nsCookieService::RemoveAll()
775 RemoveAllFromMemory();
776 NotifyChanged(nsnull
, NS_LITERAL_STRING("cleared").get());
778 // clear the cookie file
780 nsresult rv
= mDBConn
->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
782 NS_WARNING("db delete failed");
788 // helper struct for passing arguments into hash enumeration callback.
789 struct nsGetEnumeratorData
791 nsGetEnumeratorData(nsCOMArray
<nsICookie
> *aArray
, PRInt64 aTime
)
793 , currentTime(aTime
) {}
795 nsCOMArray
<nsICookie
> *array
;
799 PR_STATIC_CALLBACK(PLDHashOperator
)
800 COMArrayCallback(nsCookieEntry
*aEntry
,
803 nsGetEnumeratorData
*data
= static_cast<nsGetEnumeratorData
*>(aArg
);
805 for (nsCookie
*cookie
= aEntry
->Head(); cookie
; cookie
= cookie
->Next()) {
806 // only append non-expired cookies
807 if (cookie
->Expiry() > data
->currentTime
)
808 data
->array
->AppendObject(cookie
);
810 return PL_DHASH_NEXT
;
814 nsCookieService::GetEnumerator(nsISimpleEnumerator
**aEnumerator
)
816 nsCOMArray
<nsICookie
> cookieList(mCookieCount
);
817 nsGetEnumeratorData
data(&cookieList
, PR_Now() / PR_USEC_PER_SEC
);
819 mHostTable
.EnumerateEntries(COMArrayCallback
, &data
);
821 return NS_NewArrayEnumerator(aEnumerator
, cookieList
);
825 nsCookieService::Add(const nsACString
&aDomain
,
826 const nsACString
&aPath
,
827 const nsACString
&aName
,
828 const nsACString
&aValue
,
834 PRInt64 currentTimeInUsec
= PR_Now();
836 nsRefPtr
<nsCookie
> cookie
=
837 nsCookie::Create(aName
, aValue
, aDomain
, aPath
,
845 return NS_ERROR_OUT_OF_MEMORY
;
848 AddInternal(cookie
, currentTimeInUsec
/ PR_USEC_PER_SEC
, nsnull
, nsnull
, PR_TRUE
);
853 nsCookieService::Remove(const nsACString
&aHost
,
854 const nsACString
&aName
,
855 const nsACString
&aPath
,
858 nsListIter matchIter
;
859 if (FindCookie(PromiseFlatCString(aHost
),
860 PromiseFlatCString(aName
),
861 PromiseFlatCString(aPath
),
863 PR_Now() / PR_USEC_PER_SEC
)) {
864 nsRefPtr
<nsCookie
> cookie
= matchIter
.current
;
865 RemoveCookieFromList(matchIter
);
866 NotifyChanged(cookie
, NS_LITERAL_STRING("deleted").get());
869 // check if we need to add the host to the permissions blacklist.
870 if (aBlocked
&& mPermissionService
) {
871 nsCAutoString
host(NS_LITERAL_CSTRING("http://"));
873 // strip off the domain dot, if necessary
874 if (!aHost
.IsEmpty() && aHost
.First() == '.')
875 host
.Append(Substring(aHost
, 1, aHost
.Length() - 1));
879 nsCOMPtr
<nsIURI
> uri
;
880 NS_NewURI(getter_AddRefs(uri
), host
);
883 mPermissionService
->SetAccess(uri
, nsICookiePermission::ACCESS_DENY
);
889 /******************************************************************************
890 * nsCookieService impl:
891 * private file I/O functions
892 ******************************************************************************/
895 nsCookieService::Read()
899 // delete expired cookies, before we read in the db
901 // scope the deletion, so the write lock is released when finished
902 nsCOMPtr
<mozIStorageStatement
> stmtDeleteExpired
;
903 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE expiry <= ?1"),
904 getter_AddRefs(stmtDeleteExpired
));
905 NS_ENSURE_SUCCESS(rv
, rv
);
907 rv
= stmtDeleteExpired
->BindInt64Parameter(0, PR_Now() / PR_USEC_PER_SEC
);
908 NS_ENSURE_SUCCESS(rv
, rv
);
911 rv
= stmtDeleteExpired
->ExecuteStep(&hasResult
);
912 NS_ENSURE_SUCCESS(rv
, rv
);
915 // let the reading begin!
916 nsCOMPtr
<mozIStorageStatement
> stmt
;
917 rv
= mDBConn
->CreateStatement(NS_LITERAL_CSTRING(
918 "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly "
919 "FROM moz_cookies"), getter_AddRefs(stmt
));
920 NS_ENSURE_SUCCESS(rv
, rv
);
922 nsCAutoString name
, value
, host
, path
;
924 while (NS_SUCCEEDED(stmt
->ExecuteStep(&hasResult
)) && hasResult
) {
925 PRInt64 creationID
= stmt
->AsInt64(0);
927 stmt
->GetUTF8String(1, name
);
928 stmt
->GetUTF8String(2, value
);
929 stmt
->GetUTF8String(3, host
);
930 stmt
->GetUTF8String(4, path
);
932 PRInt64 expiry
= stmt
->AsInt64(5);
933 PRInt64 lastAccessed
= stmt
->AsInt64(6);
934 PRBool isSecure
= 0 != stmt
->AsInt32(7);
935 PRBool isHttpOnly
= 0 != stmt
->AsInt32(8);
937 // create a new nsCookie and assign the data.
938 nsCookie
* newCookie
=
939 nsCookie::Create(name
, value
, host
, path
,
947 return NS_ERROR_OUT_OF_MEMORY
;
949 if (!AddCookieToList(newCookie
, PR_FALSE
))
950 // It is purpose that created us; purpose that connects us;
951 // purpose that pulls us; that guides us; that drives us.
952 // It is purpose that defines us; purpose that binds us.
953 // When a cookie no longer has purpose, it has a choice:
954 // it can return to the source to be deleted, or it can go
955 // into exile, and stay hidden inside the Matrix.
956 // Let's choose deletion.
960 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("Read(): %ld cookies read", mCookieCount
));
966 nsCookieService::ImportCookies(nsIFile
*aCookieFile
)
969 nsCOMPtr
<nsIInputStream
> fileInputStream
;
970 rv
= NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream
), aCookieFile
);
971 if (NS_FAILED(rv
)) return rv
;
973 nsCOMPtr
<nsILineInputStream
> lineInputStream
= do_QueryInterface(fileInputStream
, &rv
);
974 if (NS_FAILED(rv
)) return rv
;
976 // start a transaction on the storage db, to optimize insertions.
977 // transaction will automically commit on completion
978 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
980 static const char kTrue
[] = "TRUE";
982 nsCAutoString buffer
;
983 PRBool isMore
= PR_TRUE
;
984 PRInt32 hostIndex
, isDomainIndex
, pathIndex
, secureIndex
, expiresIndex
, nameIndex
, cookieIndex
;
985 nsASingleFragmentCString::char_iterator iter
;
988 PRBool isDomain
, isHttpOnly
= PR_FALSE
;
989 PRUint32 originalCookieCount
= mCookieCount
;
991 PRInt64 currentTimeInUsec
= PR_Now();
992 PRInt64 currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
993 // we use lastAccessedCounter to keep cookies in recently-used order,
994 // so we start by initializing to currentTime (somewhat arbitrary)
995 PRInt64 lastAccessedCounter
= currentTimeInUsec
;
999 * host \t isDomain \t path \t secure \t expires \t name \t cookie
1001 * if this format isn't respected we move onto the next line in the file.
1002 * isDomain is "TRUE" or "FALSE" (default to "FALSE")
1003 * isSecure is "TRUE" or "FALSE" (default to "TRUE")
1004 * expires is a PRInt64 integer
1005 * note 1: cookie can contain tabs.
1006 * note 2: cookies will be stored in order of lastAccessed time:
1007 * most-recently used come first; least-recently-used come last.
1011 * ...but due to bug 178933, we hide HttpOnly cookies from older code
1012 * in a comment, so they don't expose HttpOnly cookies to JS.
1014 * The format for HttpOnly cookies is
1016 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
1020 while (isMore
&& NS_SUCCEEDED(lineInputStream
->ReadLine(buffer
, &isMore
))) {
1021 if (StringBeginsWith(buffer
, NS_LITERAL_CSTRING(kHttpOnlyPrefix
))) {
1022 isHttpOnly
= PR_TRUE
;
1023 hostIndex
= sizeof(kHttpOnlyPrefix
) - 1;
1024 } else if (buffer
.IsEmpty() || buffer
.First() == '#') {
1027 isHttpOnly
= PR_FALSE
;
1031 // this is a cheap, cheesy way of parsing a tab-delimited line into
1032 // string indexes, which can be lopped off into substrings. just for
1033 // purposes of obfuscation, it also checks that each token was found.
1034 // todo: use iterators?
1035 if ((isDomainIndex
= buffer
.FindChar('\t', hostIndex
) + 1) == 0 ||
1036 (pathIndex
= buffer
.FindChar('\t', isDomainIndex
) + 1) == 0 ||
1037 (secureIndex
= buffer
.FindChar('\t', pathIndex
) + 1) == 0 ||
1038 (expiresIndex
= buffer
.FindChar('\t', secureIndex
) + 1) == 0 ||
1039 (nameIndex
= buffer
.FindChar('\t', expiresIndex
) + 1) == 0 ||
1040 (cookieIndex
= buffer
.FindChar('\t', nameIndex
) + 1) == 0) {
1044 // check the expirytime first - if it's expired, ignore
1045 // nullstomp the trailing tab, to avoid copying the string
1046 buffer
.BeginWriting(iter
);
1047 *(iter
+= nameIndex
- 1) = char(0);
1048 numInts
= PR_sscanf(buffer
.get() + expiresIndex
, "%lld", &expires
);
1049 if (numInts
!= 1 || expires
< currentTime
) {
1053 isDomain
= Substring(buffer
, isDomainIndex
, pathIndex
- isDomainIndex
- 1).EqualsLiteral(kTrue
);
1054 const nsASingleFragmentCString
&host
= Substring(buffer
, hostIndex
, isDomainIndex
- hostIndex
- 1);
1055 // check for bad legacy cookies (domain not starting with a dot, or containing a port),
1057 if ((isDomain
&& !host
.IsEmpty() && host
.First() != '.') ||
1058 host
.FindChar(':') != kNotFound
) {
1062 // create a new nsCookie and assign the data.
1063 // we don't know the cookie creation time, so just use the current time;
1064 // this is okay, since nsCookie::Create() will make sure the creation id
1065 // ends up monotonically increasing.
1066 nsRefPtr
<nsCookie
> newCookie
=
1067 nsCookie::Create(Substring(buffer
, nameIndex
, cookieIndex
- nameIndex
- 1),
1068 Substring(buffer
, cookieIndex
, buffer
.Length() - cookieIndex
),
1070 Substring(buffer
, pathIndex
, secureIndex
- pathIndex
- 1),
1072 lastAccessedCounter
,
1075 Substring(buffer
, secureIndex
, expiresIndex
- secureIndex
- 1).EqualsLiteral(kTrue
),
1078 return NS_ERROR_OUT_OF_MEMORY
;
1081 // trick: preserve the most-recently-used cookie ordering,
1082 // by successively decrementing the lastAccessed time
1083 lastAccessedCounter
--;
1085 if (originalCookieCount
== 0)
1086 AddCookieToList(newCookie
);
1088 AddInternal(newCookie
, currentTime
, nsnull
, nsnull
, PR_TRUE
);
1091 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("ImportCookies(): %ld cookies imported", mCookieCount
));
1096 /******************************************************************************
1097 * nsCookieService impl:
1098 * private GetCookie/SetCookie helpers
1099 ******************************************************************************/
1101 // helper function for GetCookieList
1102 static inline PRBool
ispathdelimiter(char c
) { return c
== '/' || c
== '?' || c
== '#' || c
== ';'; }
1105 nsCookieService::GetCookieInternal(nsIURI
*aHostURI
,
1106 nsIChannel
*aChannel
,
1113 COOKIE_LOGFAILURE(GET_COOKIE
, nsnull
, nsnull
, "host URI is null");
1117 // check default prefs
1118 PRUint32 cookieStatus
= CheckPrefs(aHostURI
, aChannel
, nsnull
);
1119 // for GetCookie(), we don't fire rejection notifications.
1120 switch (cookieStatus
) {
1121 case STATUS_REJECTED
:
1122 case STATUS_REJECTED_WITH_ERROR
:
1126 // get host and path from the nsIURI
1127 // note: there was a "check if host has embedded whitespace" here.
1128 // it was removed since this check was added into the nsIURI impl (bug 146094).
1129 nsCAutoString hostFromURI
, pathFromURI
;
1130 if (NS_FAILED(aHostURI
->GetAsciiHost(hostFromURI
)) ||
1131 NS_FAILED(aHostURI
->GetPath(pathFromURI
))) {
1132 COOKIE_LOGFAILURE(GET_COOKIE
, aHostURI
, nsnull
, "couldn't get host/path from URI");
1135 // trim trailing dots
1136 hostFromURI
.Trim(".");
1137 // insert a leading dot, so we begin the hash lookup with the
1138 // equivalent domain cookie host
1139 hostFromURI
.Insert(NS_LITERAL_CSTRING("."), 0);
1141 // check if aHostURI is using an https secure protocol.
1142 // if it isn't, then we can't send a secure cookie over the connection.
1143 // if SchemeIs fails, assume an insecure connection, to be on the safe side
1145 if (NS_FAILED(aHostURI
->SchemeIs("https", &isSecure
))) {
1146 isSecure
= PR_FALSE
;
1150 nsAutoVoidArray foundCookieList
;
1151 PRInt64 currentTimeInUsec
= PR_Now();
1152 PRInt64 currentTime
= currentTimeInUsec
/ PR_USEC_PER_SEC
;
1153 const char *currentDot
= hostFromURI
.get();
1154 const char *nextDot
= currentDot
+ 1;
1155 PRBool stale
= PR_FALSE
;
1157 // begin hash lookup, walking up the subdomain levels.
1158 // we use nextDot to force a lookup of the original host (without leading dot).
1160 nsCookieEntry
*entry
= mHostTable
.GetEntry(currentDot
);
1161 cookie
= entry
? entry
->Head() : nsnull
;
1162 for (; cookie
; cookie
= cookie
->Next()) {
1163 // if the cookie is secure and the host scheme isn't, we can't send it
1164 if (cookie
->IsSecure() && !isSecure
) {
1168 // if the cookie is httpOnly and it's not going directly to the HTTP
1169 // connection, don't send it
1170 if (cookie
->IsHttpOnly() && !aHttpBound
) {
1174 // calculate cookie path length, excluding trailing '/'
1175 PRUint32 cookiePathLen
= cookie
->Path().Length();
1176 if (cookiePathLen
> 0 && cookie
->Path().Last() == '/') {
1180 // if the nsIURI path is shorter than the cookie path, don't send it back
1181 if (!StringBeginsWith(pathFromURI
, Substring(cookie
->Path(), 0, cookiePathLen
))) {
1185 if (pathFromURI
.Length() > cookiePathLen
&&
1186 !ispathdelimiter(pathFromURI
.CharAt(cookiePathLen
))) {
1188 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
1189 * '/' is the "standard" case; the '?' test allows a site at host/abc?def
1190 * to receive a cookie that has a path attribute of abc. this seems
1191 * strange but at least one major site (citibank, bug 156725) depends
1192 * on it. The test for # and ; are put in to proactively avoid problems
1193 * with other sites - these are the only other chars allowed in the path.
1198 // check if the cookie has expired
1199 if (cookie
->Expiry() <= currentTime
) {
1203 // all checks passed - add to list and check if lastAccessed stamp needs updating
1204 foundCookieList
.AppendElement(cookie
);
1205 if (currentTimeInUsec
- cookie
->LastAccessed() > kCookieStaleThreshold
)
1209 currentDot
= nextDot
;
1211 nextDot
= strchr(currentDot
+ 1, '.');
1213 } while (currentDot
);
1215 PRInt32 count
= foundCookieList
.Count();
1219 // update lastAccessed timestamps. we only do this if the timestamp is stale
1220 // by a certain amount, to avoid thrashing the db during pageload.
1222 // start a transaction on the storage db, to optimize updates.
1223 // transaction will automically commit on completion.
1224 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
1226 for (PRInt32 i
= 0; i
< count
; ++i
) {
1227 cookie
= static_cast<nsCookie
*>(foundCookieList
.ElementAt(i
));
1229 if (currentTimeInUsec
- cookie
->LastAccessed() > kCookieStaleThreshold
)
1230 UpdateCookieInList(cookie
, currentTimeInUsec
);
1234 // return cookies in order of path length; longest to shortest.
1235 // this is required per RFC2109. if cookies match in length,
1236 // then sort by creation time (see bug 236772).
1237 foundCookieList
.Sort(compareCookiesForSending
, nsnull
);
1239 nsCAutoString cookieData
;
1240 for (PRInt32 i
= 0; i
< count
; ++i
) {
1241 cookie
= static_cast<nsCookie
*>(foundCookieList
.ElementAt(i
));
1243 // check if we have anything to write
1244 if (!cookie
->Name().IsEmpty() || !cookie
->Value().IsEmpty()) {
1245 // if we've already added a cookie to the return list, append a "; " so
1246 // that subsequent cookies are delimited in the final list.
1247 if (!cookieData
.IsEmpty()) {
1248 cookieData
.AppendLiteral("; ");
1251 if (!cookie
->Name().IsEmpty()) {
1252 // we have a name and value - write both
1253 cookieData
+= cookie
->Name() + NS_LITERAL_CSTRING("=") + cookie
->Value();
1256 cookieData
+= cookie
->Value();
1261 // it's wasteful to alloc a new string; but we have no other choice, until we
1262 // fix the callers to use nsACStrings.
1263 if (!cookieData
.IsEmpty()) {
1264 COOKIE_LOGSUCCESS(GET_COOKIE
, aHostURI
, cookieData
, nsnull
, nsnull
);
1265 *aCookie
= ToNewCString(cookieData
);
1269 // processes a single cookie, and returns PR_TRUE if there are more cookies
1272 nsCookieService::SetCookieInternal(nsIURI
*aHostURI
,
1273 nsIChannel
*aChannel
,
1274 nsDependentCString
&aCookieHeader
,
1275 PRInt64 aServerTime
,
1278 // create a stack-based nsCookieAttributes, to store all the
1279 // attributes parsed from the cookie
1280 nsCookieAttributes cookieAttributes
;
1282 // init expiryTime such that session cookies won't prematurely expire
1283 cookieAttributes
.expiryTime
= LL_MAXINT
;
1285 // aCookieHeader is an in/out param to point to the next cookie, if
1286 // there is one. Save the present value for logging purposes
1287 nsDependentCString
savedCookieHeader(aCookieHeader
);
1289 // newCookie says whether there are multiple cookies in the header;
1290 // so we can handle them separately.
1291 PRBool newCookie
= ParseAttributes(aCookieHeader
, cookieAttributes
);
1293 PRInt64 currentTimeInUsec
= PR_Now();
1295 // calculate expiry time of cookie.
1296 cookieAttributes
.isSession
= GetExpiry(cookieAttributes
, aServerTime
,
1297 currentTimeInUsec
/ PR_USEC_PER_SEC
);
1299 // reject cookie if it's over the size limit, per RFC2109
1300 if ((cookieAttributes
.name
.Length() + cookieAttributes
.value
.Length()) > kMaxBytesPerCookie
) {
1301 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie too big (> 4kb)");
1305 if (cookieAttributes
.name
.FindChar('\t') != kNotFound
) {
1306 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "invalid name character");
1310 // domain & path checks
1311 if (!CheckDomain(cookieAttributes
, aHostURI
)) {
1312 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the domain tests");
1315 if (!CheckPath(cookieAttributes
, aHostURI
)) {
1316 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "failed the path tests");
1320 // create a new nsCookie and copy attributes
1321 nsRefPtr
<nsCookie
> cookie
=
1322 nsCookie::Create(cookieAttributes
.name
,
1323 cookieAttributes
.value
,
1324 cookieAttributes
.host
,
1325 cookieAttributes
.path
,
1326 cookieAttributes
.expiryTime
,
1329 cookieAttributes
.isSession
,
1330 cookieAttributes
.isSecure
,
1331 cookieAttributes
.isHttpOnly
);
1335 // check permissions from site permission list, or ask the user,
1336 // to determine if we can set the cookie
1337 if (mPermissionService
) {
1339 // we need to think about prompters/parent windows here - TestPermission
1340 // needs one to prompt, so right now it has to fend for itself to get one
1341 mPermissionService
->CanSetCookie(aHostURI
,
1343 static_cast<nsICookie2
*>(static_cast<nsCookie
*>(cookie
)),
1344 &cookieAttributes
.isSession
,
1345 &cookieAttributes
.expiryTime
,
1348 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, savedCookieHeader
, "cookie rejected by permission manager");
1349 NotifyRejected(aHostURI
);
1353 // update isSession and expiry attributes, in case they changed
1354 cookie
->SetIsSession(cookieAttributes
.isSession
);
1355 cookie
->SetExpiry(cookieAttributes
.expiryTime
);
1358 // add the cookie to the list. AddInternal() takes care of logging.
1359 // we get the current time again here, since it may have changed during prompting
1360 AddInternal(cookie
, PR_Now() / PR_USEC_PER_SEC
, aHostURI
, savedCookieHeader
.get(), aFromHttp
);
1364 // this is a backend function for adding a cookie to the list, via SetCookie.
1365 // also used in the cookie manager, for profile migration from IE.
1366 // it either replaces an existing cookie; or adds the cookie to the hashtable,
1367 // and deletes a cookie (if maximum number of cookies has been
1368 // reached). also performs list maintenance by removing expired cookies.
1370 nsCookieService::AddInternal(nsCookie
*aCookie
,
1371 PRInt64 aCurrentTime
,
1373 const char *aCookieHeader
,
1376 // if the new cookie is httponly, make sure we're not coming from script
1377 if (!aFromHttp
&& aCookie
->IsHttpOnly()) {
1378 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "cookie is httponly; coming from script");
1382 // start a transaction on the storage db, to optimize deletions/insertions.
1383 // transaction will automically commit on completion. if we already have a
1384 // transaction (e.g. from SetCookie*()), this will have no effect.
1385 mozStorageTransaction
transaction(mDBConn
, PR_TRUE
);
1387 nsListIter matchIter
;
1388 PRBool foundCookie
= FindCookie(aCookie
->Host(), aCookie
->Name(), aCookie
->Path(),
1389 matchIter
, aCurrentTime
);
1391 nsRefPtr
<nsCookie
> oldCookie
;
1393 oldCookie
= matchIter
.current
;
1395 // if the old cookie is httponly, make sure we're not coming from script
1396 if (!aFromHttp
&& oldCookie
->IsHttpOnly()) {
1397 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "previously stored cookie is httponly; coming from script");
1401 RemoveCookieFromList(matchIter
);
1403 // check if the cookie has expired
1404 if (aCookie
->Expiry() <= aCurrentTime
) {
1405 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "previously stored cookie was deleted");
1406 NotifyChanged(oldCookie
, NS_LITERAL_STRING("deleted").get());
1410 // preserve creation time of cookie
1412 aCookie
->SetCreationID(oldCookie
->CreationID());
1415 // check if cookie has already expired
1416 if (aCookie
->Expiry() <= aCurrentTime
) {
1417 COOKIE_LOGFAILURE(SET_COOKIE
, aHostURI
, aCookieHeader
, "cookie has already expired");
1421 // check if we have to delete an old cookie.
1422 nsEnumerationData
data(aCurrentTime
, LL_MAXINT
);
1423 if (CountCookiesFromHostInternal(aCookie
->RawHost(), data
) >= mMaxCookiesPerHost
) {
1424 // remove the oldest cookie from host
1425 oldCookie
= data
.iter
.current
;
1426 RemoveCookieFromList(data
.iter
);
1428 } else if (mCookieCount
>= mMaxNumberOfCookies
) {
1429 // try to make room, by removing expired cookies
1430 RemoveExpiredCookies(aCurrentTime
);
1432 // check if we still have to get rid of something
1433 if (mCookieCount
>= mMaxNumberOfCookies
) {
1434 // find the position of the oldest cookie, and remove it
1435 data
.oldestTime
= LL_MAXINT
;
1436 FindOldestCookie(data
);
1437 oldCookie
= data
.iter
.current
;
1438 RemoveCookieFromList(data
.iter
);
1442 // if we deleted an old cookie, notify consumers
1444 COOKIE_LOGEVICTED(oldCookie
);
1445 NotifyChanged(oldCookie
, NS_LITERAL_STRING("deleted").get());
1449 // add the cookie to head of list
1450 AddCookieToList(aCookie
);
1451 NotifyChanged(aCookie
, foundCookie
? NS_LITERAL_STRING("changed").get()
1452 : NS_LITERAL_STRING("added").get());
1454 COOKIE_LOGSUCCESS(SET_COOKIE
, aHostURI
, aCookieHeader
, aCookie
, foundCookie
!= nsnull
);
1457 /******************************************************************************
1458 * nsCookieService impl:
1459 * private cookie header parsing functions
1460 ******************************************************************************/
1462 // The following comment block elucidates the function of ParseAttributes.
1463 /******************************************************************************
1464 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
1465 ** please note: this BNF deviates from both specifications, and reflects this
1466 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
1468 ** Differences from RFC2109/2616 and explanations:
1470 The grammar described by this specification is word-based. Except
1471 where noted otherwise, linear white space (<LWS>) can be included
1472 between any two adjacent words (token or quoted-string), and
1473 between adjacent words and separators, without changing the
1474 interpretation of a field.
1475 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
1477 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
1478 common use inside values.
1480 3. tokens and values have looser restrictions on allowed characters than
1481 spec. This is also due to certain characters being in common use inside
1482 values. We allow only '=' to separate token/value pairs, and ';' to
1483 terminate tokens or values. <LWS> is allowed within tokens and values
1486 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
1487 reject control chars or non-ASCII chars. This is erring on the loose
1488 side, since there's probably no good reason to enforce this strictness.
1490 5. cookie <NAME> is optional, where spec requires it. This is a fairly
1491 trivial case, but allows the flexibility of setting only a cookie <VALUE>
1492 with a blank <NAME> and is required by some sites (see bug 169091).
1494 6. Attribute "HttpOnly", not covered in the RFCs, is supported
1498 token = 1*<any allowed-chars except separators>
1499 value = token-value | quoted-string
1500 token-value = 1*<any allowed-chars except value-sep>
1501 quoted-string = ( <"> *( qdtext | quoted-pair ) <"> )
1502 qdtext = <any allowed-chars except <">> ; CR | LF removed by necko
1503 quoted-pair = "\" <any OCTET except NUL or cookie-sep> ; CR | LF removed by necko
1504 separators = ";" | "="
1506 cookie-sep = CR | LF
1507 allowed-chars = <any OCTET except NUL or cookie-sep>
1508 OCTET = <any 8-bit sequence of data>
1510 NUL = <US-ASCII NUL, null control character (0)>
1511 CR = <US-ASCII CR, carriage return (13)>
1512 LF = <US-ASCII LF, linefeed (10)>
1513 SP = <US-ASCII SP, space (32)>
1514 HT = <US-ASCII HT, horizontal-tab (9)>
1516 set-cookie = "Set-Cookie:" cookies
1517 cookies = cookie *( cookie-sep cookie )
1518 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
1519 NAME = token ; cookie name
1520 VALUE = value ; cookie value
1521 cookie-av = token ["=" value]
1523 valid values for cookie-av (checked post-parsing) are:
1524 cookie-av = "Path" "=" value
1525 | "Domain" "=" value
1526 | "Expires" "=" value
1527 | "Max-Age" "=" value
1528 | "Comment" "=" value
1529 | "Version" "=" value
1533 ******************************************************************************/
1535 // helper functions for GetTokenValue
1536 static inline PRBool
iswhitespace (char c
) { return c
== ' ' || c
== '\t'; }
1537 static inline PRBool
isterminator (char c
) { return c
== '\n' || c
== '\r'; }
1538 static inline PRBool
isquoteterminator(char c
) { return isterminator(c
) || c
== '"'; }
1539 static inline PRBool
isvalueseparator (char c
) { return isterminator(c
) || c
== ';'; }
1540 static inline PRBool
istokenseparator (char c
) { return isvalueseparator(c
) || c
== '='; }
1542 // Parse a single token/value pair.
1543 // Returns PR_TRUE if a cookie terminator is found, so caller can parse new cookie.
1545 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator
&aIter
,
1546 nsASingleFragmentCString::const_char_iterator
&aEndIter
,
1547 nsDependentCSubstring
&aTokenString
,
1548 nsDependentCSubstring
&aTokenValue
,
1549 PRBool
&aEqualsFound
)
1551 nsASingleFragmentCString::const_char_iterator start
, lastSpace
;
1552 // initialize value string to clear garbage
1553 aTokenValue
.Rebind(aIter
, aIter
);
1555 // find <token>, including any <LWS> between the end-of-token and the
1556 // token separator. we'll remove trailing <LWS> next
1557 while (aIter
!= aEndIter
&& iswhitespace(*aIter
))
1560 while (aIter
!= aEndIter
&& !istokenseparator(*aIter
))
1563 // remove trailing <LWS>; first check we're not at the beginning
1565 if (lastSpace
!= start
) {
1566 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
));
1569 aTokenString
.Rebind(start
, lastSpace
);
1571 aEqualsFound
= (*aIter
== '=');
1574 while (++aIter
!= aEndIter
&& iswhitespace(*aIter
));
1578 if (*aIter
== '"') {
1579 // process <quoted-string>
1580 // (note: cookie terminators, CR | LF, can't happen:
1581 // they're removed by necko before the header gets here)
1582 // assume value mangled if no terminating '"', return
1583 while (++aIter
!= aEndIter
&& !isquoteterminator(*aIter
)) {
1584 // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
1585 // we increment once over the backwhack, nullcheck, then continue to the 'while',
1586 // which increments over the backwhacked char. one exception - we don't allow
1587 // CR | LF here either (see above about necko)
1588 if (*aIter
== '\\' && (++aIter
== aEndIter
|| isterminator(*aIter
)))
1592 if (aIter
!= aEndIter
&& !isterminator(*aIter
)) {
1593 // include terminating quote in attribute string
1594 aTokenValue
.Rebind(start
, ++aIter
);
1596 while (aIter
!= aEndIter
&& !isvalueseparator(*aIter
))
1600 // process <token-value>
1601 // just look for ';' to terminate ('=' allowed)
1602 while (aIter
!= aEndIter
&& !isvalueseparator(*aIter
))
1605 // remove trailing <LWS>; first check we're not at the beginning
1606 if (aIter
!= start
) {
1608 while (--lastSpace
!= start
&& iswhitespace(*lastSpace
));
1609 aTokenValue
.Rebind(start
, ++lastSpace
);
1614 // aIter is on ';', or terminator, or EOS
1615 if (aIter
!= aEndIter
) {
1616 // if on terminator, increment past & return PR_TRUE to process new cookie
1617 if (isterminator(*aIter
)) {
1621 // fall-through: aIter is on ';', increment and return PR_FALSE
1627 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
1628 // cookie struct here, because we don't know which one to use until we've parsed the header.
1630 nsCookieService::ParseAttributes(nsDependentCString
&aCookieHeader
,
1631 nsCookieAttributes
&aCookieAttributes
)
1633 static const char kPath
[] = "path";
1634 static const char kDomain
[] = "domain";
1635 static const char kExpires
[] = "expires";
1636 static const char kMaxage
[] = "max-age";
1637 static const char kSecure
[] = "secure";
1638 static const char kHttpOnly
[] = "httponly";
1640 nsASingleFragmentCString::const_char_iterator tempBegin
, tempEnd
;
1641 nsASingleFragmentCString::const_char_iterator cookieStart
, cookieEnd
;
1642 aCookieHeader
.BeginReading(cookieStart
);
1643 aCookieHeader
.EndReading(cookieEnd
);
1645 aCookieAttributes
.isSecure
= PR_FALSE
;
1646 aCookieAttributes
.isHttpOnly
= PR_FALSE
;
1648 nsDependentCSubstring
tokenString(cookieStart
, cookieStart
);
1649 nsDependentCSubstring
tokenValue (cookieStart
, cookieStart
);
1650 PRBool newCookie
, equalsFound
;
1652 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
1653 // if we find multiple cookies, return for processing
1654 // note: if there's no '=', we assume token is <VALUE>. this is required by
1655 // some sites (see bug 169091).
1656 // XXX fix the parser to parse according to <VALUE> grammar for this case
1657 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
1659 aCookieAttributes
.name
= tokenString
;
1660 aCookieAttributes
.value
= tokenValue
;
1662 aCookieAttributes
.value
= tokenString
;
1665 // extract remaining attributes
1666 while (cookieStart
!= cookieEnd
&& !newCookie
) {
1667 newCookie
= GetTokenValue(cookieStart
, cookieEnd
, tokenString
, tokenValue
, equalsFound
);
1669 if (!tokenValue
.IsEmpty()) {
1670 tokenValue
.BeginReading(tempBegin
);
1671 tokenValue
.EndReading(tempEnd
);
1672 if (*tempBegin
== '"' && *--tempEnd
== '"') {
1673 // our parameter is a quoted-string; remove quotes for later parsing
1674 tokenValue
.Rebind(++tempBegin
, tempEnd
);
1678 // decide which attribute we have, and copy the string
1679 if (tokenString
.LowerCaseEqualsLiteral(kPath
))
1680 aCookieAttributes
.path
= tokenValue
;
1682 else if (tokenString
.LowerCaseEqualsLiteral(kDomain
))
1683 aCookieAttributes
.host
= tokenValue
;
1685 else if (tokenString
.LowerCaseEqualsLiteral(kExpires
))
1686 aCookieAttributes
.expires
= tokenValue
;
1688 else if (tokenString
.LowerCaseEqualsLiteral(kMaxage
))
1689 aCookieAttributes
.maxage
= tokenValue
;
1691 // ignore any tokenValue for isSecure; just set the boolean
1692 else if (tokenString
.LowerCaseEqualsLiteral(kSecure
))
1693 aCookieAttributes
.isSecure
= PR_TRUE
;
1695 // ignore any tokenValue for isHttpOnly (see bug 178993);
1696 // just set the boolean
1697 else if (tokenString
.LowerCaseEqualsLiteral(kHttpOnly
))
1698 aCookieAttributes
.isHttpOnly
= PR_TRUE
;
1701 // rebind aCookieHeader, in case we need to process another cookie
1702 aCookieHeader
.Rebind(cookieStart
, cookieEnd
);
1706 /******************************************************************************
1707 * nsCookieService impl:
1708 * private domain & permission compliance enforcement functions
1709 ******************************************************************************/
1712 nsCookieService::IsForeign(nsIURI
*aHostURI
,
1716 nsCAutoString currentHost
, firstHost
;
1717 if (NS_FAILED(aHostURI
->GetAsciiHost(currentHost
)) ||
1718 NS_FAILED(aFirstURI
->GetAsciiHost(firstHost
))) {
1722 // trim trailing dots
1723 currentHost
.Trim(".");
1724 firstHost
.Trim(".");
1726 // fast path: check if the two hosts are identical.
1727 // this also covers two special cases:
1728 // 1) if we're dealing with IP addresses, require an exact match. this
1729 // eliminates any chance of IP address funkiness (e.g. the alias 127.1
1730 // domain-matching 99.54.127.1). bug 105917 originally noted the requirement
1731 // to deal with IP addresses. note that GetBaseDomain() below will return an
1732 // error if the URI is an IP address.
1733 // 2) we also need this for the (rare) case where the site is actually an eTLD,
1734 // e.g. http://co.tv; GetBaseDomain() will throw an error and we might
1735 // erroneously think currentHost is foreign. so we consider this case non-
1736 // foreign only if the hosts exactly match.
1737 if (firstHost
.Equals(currentHost
))
1740 // get the base domain for the originating URI.
1741 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
1742 nsCAutoString baseDomain
;
1743 nsresult rv
= mTLDService
->GetBaseDomain(aFirstURI
, 0, baseDomain
);
1744 if (NS_FAILED(rv
)) {
1745 // URI is an IP, eTLD, or something else went wrong - assume foreign
1748 baseDomain
.Trim(".");
1750 // ensure the host domain is derived from the base domain.
1751 // we prepend dots before the comparison to ensure e.g.
1752 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1753 currentHost
.Insert(NS_LITERAL_CSTRING("."), 0);
1754 baseDomain
.Insert(NS_LITERAL_CSTRING("."), 0);
1755 return !StringEndsWith(currentHost
, baseDomain
);
1759 nsCookieService::CheckPrefs(nsIURI
*aHostURI
,
1760 nsIChannel
*aChannel
,
1761 const char *aCookieHeader
)
1765 // don't let ftp sites get/set cookies (could be a security issue)
1767 if (NS_SUCCEEDED(aHostURI
->SchemeIs("ftp", &ftp
)) && ftp
) {
1768 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "ftp sites cannot read cookies");
1769 return STATUS_REJECTED_WITH_ERROR
;
1772 // check the permission list first; if we find an entry, it overrides
1773 // default prefs. see bug 184059.
1774 if (mPermissionService
) {
1775 nsCookieAccess access
;
1776 rv
= mPermissionService
->CanAccess(aHostURI
, aChannel
, &access
);
1778 // if we found an entry, use it
1779 if (NS_SUCCEEDED(rv
)) {
1781 case nsICookiePermission::ACCESS_DENY
:
1782 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are blocked for this site");
1783 return STATUS_REJECTED
;
1785 case nsICookiePermission::ACCESS_ALLOW
:
1786 return STATUS_ACCEPTED
;
1791 // check default prefs
1792 if (mCookiesPermissions
== BEHAVIOR_REJECT
) {
1793 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "cookies are disabled");
1794 return STATUS_REJECTED
;
1796 } else if (mCookiesPermissions
== BEHAVIOR_REJECTFOREIGN
) {
1797 // check if cookie is foreign
1798 if (!mPermissionService
) {
1799 NS_WARNING("Foreign cookie blocking enabled, but nsICookiePermission unavailable! Rejecting cookie");
1800 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("CheckPrefs(): foreign blocking enabled, but nsICookiePermission unavailable! Rejecting cookie"));
1801 return STATUS_REJECTED
;
1804 nsCOMPtr
<nsIURI
> firstURI
;
1805 rv
= mPermissionService
->GetOriginatingURI(aChannel
, getter_AddRefs(firstURI
));
1807 if (NS_FAILED(rv
) || IsForeign(aHostURI
, firstURI
)) {
1808 COOKIE_LOGFAILURE(aCookieHeader
? SET_COOKIE
: GET_COOKIE
, aHostURI
, aCookieHeader
, "originating server test failed");
1809 return STATUS_REJECTED
;
1813 // if nothing has complained, accept cookie
1814 return STATUS_ACCEPTED
;
1817 // processes domain attribute, and returns PR_TRUE if host has permission to set for this domain.
1819 nsCookieService::CheckDomain(nsCookieAttributes
&aCookieAttributes
,
1824 // get host from aHostURI
1825 nsCAutoString hostFromURI
;
1826 if (NS_FAILED(aHostURI
->GetAsciiHost(hostFromURI
))) {
1829 // trim trailing dots
1830 hostFromURI
.Trim(".");
1832 // if a domain is given, check the host has permission
1833 if (!aCookieAttributes
.host
.IsEmpty()) {
1834 aCookieAttributes
.host
.Trim(".");
1835 // switch to lowercase now, to avoid case-insensitive compares everywhere
1836 ToLowerCase(aCookieAttributes
.host
);
1838 // get the base domain for the host URI.
1839 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk", which
1840 // represents the lowest level domain a cookie can be set for.
1841 nsCAutoString baseDomain
;
1842 rv
= mTLDService
->GetBaseDomain(aHostURI
, 0, baseDomain
);
1843 baseDomain
.Trim(".");
1844 if (NS_FAILED(rv
)) {
1845 // check whether the host is an IP address, and leave the cookie as
1846 // a non-domain one. this will require an exact host match for the cookie,
1847 // so we eliminate any chance of IP address funkiness (e.g. the alias 127.1
1848 // domain-matching 99.54.127.1). bug 105917 originally noted the
1849 // requirement to deal with IP addresses.
1850 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
)
1851 return hostFromURI
.Equals(aCookieAttributes
.host
);
1856 // ensure the proposed domain is derived from the base domain; and also
1857 // that the host domain is derived from the proposed domain (per RFC2109).
1858 // we prepend a dot before the comparison to ensure e.g.
1859 // "mybbc.co.uk" isn't matched as a superset of "bbc.co.uk".
1860 hostFromURI
.Insert(NS_LITERAL_CSTRING("."), 0);
1861 aCookieAttributes
.host
.Insert(NS_LITERAL_CSTRING("."), 0);
1862 baseDomain
.Insert(NS_LITERAL_CSTRING("."), 0);
1863 return StringEndsWith(aCookieAttributes
.host
, baseDomain
) &&
1864 StringEndsWith(hostFromURI
, aCookieAttributes
.host
);
1867 * note: RFC2109 section 4.3.2 requires that we check the following:
1868 * that the portion of host not in domain does not contain a dot.
1869 * this prevents hosts of the form x.y.co.nz from setting cookies in the
1870 * entire .co.nz domain. however, it's only a only a partial solution and
1871 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
1875 // block any URIs without a host that aren't file:/// URIs
1876 if (hostFromURI
.IsEmpty()) {
1877 PRBool isFileURI
= PR_FALSE
;
1878 aHostURI
->SchemeIs("file", &isFileURI
);
1883 // no domain specified, use hostFromURI
1884 aCookieAttributes
.host
= hostFromURI
;
1890 nsCookieService::CheckPath(nsCookieAttributes
&aCookieAttributes
,
1893 // if a path is given, check the host has permission
1894 if (aCookieAttributes
.path
.IsEmpty()) {
1895 // strip down everything after the last slash to get the path,
1896 // ignoring slashes in the query string part.
1897 // if we can QI to nsIURL, that'll take care of the query string portion.
1898 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
1899 nsCOMPtr
<nsIURL
> hostURL
= do_QueryInterface(aHostURI
);
1901 hostURL
->GetDirectory(aCookieAttributes
.path
);
1903 aHostURI
->GetPath(aCookieAttributes
.path
);
1904 PRInt32 slash
= aCookieAttributes
.path
.RFindChar('/');
1905 if (slash
!= kNotFound
) {
1906 aCookieAttributes
.path
.Truncate(slash
+ 1);
1913 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
1914 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
1915 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
1916 * been disabled, unless we can evangelize these sites.
1918 // get path from aHostURI
1919 nsCAutoString pathFromURI
;
1920 if (NS_FAILED(aHostURI
->GetPath(pathFromURI
)) ||
1921 !StringBeginsWith(pathFromURI
, aCookieAttributes
.path
)) {
1927 if (aCookieAttributes
.path
.Length() > kMaxBytesPerPath
||
1928 aCookieAttributes
.path
.FindChar('\t') != kNotFound
)
1935 nsCookieService::GetExpiry(nsCookieAttributes
&aCookieAttributes
,
1936 PRInt64 aServerTime
,
1937 PRInt64 aCurrentTime
)
1939 /* Determine when the cookie should expire. This is done by taking the difference between
1940 * the server time and the time the server wants the cookie to expire, and adding that
1941 * difference to the client time. This localizes the client time regardless of whether or
1942 * not the TZ environment variable was set on the client.
1944 * Note: We need to consider accounting for network lag here, per RFC.
1948 // check for max-age attribute first; this overrides expires attribute
1949 if (!aCookieAttributes
.maxage
.IsEmpty()) {
1950 // obtain numeric value of maxageAttribute
1952 PRInt32 numInts
= PR_sscanf(aCookieAttributes
.maxage
.get(), "%lld", &maxage
);
1954 // default to session cookie if the conversion failed
1961 // check for expires attribute
1962 } else if (!aCookieAttributes
.expires
.IsEmpty()) {
1966 // parse expiry time
1967 if (PR_ParseTimeString(aCookieAttributes
.expires
.get(), PR_TRUE
, &tempExpires
) == PR_SUCCESS
) {
1968 expires
= tempExpires
/ PR_USEC_PER_SEC
;
1973 delta
= expires
- aServerTime
;
1975 // default to session cookie if no attributes found
1980 // if this addition overflows, expiryTime will be less than currentTime
1981 // and the cookie will be expired - that's okay.
1982 aCookieAttributes
.expiryTime
= aCurrentTime
+ delta
;
1987 /******************************************************************************
1988 * nsCookieService impl:
1989 * private cookielist management functions
1990 ******************************************************************************/
1993 nsCookieService::RemoveAllFromMemory()
1995 // clearing the hashtable will call each nsCookieEntry's dtor,
1996 // which releases all their respective children.
2001 PLDHashOperator PR_CALLBACK
2002 removeExpiredCallback(nsCookieEntry
*aEntry
,
2005 const PRInt64
¤tTime
= *static_cast<PRInt64
*>(aArg
);
2006 for (nsListIter
iter(aEntry
, nsnull
, aEntry
->Head()); iter
.current
; ) {
2007 if (iter
.current
->Expiry() <= currentTime
)
2008 // remove from list. this takes care of updating the iterator for us
2009 nsCookieService::gCookieService
->RemoveCookieFromList(iter
);
2013 return PL_DHASH_NEXT
;
2016 // removes any expired cookies from memory
2018 nsCookieService::RemoveExpiredCookies(PRInt64 aCurrentTime
)
2021 PRUint32 initialCookieCount
= mCookieCount
;
2023 mHostTable
.EnumerateEntries(removeExpiredCallback
, &aCurrentTime
);
2024 COOKIE_LOGSTRING(PR_LOG_DEBUG
, ("RemoveExpiredCookies(): %ld purged; %ld remain", initialCookieCount
- mCookieCount
, mCookieCount
));
2027 // find whether a given cookie has been previously set. this is provided by the
2028 // nsICookieManager2 interface.
2030 nsCookieService::CookieExists(nsICookie2
*aCookie
,
2031 PRBool
*aFoundCookie
)
2033 NS_ENSURE_ARG_POINTER(aCookie
);
2035 // just a placeholder
2037 nsCookie
*cookie
= static_cast<nsCookie
*>(aCookie
);
2039 *aFoundCookie
= FindCookie(cookie
->Host(), cookie
->Name(), cookie
->Path(),
2040 iter
, PR_Now() / PR_USEC_PER_SEC
);
2044 // count the number of cookies from a given host, and simultaneously find the
2045 // oldest cookie from the host.
2047 nsCookieService::CountCookiesFromHostInternal(const nsACString
&aHost
,
2048 nsEnumerationData
&aData
)
2050 PRUint32 countFromHost
= 0;
2052 nsCAutoString
hostWithDot(NS_LITERAL_CSTRING(".") + aHost
);
2054 const char *currentDot
= hostWithDot
.get();
2055 const char *nextDot
= currentDot
+ 1;
2057 nsCookieEntry
*entry
= mHostTable
.GetEntry(currentDot
);
2058 for (nsListIter
iter(entry
); iter
.current
; ++iter
) {
2059 // only count non-expired cookies
2060 if (iter
.current
->Expiry() > aData
.currentTime
) {
2063 // check if we've found the oldest cookie so far
2064 if (aData
.oldestTime
> iter
.current
->LastAccessed()) {
2065 aData
.oldestTime
= iter
.current
->LastAccessed();
2071 currentDot
= nextDot
;
2073 nextDot
= strchr(currentDot
+ 1, '.');
2075 } while (currentDot
);
2077 return countFromHost
;
2080 // count the number of cookies stored by a particular host. this is provided by the
2081 // nsICookieManager2 interface.
2083 nsCookieService::CountCookiesFromHost(const nsACString
&aHost
,
2084 PRUint32
*aCountFromHost
)
2086 // we don't care about finding the oldest cookie here, so disable the search
2087 nsEnumerationData
data(PR_Now() / PR_USEC_PER_SEC
, LL_MININT
);
2089 *aCountFromHost
= CountCookiesFromHostInternal(aHost
, data
);
2093 // find an exact cookie specified by host, name, and path that hasn't expired.
2095 nsCookieService::FindCookie(const nsAFlatCString
&aHost
,
2096 const nsAFlatCString
&aName
,
2097 const nsAFlatCString
&aPath
,
2099 PRInt64 aCurrentTime
)
2101 nsCookieEntry
*entry
= mHostTable
.GetEntry(aHost
.get());
2102 for (aIter
= nsListIter(entry
); aIter
.current
; ++aIter
) {
2103 if (aIter
.current
->Expiry() > aCurrentTime
&&
2104 aPath
.Equals(aIter
.current
->Path()) &&
2105 aName
.Equals(aIter
.current
->Name())) {
2113 // removes a cookie from the hashtable, and update the iterator state.
2115 nsCookieService::RemoveCookieFromList(nsListIter
&aIter
)
2117 // if it's a non-session cookie, remove it from the db
2118 if (!aIter
.current
->IsSession() && mStmtDelete
) {
2119 // use our cached sqlite "delete" statement
2120 mozStorageStatementScoper
scoper(mStmtDelete
);
2122 nsresult rv
= mStmtDelete
->BindInt64Parameter(0, aIter
.current
->CreationID());
2123 if (NS_SUCCEEDED(rv
)) {
2125 rv
= mStmtDelete
->ExecuteStep(&hasResult
);
2128 if (NS_FAILED(rv
)) {
2129 NS_WARNING("db remove failed!");
2130 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("RemoveCookieFromList(): removing from db gave error %x", rv
));
2134 if (!aIter
.prev
&& !aIter
.current
->Next()) {
2135 // we're removing the last element in the list - so just remove the entry
2136 // from the hash. note that the entryclass' dtor will take care of
2137 // releasing this last element for us!
2138 mHostTable
.RawRemoveEntry(aIter
.entry
);
2139 aIter
.current
= nsnull
;
2142 // just remove the element from the list, and increment the iterator
2143 nsCookie
*next
= aIter
.current
->Next();
2144 NS_RELEASE(aIter
.current
);
2146 // element to remove is not the head
2147 aIter
.current
= aIter
.prev
->Next() = next
;
2149 // element to remove is the head
2150 aIter
.current
= aIter
.entry
->Head() = next
;
2158 bindCookieParameters(mozIStorageStatement
* aStmt
, const nsCookie
* aCookie
)
2162 rv
= aStmt
->BindInt64Parameter(0, aCookie
->CreationID());
2163 if (NS_FAILED(rv
)) return rv
;
2165 rv
= aStmt
->BindUTF8StringParameter(1, aCookie
->Name());
2166 if (NS_FAILED(rv
)) return rv
;
2168 rv
= aStmt
->BindUTF8StringParameter(2, aCookie
->Value());
2169 if (NS_FAILED(rv
)) return rv
;
2171 rv
= aStmt
->BindUTF8StringParameter(3, aCookie
->Host());
2172 if (NS_FAILED(rv
)) return rv
;
2174 rv
= aStmt
->BindUTF8StringParameter(4, aCookie
->Path());
2175 if (NS_FAILED(rv
)) return rv
;
2177 rv
= aStmt
->BindInt64Parameter(5, aCookie
->Expiry());
2178 if (NS_FAILED(rv
)) return rv
;
2180 rv
= aStmt
->BindInt64Parameter(6, aCookie
->LastAccessed());
2181 if (NS_FAILED(rv
)) return rv
;
2183 rv
= aStmt
->BindInt32Parameter(7, aCookie
->IsSecure());
2184 if (NS_FAILED(rv
)) return rv
;
2186 rv
= aStmt
->BindInt32Parameter(8, aCookie
->IsHttpOnly());
2191 nsCookieService::AddCookieToList(nsCookie
*aCookie
, PRBool aWriteToDB
)
2193 nsCookieEntry
*entry
= mHostTable
.PutEntry(aCookie
->Host().get());
2196 NS_ERROR("can't insert element into a null entry!");
2202 aCookie
->Next() = entry
->Head();
2203 entry
->Head() = aCookie
;
2206 // if it's a non-session cookie and hasn't just been read from the db, write it out.
2207 if (aWriteToDB
&& !aCookie
->IsSession() && mStmtInsert
) {
2208 // use our cached sqlite "insert" statement
2209 mozStorageStatementScoper
scoper(mStmtInsert
);
2211 nsresult rv
= bindCookieParameters(mStmtInsert
, aCookie
);
2212 if (NS_SUCCEEDED(rv
)) {
2214 rv
= mStmtInsert
->ExecuteStep(&hasResult
);
2217 if (NS_FAILED(rv
)) {
2218 NS_WARNING("db insert failed!");
2219 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("AddCookieToList(): adding to db gave error %x", rv
));
2227 nsCookieService::UpdateCookieInList(nsCookie
*aCookie
, PRInt64 aLastAccessed
)
2229 // update the lastAccessed timestamp
2230 aCookie
->SetLastAccessed(aLastAccessed
);
2232 // if it's a non-session cookie, update it in the db too
2233 if (!aCookie
->IsSession() && mStmtUpdate
) {
2234 // use our cached sqlite "update" statement
2235 mozStorageStatementScoper
scoper(mStmtUpdate
);
2237 nsresult rv
= mStmtUpdate
->BindInt64Parameter(0, aLastAccessed
);
2238 if (NS_SUCCEEDED(rv
)) {
2239 rv
= mStmtUpdate
->BindInt64Parameter(1, aCookie
->CreationID());
2240 if (NS_SUCCEEDED(rv
)) {
2242 rv
= mStmtUpdate
->ExecuteStep(&hasResult
);
2246 if (NS_FAILED(rv
)) {
2247 NS_WARNING("db update failed!");
2248 COOKIE_LOGSTRING(PR_LOG_WARNING
, ("UpdateCookieInList(): updating db gave error %x", rv
));
2253 PR_STATIC_CALLBACK(PLDHashOperator
)
2254 findOldestCallback(nsCookieEntry
*aEntry
,
2257 nsEnumerationData
*data
= static_cast<nsEnumerationData
*>(aArg
);
2258 for (nsListIter
iter(aEntry
, nsnull
, aEntry
->Head()); iter
.current
; ++iter
) {
2259 // check if we've found the oldest cookie so far
2260 if (data
->oldestTime
> iter
.current
->LastAccessed()) {
2261 data
->oldestTime
= iter
.current
->LastAccessed();
2265 return PL_DHASH_NEXT
;
2269 nsCookieService::FindOldestCookie(nsEnumerationData
&aData
)
2271 mHostTable
.EnumerateEntries(findOldestCallback
, &aData
);