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