Back to index

lightning-sunbird  0.9+nobinonly
nsCookieService.cpp
Go to the documentation of this file.
00001 // vim:ts=2:sw=2:et:
00002 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2003
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Daniel Witte (dwitte@stanford.edu)
00025  *   Michiel van Leeuwen (mvl@exedo.nl)
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nsCookieService.h"
00042 #include "nsIServiceManager.h"
00043 
00044 #include "nsIIOService.h"
00045 #include "nsIPrefBranch.h"
00046 #include "nsIPrefBranch2.h"
00047 #include "nsIPrefService.h"
00048 #include "nsICookieConsent.h"
00049 #include "nsICookiePermission.h"
00050 #include "nsIURI.h"
00051 #include "nsIURL.h"
00052 #include "nsIChannel.h"
00053 #include "nsIHttpChannel.h"
00054 #include "nsIHttpChannelInternal.h" // evil hack!
00055 #include "nsIPrompt.h"
00056 #include "nsITimer.h"
00057 #include "nsIFile.h"
00058 #include "nsIObserverService.h"
00059 #include "nsILineInputStream.h"
00060 
00061 #include "nsCOMArray.h"
00062 #include "nsArrayEnumerator.h"
00063 #include "nsAutoPtr.h"
00064 #include "nsReadableUtils.h"
00065 #include "nsCRT.h"
00066 #include "prtime.h"
00067 #include "prprf.h"
00068 #include "prnetdb.h"
00069 #include "nsNetUtil.h"
00070 #include "nsNetCID.h"
00071 #include "nsAppDirectoryServiceDefs.h"
00072 
00073 /******************************************************************************
00074  * nsCookieService impl:
00075  * useful types & constants
00076  ******************************************************************************/
00077 
00078 // XXX_hack. See bug 178993.
00079 // This is a hack to hide HttpOnly cookies from older browsers
00080 //
00081 
00082 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
00083 
00084 static const char kCookieFileName[] = "cookies.txt";
00085 
00086 static const PRUint32 kLazyWriteTimeout = 5000; //msec
00087 
00088 #undef  LIMIT
00089 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
00090 
00091 // default limits for the cookie list. these can be tuned by the
00092 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
00093 static const PRUint32 kMaxNumberOfCookies = 1000;
00094 static const PRUint32 kMaxCookiesPerHost  = 50;
00095 static const PRUint32 kMaxBytesPerCookie  = 4096;
00096 static const PRUint32 kMaxBytesPerPath    = 1024;
00097 
00098 // this constant augments those defined on nsICookie, and indicates
00099 // the cookie should be rejected because of an error (rather than
00100 // something the user can control). this is used for notifying about
00101 // rejected cookies, since we only want to notify of rejections where
00102 // the user can do something about it (e.g. whitelist the site).
00103 static const nsCookieStatus STATUS_REJECTED_WITH_ERROR = 5;
00104 
00105 // XXX these casts and constructs are horrible, but our nsInt64/nsTime
00106 // classes are lacking so we need them for now. see bug 198694.
00107 #define USEC_PER_SEC   (nsInt64(1000000))
00108 #define NOW_IN_SECONDS (nsInt64(PR_Now()) / USEC_PER_SEC)
00109 
00110 // behavior pref constants 
00111 static const PRUint32 BEHAVIOR_ACCEPT        = 0;
00112 static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1;
00113 static const PRUint32 BEHAVIOR_REJECT        = 2;
00114 static const PRUint32 BEHAVIOR_P3P           = 3;
00115 
00116 // pref string constants
00117 static const char kPrefCookiesPermissions[] = "network.cookie.cookieBehavior";
00118 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
00119 static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
00120 
00121 // struct for temporarily storing cookie attributes during header parsing
00122 struct nsCookieAttributes
00123 {
00124   nsCAutoString name;
00125   nsCAutoString value;
00126   nsCAutoString host;
00127   nsCAutoString path;
00128   nsCAutoString expires;
00129   nsCAutoString maxage;
00130   nsInt64 expiryTime;
00131   PRBool isSession;
00132   PRBool isSecure;
00133   PRBool isHttpOnly;
00134 };
00135 
00136 // stores linked list iteration state, and provides a rudimentary
00137 // list traversal method
00138 struct nsListIter
00139 {
00140   nsListIter() {}
00141 
00142   nsListIter(nsCookieEntry *aEntry)
00143    : entry(aEntry)
00144    , prev(nsnull)
00145    , current(aEntry ? aEntry->Head() : nsnull) {}
00146 
00147   nsListIter(nsCookieEntry *aEntry,
00148              nsCookie      *aPrev,
00149              nsCookie      *aCurrent)
00150    : entry(aEntry)
00151    , prev(aPrev)
00152    , current(aCurrent) {}
00153 
00154   nsListIter& operator++() { prev = current; current = current->Next(); return *this; }
00155 
00156   nsCookieEntry *entry;
00157   nsCookie      *prev;
00158   nsCookie      *current;
00159 };
00160 
00161 // stores temporary data for enumerating over the hash entries,
00162 // since enumeration is done using callback functions
00163 struct nsEnumerationData
00164 {
00165   nsEnumerationData(nsInt64 aCurrentTime,
00166                     PRInt64 aOldestTime)
00167    : currentTime(aCurrentTime)
00168    , oldestTime(aOldestTime)
00169    , iter(nsnull, nsnull, nsnull) {}
00170 
00171   // the current time
00172   nsInt64 currentTime;
00173 
00174   // oldest lastAccessed time in the cookie list. use aOldestTime = LL_MAXINT
00175   // to enable this search, LL_MININT to disable it.
00176   nsInt64 oldestTime;
00177 
00178   // an iterator object that points to the desired cookie
00179   nsListIter iter;
00180 };
00181 
00182 /******************************************************************************
00183  * Cookie logging handlers
00184  * used for logging in nsCookieService
00185  ******************************************************************************/
00186 
00187 // logging handlers
00188 #ifdef MOZ_LOGGING
00189 // in order to do logging, the following environment variables need to be set:
00190 //
00191 //    set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
00192 //    set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
00193 //    set NSPR_LOG_FILE=cookie.log
00194 //
00195 // this next define has to appear before the include of prlog.h
00196 #define FORCE_PR_LOG // Allow logging in the release build
00197 #include "prlog.h"
00198 #endif
00199 
00200 // define logging macros for convenience
00201 #define SET_COOKIE PR_TRUE
00202 #define GET_COOKIE PR_FALSE
00203 
00204 #ifdef PR_LOGGING
00205 static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie");
00206 
00207 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
00208 #define COOKIE_LOGSUCCESS(a, b, c, d) LogSuccess(a, b, c, d)
00209 
00210 static void
00211 LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
00212 {
00213   // if logging isn't enabled, return now to save cycles
00214   if (!PR_LOG_TEST(sCookieLog, PR_LOG_WARNING)) {
00215     return;
00216   }
00217 
00218   nsCAutoString spec;
00219   if (aHostURI)
00220     aHostURI->GetAsciiSpec(spec);
00221 
00222   PR_LOG(sCookieLog, PR_LOG_WARNING,
00223     ("%s%s%s\n", "===== ", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT", " ====="));
00224   PR_LOG(sCookieLog, PR_LOG_WARNING,("request URL: %s\n", spec.get()));
00225   if (aSetCookie) {
00226     PR_LOG(sCookieLog, PR_LOG_WARNING,("cookie string: %s\n", aCookieString));
00227   }
00228 
00229   PRExplodedTime explodedTime;
00230   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
00231   char timeString[40];
00232   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
00233 
00234   PR_LOG(sCookieLog, PR_LOG_WARNING,("current time: %s", timeString));
00235   PR_LOG(sCookieLog, PR_LOG_WARNING,("rejected because %s\n", aReason));
00236   PR_LOG(sCookieLog, PR_LOG_WARNING,("\n"));
00237 }
00238 
00239 static void
00240 LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie)
00241 {
00242   // if logging isn't enabled, return now to save cycles
00243   if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) {
00244     return;
00245   }
00246 
00247   nsCAutoString spec;
00248   if (aHostURI)
00249     aHostURI->GetAsciiSpec(spec);
00250 
00251   PR_LOG(sCookieLog, PR_LOG_DEBUG,
00252     ("%s%s%s\n", "===== ", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT", " ====="));
00253   PR_LOG(sCookieLog, PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
00254   PR_LOG(sCookieLog, PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
00255 
00256   PRExplodedTime explodedTime;
00257   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
00258   char timeString[40];
00259   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
00260 
00261   PR_LOG(sCookieLog, PR_LOG_DEBUG,("current time: %s", timeString));
00262 
00263   if (aSetCookie) {
00264     PR_LOG(sCookieLog, PR_LOG_DEBUG,("----------------\n"));
00265     PR_LOG(sCookieLog, PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get()));
00266     PR_LOG(sCookieLog, PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get()));
00267     PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
00268     PR_LOG(sCookieLog, PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get()));
00269 
00270     PR_ExplodeTime(aCookie->Expiry() * USEC_PER_SEC, PR_GMTParameters, &explodedTime);
00271     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
00272     PR_LOG(sCookieLog, PR_LOG_DEBUG,
00273       ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
00274 
00275     PR_LOG(sCookieLog, PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
00276     PR_LOG(sCookieLog, PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
00277   }
00278   PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n"));
00279 }
00280 
00281 // inline wrappers to make passing in nsAFlatCStrings easier
00282 static inline void
00283 LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
00284 {
00285   LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
00286 }
00287 
00288 static inline void
00289 LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie)
00290 {
00291   LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie);
00292 }
00293 
00294 #else
00295 #define COOKIE_LOGFAILURE(a, b, c, d) /* nothing */
00296 #define COOKIE_LOGSUCCESS(a, b, c, d) /* nothing */
00297 #endif
00298 
00299 /******************************************************************************
00300  * nsCookieService impl:
00301  * private list sorting callbacks
00302  *
00303  * these functions return:
00304  *   < 0 if the first element should come before the second element,
00305  *     0 if the first element may come before or after the second element,
00306  *   > 0 if the first element should come after the second element.
00307  ******************************************************************************/
00308 
00309 // comparison function for sorting cookies before sending to a server.
00310 PR_STATIC_CALLBACK(int)
00311 compareCookiesForSending(const void *aElement1,
00312                          const void *aElement2,
00313                          void       *aData)
00314 {
00315   const nsCookie *cookie1 = NS_STATIC_CAST(const nsCookie*, aElement1);
00316   const nsCookie *cookie2 = NS_STATIC_CAST(const nsCookie*, aElement2);
00317 
00318   // compare by cookie path length in accordance with RFC2109
00319   int rv = cookie2->Path().Length() - cookie1->Path().Length();
00320   if (rv == 0) {
00321     // when path lengths match, older cookies should be listed first.  this is
00322     // required for backwards compatibility since some websites erroneously
00323     // depend on receiving cookies in the order in which they were sent to the
00324     // browser!  see bug 236772.
00325     rv = cookie1->CreationTime() - cookie2->CreationTime();
00326   }
00327   return rv;
00328 }
00329 
00330 // comparison function for sorting cookies by lastAccessed time, with most-
00331 // recently-used cookies listed first.
00332 PR_STATIC_CALLBACK(int)
00333 compareCookiesForWriting(const void *aElement1,
00334                          const void *aElement2,
00335                          void       *aData)
00336 {
00337   const nsCookie *cookie1 = NS_STATIC_CAST(const nsCookie*, aElement1);
00338   const nsCookie *cookie2 = NS_STATIC_CAST(const nsCookie*, aElement2);
00339 
00340   // we may have overflow problems returning the result directly, so we need branches
00341   nsInt64 difference = cookie2->LastAccessed() - cookie1->LastAccessed();
00342   return (difference > nsInt64(0)) ? 1 : (difference < nsInt64(0)) ? -1 : 0;
00343 }
00344 
00345 /******************************************************************************
00346  * nsCookieService impl:
00347  * singleton instance ctor/dtor methods
00348  ******************************************************************************/
00349 
00350 nsCookieService *nsCookieService::gCookieService = nsnull;
00351 
00352 nsCookieService*
00353 nsCookieService::GetSingleton()
00354 {
00355   if (gCookieService) {
00356     NS_ADDREF(gCookieService);
00357     return gCookieService;
00358   }
00359 
00360   // Create a new singleton nsCookieService.
00361   // We AddRef only once since XPCOM has rules about the ordering of module
00362   // teardowns - by the time our module destructor is called, it's too late to
00363   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
00364   // cycles have already been completed and would result in serious leaks.
00365   // See bug 209571.
00366   gCookieService = new nsCookieService();
00367   if (gCookieService) {
00368     NS_ADDREF(gCookieService);
00369     if (NS_FAILED(gCookieService->Init())) {
00370       NS_RELEASE(gCookieService);
00371     }
00372   }
00373 
00374   return gCookieService;
00375 }
00376 
00377 /******************************************************************************
00378  * nsCookieService impl:
00379  * public methods
00380  ******************************************************************************/
00381 
00382 NS_IMPL_ISUPPORTS5(nsCookieService,
00383                    nsICookieService,
00384                    nsICookieManager,
00385                    nsICookieManager2,
00386                    nsIObserver,
00387                    nsISupportsWeakReference)
00388 
00389 nsCookieService::nsCookieService()
00390  : mCookieCount(0)
00391  , mCookieChanged(PR_FALSE)
00392  , mCookieIconVisible(PR_FALSE)
00393  , mCookiesPermissions(BEHAVIOR_ACCEPT)
00394  , mMaxNumberOfCookies(kMaxNumberOfCookies)
00395  , mMaxCookiesPerHost(kMaxCookiesPerHost)
00396 {
00397 }
00398 
00399 nsresult
00400 nsCookieService::Init()
00401 {
00402   if (!mHostTable.Init()) {
00403     return NS_ERROR_OUT_OF_MEMORY;
00404   }
00405 
00406   // init our pref and observer
00407   nsCOMPtr<nsIPrefBranch2> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
00408   if (prefBranch) {
00409     prefBranch->AddObserver(kPrefCookiesPermissions, this, PR_TRUE);
00410     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, PR_TRUE);
00411     prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, PR_TRUE);
00412     PrefChanged(prefBranch);
00413   }
00414 
00415   // cache mCookieFile
00416   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mCookieFile));
00417   if (mCookieFile) {
00418     mCookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName));
00419   }
00420 
00421   Read();
00422 
00423   mObserverService = do_GetService("@mozilla.org/observer-service;1");
00424   if (mObserverService) {
00425     mObserverService->AddObserver(this, "profile-before-change", PR_TRUE);
00426     mObserverService->AddObserver(this, "profile-do-change", PR_TRUE);
00427     mObserverService->AddObserver(this, "cookieIcon", PR_TRUE);
00428   }
00429 
00430   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
00431 
00432   return NS_OK;
00433 }
00434 
00435 nsCookieService::~nsCookieService()
00436 {
00437   gCookieService = nsnull;
00438 
00439   if (mWriteTimer)
00440     mWriteTimer->Cancel();
00441 }
00442 
00443 NS_IMETHODIMP
00444 nsCookieService::Observe(nsISupports     *aSubject,
00445                          const char      *aTopic,
00446                          const PRUnichar *aData)
00447 {
00448   // check the topic
00449   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
00450     // The profile is about to change,
00451     // or is going away because the application is shutting down.
00452     if (mWriteTimer) {
00453       mWriteTimer->Cancel();
00454       mWriteTimer = 0;
00455     }
00456 
00457     if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
00458       RemoveAllFromMemory();
00459       // delete the cookie file
00460       if (mCookieFile) {
00461         mCookieFile->Remove(PR_FALSE);
00462       }
00463     } else {
00464       Write();
00465       RemoveAllFromMemory();
00466     }
00467 
00468   } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
00469     // The profile has already changed.    
00470     // Now just read them from the new profile location.
00471     // we also need to update the cached cookie file location
00472     nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mCookieFile));
00473     if (NS_SUCCEEDED(rv)) {
00474       mCookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName));
00475     }
00476     Read();
00477 
00478   } else if (!nsCRT::strcmp(aTopic, "cookieIcon")) {
00479     // this is an evil trick to avoid the blatant inefficiency of
00480     // (!nsCRT::strcmp(aData, NS_LITERAL_STRING("on").get()))
00481     mCookieIconVisible = (aData[0] == 'o' && aData[1] == 'n' && aData[2] == '\0');
00482 
00483   } else if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
00484     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
00485     if (prefBranch)
00486       PrefChanged(prefBranch);
00487   }
00488 
00489   return NS_OK;
00490 }
00491 
00492 // helper function for GetCookieStringFromHttp
00493 static inline PRBool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
00494 
00495 nsresult nsCookieService::GetCookieInternal(nsIURI     *aHostURI,
00496                                             nsIURI     *aFirstURI,
00497                                             nsIChannel *aChannel,
00498                                             PRBool     aHttpBound,
00499                                             char       **aCookie)
00500 {
00501   *aCookie = nsnull;
00502 
00503   if (!aHostURI) {
00504     COOKIE_LOGFAILURE(GET_COOKIE, nsnull, nsnull, "host URI is null");
00505     return NS_OK;
00506   }
00507 
00508   // check default prefs
00509   nsCookiePolicy cookiePolicy; // we don't use this here... just a placeholder
00510   nsCookieStatus cookieStatus = CheckPrefs(aHostURI, aFirstURI, aChannel, nsnull, cookiePolicy);
00511   // for GetCookie(), we don't fire rejection notifications.
00512   switch (cookieStatus) {
00513   case nsICookie::STATUS_REJECTED:
00514   case STATUS_REJECTED_WITH_ERROR:
00515     return NS_OK;
00516   }
00517 
00518   // get host and path from the nsIURI
00519   // note: there was a "check if host has embedded whitespace" here.
00520   // it was removed since this check was added into the nsIURI impl (bug 146094).
00521   nsCAutoString hostFromURI, pathFromURI;
00522   if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI)) ||
00523       NS_FAILED(aHostURI->GetPath(pathFromURI))) {
00524     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nsnull, "couldn't get host/path from URI");
00525     return NS_OK;
00526   }
00527   // trim trailing dots
00528   hostFromURI.Trim(".");
00529   // insert a leading dot, so we begin the hash lookup with the
00530   // equivalent domain cookie host
00531   hostFromURI.Insert(NS_LITERAL_CSTRING("."), 0);
00532   ToLowerCase(hostFromURI);
00533 
00534   // check if aHostURI is using an https secure protocol.
00535   // if it isn't, then we can't send a secure cookie over the connection.
00536   // if SchemeIs fails, assume an insecure connection, to be on the safe side
00537   PRBool isSecure;
00538   if NS_FAILED(aHostURI->SchemeIs("https", &isSecure)) {
00539     isSecure = PR_FALSE;
00540   }
00541 
00542   nsCookie *cookie;
00543   nsAutoVoidArray foundCookieList;
00544   nsInt64 currentTime = NOW_IN_SECONDS;
00545   const char *currentDot = hostFromURI.get();
00546   const char *nextDot = currentDot + 1;
00547 
00548   // begin hash lookup, walking up the subdomain levels.
00549   // we use nextDot to force a lookup of the original host (without leading dot).
00550   do {
00551     nsCookieEntry *entry = mHostTable.GetEntry(currentDot);
00552     cookie = entry ? entry->Head() : nsnull;
00553     for (; cookie; cookie = cookie->Next()) {
00554       // if the cookie is secure and the host scheme isn't, we can't send it
00555       if (cookie->IsSecure() && !isSecure) {
00556         continue;
00557       }
00558 
00559       // if the cookie is httpOnly and it's not going directly to the HTTP
00560       // connection, don't send it
00561       if (cookie->IsHttpOnly() && !aHttpBound) {
00562         continue;
00563       }
00564 
00565       // calculate cookie path length, excluding trailing '/'
00566       PRUint32 cookiePathLen = cookie->Path().Length();
00567       if (cookiePathLen > 0 && cookie->Path().Last() == '/') {
00568         --cookiePathLen;
00569       }
00570 
00571       // if the nsIURI path is shorter than the cookie path, don't send it back
00572       if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen))) {
00573         continue;
00574       }
00575 
00576       if (pathFromURI.Length() > cookiePathLen &&
00577           !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
00578         /*
00579          * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
00580          * '/' is the "standard" case; the '?' test allows a site at host/abc?def
00581          * to receive a cookie that has a path attribute of abc.  this seems
00582          * strange but at least one major site (citibank, bug 156725) depends
00583          * on it.  The test for # and ; are put in to proactively avoid problems
00584          * with other sites - these are the only other chars allowed in the path.
00585          */
00586         continue;
00587       }
00588 
00589       // check if the cookie has expired
00590       if (cookie->Expiry() <= currentTime) {
00591         continue;
00592       }
00593 
00594       // all checks passed - add to list and update lastAccessed stamp of cookie
00595       foundCookieList.AppendElement(cookie);
00596       cookie->SetLastAccessed(currentTime);
00597     }
00598 
00599     currentDot = nextDot;
00600     if (currentDot)
00601       nextDot = strchr(currentDot + 1, '.');
00602 
00603   } while (currentDot);
00604 
00605   // return cookies in order of path length; longest to shortest.
00606   // this is required per RFC2109.  if cookies match in length,
00607   // then sort by creation time (see bug 236772).
00608   foundCookieList.Sort(compareCookiesForSending, nsnull);
00609 
00610   nsCAutoString cookieData;
00611   PRInt32 count = foundCookieList.Count();
00612   for (PRInt32 i = 0; i < count; ++i) {
00613     cookie = NS_STATIC_CAST(nsCookie*, foundCookieList.ElementAt(i));
00614 
00615     // check if we have anything to write
00616     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
00617       // if we've already added a cookie to the return list, append a "; " so
00618       // that subsequent cookies are delimited in the final list.
00619       if (!cookieData.IsEmpty()) {
00620         cookieData.AppendLiteral("; ");
00621       }
00622 
00623       if (!cookie->Name().IsEmpty()) {
00624         // we have a name and value - write both
00625         cookieData += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
00626       } else {
00627         // just write value
00628         cookieData += cookie->Value();
00629       }
00630     }
00631   }
00632 
00633   // it's wasteful to alloc a new string; but we have no other choice, until we
00634   // fix the callers to use nsACStrings.
00635   if (!cookieData.IsEmpty()) {
00636     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, cookieData, nsnull);
00637     *aCookie = ToNewCString(cookieData);
00638   }
00639 
00640   return NS_OK;
00641 }
00642 
00643 NS_IMETHODIMP
00644 nsCookieService::GetCookieString(nsIURI     *aHostURI,
00645                                  nsIChannel *aChannel,
00646                                  char       **aCookie)
00647 {
00648   // try to determine first party URI
00649   nsCOMPtr<nsIURI> firstURI;
00650   if (aChannel) {
00651     nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
00652     if (httpInternal)
00653       httpInternal->GetDocumentURI(getter_AddRefs(firstURI));
00654   }
00655 
00656   return GetCookieInternal(aHostURI, firstURI, aChannel, PR_FALSE, aCookie);
00657 }
00658 
00659 NS_IMETHODIMP
00660 nsCookieService::GetCookieStringFromHttp(nsIURI     *aHostURI,
00661                                          nsIURI     *aFirstURI,
00662                                          nsIChannel *aChannel,
00663                                          char       **aCookie)
00664 {
00665   return GetCookieInternal(aHostURI, aFirstURI, aChannel, PR_TRUE, aCookie);
00666 }
00667 
00668 NS_IMETHODIMP
00669 nsCookieService::SetCookieString(nsIURI     *aHostURI,
00670                                  nsIPrompt  *aPrompt,
00671                                  const char *aCookieHeader,
00672                                  nsIChannel *aChannel)
00673 {
00674   // try to determine first party URI
00675   nsCOMPtr<nsIURI> firstURI;
00676 
00677   if (aChannel) {
00678     nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
00679     if (httpInternal)
00680       httpInternal->GetDocumentURI(getter_AddRefs(firstURI));
00681   }
00682 
00683   return SetCookieStringInternal(aHostURI, firstURI, aPrompt, aCookieHeader, nsnull, aChannel, PR_FALSE);
00684 }
00685 
00686 NS_IMETHODIMP
00687 nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
00688                                          nsIURI     *aFirstURI,
00689                                          nsIPrompt  *aPrompt,
00690                                          const char *aCookieHeader,
00691                                          const char *aServerTime,
00692                                          nsIChannel *aChannel) 
00693 {
00694   return SetCookieStringInternal(aHostURI, aFirstURI, aPrompt, aCookieHeader, aServerTime, aChannel, PR_TRUE);
00695 }
00696 
00697 nsresult
00698 nsCookieService::SetCookieStringInternal(nsIURI     *aHostURI,
00699                                          nsIURI     *aFirstURI,
00700                                          nsIPrompt  *aPrompt,
00701                                          const char *aCookieHeader,
00702                                          const char *aServerTime,
00703                                          nsIChannel *aChannel,
00704                                          PRBool      aFromHttp) 
00705 {
00706   if (!aHostURI) {
00707     COOKIE_LOGFAILURE(SET_COOKIE, nsnull, aCookieHeader, "host URI is null");
00708     return NS_OK;
00709   }
00710 
00711   // check default prefs
00712   nsCookiePolicy cookiePolicy = nsICookie::POLICY_UNKNOWN;
00713   nsCookieStatus cookieStatus = CheckPrefs(aHostURI, aFirstURI, aChannel, aCookieHeader, cookiePolicy);
00714   // fire a notification if cookie was rejected (but not if there was an error)
00715   switch (cookieStatus) {
00716   case nsICookie::STATUS_REJECTED:
00717     NotifyRejected(aHostURI);
00718   case STATUS_REJECTED_WITH_ERROR:
00719     return NS_OK;
00720   }
00721 
00722   // parse server local time. this is not just done here for efficiency
00723   // reasons - if there's an error parsing it, and we need to default it
00724   // to the current time, we must do it here since the current time in
00725   // SetCookieInternal() will change for each cookie processed (e.g. if the
00726   // user is prompted).
00727   nsInt64 serverTime;
00728   PRTime tempServerTime;
00729   if (aServerTime && PR_ParseTimeString(aServerTime, PR_TRUE, &tempServerTime) == PR_SUCCESS) {
00730     serverTime = nsInt64(tempServerTime) / USEC_PER_SEC;
00731   } else {
00732     serverTime = NOW_IN_SECONDS;
00733   }
00734 
00735   // switch to a nice string type now, and process each cookie in the header
00736   nsDependentCString cookieHeader(aCookieHeader);
00737   while (SetCookieInternal(aHostURI, aChannel,
00738                            cookieHeader, serverTime, aFromHttp,
00739                            cookieStatus, cookiePolicy));
00740 
00741   // write out the cookie file
00742   LazyWrite();
00743   return NS_OK;
00744 }
00745 
00746 void
00747 nsCookieService::LazyWrite()
00748 {
00749   if (mWriteTimer) {
00750     mWriteTimer->SetDelay(kLazyWriteTimeout);
00751   } else {
00752     mWriteTimer = do_CreateInstance("@mozilla.org/timer;1");
00753     if (mWriteTimer) {
00754       mWriteTimer->InitWithFuncCallback(DoLazyWrite, this, kLazyWriteTimeout,
00755                                         nsITimer::TYPE_ONE_SHOT);
00756     }
00757   }
00758 }
00759 
00760 void
00761 nsCookieService::DoLazyWrite(nsITimer *aTimer,
00762                              void     *aClosure)
00763 {
00764   nsCookieService *service = NS_REINTERPRET_CAST(nsCookieService*, aClosure);
00765   service->Write();
00766   service->mWriteTimer = 0;
00767 }
00768 
00769 // notify observers that a cookie was rejected due to the users' prefs.
00770 void
00771 nsCookieService::NotifyRejected(nsIURI *aHostURI)
00772 {
00773   if (mObserverService)
00774     mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nsnull);
00775 }
00776 
00777 // notify observers that the cookie list changed. there are four possible
00778 // values for aData:
00779 // "deleted" means a cookie was deleted. aCookie is the deleted cookie.
00780 // "added"   means a cookie was added. aCookie is the added cookie.
00781 // "changed" means a cookie was altered. aCookie is the new cookie.
00782 // "cleared" means the entire cookie list was cleared. aCookie is null.
00783 void
00784 nsCookieService::NotifyChanged(nsICookie2      *aCookie,
00785                                const PRUnichar *aData)
00786 {
00787   mCookieChanged = PR_TRUE;
00788 
00789   if (mObserverService)
00790     mObserverService->NotifyObservers(aCookie, "cookie-changed", aData);
00791 
00792   // fire a cookieIcon notification if the cookie was downgraded or flagged
00793   // by p3p. the cookieIcon notification is now deprecated, but we still need
00794   // this until consumers can be fixed. to see if cookies have been
00795   // downgraded or flagged, listen to cookie-changed directly.
00796   if (mCookiesPermissions == BEHAVIOR_P3P &&
00797       (!nsCRT::strcmp(aData, NS_LITERAL_STRING("added").get()) ||
00798        !nsCRT::strcmp(aData, NS_LITERAL_STRING("changed").get()))) {
00799     nsCookieStatus status;
00800     aCookie->GetStatus(&status);
00801     if (status == nsICookie::STATUS_DOWNGRADED ||
00802         status == nsICookie::STATUS_FLAGGED) {
00803       mCookieIconVisible = PR_TRUE;
00804       if (mObserverService)
00805         mObserverService->NotifyObservers(nsnull, "cookieIcon", NS_LITERAL_STRING("on").get());
00806     }
00807   }
00808 }
00809 
00810 // this method is deprecated. listen to the cookie-changed notification instead.
00811 NS_IMETHODIMP
00812 nsCookieService::GetCookieIconIsVisible(PRBool *aIsVisible)
00813 {
00814   *aIsVisible = mCookieIconVisible;
00815   return NS_OK;
00816 }
00817 
00818 /******************************************************************************
00819  * nsCookieService:
00820  * pref observer impl
00821  ******************************************************************************/
00822 
00823 void
00824 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
00825 {
00826   PRInt32 val;
00827   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiesPermissions, &val)))
00828     mCookiesPermissions = LIMIT(val, 0, 3, 0);
00829 
00830   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
00831     mMaxNumberOfCookies = LIMIT(val, 0, 0xFFFF, 0xFFFF);
00832 
00833   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
00834     mMaxCookiesPerHost = LIMIT(val, 0, 0xFFFF, 0xFFFF);
00835 }
00836 
00837 /******************************************************************************
00838  * nsICookieManager impl:
00839  * nsICookieManager
00840  ******************************************************************************/
00841 
00842 NS_IMETHODIMP
00843 nsCookieService::RemoveAll()
00844 {
00845   RemoveAllFromMemory();
00846   NotifyChanged(nsnull, NS_LITERAL_STRING("cleared").get());
00847   Write();
00848   return NS_OK;
00849 }
00850 
00851 PR_STATIC_CALLBACK(PLDHashOperator)
00852 COMArrayCallback(nsCookieEntry *aEntry,
00853                  void          *aArg)
00854 {
00855   for (nsCookie *cookie = aEntry->Head(); cookie; cookie = cookie->Next()) {
00856     NS_STATIC_CAST(nsCOMArray<nsICookie>*, aArg)->AppendObject(cookie);
00857   }
00858   return PL_DHASH_NEXT;
00859 }
00860 
00861 NS_IMETHODIMP
00862 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
00863 {
00864   RemoveExpiredCookies(NOW_IN_SECONDS);
00865 
00866   nsCOMArray<nsICookie> cookieList(mCookieCount);
00867   mHostTable.EnumerateEntries(COMArrayCallback, &cookieList);
00868 
00869   return NS_NewArrayEnumerator(aEnumerator, cookieList);
00870 }
00871 
00872 NS_IMETHODIMP
00873 nsCookieService::Add(const nsACString &aDomain,
00874                      const nsACString &aPath,
00875                      const nsACString &aName,
00876                      const nsACString &aValue,
00877                      PRBool            aIsSecure,
00878                      PRBool            aIsSession,
00879                      PRInt64           aExpiry)
00880 {
00881   nsInt64 currentTime = NOW_IN_SECONDS;
00882 
00883   nsRefPtr<nsCookie> cookie =
00884     nsCookie::Create(aName, aValue, aDomain, aPath,
00885                      nsInt64(aExpiry),
00886                      currentTime,
00887                      aIsSession,
00888                      aIsSecure,
00889                      PR_FALSE,
00890                      nsICookie::STATUS_UNKNOWN,
00891                      nsICookie::POLICY_UNKNOWN);
00892   if (!cookie) {
00893     return NS_ERROR_OUT_OF_MEMORY;
00894   }
00895 
00896   AddInternal(cookie, currentTime, nsnull, nsnull, PR_TRUE);
00897   return NS_OK;
00898 }
00899 
00900 NS_IMETHODIMP
00901 nsCookieService::Remove(const nsACString &aHost,
00902                         const nsACString &aName,
00903                         const nsACString &aPath,
00904                         PRBool           aBlocked)
00905 {
00906   nsListIter matchIter;
00907   if (FindCookie(PromiseFlatCString(aHost),
00908                  PromiseFlatCString(aName),
00909                  PromiseFlatCString(aPath),
00910                  matchIter)) {
00911     nsRefPtr<nsCookie> cookie = matchIter.current;
00912     RemoveCookieFromList(matchIter);
00913     NotifyChanged(cookie, NS_LITERAL_STRING("deleted").get());
00914 
00915     // check if we need to add the host to the permissions blacklist.
00916     if (aBlocked && mPermissionService) {
00917       nsCAutoString host(NS_LITERAL_CSTRING("http://") + cookie->RawHost());
00918       nsCOMPtr<nsIURI> uri;
00919       NS_NewURI(getter_AddRefs(uri), host);
00920 
00921       if (uri)
00922         mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
00923     }
00924 
00925     LazyWrite();
00926   }
00927   return NS_OK;
00928 }
00929 
00930 /******************************************************************************
00931  * nsCookieService impl:
00932  * private file I/O functions
00933  ******************************************************************************/
00934 
00935 nsresult
00936 nsCookieService::Read()
00937 {
00938   nsresult rv;
00939   nsCOMPtr<nsIInputStream> fileInputStream;
00940   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), mCookieFile);
00941   if (NS_FAILED(rv)) {
00942     return rv;
00943   }
00944 
00945   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
00946   if (NS_FAILED(rv)) {
00947     return rv;
00948   }
00949 
00950   static const char kTrue[] = "TRUE";
00951 
00952   nsCAutoString buffer;
00953   PRBool isMore = PR_TRUE;
00954   PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
00955   nsASingleFragmentCString::char_iterator iter;
00956   PRInt32 numInts;
00957   PRInt64 expires;
00958   PRBool isDomain, isHttpOnly = PR_FALSE;
00959   nsInt64 currentTime = NOW_IN_SECONDS;
00960   // we use lastAccessedCounter to keep cookies in recently-used order,
00961   // so we start by initializing to currentTime (somewhat arbitrary)
00962   nsInt64 lastAccessedCounter = currentTime;
00963   nsCookie *newCookie;
00964 
00965   /* file format is:
00966    *
00967    * host \t isDomain \t path \t secure \t expires \t name \t cookie
00968    *
00969    * if this format isn't respected we move onto the next line in the file.
00970    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
00971    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
00972    * expires is a PRInt64 integer
00973    * note 1: cookie can contain tabs.
00974    * note 2: cookies are written in order of lastAccessed time:
00975    *         most-recently used come first; least-recently-used come last.
00976    */
00977 
00978   /*
00979    * ...but due to bug 178933, we hide HttpOnly cookies from older code
00980    * in a comment, so they don't expose HttpOnly cookies to JS.
00981    *
00982    * The format for HttpOnly cookies is
00983    *
00984    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
00985    *
00986    */
00987 
00988   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
00989     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
00990       isHttpOnly = PR_TRUE;
00991       hostIndex = sizeof(kHttpOnlyPrefix) - 1;
00992     } else if (buffer.IsEmpty() || buffer.First() == '#') {
00993       continue;
00994     } else {
00995       isHttpOnly = PR_FALSE;
00996       hostIndex = 0;
00997     }
00998     // this is a cheap, cheesy way of parsing a tab-delimited line into
00999     // string indexes, which can be lopped off into substrings. just for
01000     // purposes of obfuscation, it also checks that each token was found.
01001     // todo: use iterators?
01002     if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
01003         (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
01004         (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
01005         (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
01006         (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
01007         (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
01008       continue;
01009     }
01010 
01011     // check the expirytime first - if it's expired, ignore
01012     // nullstomp the trailing tab, to avoid copying the string
01013     buffer.BeginWriting(iter);
01014     *(iter += nameIndex - 1) = char(0);
01015     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
01016     if (numInts != 1 || nsInt64(expires) < currentTime) {
01017       continue;
01018     }
01019 
01020     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1)
01021                .EqualsLiteral(kTrue);
01022     const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
01023     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
01024     // and discard
01025     if (isDomain && !host.IsEmpty() && host.First() != '.' ||
01026         host.FindChar(':') != kNotFound) {
01027       continue;
01028     }
01029 
01030     // create a new nsCookie and assign the data.
01031     newCookie =
01032       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
01033                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
01034                        host,
01035                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
01036                        nsInt64(expires),
01037                        lastAccessedCounter,
01038                        PR_FALSE,
01039                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
01040                        isHttpOnly,
01041                        nsICookie::STATUS_UNKNOWN,
01042                        nsICookie::POLICY_UNKNOWN);
01043     if (!newCookie) {
01044       return NS_ERROR_OUT_OF_MEMORY;
01045     }
01046 
01047     // trick: keep the cookies in most-recently-used order,
01048     // by successively decrementing the lastAccessed time
01049     lastAccessedCounter -= nsInt64(1);
01050 
01051     if (!AddCookieToList(newCookie)) {
01052       // It is purpose that created us; purpose that connects us;
01053       // purpose that pulls us; that guides us; that drives us.
01054       // It is purpose that defines us; purpose that binds us.
01055       // When a cookie no longer has purpose, it has a choice:
01056       // it can return to the source to be deleted, or it can go
01057       // into exile, and stay hidden inside the Matrix.
01058       // Let's choose deletion.
01059       delete newCookie;
01060     }
01061   }
01062 
01063   mCookieChanged = PR_FALSE;
01064   return NS_OK;
01065 }
01066 
01067 PR_STATIC_CALLBACK(PLDHashOperator)
01068 cookieListCallback(nsCookieEntry *aEntry,
01069                    void          *aArg)
01070 {
01071   for (nsCookie *cookie = aEntry->Head(); cookie; cookie = cookie->Next()) {
01072     NS_STATIC_CAST(nsVoidArray*, aArg)->AppendElement(cookie);
01073   }
01074   return PL_DHASH_NEXT;
01075 }
01076 
01077 nsresult
01078 nsCookieService::Write()
01079 {
01080   if (!mCookieChanged) {
01081     return NS_OK;
01082   }
01083 
01084   if (!mCookieFile) {
01085     return NS_ERROR_NULL_POINTER;
01086   }
01087 
01088   nsresult rv;
01089   nsCOMPtr<nsIOutputStream> fileOutputStream;
01090   rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(fileOutputStream),
01091                                        mCookieFile,
01092                                        -1,
01093                                        0600);
01094   if (NS_FAILED(rv)) {
01095     NS_ERROR("failed to open cookies.txt for writing");
01096     return rv;
01097   }
01098 
01099   // get a buffered output stream 4096 bytes big, to optimize writes
01100   nsCOMPtr<nsIOutputStream> bufferedOutputStream;
01101   rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), fileOutputStream, 4096);
01102   if (NS_FAILED(rv)) {
01103     return rv;
01104   }
01105 
01106   static const char kHeader[] =
01107       "# HTTP Cookie File\n"
01108       "# http://www.netscape.com/newsref/std/cookie_spec.html\n"
01109       "# This is a generated file!  Do not edit.\n"
01110       "# To delete cookies, use the Cookie Manager.\n\n";
01111   // note: kTrue and kFalse have leading/trailing tabs already added
01112   static const char kTrue[] = "\tTRUE\t";
01113   static const char kFalse[] = "\tFALSE\t";
01114   static const char kTab[] = "\t";
01115   static const char kNew[] = "\n";
01116 
01117   // create a new nsVoidArray to hold the cookie list, and sort it
01118   // such that least-recently-used cookies come last
01119   nsVoidArray sortedCookieList(mCookieCount);
01120   mHostTable.EnumerateEntries(cookieListCallback, &sortedCookieList);
01121   sortedCookieList.Sort(compareCookiesForWriting, nsnull);
01122 
01123   bufferedOutputStream->Write(kHeader, sizeof(kHeader) - 1, &rv);
01124 
01125   /* file format is:
01126    *
01127    * host \t isDomain \t path \t secure \t expires \t name \t cookie
01128    *
01129    * isDomain is "TRUE" or "FALSE"
01130    * isSecure is "TRUE" or "FALSE"
01131    * expires is a PRInt64 integer
01132    * note 1: cookie can contain tabs.
01133    * note 2: cookies are written in order of lastAccessed time:
01134    *         most-recently used come first; least-recently-used come last.
01135    */
01136 
01137   /*
01138    * XXX but see above in ::Read for the HttpOnly hack
01139    */
01140    
01141   nsCookie *cookie;
01142   nsInt64 currentTime = NOW_IN_SECONDS;
01143   char dateString[22];
01144   PRUint32 dateLen;
01145   for (PRUint32 i = 0; i < mCookieCount; ++i) {
01146     cookie = NS_STATIC_CAST(nsCookie*, sortedCookieList.ElementAt(i));
01147 
01148     // don't write entry if cookie has expired, or is a session cookie
01149     if (cookie->IsSession() || cookie->Expiry() <= currentTime) {
01150       continue;
01151     }
01152 
01153     // XXX hack for HttpOnly. see bug 178993.
01154     if (cookie->IsHttpOnly()) {
01155       bufferedOutputStream->Write(kHttpOnlyPrefix, sizeof(kHttpOnlyPrefix) - 1, &rv);
01156     }
01157     bufferedOutputStream->Write(cookie->Host().get(), cookie->Host().Length(), &rv);
01158     if (cookie->IsDomain()) {
01159       bufferedOutputStream->Write(kTrue, sizeof(kTrue) - 1, &rv);
01160     } else {
01161       bufferedOutputStream->Write(kFalse, sizeof(kFalse) - 1, &rv);
01162     }
01163     bufferedOutputStream->Write(cookie->Path().get(), cookie->Path().Length(), &rv);
01164     if (cookie->IsSecure()) {
01165       bufferedOutputStream->Write(kTrue, sizeof(kTrue) - 1, &rv);
01166     } else {
01167       bufferedOutputStream->Write(kFalse, sizeof(kFalse) - 1, &rv);
01168     }
01169     dateLen = PR_snprintf(dateString, sizeof(dateString), "%lld", PRInt64(cookie->Expiry()));
01170     bufferedOutputStream->Write(dateString, dateLen, &rv);
01171     bufferedOutputStream->Write(kTab, sizeof(kTab) - 1, &rv);
01172     bufferedOutputStream->Write(cookie->Name().get(), cookie->Name().Length(), &rv);
01173     bufferedOutputStream->Write(kTab, sizeof(kTab) - 1, &rv);
01174     bufferedOutputStream->Write(cookie->Value().get(), cookie->Value().Length(), &rv);
01175     bufferedOutputStream->Write(kNew, sizeof(kNew) - 1, &rv);
01176   }
01177 
01178   // All went ok. Maybe except for problems in Write(), but the stream detects
01179   // that for us
01180   nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(bufferedOutputStream);
01181   NS_ASSERTION(safeStream, "expected a safe output stream!");
01182   if (safeStream) {
01183     rv = safeStream->Finish();
01184     if (NS_FAILED(rv)) {
01185       NS_WARNING("failed to save cookie file! possible dataloss");
01186       return rv;
01187     }
01188   }
01189 
01190   mCookieChanged = PR_FALSE;
01191   return NS_OK;
01192 }
01193 
01194 /******************************************************************************
01195  * nsCookieService impl:
01196  * private GetCookie/SetCookie helpers
01197  ******************************************************************************/
01198 
01199 // processes a single cookie, and returns PR_TRUE if there are more cookies
01200 // to be processed
01201 PRBool
01202 nsCookieService::SetCookieInternal(nsIURI             *aHostURI,
01203                                    nsIChannel         *aChannel,
01204                                    nsDependentCString &aCookieHeader,
01205                                    nsInt64            aServerTime,
01206                                    PRBool             aFromHttp,
01207                                    nsCookieStatus     aStatus,
01208                                    nsCookiePolicy     aPolicy)
01209 {
01210   // keep a |const char*| version of the unmodified aCookieHeader,
01211   // for logging purposes
01212   const char *cookieHeader = aCookieHeader.get();
01213 
01214   // create a stack-based nsCookieAttributes, to store all the
01215   // attributes parsed from the cookie
01216   nsCookieAttributes cookieAttributes;
01217 
01218   // init expiryTime such that session cookies won't prematurely expire
01219   cookieAttributes.expiryTime = LL_MAXINT;
01220 
01221   // newCookie says whether there are multiple cookies in the header; so we can handle them separately.
01222   // after this function, we don't need the cookieHeader string for processing this cookie anymore;
01223   // so this function uses it as an outparam to point to the next cookie, if there is one.
01224   const PRBool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
01225 
01226   // reject cookie if it's over the size limit, per RFC2109
01227   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
01228     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "cookie too big (> 4kb)");
01229     return newCookie;
01230   }
01231 
01232   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
01233     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "invalid name character");
01234     return newCookie;
01235   }
01236 
01237   // calculate expiry time of cookie. we need to pass in cookieStatus, since
01238   // the cookie may have been downgraded to a session cookie by p3p.
01239   const nsInt64 currentTime = NOW_IN_SECONDS;
01240   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
01241                                          currentTime, aStatus);
01242 
01243   // domain & path checks
01244   if (!CheckDomain(cookieAttributes, aHostURI)) {
01245     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "failed the domain tests");
01246     return newCookie;
01247   }
01248   if (!CheckPath(cookieAttributes, aHostURI)) {
01249     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "failed the path tests");
01250     return newCookie;
01251   }
01252 
01253   // create a new nsCookie and copy attributes
01254   nsRefPtr<nsCookie> cookie =
01255     nsCookie::Create(cookieAttributes.name,
01256                      cookieAttributes.value,
01257                      cookieAttributes.host,
01258                      cookieAttributes.path,
01259                      cookieAttributes.expiryTime,
01260                      currentTime,
01261                      cookieAttributes.isSession,
01262                      cookieAttributes.isSecure,
01263                      cookieAttributes.isHttpOnly,
01264                      aStatus,
01265                      aPolicy);
01266   if (!cookie) {
01267     return newCookie;
01268   }
01269 
01270   // check permissions from site permission list, or ask the user,
01271   // to determine if we can set the cookie
01272   if (mPermissionService) {
01273     PRBool permission;
01274     // we need to think about prompters/parent windows here - TestPermission
01275     // needs one to prompt, so right now it has to fend for itself to get one
01276     mPermissionService->CanSetCookie(aHostURI,
01277                                      aChannel,
01278                                      NS_STATIC_CAST(nsICookie2*, NS_STATIC_CAST(nsCookie*, cookie)),
01279                                      &cookieAttributes.isSession,
01280                                      &cookieAttributes.expiryTime.mValue,
01281                                      &permission);
01282     if (!permission) {
01283       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "cookie rejected by permission manager");
01284       NotifyRejected(aHostURI);
01285       return newCookie;
01286     }
01287 
01288     // update isSession and expiry attributes, in case they changed
01289     cookie->SetIsSession(cookieAttributes.isSession);
01290     cookie->SetExpiry(cookieAttributes.expiryTime);
01291   }
01292 
01293   // add the cookie to the list. AddInternal() takes care of logging.
01294   AddInternal(cookie, NOW_IN_SECONDS, aHostURI, cookieHeader, aFromHttp);
01295   return newCookie;
01296 }
01297 
01298 // this is a backend function for adding a cookie to the list, via SetCookie.
01299 // also used in the cookie manager, for profile migration from IE.
01300 // it either replaces an existing cookie; or adds the cookie to the hashtable,
01301 // and deletes a cookie (if maximum number of cookies has been
01302 // reached). also performs list maintenance by removing expired cookies.
01303 void
01304 nsCookieService::AddInternal(nsCookie   *aCookie,
01305                              nsInt64    aCurrentTime,
01306                              nsIURI     *aHostURI,
01307                              const char *aCookieHeader,
01308                              PRBool     aFromHttp)
01309 {
01310   // if the new cookie is httponly, make sure we're not coming from script
01311   if (!aFromHttp && aCookie->IsHttpOnly()) {
01312     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script");
01313     return;
01314   }
01315 
01316   nsListIter matchIter;
01317   const PRBool foundCookie =
01318     FindCookie(aCookie->Host(), aCookie->Name(), aCookie->Path(), matchIter);
01319 
01320   nsRefPtr<nsCookie> oldCookie;
01321   if (foundCookie) {
01322     oldCookie = matchIter.current;
01323 
01324     // if the old cookie is httponly, make sure we're not coming from script
01325     if (!aFromHttp && oldCookie->IsHttpOnly()) {
01326       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie is httponly; coming from script");
01327       return;
01328     }
01329 
01330     RemoveCookieFromList(matchIter);
01331 
01332     // check if the cookie has expired
01333     if (aCookie->Expiry() <= aCurrentTime) {
01334       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie was deleted");
01335       NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
01336       return;
01337     }
01338 
01339     // preserve creation time of cookie
01340     if (oldCookie) {
01341       aCookie->SetCreationTime(oldCookie->CreationTime());
01342     }
01343 
01344   } else {
01345     // check if cookie has already expired
01346     if (aCookie->Expiry() <= aCurrentTime) {
01347       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired");
01348       return;
01349     }
01350 
01351     // check if we have to delete an old cookie.
01352     nsEnumerationData data(aCurrentTime, LL_MAXINT);
01353     if (CountCookiesFromHost(aCookie, data) >= mMaxCookiesPerHost) {
01354       // remove the oldest cookie from host
01355       oldCookie = data.iter.current;
01356       RemoveCookieFromList(data.iter);
01357 
01358     } else if (mCookieCount >= mMaxNumberOfCookies) {
01359       // try to make room, by removing expired cookies
01360       RemoveExpiredCookies(aCurrentTime);
01361 
01362       // check if we still have to get rid of something
01363       if (mCookieCount >= mMaxNumberOfCookies) {
01364         // find the position of the oldest cookie, and remove it
01365         data.oldestTime = LL_MAXINT;
01366         FindOldestCookie(data);
01367         oldCookie = data.iter.current;
01368         RemoveCookieFromList(data.iter);
01369       }
01370     }
01371 
01372     // if we deleted an old cookie, notify consumers
01373     if (oldCookie)
01374       NotifyChanged(oldCookie, NS_LITERAL_STRING("deleted").get());
01375   }
01376 
01377   // add the cookie to head of list
01378   AddCookieToList(aCookie);
01379   NotifyChanged(aCookie, foundCookie ? NS_LITERAL_STRING("changed").get()
01380                                      : NS_LITERAL_STRING("added").get());
01381 
01382   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie);
01383 }
01384 
01385 /******************************************************************************
01386  * nsCookieService impl:
01387  * private cookie header parsing functions
01388  ******************************************************************************/
01389 
01390 // The following comment block elucidates the function of ParseAttributes.
01391 /******************************************************************************
01392  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
01393  ** please note: this BNF deviates from both specifications, and reflects this
01394  ** implementation. <bnf> indicates a reference to the defined grammer "bnf".
01395 
01396  ** Differences from RFC2109/2616 and explanations:
01397     1. implied *LWS
01398          The grammar described by this specification is word-based. Except
01399          where noted otherwise, linear white space (<LWS>) can be included
01400          between any two adjacent words (token or quoted-string), and
01401          between adjacent words and separators, without changing the
01402          interpretation of a field.
01403        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
01404 
01405     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
01406        common use inside values.
01407 
01408     3. tokens and values have looser restrictions on allowed characters than
01409        spec. This is also due to certain characters being in common use inside
01410        values. We allow only '=' to separate token/value pairs, and ';' to
01411        terminate tokens or values. <LWS> is allowed within tokens and values
01412        (see bug 206022).
01413 
01414     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
01415        reject control chars or non-ASCII chars. This is erring on the loose
01416        side, since there's probably no good reason to enforce this strictness.
01417 
01418     5. cookie <NAME> is optional, where spec requires it. This is a fairly
01419        trivial case, but allows the flexibility of setting only a cookie <VALUE>
01420        with a blank <NAME> and is required by some sites (see bug 169091).
01421 
01422     6. Attribute "HttpOnly", not covered in the RFCs, is supported
01423        (see bug 178993).
01424 
01425  ** Begin BNF:
01426     token         = 1*<any allowed-chars except separators>
01427     value         = token-value | quoted-string
01428     token-value   = 1*<any allowed-chars except value-sep>
01429     quoted-string = ( <"> *( qdtext | quoted-pair ) <"> )
01430     qdtext        = <any allowed-chars except <">>             ; CR | LF removed by necko
01431     quoted-pair   = "\" <any OCTET except NUL or cookie-sep>   ; CR | LF removed by necko
01432     separators    = ";" | "="
01433     value-sep     = ";"
01434     cookie-sep    = CR | LF
01435     allowed-chars = <any OCTET except NUL or cookie-sep>
01436     OCTET         = <any 8-bit sequence of data>
01437     LWS           = SP | HT
01438     NUL           = <US-ASCII NUL, null control character (0)>
01439     CR            = <US-ASCII CR, carriage return (13)>
01440     LF            = <US-ASCII LF, linefeed (10)>
01441     SP            = <US-ASCII SP, space (32)>
01442     HT            = <US-ASCII HT, horizontal-tab (9)>
01443 
01444     set-cookie    = "Set-Cookie:" cookies
01445     cookies       = cookie *( cookie-sep cookie )
01446     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
01447     NAME          = token                                ; cookie name
01448     VALUE         = value                                ; cookie value
01449     cookie-av     = token ["=" value]
01450 
01451     valid values for cookie-av (checked post-parsing) are:
01452     cookie-av     = "Path"    "=" value
01453                   | "Domain"  "=" value
01454                   | "Expires" "=" value
01455                   | "Max-Age" "=" value
01456                   | "Comment" "=" value
01457                   | "Version" "=" value
01458                   | "Secure"
01459                   | "HttpOnly"
01460 
01461 ******************************************************************************/
01462 
01463 // helper functions for GetTokenValue
01464 static inline PRBool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
01465 static inline PRBool isterminator     (char c) { return c == '\n' || c == '\r'; }
01466 static inline PRBool isquoteterminator(char c) { return isterminator(c) || c == '"'; }
01467 static inline PRBool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
01468 static inline PRBool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
01469 
01470 // Parse a single token/value pair.
01471 // Returns PR_TRUE if a cookie terminator is found, so caller can parse new cookie.
01472 PRBool
01473 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
01474                                nsASingleFragmentCString::const_char_iterator &aEndIter,
01475                                nsDependentCSubstring                         &aTokenString,
01476                                nsDependentCSubstring                         &aTokenValue,
01477                                PRBool                                        &aEqualsFound)
01478 {
01479   nsASingleFragmentCString::const_char_iterator start, lastSpace;
01480   // initialize value string to clear garbage
01481   aTokenValue.Rebind(aIter, aIter);
01482 
01483   // find <token>, including any <LWS> between the end-of-token and the
01484   // token separator. we'll remove trailing <LWS> next
01485   while (aIter != aEndIter && iswhitespace(*aIter))
01486     ++aIter;
01487   start = aIter;
01488   while (aIter != aEndIter && !istokenseparator(*aIter))
01489     ++aIter;
01490 
01491   // remove trailing <LWS>; first check we're not at the beginning
01492   lastSpace = aIter;
01493   if (lastSpace != start) {
01494     while (--lastSpace != start && iswhitespace(*lastSpace));
01495     ++lastSpace;
01496   }
01497   aTokenString.Rebind(start, lastSpace);
01498 
01499   aEqualsFound = (*aIter == '=');
01500   if (aEqualsFound) {
01501     // find <value>
01502     while (++aIter != aEndIter && iswhitespace(*aIter));
01503 
01504     start = aIter;
01505 
01506     if (*aIter == '"') {
01507       // process <quoted-string>
01508       // (note: cookie terminators, CR | LF, can't happen:
01509       // they're removed by necko before the header gets here)
01510       // assume value mangled if no terminating '"', return
01511       while (++aIter != aEndIter && !isquoteterminator(*aIter)) {
01512         // if <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
01513         // we increment once over the backwhack, nullcheck, then continue to the 'while',
01514         // which increments over the backwhacked char. one exception - we don't allow
01515         // CR | LF here either (see above about necko)
01516         if (*aIter == '\\' && (++aIter == aEndIter || isterminator(*aIter)))
01517           break;
01518       }
01519 
01520       if (aIter != aEndIter && !isterminator(*aIter)) {
01521         // include terminating quote in attribute string
01522         aTokenValue.Rebind(start, ++aIter);
01523         // skip to next ';'
01524         while (aIter != aEndIter && !isvalueseparator(*aIter))
01525           ++aIter;
01526       }
01527     } else {
01528       // process <token-value>
01529       // just look for ';' to terminate ('=' allowed)
01530       while (aIter != aEndIter && !isvalueseparator(*aIter))
01531         ++aIter;
01532 
01533       // remove trailing <LWS>; first check we're not at the beginning
01534       if (aIter != start) {
01535         lastSpace = aIter;
01536         while (--lastSpace != start && iswhitespace(*lastSpace));
01537         aTokenValue.Rebind(start, ++lastSpace);
01538       }
01539     }
01540   }
01541 
01542   // aIter is on ';', or terminator, or EOS
01543   if (aIter != aEndIter) {
01544     // if on terminator, increment past & return PR_TRUE to process new cookie
01545     if (isterminator(*aIter)) {
01546       ++aIter;
01547       return PR_TRUE;
01548     }
01549     // fall-through: aIter is on ';', increment and return PR_FALSE
01550     ++aIter;
01551   }
01552   return PR_FALSE;
01553 }
01554 
01555 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
01556 // cookie struct here, because we don't know which one to use until we've parsed the header.
01557 PRBool
01558 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
01559                                  nsCookieAttributes &aCookieAttributes)
01560 {
01561   static const char kPath[]    = "path";
01562   static const char kDomain[]  = "domain";
01563   static const char kExpires[] = "expires";
01564   static const char kMaxage[]  = "max-age";
01565   static const char kSecure[]  = "secure";
01566   static const char kHttpOnly[]  = "httponly";
01567 
01568   nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
01569   nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
01570   aCookieHeader.BeginReading(cookieStart);
01571   aCookieHeader.EndReading(cookieEnd);
01572 
01573   aCookieAttributes.isSecure = PR_FALSE;
01574 
01575   aCookieAttributes.isHttpOnly = PR_FALSE;
01576 
01577   nsDependentCSubstring tokenString(cookieStart, cookieStart);
01578   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
01579   PRBool newCookie, equalsFound;
01580 
01581   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
01582   // if we find multiple cookies, return for processing
01583   // note: if there's no '=', we assume token is <VALUE>. this is required by
01584   //       some sites (see bug 169091).
01585   // XXX fix the parser to parse according to <VALUE> grammar for this case
01586   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
01587   if (equalsFound) {
01588     aCookieAttributes.name = tokenString;
01589     aCookieAttributes.value = tokenValue;
01590   } else {
01591     aCookieAttributes.value = tokenString;
01592   }
01593 
01594   // extract remaining attributes
01595   while (cookieStart != cookieEnd && !newCookie) {
01596     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
01597 
01598     if (!tokenValue.IsEmpty()) {
01599       tokenValue.BeginReading(tempBegin);
01600       tokenValue.EndReading(tempEnd);
01601       if (*tempBegin == '"' && *--tempEnd == '"') {
01602         // our parameter is a quoted-string; remove quotes for later parsing
01603         tokenValue.Rebind(++tempBegin, tempEnd);
01604       }
01605     }
01606 
01607     // decide which attribute we have, and copy the string
01608     if (tokenString.LowerCaseEqualsLiteral(kPath))
01609       aCookieAttributes.path = tokenValue;
01610 
01611     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
01612       aCookieAttributes.host = tokenValue;
01613 
01614     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
01615       aCookieAttributes.expires = tokenValue;
01616 
01617     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
01618       aCookieAttributes.maxage = tokenValue;
01619 
01620     // ignore any tokenValue for isSecure; just set the boolean
01621     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
01622       aCookieAttributes.isSecure = PR_TRUE;
01623 
01624     // ignore any tokenValue for isHttpOnly (see bug 178993);
01625     // just set the boolean
01626     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
01627       aCookieAttributes.isHttpOnly = PR_TRUE;
01628   }
01629 
01630   // rebind aCookieHeader, in case we need to process another cookie
01631   aCookieHeader.Rebind(cookieStart, cookieEnd);
01632   return newCookie;
01633 }
01634 
01635 /******************************************************************************
01636  * nsCookieService impl:
01637  * private domain & permission compliance enforcement functions
01638  ******************************************************************************/
01639 
01640 // returns PR_TRUE if aHost is an IP address
01641 PRBool
01642 nsCookieService::IsIPAddress(const nsAFlatCString &aHost)
01643 {
01644   PRNetAddr addr;
01645   return (PR_StringToNetAddr(aHost.get(), &addr) == PR_SUCCESS);
01646 }
01647 
01648 PRBool
01649 nsCookieService::IsInDomain(const nsACString &aDomain,
01650                             const nsACString &aHost,
01651                             PRBool           aIsDomain)
01652 {
01653   // if we have a non-domain cookie, require an exact match between domain and host.
01654   // RFC2109 specifies this behavior; it allows a site to prevent its subdomains
01655   // from accessing a cookie, for whatever reason.
01656   if (!aIsDomain) {
01657     return aDomain.Equals(aHost);
01658   }
01659 
01660   // we have a domain cookie; test the following two cases:
01661   /*
01662    * normal case for hostName = x<domainName>
01663    *    e.g., hostName = home.netscape.com
01664    *          domainName = .netscape.com
01665    *
01666    * special case for domainName = .hostName
01667    *    e.g., hostName = netscape.com
01668    *          domainName = .netscape.com
01669    */
01670   // the lengthDifference tests are for efficiency, so we do only one .Equals()
01671   PRUint32 domainLength = aDomain.Length();
01672   PRInt32 lengthDifference = aHost.Length() - domainLength;
01673   // case for host & domain equal
01674   // (e.g. .netscape.com & .netscape.com)
01675   // this gives us slightly more efficiency, since we don't have
01676   // to call up Substring().
01677   if (lengthDifference == 0) {
01678     return aDomain.Equals(aHost);
01679   }
01680   // normal case
01681   if (lengthDifference > 0) {
01682     return aDomain.Equals(Substring(aHost, lengthDifference, domainLength));
01683   }
01684   // special case
01685   if (lengthDifference == -1) {
01686     return Substring(aDomain, 1, domainLength - 1).Equals(aHost);
01687   }
01688   // no match
01689   return PR_FALSE;
01690 }
01691 
01692 PRBool
01693 nsCookieService::IsForeign(nsIURI *aHostURI,
01694                            nsIURI *aFirstURI)
01695 {
01696   // if aFirstURI is null, default to not foreign
01697   if (!aFirstURI) {
01698     return PR_FALSE;
01699   }
01700 
01701   // chrome URLs are never foreign (otherwise sidebar cookies won't work).
01702   // eventually we want to have a protocol whitelist here,
01703   // _or_ do something smart with nsIProtocolHandler::protocolFlags.
01704   PRBool isChrome = PR_FALSE;
01705   nsresult rv = aFirstURI->SchemeIs("chrome", &isChrome);
01706   if (NS_SUCCEEDED(rv) && isChrome) {
01707     return PR_FALSE;
01708   }
01709 
01710   // Get hosts
01711   nsCAutoString currentHost, firstHost;
01712   if (NS_FAILED(aHostURI->GetAsciiHost(currentHost)) ||
01713       NS_FAILED(aFirstURI->GetAsciiHost(firstHost))) {
01714     return PR_TRUE;
01715   }
01716   // trim trailing dots
01717   currentHost.Trim(".");
01718   firstHost.Trim(".");
01719   ToLowerCase(currentHost);
01720   ToLowerCase(firstHost);
01721 
01722   // determine if it's foreign. we have a new algorithm for doing this,
01723   // since the old behavior was broken:
01724 
01725   // first ensure we're not dealing with IP addresses; if we are, require an
01726   // exact match. we can't avoid this, otherwise the algo below will allow two
01727   // IP's such as 128.12.96.5 and 213.12.96.5 to match.
01728   if (IsIPAddress(firstHost)) {
01729     return !IsInDomain(firstHost, currentHost, PR_FALSE);
01730   }
01731 
01732   // next, allow a one-subdomain-level "fuzz" in the comparison. first, we need
01733   // to find how many subdomain levels each host has; we only do the looser
01734   // comparison if they have the same number of levels. e.g.
01735   //  firstHost = weather.yahoo.com, currentHost = cookies.yahoo.com -> match
01736   //  firstHost =     a.b.yahoo.com, currentHost =       b.yahoo.com -> no match
01737   //  firstHost =         yahoo.com, currentHost = weather.yahoo.com -> no match
01738   //  (since the normal test (next) will catch this case and give a match.)
01739   // also, we can only do this if they have >=2 subdomain levels, to avoid
01740   // matching yahoo.com with netscape.com (yes, this breaks for .co.nz etc...)
01741   PRUint32 dotsInFirstHost = firstHost.CountChar('.');
01742   if (dotsInFirstHost == currentHost.CountChar('.') &&
01743       dotsInFirstHost >= 2) {
01744     // we have enough dots - check IsInDomain(choppedFirstHost, currentHost)
01745     PRInt32 dot1 = firstHost.FindChar('.');
01746     return !IsInDomain(Substring(firstHost, dot1, firstHost.Length() - dot1), currentHost);
01747   }
01748 
01749   // don't have enough dots to chop firstHost, or the subdomain levels differ;
01750   // so we just do the plain old check, IsInDomain(firstHost, currentHost).
01751   return !IsInDomain(NS_LITERAL_CSTRING(".") + firstHost, currentHost);
01752 }
01753 
01754 nsCookieStatus
01755 nsCookieService::CheckPrefs(nsIURI         *aHostURI,
01756                             nsIURI         *aFirstURI,
01757                             nsIChannel     *aChannel,
01758                             const char     *aCookieHeader,
01759                             nsCookiePolicy &aPolicy)
01760 {
01761   // pref tree:
01762   // 0) get the scheme strings from the two URI's
01763   // 1) disallow ftp
01764   // 2) disallow mailnews, if pref set
01765   // 3) perform a permissionlist lookup to see if an entry exists for this host
01766   //    (a match here will override defaults in 4)
01767   // 4) go through enumerated permissions to see which one we have:
01768   // -> cookies disabled: return
01769   // -> dontacceptforeign: check if cookie is foreign
01770   // -> p3p: check p3p cookie data
01771 
01772   // we've extended the "nsCookieStatus" type to be used for all cases now
01773   // (used to be only for p3p), so beware that its interpretation is not p3p-
01774   // specific anymore.
01775 
01776   // first, get the URI scheme for further use
01777   // if GetScheme fails on aHostURI, reject; aFirstURI is optional, so failing is ok
01778   nsCAutoString currentURIScheme, firstURIScheme;
01779   nsresult rv, rv2 = NS_OK;
01780   rv = aHostURI->GetScheme(currentURIScheme);
01781   if (aFirstURI) {
01782     rv2 = aFirstURI->GetScheme(firstURIScheme);
01783   }
01784   if (NS_FAILED(rv) || NS_FAILED(rv2)) {
01785     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "couldn't get scheme of host URI");
01786     return STATUS_REJECTED_WITH_ERROR;
01787   }
01788 
01789   // don't let ftp sites get/set cookies (could be a security issue)
01790   if (currentURIScheme.EqualsLiteral("ftp")) {
01791     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
01792     return STATUS_REJECTED_WITH_ERROR;
01793   }
01794 
01795   // check the permission list first; if we find an entry, it overrides
01796   // default prefs. see bug 184059.
01797   if (mPermissionService) {
01798     nsCookieAccess access;
01799     rv = mPermissionService->CanAccess(aHostURI, aFirstURI, aChannel, &access);
01800 
01801     // if we found an entry, use it
01802     if (NS_SUCCEEDED(rv)) {
01803       switch (access) {
01804       case nsICookiePermission::ACCESS_DENY:
01805         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are blocked for this site");
01806         return nsICookie::STATUS_REJECTED;
01807 
01808       case nsICookiePermission::ACCESS_ALLOW:
01809         return nsICookie::STATUS_ACCEPTED;
01810       }
01811     }
01812   }
01813 
01814   // check default prefs - go thru enumerated permissions
01815   if (mCookiesPermissions == BEHAVIOR_REJECT) {
01816     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
01817     return nsICookie::STATUS_REJECTED;
01818 
01819   } else if (mCookiesPermissions == BEHAVIOR_REJECTFOREIGN) {
01820     // check if cookie is foreign.
01821     // if aFirstURI is null, allow by default
01822 
01823     // note: this can be circumvented if we have http redirects within html,
01824     // since the documentURI attribute isn't always correctly
01825     // passed to the redirected channels. (or isn't correctly set in the first place)
01826     if (IsForeign(aHostURI, aFirstURI)) {
01827       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "originating server test failed");
01828       return nsICookie::STATUS_REJECTED;
01829     }
01830 
01831   } else if (mCookiesPermissions == BEHAVIOR_P3P) {
01832     // check to see if P3P conditions are satisfied. see nsICookie.idl for
01833     // P3P-related constants.
01834 
01835     nsCookieStatus p3pStatus = nsICookie::STATUS_UNKNOWN;
01836 
01837     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
01838 
01839     // lazily init the P3P service
01840     if (!mP3PService)
01841       mP3PService = do_GetService(NS_COOKIECONSENT_CONTRACTID);
01842 
01843     if (mP3PService) {
01844       // get the site policy and a status decision for the cookie
01845       PRBool isForeign = IsForeign(aHostURI, aFirstURI);
01846       mP3PService->GetConsent(aHostURI, httpChannel, isForeign, &aPolicy, &p3pStatus);
01847     }
01848 
01849     if (p3pStatus == nsICookie::STATUS_REJECTED) {
01850       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "P3P test failed");
01851     }
01852     return p3pStatus;
01853   }
01854 
01855   // if nothing has complained, accept cookie
01856   return nsICookie::STATUS_ACCEPTED;
01857 }
01858 
01859 // processes domain attribute, and returns PR_TRUE if host has permission to set for this domain.
01860 PRBool
01861 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
01862                              nsIURI             *aHostURI)
01863 {
01864   // get host from aHostURI
01865   nsCAutoString hostFromURI;
01866   if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI))) {
01867     return PR_FALSE;
01868   }
01869   // trim trailing dots
01870   hostFromURI.Trim(".");
01871   ToLowerCase(hostFromURI);
01872 
01873   // if a domain is given, check the host has permission
01874   if (!aCookieAttributes.host.IsEmpty()) {
01875     aCookieAttributes.host.Trim(".");
01876     // switch to lowercase now, to avoid case-insensitive compares everywhere
01877     ToLowerCase(aCookieAttributes.host);
01878 
01879     // check whether the host is an IP address, and override isDomain to
01880     // make the cookie a non-domain one. this will require an exact host
01881     // match for the cookie, so we eliminate any chance of IP address
01882     // funkiness (e.g. the alias 127.1 domain-matching 99.54.127.1).
01883     // bug 105917 originally noted the requirement to deal with IP addresses.
01884     if (IsIPAddress(aCookieAttributes.host)) {
01885       return IsInDomain(aCookieAttributes.host, hostFromURI, PR_FALSE);
01886     }
01887 
01888     /*
01889      * verify that this host has the authority to set for this domain.   We do
01890      * this by making sure that the host is in the domain.  We also require
01891      * that a domain have at least one embedded period to prevent domains of the form
01892      * ".com" and ".edu"
01893      */
01894     PRInt32 dot = aCookieAttributes.host.FindChar('.');
01895     if (dot == kNotFound) {
01896       // fail dot test
01897       return PR_FALSE;
01898     }
01899 
01900     // prepend a dot, and check if the host is in the domain
01901     aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
01902     if (!IsInDomain(aCookieAttributes.host, hostFromURI)) {
01903       return PR_FALSE;
01904     }
01905 
01906     /*
01907      * note: RFC2109 section 4.3.2 requires that we check the following:
01908      * that the portion of host not in domain does not contain a dot.
01909      * this prevents hosts of the form x.y.co.nz from setting cookies in the
01910      * entire .co.nz domain. however, it's only a only a partial solution and
01911      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
01912      */
01913 
01914   // no domain specified, use hostFromURI
01915   } else {
01916     // block any URIs without a host that aren't file:/// URIs
01917     if (hostFromURI.IsEmpty()) {
01918       PRBool isFileURI = PR_FALSE;
01919       aHostURI->SchemeIs("file", &isFileURI);
01920       if (!isFileURI)
01921         return PR_FALSE;
01922     }
01923     aCookieAttributes.host = hostFromURI;
01924   }
01925 
01926   return PR_TRUE;
01927 }
01928 
01929 PRBool
01930 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
01931                            nsIURI             *aHostURI)
01932 {
01933   // if a path is given, check the host has permission
01934   if (aCookieAttributes.path.IsEmpty()) {
01935     // strip down everything after the last slash to get the path,
01936     // ignoring slashes in the query string part.
01937     // if we can QI to nsIURL, that'll take care of the query string portion.
01938     // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
01939     nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
01940     if (hostURL) {
01941       hostURL->GetDirectory(aCookieAttributes.path);
01942     } else {
01943       aHostURI->GetPath(aCookieAttributes.path);
01944       PRInt32 slash = aCookieAttributes.path.RFindChar('/');
01945       if (slash != kNotFound) {
01946         aCookieAttributes.path.Truncate(slash + 1);
01947       }
01948     }
01949 
01950 #if 0
01951   } else {
01958     // get path from aHostURI
01959     nsCAutoString pathFromURI;
01960     if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
01961         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
01962       return PR_FALSE;
01963     }
01964 #endif
01965   }
01966 
01967   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
01968       aCookieAttributes.path.FindChar('\t') != kNotFound )
01969     return PR_FALSE;
01970 
01971   return PR_TRUE;
01972 }
01973 
01974 PRBool
01975 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
01976                            nsInt64            aServerTime,
01977                            nsInt64            aCurrentTime,
01978                            nsCookieStatus     aStatus)
01979 {
01980   /* Determine when the cookie should expire. This is done by taking the difference between 
01981    * the server time and the time the server wants the cookie to expire, and adding that 
01982    * difference to the client time. This localizes the client time regardless of whether or
01983    * not the TZ environment variable was set on the client.
01984    *
01985    * Note: We need to consider accounting for network lag here, per RFC.
01986    */
01987   nsInt64 delta;
01988 
01989   // check for max-age attribute first; this overrides expires attribute
01990   if (!aCookieAttributes.maxage.IsEmpty()) {
01991     // obtain numeric value of maxageAttribute
01992     PRInt64 maxage;
01993     PRInt32 numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
01994 
01995     // default to session cookie if the conversion failed
01996     if (numInts != 1) {
01997       return PR_TRUE;
01998     }
01999 
02000     delta = nsInt64(maxage);
02001 
02002   // check for expires attribute
02003   } else if (!aCookieAttributes.expires.IsEmpty()) {
02004     nsInt64 expires;
02005     PRTime tempExpires;
02006 
02007     // parse expiry time
02008     if (PR_ParseTimeString(aCookieAttributes.expires.get(), PR_TRUE, &tempExpires) == PR_SUCCESS) {
02009       expires = nsInt64(tempExpires) / USEC_PER_SEC;
02010     } else {
02011       return PR_TRUE;
02012     }
02013 
02014     delta = expires - aServerTime;
02015 
02016   // default to session cookie if no attributes found
02017   } else {
02018     return PR_TRUE;
02019   }
02020 
02021   // if this addition overflows, expiryTime will be less than currentTime
02022   // and the cookie will be expired - that's okay.
02023   aCookieAttributes.expiryTime = aCurrentTime + delta;
02024 
02025   // we need to return whether the cookie is a session cookie or not:
02026   // the cookie may have been previously downgraded by p3p prefs,
02027   // so we take that into account here. only applies to non-expired cookies.
02028   return aStatus == nsICookie::STATUS_DOWNGRADED &&
02029          aCookieAttributes.expiryTime > aCurrentTime;
02030 }
02031 
02032 /******************************************************************************
02033  * nsCookieService impl:
02034  * private cookielist management functions
02035  ******************************************************************************/
02036 
02037 void
02038 nsCookieService::RemoveAllFromMemory()
02039 {
02040   // clearing the hashtable will call each nsCookieEntry's dtor,
02041   // which releases all their respective children.
02042   mHostTable.Clear();
02043   mCookieCount = 0;
02044   mCookieChanged = PR_TRUE;
02045 }
02046 
02047 PLDHashOperator PR_CALLBACK
02048 removeExpiredCallback(nsCookieEntry *aEntry,
02049                       void          *aArg)
02050 {
02051   const nsInt64 &currentTime = *NS_STATIC_CAST(nsInt64*, aArg);
02052   for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ) {
02053     if (iter.current->Expiry() <= currentTime)
02054       // remove from list. this takes care of updating the iterator for us
02055       nsCookieService::gCookieService->RemoveCookieFromList(iter);
02056     else
02057       ++iter;
02058   }
02059   return PL_DHASH_NEXT;
02060 }
02061 
02062 // removes any expired cookies from memory
02063 void
02064 nsCookieService::RemoveExpiredCookies(nsInt64 aCurrentTime)
02065 {
02066   mHostTable.EnumerateEntries(removeExpiredCallback, &aCurrentTime);
02067 }
02068 
02069 // find whether a previous cookie has been set, and count the number of cookies from
02070 // this host, for prompting purposes. this is provided by the nsICookieManager2
02071 // interface.
02072 NS_IMETHODIMP
02073 nsCookieService::FindMatchingCookie(nsICookie2 *aCookie,
02074                                     PRUint32   *aCountFromHost,
02075                                     PRBool     *aFoundCookie)
02076 {
02077   NS_ENSURE_ARG_POINTER(aCookie);
02078 
02079   // we don't care about finding the oldest cookie here, so disable the search
02080   nsEnumerationData data(NOW_IN_SECONDS, LL_MININT);
02081   nsCookie *cookie = NS_STATIC_CAST(nsCookie*, aCookie);
02082 
02083   *aCountFromHost = CountCookiesFromHost(cookie, data);
02084   *aFoundCookie = FindCookie(cookie->Host(), cookie->Name(), cookie->Path(), data.iter);
02085   return NS_OK;
02086 }
02087 
02088 // count the number of cookies from this host, and find the oldest cookie
02089 // from this host.
02090 PRUint32
02091 nsCookieService::CountCookiesFromHost(nsCookie          *aCookie,
02092                                       nsEnumerationData &aData)
02093 {
02094   PRUint32 countFromHost = 0;
02095 
02096   nsCAutoString hostWithDot(NS_LITERAL_CSTRING(".") + aCookie->RawHost());
02097 
02098   const char *currentDot = hostWithDot.get();
02099   const char *nextDot = currentDot + 1;
02100   do {
02101     nsCookieEntry *entry = mHostTable.GetEntry(currentDot);
02102     for (nsListIter iter(entry); iter.current; ++iter) {
02103       // only count non-expired cookies
02104       if (iter.current->Expiry() > aData.currentTime) {
02105         ++countFromHost;
02106 
02107         // check if we've found the oldest cookie so far
02108         if (aData.oldestTime > iter.current->LastAccessed()) {
02109           aData.oldestTime = iter.current->LastAccessed();
02110           aData.iter = iter;
02111         }
02112       }
02113     }
02114 
02115     currentDot = nextDot;
02116     if (currentDot)
02117       nextDot = strchr(currentDot + 1, '.');
02118 
02119   } while (currentDot);
02120 
02121   return countFromHost;
02122 }
02123 
02124 // find an exact previous match.
02125 PRBool
02126 nsCookieService::FindCookie(const nsAFlatCString &aHost,
02127                             const nsAFlatCString &aName,
02128                             const nsAFlatCString &aPath,
02129                             nsListIter           &aIter)
02130 {
02131   nsCookieEntry *entry = mHostTable.GetEntry(aHost.get());
02132   for (aIter = nsListIter(entry); aIter.current; ++aIter) {
02133     if (aPath.Equals(aIter.current->Path()) &&
02134         aName.Equals(aIter.current->Name())) {
02135       return PR_TRUE;
02136     }
02137   }
02138 
02139   return PR_FALSE;
02140 }
02141 
02142 // removes a cookie from the hashtable, and update the iterator state.
02143 void
02144 nsCookieService::RemoveCookieFromList(nsListIter &aIter)
02145 {
02146   if (!aIter.prev && !aIter.current->Next()) {
02147     // we're removing the last element in the list - so just remove the entry
02148     // from the hash. note that the entryclass' dtor will take care of
02149     // releasing this last element for us!
02150     mHostTable.RawRemoveEntry(aIter.entry);
02151     aIter.current = nsnull;
02152 
02153   } else {
02154     // just remove the element from the list, and increment the iterator
02155     nsCookie *next = aIter.current->Next();
02156     NS_RELEASE(aIter.current);
02157     if (aIter.prev) {
02158       // element to remove is not the head
02159       aIter.current = aIter.prev->Next() = next;
02160     } else {
02161       // element to remove is the head
02162       aIter.current = aIter.entry->Head() = next;
02163     }
02164   }
02165 
02166   --mCookieCount;
02167   mCookieChanged = PR_TRUE;
02168 }
02169 
02170 PRBool
02171 nsCookieService::AddCookieToList(nsCookie *aCookie)
02172 {
02173   nsCookieEntry *entry = mHostTable.PutEntry(aCookie->Host().get());
02174 
02175   if (!entry) {
02176     NS_ERROR("can't insert element into a null entry!");
02177     return PR_FALSE;
02178   }
02179 
02180   NS_ADDREF(aCookie);
02181 
02182   aCookie->Next() = entry->Head();
02183   entry->Head() = aCookie;
02184   ++mCookieCount;
02185   mCookieChanged = PR_TRUE;
02186 
02187   return PR_TRUE;
02188 }
02189 
02190 PR_STATIC_CALLBACK(PLDHashOperator)
02191 findOldestCallback(nsCookieEntry *aEntry,
02192                    void          *aArg)
02193 {
02194   nsEnumerationData *data = NS_STATIC_CAST(nsEnumerationData*, aArg);
02195   for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ++iter) {
02196     // check if we've found the oldest cookie so far
02197     if (data->oldestTime > iter.current->LastAccessed()) {
02198       data->oldestTime = iter.current->LastAccessed();
02199       data->iter = iter;
02200     }
02201   }
02202   return PL_DHASH_NEXT;
02203 }
02204 
02205 void
02206 nsCookieService::FindOldestCookie(nsEnumerationData &aData)
02207 {
02208   mHostTable.EnumerateEntries(findOldestCallback, &aData);
02209 }