From 948c1c9c08cb283bee3883c4dc152d7e9601a599 Mon Sep 17 00:00:00 2001 From: "darin%meer.net" Date: Fri, 10 Oct 2003 23:09:18 +0000 Subject: [PATCH] moving cookies backend into necko: b=210561 r=dwitte sr=bryner these files were moved from their old home in extensions/cookie. the CVS records cannot be simply moved because attic files exist in the new location, and so i will unfortunately take cvs blame for all. that's unfair to dwitte who has made so many wonderful changes to the cookies backend code! ;-) so, to find out the real cvs blame for these files, please look at the following cvs remove'd files: mozilla/extensions/cookie/nsCookieService.cpp mozilla/extensions/cookie/nsCookieService.h mozilla/extensions/cookie/nsCookie.cpp mozilla/extensions/cookie/nsCookie.h mozilla/extensions/cookie/nsICookie2.idl mozilla/extensions/cookie/nsICookieConsent.idl mozilla/extensions/cookie/nsICookie.idl mozilla/extensions/cookie/nsICookieManager2.idl mozilla/extensions/cookie/nsICookieManager.idl mozilla/extensions/cookie/nsICookiePermission.idl --- netwerk/cookie/src/nsCookie.cpp | 147 ++ netwerk/cookie/src/nsCookie.h | 127 ++ netwerk/cookie/src/nsCookieService.cpp | 2418 ++++++++++++++++++++++++ netwerk/cookie/src/nsCookieService.h | 178 ++ 4 files changed, 2870 insertions(+) create mode 100644 netwerk/cookie/src/nsCookie.cpp create mode 100644 netwerk/cookie/src/nsCookie.h create mode 100644 netwerk/cookie/src/nsCookieService.cpp create mode 100644 netwerk/cookie/src/nsCookieService.h diff --git a/netwerk/cookie/src/nsCookie.cpp b/netwerk/cookie/src/nsCookie.cpp new file mode 100644 index 00000000000..38256653312 --- /dev/null +++ b/netwerk/cookie/src/nsCookie.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Daniel Witte. + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Daniel Witte (dwitte@stanford.edu) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCookie.h" + +/****************************************************************************** + * nsCookie: + * string helper impl + ******************************************************************************/ + +// allocate contiguous storage and copy aSource strings, +// providing terminating nulls for each destination string. +// XXX consider arena allocation here +static inline void +StrBlockCopy(const nsACString &aSource1, + const nsACString &aSource2, + const nsACString &aSource3, + const nsACString &aSource4, + char *&aDest1, + char *&aDest2, + char *&aDest3, + char *&aDest4, + char *&aDestEnd) +{ + // find the required buffer size, adding 4 for the terminating nulls + const PRUint32 totalLength = aSource1.Length() + aSource2.Length() + aSource3.Length() + aSource4.Length() + 4; + + char *toBegin = NS_STATIC_CAST(char*, nsMemory::Alloc(totalLength * sizeof(char))); + NS_ASSERTION(toBegin, "out of memory allocating for nsCookie!"); + + aDest1 = toBegin; + if (toBegin) { + nsACString::const_iterator fromBegin, fromEnd; + + *copy_string(aSource1.BeginReading(fromBegin), aSource1.EndReading(fromEnd), toBegin) = char(0); + aDest2 = ++toBegin; + *copy_string(aSource2.BeginReading(fromBegin), aSource2.EndReading(fromEnd), toBegin) = char(0); + aDest3 = ++toBegin; + *copy_string(aSource3.BeginReading(fromBegin), aSource3.EndReading(fromEnd), toBegin) = char(0); + aDest4 = ++toBegin; + *copy_string(aSource4.BeginReading(fromBegin), aSource4.EndReading(fromEnd), toBegin) = char(0); + aDestEnd = toBegin; + } +} + +/****************************************************************************** + * nsCookie: + * ctor/dtor + ******************************************************************************/ + +nsCookie::nsCookie(const nsACString &aName, + const nsACString &aValue, + const nsACString &aHost, + const nsACString &aPath, + nsInt64 aExpiry, + nsInt64 aLastAccessed, + PRBool aIsSession, + PRBool aIsDomain, + PRBool aIsSecure, + nsCookieStatus aStatus, + nsCookiePolicy aPolicy) + : mExpiry(aExpiry) + , mLastAccessed(aLastAccessed) + , mRefCnt(0) + , mIsSession(aIsSession != PR_FALSE) + , mIsDomain(aIsDomain != PR_FALSE) + , mIsSecure(aIsSecure != PR_FALSE) + , mStatus(aStatus) + , mPolicy(aPolicy) +{ + // allocate a new (contiguous) string, and assign string members + StrBlockCopy(aName, aValue, aHost, aPath, + mName, mValue, mHost, mPath, mEnd); +} + +nsCookie::~nsCookie() +{ + if (mName) + nsMemory::Free(mName); +} + +/****************************************************************************** + * nsCookie: + * xpcom impl + ******************************************************************************/ + +// xpcom getters +NS_IMETHODIMP nsCookie::GetName(nsACString &aName) { aName = Name(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetValue(nsACString &aValue) { aValue = Value(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetHost(nsACString &aHost) { aHost = Host(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetPath(nsACString &aPath) { aPath = Path(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetExpiry(PRInt64 *aExpiry) { *aExpiry = Expiry(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetIsSession(PRBool *aIsSession) { *aIsSession = IsSession(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetIsDomain(PRBool *aIsDomain) { *aIsDomain = IsDomain(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetIsSecure(PRBool *aIsSecure) { *aIsSecure = IsSecure(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetStatus(nsCookieStatus *aStatus) { *aStatus = Status(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetPolicy(nsCookiePolicy *aPolicy) { *aPolicy = Policy(); return NS_OK; } + +// compatibility method, for use with the legacy nsICookie interface. +// here, expires == 0 denotes a session cookie. +NS_IMETHODIMP +nsCookie::GetExpires(PRUint64 *aExpires) +{ + if (IsSession()) { + *aExpires = 0; + } else { + *aExpires = Expiry() > nsInt64(0) ? PRInt64(Expiry()) : 1; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS2(nsCookie, nsICookie, nsICookie2) diff --git a/netwerk/cookie/src/nsCookie.h b/netwerk/cookie/src/nsCookie.h new file mode 100644 index 00000000000..63d506039a0 --- /dev/null +++ b/netwerk/cookie/src/nsCookie.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Daniel Witte. + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Daniel Witte (dwitte@stanford.edu) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsCookie_h__ +#define nsCookie_h__ + +#include "nsICookie.h" +#include "nsICookie2.h" +#include "nsString.h" +#include "nsMemory.h" +#include "nsInt64.h" + +/** + * The nsCookie class is the main cookie storage medium for use within cookie + * code. It implements nsICookie2, which extends nsICookie, a frozen interface + * for xpcom access of cookie objects. + */ + +/****************************************************************************** + * nsCookie: + * implementation + ******************************************************************************/ + +class nsCookie : public nsICookie2 +{ + // this is required because we use a bitfield refcount member. + public: + NS_DECL_ISUPPORTS_INHERITED + protected: + NS_DECL_OWNINGTHREAD + + public: + // nsISupports + NS_DECL_NSICOOKIE + NS_DECL_NSICOOKIE2 + + nsCookie(const nsACString &aName, + const nsACString &aValue, + const nsACString &aHost, + const nsACString &aPath, + nsInt64 aExpiry, + nsInt64 aLastAccessed, + PRBool aIsSession, + PRBool aIsDomain, + PRBool aIsSecure, + nsCookieStatus aStatus, + nsCookiePolicy aPolicy); + + virtual ~nsCookie(); + + // fast (inline, non-xpcom) getters + inline const nsDependentCString Name() const { return nsDependentCString(mName, mValue - 1); } + inline const nsDependentCString Value() const { return nsDependentCString(mValue, mHost - 1); } + inline const nsDependentCString Host() const { return nsDependentCString(mHost, mPath - 1); } + inline const nsDependentCString Path() const { return nsDependentCString(mPath, mEnd); } + inline nsInt64 Expiry() const { NS_ASSERTION(!IsSession(), "can't get expiry time for a session cookie"); return mExpiry; } + inline nsInt64 LastAccessed() const { return mLastAccessed; } + inline PRBool IsSession() const { return mIsSession; } + inline PRBool IsDomain() const { return mIsDomain; } + inline PRBool IsSecure() const { return mIsSecure; } + inline nsCookieStatus Status() const { return mStatus; } + inline nsCookiePolicy Policy() const { return mPolicy; } + + // setters on nsCookie only exist for SetLastAccessed(). + // except for this method, an nsCookie is immutable + // and must be deleted & recreated if it needs to be changed. + inline void SetLastAccessed(nsInt64 aLastAccessed) { mLastAccessed = aLastAccessed; } + + protected: + // member variables + // we use char* ptrs to store the strings in a contiguous block, + // so we save on the overhead of using nsCStrings. However, we + // store a terminating null for each string, so we can hand them + // out as nsAFlatCStrings. + + // sizeof(nsCookie) = 44 bytes + Length(strings) + char *mName; + char *mValue; + char *mHost; + char *mPath; + char *mEnd; + nsInt64 mExpiry; + nsInt64 mLastAccessed; + PRUint32 mRefCnt : 16; + PRUint32 mIsSession : 1; + PRUint32 mIsDomain : 1; + PRUint32 mIsSecure : 1; + PRUint32 mStatus : 3; + PRUint32 mPolicy : 3; +}; + +#endif // nsCookie_h__ diff --git a/netwerk/cookie/src/nsCookieService.cpp b/netwerk/cookie/src/nsCookieService.cpp new file mode 100644 index 00000000000..44695aa8f4f --- /dev/null +++ b/netwerk/cookie/src/nsCookieService.cpp @@ -0,0 +1,2418 @@ +// vim:ts=2:sw=2:et: +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Daniel Witte (dwitte@stanford.edu) + * Michiel van Leeuwen (mvl@exedo.nl) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCookieService.h" +#include "nsIServiceManager.h" + +#include "nsIIOService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefBranchInternal.h" +#include "nsIPrefService.h" +#include "nsICookieConsent.h" +#include "nsICookiePermission.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsIChannel.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" // evil hack! +#include "nsIPrompt.h" +#include "nsITimer.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsILineInputStream.h" + +#include "nsAutoPtr.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsInt64.h" +#include "prtime.h" +#include "prprf.h" +#include "prnetdb.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsAppDirectoryServiceDefs.h" + +/****************************************************************************** + * nsCookieService impl: + * useful types & constants + ******************************************************************************/ + +static const char kCookieFileName[] = "cookies.txt"; + +static const PRUint32 kLazyWriteTimeout = 5000; //msec +static const PRUint32 kLazyNotifyTimeout = 1000; //msec + +static const PRInt32 kMaxNumberOfCookies = 300; +static const PRInt32 kMaxCookiesPerHost = 20; +static const PRUint32 kMaxBytesPerCookie = 4096; + +// the following P3P constants are used to mangle one enumerated type into +// another. we may be able to clean up the p3pservice to use consistent +// types so these aren't required. +// do we need P3P_UnknownPolicy? can't we default to P3P_NoPolicy? +static const PRInt32 P3P_UnknownPolicy = -1; +static const PRInt32 P3P_NoPolicy = 0; +static const PRInt32 P3P_NoConsent = 2; +static const PRInt32 P3P_ImplicitConsent = 4; +static const PRInt32 P3P_ExplicitConsent = 6; +static const PRInt32 P3P_NoIdentInfo = 8; + +static const char P3P_Unknown = ' '; +static const char P3P_Accept = 'a'; +static const char P3P_Downgrade = 'd'; +static const char P3P_Reject = 'r'; +static const char P3P_Flag = 'f'; + +// XXX these casts and constructs are horrible, but our nsInt64/nsTime +// classes are lacking so we need them for now. see bug 198694. +#define USEC_PER_SEC (nsInt64(1000000)) +#define NOW_IN_SECONDS (nsInt64(PR_Now()) / USEC_PER_SEC) + +// behavior pref constants +static const PRUint32 BEHAVIOR_ACCEPT = 0; +static const PRUint32 BEHAVIOR_REJECTFOREIGN = 1; +static const PRUint32 BEHAVIOR_REJECT = 2; +static const PRUint32 BEHAVIOR_P3P = 3; + +// pref string constants +#ifdef MOZ_PHOENIX +static const char kCookiesEnabled[] = "network.cookie.enable"; +static const char kCookiesForDomainOnly[] = "network.cookie.enableForOriginatingWebsiteOnly"; +static const char kCookiesLifetimeCurrentSession[] = "network.cookie.enableForCurrentSessionOnly"; +#else +static const char kCookiesPermissions[] = "network.cookie.cookieBehavior"; +static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled"; +static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days"; +static const char kCookiesLifetimeCurrentSession[] = "network.cookie.lifetime.behavior"; +static const char kCookiesP3PString[] = "network.cookie.p3p"; +static const char kCookiesP3PString_Default[] = "drdraaaa"; +#endif +static const char kCookiesStrictDomains[] = "network.cookie.strictDomains"; + +// struct for temporarily storing cookie attributes during header parsing +struct nsCookieAttributes +{ + nsCAutoString name; + nsCAutoString value; + nsCAutoString host; + nsCAutoString path; + nsCAutoString expires; + nsCAutoString maxage; + nsInt64 expiryTime; + PRBool isSession; + PRBool isSecure; + PRBool isDomain; +}; + +/****************************************************************************** + * Cookie logging handlers + * used for logging in nsCookieService + ******************************************************************************/ + +// logging handlers +#ifdef MOZ_LOGGING +// in order to do logging, the following environment variables need to be set: +// +// set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies +// set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies +// set NSPR_LOG_FILE=c:\cookie.log +// +// this next define has to appear before the include of prlog.h +#define FORCE_PR_LOG // Allow logging in the release build +#include "prlog.h" +#endif + +// define logging macros for convenience +#define SET_COOKIE PR_TRUE +#define GET_COOKIE PR_FALSE + +#ifdef PR_LOGGING +static PRLogModuleInfo *sCookieLog = PR_NewLogModule("cookie"); + +#define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d) +#define COOKIE_LOGSUCCESS(a, b, c, d) LogSuccess(a, b, c, d) + +static void +LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason) +{ + // if logging isn't enabled, return now to save cycles + if (!PR_LOG_TEST(sCookieLog, PR_LOG_WARNING)) { + return; + } + + nsCAutoString spec; + if (aHostURI) + aHostURI->GetAsciiSpec(spec); + + PR_LOG(sCookieLog, PR_LOG_WARNING, + ("%s%s%s\n", "===== ", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT", " =====")); + PR_LOG(sCookieLog, PR_LOG_WARNING,("request URL: %s\n", spec.get())); + if (aSetCookie) { + PR_LOG(sCookieLog, PR_LOG_WARNING,("cookie string: %s\n", aCookieString)); + } + + PRExplodedTime explodedTime; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); + char timeString[40]; + PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); + + PR_LOG(sCookieLog, PR_LOG_WARNING,("current time: %s", timeString)); + PR_LOG(sCookieLog, PR_LOG_WARNING,("rejected because %s\n", aReason)); + PR_LOG(sCookieLog, PR_LOG_WARNING,("\n")); +} + +static void +LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie) +{ + // if logging isn't enabled, return now to save cycles + if (!PR_LOG_TEST(sCookieLog, PR_LOG_DEBUG)) { + return; + } + + nsCAutoString spec; + aHostURI->GetAsciiSpec(spec); + + PR_LOG(sCookieLog, PR_LOG_DEBUG, + ("%s%s%s\n", "===== ", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT", " =====")); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("request URL: %s\n", spec.get())); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("cookie string: %s\n", aCookieString)); + + PRExplodedTime explodedTime; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); + char timeString[40]; + PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); + + PR_LOG(sCookieLog, PR_LOG_DEBUG,("current time: %s", timeString)); + + if (aSetCookie) { + PR_LOG(sCookieLog, PR_LOG_DEBUG,("----------------\n")); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get())); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get())); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get())); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get())); + + if (!aCookie->IsSession()) { + PR_ExplodeTime(aCookie->Expiry() * USEC_PER_SEC, PR_GMTParameters, &explodedTime); + PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); + } + + PR_LOG(sCookieLog, PR_LOG_DEBUG, + ("expires: %s", aCookie->IsSession() ? "at end of session" : timeString)); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false")); + } + PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n")); +} + +// inline wrappers to make passing in nsAStrings easier +inline void +LogFailure(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason) +{ + LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason); +} + +inline void +LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie) +{ + LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie); +} + +#else +#define COOKIE_LOGFAILURE(a, b, c, d) /* nothing */ +#define COOKIE_LOGSUCCESS(a, b, c, d) /* nothing */ +#endif + +/****************************************************************************** + * nsCookieService impl: + * singleton instance ctor/dtor methods + ******************************************************************************/ + +nsCookieService *nsCookieService::gCookieService = nsnull; + +nsCookieService* +nsCookieService::GetSingleton() +{ + if (gCookieService) { + NS_ADDREF(gCookieService); + return gCookieService; + } + + // Create a new singleton nsCookieService (note: the ctor AddRefs for us). + // We AddRef only once since XPCOM has rules about the ordering of module + // teardowns - by the time our module destructor is called, it's too late to + // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC + // cycles have already been completed and would result in serious leaks. + // See bug 209571. + gCookieService = new nsCookieService(); + + return gCookieService; +} + +/****************************************************************************** + * nsCookieService impl: + * public methods + ******************************************************************************/ + +NS_IMPL_ISUPPORTS5(nsCookieService, + nsICookieService, + nsICookieManager, + nsICookieManager2, + nsIObserver, + nsISupportsWeakReference) + +nsCookieService::nsCookieService() + : mCookieChanged(PR_FALSE) + , mCookieIconVisible(PR_FALSE) +{ + // AddRef now, so we don't cross XPCOM boundaries with a zero refcount + NS_ADDREF_THIS(); + + // cache and init the preferences observer + InitPrefObservers(); + + // cache mCookieFile + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mCookieFile)); + if (mCookieFile) { + mCookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName)); + } + + Read(); + + mObserverService = do_GetService("@mozilla.org/observer-service;1"); + if (mObserverService) { + mObserverService->AddObserver(this, "profile-before-change", PR_TRUE); + mObserverService->AddObserver(this, "profile-do-change", PR_TRUE); + mObserverService->AddObserver(this, "cookieIcon", PR_TRUE); + } + + mP3PService = do_GetService(NS_COOKIECONSENT_CONTRACTID); + mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID); +} + +nsCookieService::~nsCookieService() +{ + gCookieService = nsnull; + + if (mWriteTimer) + mWriteTimer->Cancel(); + if (mNotifyTimer) + mNotifyTimer->Cancel(); + + // clean up memory + RemoveAllFromMemory(); +} + +NS_IMETHODIMP +nsCookieService::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) +{ + nsresult rv; + + // check the topic + if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + // The profile is about to change, + // or is going away because the application is shutting down. + if (mWriteTimer) { + mWriteTimer->Cancel(); + mWriteTimer = 0; + } + if (mNotifyTimer) { + mNotifyTimer->Cancel(); + mNotifyTimer = 0; + } + + if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get())) { + RemoveAllFromMemory(); + // delete the cookie file + if (mCookieFile) { + mCookieFile->Remove(PR_FALSE); + } + } else { + Write(); + RemoveAllFromMemory(); + } + + } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) { + // The profile has already changed. + // Now just read them from the new profile location. + // we also need to update the cached cookie file location + nsresult rv; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mCookieFile)); + if (NS_SUCCEEDED(rv)) { + rv = mCookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName)); + } + Read(); + + } else if (!nsCRT::strcmp(aTopic, "cookieIcon")) { + // this is an evil trick to avoid the blatant inefficiency of + // (!nsCRT::strcmp(aData, NS_LITERAL_STRING("on").get())) + mCookieIconVisible = (aData[0] == 'o' && aData[1] == 'n' && aData[2] == '\0'); + + } else if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + // which pref changed? + NS_LossyConvertUCS2toASCII pref(aData); + PRInt32 tempPrefValue; + +#ifdef MOZ_PHOENIX + PRBool computePermissions = PR_FALSE; + + if (pref.Equals(kCookiesEnabled)) { + rv = mPrefBranch->GetBoolPref(kCookiesEnabled, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + } + mCookiesEnabled_temp = tempPrefValue; + // set flag so we know to update the enumerated permissions + computePermissions = PR_TRUE; + + } else if (pref.Equals(kCookiesForDomainOnly)) { + rv = mPrefBranch->GetBoolPref(kCookiesForDomainOnly, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + } + mCookiesForDomainOnly_temp = tempPrefValue; + // set flag so we know to update the enumerated permissions + computePermissions = PR_TRUE; + + } else if (pref.Equals(kCookiesLifetimeCurrentSession)) { + rv = mPrefBranch->GetBoolPref(kCookiesLifetimeCurrentSession, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + } + mCookiesLifetimeCurrentSession = tempPrefValue; + // Phoenix hack to reduce ifdefs in code + mCookiesLifetimeEnabled = mCookiesLifetimeCurrentSession; + +#else + if (pref.Equals(kCookiesPermissions)) { + rv = mPrefBranch->GetIntPref(kCookiesPermissions, &tempPrefValue); + if (NS_FAILED(rv) || tempPrefValue < 0 || tempPrefValue > 3) { + tempPrefValue = BEHAVIOR_REJECT; + } + mCookiesPermissions = tempPrefValue; + + } else if (pref.Equals(kCookiesLifetimeEnabled)) { + rv = mPrefBranch->GetBoolPref(kCookiesLifetimeEnabled, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + } + mCookiesLifetimeEnabled = tempPrefValue; + + } else if (pref.Equals(kCookiesLifetimeDays)) { + rv = mPrefBranch->GetIntPref(kCookiesLifetimeDays, &mCookiesLifetimeSec); + if (NS_FAILED(rv)) { + mCookiesLifetimeEnabled = PR_FALSE; // disable lifetime limit... + mCookiesLifetimeSec = 0; + } + // save cookie lifetime in seconds instead of days + mCookiesLifetimeSec *= 24*60*60; + + } else if (pref.Equals(kCookiesLifetimeCurrentSession)) { + rv = mPrefBranch->GetIntPref(kCookiesLifetimeCurrentSession, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = 1; // disable currentSession limit + } + mCookiesLifetimeCurrentSession = (tempPrefValue == 0); + + // P3P prefs + } else if (pref.Equals(kCookiesP3PString)) { + rv = mPrefBranch->GetCharPref(kCookiesP3PString, getter_Copies(mCookiesP3PString)); + // check for a malformed string + if (NS_FAILED(rv) || mCookiesP3PString.Length() != 8) { + // reassign to default string + mCookiesP3PString = NS_LITERAL_CSTRING(kCookiesP3PString_Default); + } + +#endif + // common prefs between Phoenix & Mozilla + } else if (pref.Equals(kCookiesStrictDomains)) { + rv = mPrefBranch->GetBoolPref(kCookiesStrictDomains, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + } + mCookiesStrictDomains = tempPrefValue; + } + +#ifdef MOZ_PHOENIX + // collapse two boolean prefs into enumerated permissions + // note: BEHAVIOR_P3P is not used in Phoenix, so we won't reach any P3P code. + if (computePermissions) { + if (mCookiesEnabled_temp) { + // check if user wants cookies only for site domain + if (mCookiesForDomainOnly_temp) { + mCookiesPermissions = BEHAVIOR_REJECTFOREIGN; + } else { + mCookiesPermissions = BEHAVIOR_ACCEPT; + } + } else { + mCookiesPermissions = BEHAVIOR_REJECT; + } + } +#endif + + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCookieService::GetCookieString(nsIURI *aHostURI, + nsIChannel *aChannel, + char **aCookie) +{ + // try to determine first party URI + nsCOMPtr firstURI; + if (aChannel) { + nsCOMPtr httpInternal = do_QueryInterface(aChannel); + if (httpInternal) + httpInternal->GetDocumentURI(getter_AddRefs(firstURI)); + } + + return GetCookieStringFromHttp(aHostURI, firstURI, aChannel, aCookie); +} + +// helper function for GetCookieStringFromHttp +static inline PRBool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; } + +NS_IMETHODIMP +nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIChannel *aChannel, + char **aCookie) +{ + *aCookie = nsnull; + + if (!aHostURI) { + COOKIE_LOGFAILURE(GET_COOKIE, nsnull, nsnull, "host URI is null"); + return NS_OK; + } + + // check default prefs + nsCookieStatus cookieStatus = CheckPrefs(aHostURI, aFirstURI, aChannel, nsnull); + // for GetCookie(), we don't update the UI icon if cookie was rejected. + if (cookieStatus == nsICookie::STATUS_REJECTED) { + return NS_OK; + } + + // get host and path from the nsIURI + // note: there was a "check if host has embedded whitespace" here. + // it was removed since this check was added into the nsIURI impl (bug 146094). + nsCAutoString hostFromURI, pathFromURI; + if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI)) || + NS_FAILED(aHostURI->GetPath(pathFromURI))) { + COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nsnull, "couldn't get host/path from URI"); + return NS_OK; + } + // trim trailing dots + hostFromURI.Trim("."); + ToLowerCase(hostFromURI); + + // initialize variables used in the list traversal + nsInt64 currentTime = NOW_IN_SECONDS; + nsCookie *cookieInList; + // initialize string for return data + nsCAutoString cookieData; + + // check if aHostURI is using an https secure protocol. + // if it isn't, then we can't send a secure cookie over the connection. + // if SchemeIs fails, assume an insecure connection, to be on the safe side + PRBool isSecure; + if NS_FAILED(aHostURI->SchemeIs("https", &isSecure)) { + isSecure = PR_FALSE; + } + + // begin mCookieList traversal + PRInt32 count = mCookieList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + // if the cookie is secure and the host scheme isn't, we can't send it + if (cookieInList->IsSecure() & !isSecure) { + continue; + } + + // check if the host is in the cookie's domain + // (taking into account whether it's a domain cookie) + if (!IsInDomain(cookieInList->Host(), hostFromURI, cookieInList->IsDomain())) { + continue; + } + + // calculate cookie path length, excluding trailing '/' + PRUint32 cookiePathLen = cookieInList->Path().Length(); + if (cookiePathLen > 0 && cookieInList->Path().Last() == '/') { + --cookiePathLen; + } + + // the cookie list is in order of path length; longest to shortest. + // if the nsIURI path is shorter than the cookie path, then we know the path + // isn't on the cookie path. + if (!StringBeginsWith(pathFromURI, Substring(cookieInList->Path(), 0, cookiePathLen))) { + continue; + } + + if (pathFromURI.Length() > cookiePathLen && + !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) { + /* + * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'. + * '/' is the "standard" case; the '?' test allows a site at host/abc?def + * to receive a cookie that has a path attribute of abc. this seems + * strange but at least one major site (citibank, bug 156725) depends + * on it. The test for # and ; are put in to proactively avoid problems + * with other sites - these are the only other chars allowed in the path. + */ + continue; + } + + // check if the cookie has expired, and remove if so. + // note that we do this *after* previous tests passed - so we're only removing + // the ones that are relevant to this particular search. + if (!cookieInList->IsSession() && cookieInList->Expiry() <= currentTime) { + mCookieList.RemoveElementAt(i--); // decrement i so next cookie isn't skipped + NS_RELEASE(cookieInList); + --count; // update the count + mCookieChanged = PR_TRUE; + continue; + } + + // all checks passed - update lastAccessed stamp of cookie + cookieInList->SetLastAccessed(currentTime); + + // check if we have anything to write + if (!cookieInList->Name().IsEmpty() || !cookieInList->Value().IsEmpty()) { + // if we've already added a cookie to the return list, append a "; " so + // that subsequent cookies are delimited in the final list. + if (!cookieData.IsEmpty()) { + cookieData += NS_LITERAL_CSTRING("; "); + } + + // NOTE: we used to have an #ifdef PREVENT_DUPLICATE_NAMES here, + // which would prevent multiple cookies with the same name being sent (i.e. + // only the first instance is sent). This wasn't invoked in our previous code, + // and RFC2109 implicitly allows duplicate names, so I've removed it. + + if (!cookieInList->Name().IsEmpty()) { + // we have a cookie->name and cookie->cookie - write both + cookieData += cookieInList->Name() + NS_LITERAL_CSTRING("=") + cookieInList->Value(); + } else { + // just write cookie->cookie + cookieData += cookieInList->Value(); + } + } + } // for() + + // it's wasteful to alloc a new string; but we have no other choice, until we + // fix the callers to use nsACStrings. + if (!cookieData.IsEmpty()) { + COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, cookieData, nsnull); + *aCookie = ToNewCString(cookieData); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCookieService::SetCookieString(nsIURI *aHostURI, + nsIPrompt *aPrompt, + const char *aCookieHeader, + nsIChannel *aChannel) +{ + // try to determine first party URI + nsCOMPtr firstURI; + + if (aChannel) { + nsCOMPtr httpInternal = do_QueryInterface(aChannel); + if (httpInternal) + httpInternal->GetDocumentURI(getter_AddRefs(firstURI)); + } + + return SetCookieStringFromHttp(aHostURI, firstURI, aPrompt, aCookieHeader, nsnull, aChannel); +} + +NS_IMETHODIMP +nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIPrompt *aPrompt, + const char *aCookieHeader, + const char *aServerTime, + nsIChannel *aChannel) +{ + if (!aHostURI) { + COOKIE_LOGFAILURE(SET_COOKIE, nsnull, aCookieHeader, "host URI is null"); + return NS_OK; + } + + // check default prefs + nsCookieStatus cookieStatus = CheckPrefs(aHostURI, aFirstURI, aChannel, aCookieHeader); + // update UI icon, and return, if cookie was rejected. + // should we be doing this just for p3p? + if (cookieStatus == nsICookie::STATUS_REJECTED) { + UpdateCookieIcon(); + return NS_OK; + } + // get the site's p3p policy now (common to all cookies) + nsCookiePolicy cookiePolicy = GetP3PPolicy(SiteP3PPolicy(aHostURI, aChannel)); + + // parse server local time. this is not just done here for efficiency + // reasons - if there's an error parsing it, and we need to default it + // to the current time, we must do it here since the current time in + // SetCookieInternal() will change for each cookie processed (e.g. if the + // user is prompted). + nsInt64 serverTime; + PRTime tempServerTime; + if (aServerTime && PR_ParseTimeString(aServerTime, PR_TRUE, &tempServerTime) == PR_SUCCESS) { + serverTime = nsInt64(tempServerTime) / USEC_PER_SEC; + } else { + serverTime = NOW_IN_SECONDS; + } + + // switch to a nice string type now, and process each cookie in the header + nsDependentCString cookieHeader(aCookieHeader); + while (SetCookieInternal(aHostURI, aChannel, + cookieHeader, serverTime, + cookieStatus, cookiePolicy)); + + // write out the cookie file + LazyWrite(); + LazyNotify(); + return NS_OK; +} + +void +nsCookieService::LazyWrite() +{ + if (mWriteTimer) { + mWriteTimer->SetDelay(kLazyWriteTimeout); + } else { + mWriteTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mWriteTimer) { + mWriteTimer->InitWithFuncCallback(DoLazyWrite, this, kLazyWriteTimeout, + nsITimer::TYPE_ONE_SHOT); + } + } +} + +void +nsCookieService::DoLazyWrite(nsITimer *aTimer, + void *aClosure) +{ + nsCookieService *service = NS_REINTERPRET_CAST(nsCookieService*, aClosure); + service->Write(); + service->mWriteTimer = 0; +} + +void +nsCookieService::LazyNotify() +{ + // this algorithm is slightly different than the one used to write cookies. + // here we are concerned with update frequency of the UI (yes, yes, this + // code really shouldn't have to worry about the UI!)... so, we do not + // further delay an already delayed notification. + + if (!mNotifyTimer) { + mNotifyTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mNotifyTimer) { + mNotifyTimer->InitWithFuncCallback(DoLazyNotify, this, kLazyNotifyTimeout, + nsITimer::TYPE_ONE_SHOT); + } + } +} + +void +nsCookieService::DoLazyNotify(nsITimer *aTimer, + void *aClosure) +{ + nsCookieService *service = NS_REINTERPRET_CAST(nsCookieService*, aClosure); + if (service->mObserverService) + service->mObserverService->NotifyObservers(nsnull, "cookieChanged", NS_LITERAL_STRING("cookies").get()); + service->mNotifyTimer = 0; +} + +void +nsCookieService::UpdateCookieIcon() +{ + mCookieIconVisible = PR_TRUE; + if (mObserverService) { + mObserverService->NotifyObservers(nsnull, "cookieIcon", NS_LITERAL_STRING("on").get()); + } +} + +NS_IMETHODIMP +nsCookieService::GetCookieIconIsVisible(PRBool *aIsVisible) +{ + *aIsVisible = mCookieIconVisible; + return NS_OK; +} + +/****************************************************************************** + * nsCookieService: + * pref observer impl + ******************************************************************************/ + +void +nsCookieService::InitPrefObservers() +{ + nsresult rv; + + // install and cache the preferences observer + mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr prefInternal = do_QueryInterface(mPrefBranch, &rv); + + // add observers + if (NS_SUCCEEDED(rv)) { +#ifdef MOZ_PHOENIX + prefInternal->AddObserver(kCookiesEnabled, this, PR_TRUE); + prefInternal->AddObserver(kCookiesForDomainOnly, this, PR_TRUE); + prefInternal->AddObserver(kCookiesLifetimeCurrentSession, this, PR_TRUE); +#else + prefInternal->AddObserver(kCookiesPermissions, this, PR_TRUE); + prefInternal->AddObserver(kCookiesLifetimeEnabled, this, PR_TRUE); + prefInternal->AddObserver(kCookiesLifetimeDays, this, PR_TRUE); + prefInternal->AddObserver(kCookiesLifetimeCurrentSession, this, PR_TRUE); + prefInternal->AddObserver(kCookiesP3PString, this, PR_TRUE); +#endif + prefInternal->AddObserver(kCookiesStrictDomains, this, PR_TRUE); + } + + // initialize prefs + rv = ReadPrefs(); + if (NS_FAILED(rv)) { + NS_WARNING("Error occured reading cookie preferences"); + } + + } else { + // only called if getting the prefbranch failed. +#ifndef MOZ_PHOENIX + mCookiesP3PString = NS_LITERAL_CSTRING(kCookiesP3PString_Default); +#endif + mCookiesPermissions = BEHAVIOR_REJECT; + mCookiesLifetimeEnabled = PR_FALSE; + mCookiesStrictDomains = PR_FALSE; + } +} + +nsresult +nsCookieService::ReadPrefs() +{ + nsresult rv, rv2 = NS_OK; + + PRInt32 tempPrefValue; +#ifdef MOZ_PHOENIX + rv = mPrefBranch->GetBoolPref(kCookiesEnabled, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + rv2 = rv; + } + mCookiesEnabled_temp = tempPrefValue; + + rv = mPrefBranch->GetBoolPref(kCookiesForDomainOnly, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + rv2 = rv; + } + mCookiesForDomainOnly_temp = tempPrefValue; + + // collapse two boolean prefs into enumerated permissions + // note: BEHAVIOR_P3P is not used in Phoenix + if (mCookiesEnabled_temp) { + // check if user wants cookies only for site domain + if (mCookiesForDomainOnly_temp) { + mCookiesPermissions = BEHAVIOR_REJECTFOREIGN; + } else { + mCookiesPermissions = BEHAVIOR_ACCEPT; + } + } else { + mCookiesPermissions = BEHAVIOR_REJECT; + } + + rv = mPrefBranch->GetBoolPref(kCookiesLifetimeCurrentSession, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + rv2 = rv; + } + mCookiesLifetimeCurrentSession = tempPrefValue; + // Phoenix hacks to reduce ifdefs in code + mCookiesLifetimeEnabled = mCookiesLifetimeCurrentSession; + mCookiesLifetimeSec = 0; + +#else + rv = mPrefBranch->GetIntPref(kCookiesPermissions, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = BEHAVIOR_REJECT; + rv2 = rv; + } + mCookiesPermissions = tempPrefValue; + + rv = mPrefBranch->GetBoolPref(kCookiesLifetimeEnabled, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + rv2 = rv; + } + mCookiesLifetimeEnabled = tempPrefValue; + + rv = mPrefBranch->GetIntPref(kCookiesLifetimeDays, &mCookiesLifetimeSec); + if (NS_FAILED(rv)) { + mCookiesLifetimeEnabled = PR_FALSE; // disable lifetime limit... + mCookiesLifetimeSec = 0; + rv2 = rv; + } + // save cookie lifetime in seconds instead of days + mCookiesLifetimeSec *= 24*60*60; + + rv = mPrefBranch->GetIntPref(kCookiesLifetimeCurrentSession, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = 1; // disable currentSession limit + rv2 = rv; + } + mCookiesLifetimeCurrentSession = (tempPrefValue == 0); + + // P3P prefs + rv = mPrefBranch->GetCharPref(kCookiesP3PString, getter_Copies(mCookiesP3PString)); + // check for a malformed string + if (NS_FAILED(rv) || mCookiesP3PString.Length() != 8) { + // reassign to default string + mCookiesP3PString = NS_LITERAL_CSTRING(kCookiesP3PString_Default); + rv2 = rv; + } + +#endif + // common prefs between Phoenix & Mozilla + + rv = mPrefBranch->GetBoolPref(kCookiesStrictDomains, &tempPrefValue); + if (NS_FAILED(rv)) { + tempPrefValue = PR_FALSE; + // we don't update rv2 here like we do for other prefs, since this pref + // is optional (most profiles won't have it set), and ReadPrefs' caller + // will NS_WARNING on NS_FAILED(rv2). so this is a little bit quieter... + } + mCookiesStrictDomains = tempPrefValue; + + return rv2; +} + +/****************************************************************************** + * nsICookieManager impl: + * nsCookieEnumerator + ******************************************************************************/ + +class nsCookieEnumerator : public nsISimpleEnumerator +{ + public: + NS_DECL_ISUPPORTS + + // note: mCookieCount is initialized just once in the ctor. While it might + // appear that the cookie list can change while the cookiemanager is running, + // the cookieservice is actually on the same thread, so it can't. Note that + // a new nsCookieEnumerator is created each time the cookiemanager is loaded. + // So we only need to get the count once. If we ever change the cookieservice to + // run on a different thread, then something to the effect of a lock will be + // required. see bug 191682 for details. + nsCookieEnumerator(const nsVoidArray &aCookieList) + : mCookieList(aCookieList) + , mCookieCount(aCookieList.Count()) + , mCookieIndex(0) + { + } + + NS_IMETHOD + HasMoreElements(PRBool *aResult) + { + *aResult = mCookieIndex < mCookieCount; + return NS_OK; + } + + NS_IMETHOD + GetNext(nsISupports **aResult) + { + if (mCookieIndex >= mCookieCount) { + *aResult = nsnull; + NS_ERROR("bad cookie index"); + return NS_ERROR_UNEXPECTED; + } + + // cast the nsCookie to an nsICookie + nsICookie *cookieInList = NS_STATIC_CAST(nsICookie*, NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(mCookieIndex++))); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + *aResult = cookieInList; + NS_ADDREF(cookieInList); + + return NS_OK; + } + + virtual ~nsCookieEnumerator() + { + } + + protected: + const nsVoidArray &mCookieList; + PRInt32 mCookieCount; + PRInt32 mCookieIndex; +}; + +NS_IMPL_ISUPPORTS1(nsCookieEnumerator, nsISimpleEnumerator) + +/****************************************************************************** + * nsICookieManager impl: + * nsICookieManager + ******************************************************************************/ + +NS_IMETHODIMP +nsCookieService::RemoveAll() +{ + RemoveAllFromMemory(); + Write(); + return NS_OK; +} + +NS_IMETHODIMP +nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator) +{ + PRInt32 temp; + RemoveExpiredCookies(NOW_IN_SECONDS, temp); + + nsCookieEnumerator* enumerator = new nsCookieEnumerator(mCookieList); + if (!enumerator) { + *aEnumerator = nsnull; + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(enumerator); + *aEnumerator = enumerator; + return NS_OK; +} + +NS_IMETHODIMP +nsCookieService::Add(const nsACString &aDomain, + const nsACString &aPath, + const nsACString &aName, + const nsACString &aValue, + PRBool aIsSecure, + PRInt32 aExpires) +{ + nsInt64 currentTime = NOW_IN_SECONDS; + + nsRefPtr cookie = + new nsCookie(aName, aValue, aDomain, aPath, + nsInt64(aExpires), currentTime, + nsInt64(aExpires) == nsInt64(0), PR_TRUE, aIsSecure, + nsICookie::STATUS_UNKNOWN, + nsICookie::POLICY_UNKNOWN); + if (!cookie) { + return NS_ERROR_OUT_OF_MEMORY; + } + + AddInternal(cookie, currentTime, nsnull, nsnull); + return NS_OK; +} + +NS_IMETHODIMP +nsCookieService::Remove(const nsACString &aHost, + const nsACString &aName, + const nsACString &aPath, + PRBool aBlocked) +{ + nsCookie *cookieInList; + + // step through all cookies, searching for indicated one + PRInt32 count = mCookieList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + if (cookieInList->Path().Equals(aPath) && + cookieInList->Host().Equals(aHost) && + cookieInList->Name().Equals(aName)) { + // check if we need to add the host to the permissions blacklist. + // we should push this portion into the UI, it shouldn't live here in the backend. + if (aBlocked && mPermissionService) { + nsCAutoString buf; + NS_NAMED_LITERAL_CSTRING(httpPrefix, "http://"); + + // remove leading dot from host + if (cookieInList->IsDomain()) + buf = httpPrefix + Substring(cookieInList->Host(), 1, cookieInList->Host().Length() - 1); + else + buf = httpPrefix + cookieInList->Host(); + + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), buf); + + if (uri) + mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY); + } + + mCookieList.RemoveElementAt(i); + NS_RELEASE(cookieInList); + mCookieChanged = PR_TRUE; + // we might want to eventually push this Write() call into the UI, + // to just write once on cookiemanager close. + Write(); + break; + } + } + + return NS_OK; +} + +/****************************************************************************** + * nsCookieService impl: + * private file I/O functions + ******************************************************************************/ + +// comparison function for sorting cookies by path length: +// returns < 0 if the first element has a greater path length than the second element, +// 0 if they both have the same path length, +// > 0 if the second element has a greater path length than the first element. +PR_STATIC_CALLBACK(int) +compareCookiesByPath(const void *aElement1, + const void *aElement2, + void *aData) +{ + const nsCookie *cookie1 = NS_STATIC_CAST(const nsCookie*, aElement1); + const nsCookie *cookie2 = NS_STATIC_CAST(const nsCookie*, aElement2); + NS_ASSERTION(cookie1 && cookie2, "corrupt cookie list"); + + return cookie2->Path().Length() - cookie1->Path().Length(); +} + +// comparison function for sorting cookies by lastAccessed time: +// returns < 0 if the first element was used more recently than the second element, +// 0 if they both have the same last-use time, +// > 0 if the second element was used more recently than the first element. +PR_STATIC_CALLBACK(int) +compareCookiesByLRU(const void *aElement1, + const void *aElement2, + void *aData) +{ + const nsCookie *cookie1 = NS_STATIC_CAST(const nsCookie*, aElement1); + const nsCookie *cookie2 = NS_STATIC_CAST(const nsCookie*, aElement2); + NS_ASSERTION(cookie1 && cookie2, "corrupt cookie list"); + + // we may have overflow problems returning the result directly, so we need branches + nsInt64 difference = cookie2->LastAccessed() - cookie1->LastAccessed(); + return (difference > nsInt64(0)) ? 1 : (difference < nsInt64(0)) ? -1 : 0; +} + +nsresult +nsCookieService::Read() +{ + nsresult rv; + nsCOMPtr fileInputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), mCookieFile); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr lineInputStream = do_QueryInterface(fileInputStream, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // presize mCookieList to the maximum size, to avoid excessive malloc's. + // we'll compact it when we're done reading + mCookieList.SizeTo(kMaxNumberOfCookies); + + static NS_NAMED_LITERAL_CSTRING(kTrue, "TRUE"); + + nsAutoString bufferUnicode; + nsCAutoString buffer; + PRBool isMore = PR_TRUE; + PRInt32 hostIndex = 0, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex; + nsASingleFragmentCString::char_iterator iter; + PRInt32 numInts; + PRInt64 expires; + PRBool isDomain; + nsInt64 currentTime = NOW_IN_SECONDS; + // we use lastAccessedCounter to keep cookies in recently-used order, + // so we start by initializing to currentTime (somewhat arbitrary) + nsInt64 lastAccessedCounter = currentTime; + nsCookie *newCookie; + + /* file format is: + * + * host \t isDomain \t path \t secure \t expires \t name \t cookie + * + * if this format isn't respected we move onto the next line in the file. + * isDomain is "TRUE" or "FALSE" (default to "FALSE") + * isSecure is "TRUE" or "FALSE" (default to "TRUE") + * expires is a PRInt64 integer + * note 1: cookie can contain tabs. + * note 2: cookies are written in order of lastAccessed time: + * most-recently used come first; least-recently-used come last. + */ + + while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(bufferUnicode, &isMore))) { + // downconvert to ASCII. eventually, we want to fix nsILineInputStream + // to operate on a CString buffer... + CopyUCS2toASCII(bufferUnicode, buffer); + + if (buffer.IsEmpty() || buffer.First() == '#') { + continue; + } + + // this is a cheap, cheesy way of parsing a tab-delimited line into + // string indexes, which can be lopped off into substrings. just for + // purposes of obfuscation, it also checks that each token was found. + // todo: use iterators? + if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 || + (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 || + (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 || + (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 || + (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 || + (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) { + continue; + } + + // check the expirytime first - if it's expired, ignore + // nullstomp the trailing tab, to avoid copying the string + buffer.BeginWriting(iter); + *(iter += nameIndex - 1) = char(0); + numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires); + if (numInts != 1 || nsInt64(expires) < currentTime) { + continue; + } + + isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).Equals(kTrue); + const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1); + // check for bad legacy cookies (domain not starting with a dot, or containing a port), + // and discard + if (isDomain && !host.IsEmpty() && host.First() != '.' || + host.FindChar(':') != kNotFound) { + continue; + } + + // create a new nsCookie and assign the data + newCookie = + new nsCookie(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1), + Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), + host, + Substring(buffer, pathIndex, secureIndex - pathIndex - 1), + nsInt64(expires), + lastAccessedCounter, + PR_FALSE, + isDomain, + Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).Equals(kTrue), + nsICookie::STATUS_UNKNOWN, + nsICookie::POLICY_UNKNOWN); + if (!newCookie) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // trick: keep the cookies in most-recently-used order, + // by successively decrementing the lastAccessed time + lastAccessedCounter -= nsInt64(1); + + // add new cookie to the list + mCookieList.AppendElement(newCookie); + NS_ADDREF(newCookie); + } + + // compact the array, now that we're done reading data + mCookieList.Compact(); + // sort the list in order of descending path length + mCookieList.Sort(compareCookiesByPath, nsnull); + + mCookieChanged = PR_FALSE; + return NS_OK; +} + +nsresult +nsCookieService::Write() +{ + if (!mCookieChanged) { + return NS_OK; + } + + nsresult rv; + nsCOMPtr fileOutputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOutputStream), mCookieFile); + if (NS_FAILED(rv)) { + NS_ERROR("failed to open cookies.txt for writing"); + return rv; + } + + // get a buffered output stream 4096 bytes big, to optimize writes + nsCOMPtr bufferedOutputStream; + rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), fileOutputStream, 4096); + if (NS_FAILED(rv)) { + return rv; + } + + static const char kHeader[] = + "# HTTP Cookie File\n" + "# http://www.netscape.com/newsref/std/cookie_spec.html\n" + "# This is a generated file! Do not edit.\n" + "# To delete cookies, use the Cookie Manager.\n\n"; + // note: kTrue and kFalse have leading/trailing tabs already added + static const char kTrue[] = "\tTRUE\t"; + static const char kFalse[] = "\tFALSE\t"; + static const char kTab[] = "\t"; + static const char kNew[] = "\n"; + + // create a new nsVoidArray to hold the cookie list, and sort it + // such that least-recently-used cookies come last + nsVoidArray sortedCookieList; + sortedCookieList = mCookieList; + sortedCookieList.Sort(compareCookiesByLRU, nsnull); + + bufferedOutputStream->Write(kHeader, sizeof(kHeader) - 1, &rv); + + /* file format is: + * + * host \t isDomain \t path \t secure \t expires \t name \t cookie + * + * isDomain is "TRUE" or "FALSE" + * isSecure is "TRUE" or "FALSE" + * expires is a PRInt64 integer + * note 1: cookie can contain tabs. + * note 2: cookies are written in order of lastAccessed time: + * most-recently used come first; least-recently-used come last. + */ + nsCookie *cookieInList; + nsInt64 currentTime = NOW_IN_SECONDS; + char dateString[22]; + PRUint32 dateLen; + PRInt32 count = sortedCookieList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + cookieInList = NS_STATIC_CAST(nsCookie*, sortedCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + // don't write entry if cookie has expired, or is a session cookie + if (cookieInList->IsSession() || cookieInList->Expiry() <= currentTime) { + continue; + } + + bufferedOutputStream->Write(cookieInList->Host().get(), cookieInList->Host().Length(), &rv); + if (cookieInList->IsDomain()) { + bufferedOutputStream->Write(kTrue, sizeof(kTrue) - 1, &rv); + } else { + bufferedOutputStream->Write(kFalse, sizeof(kFalse) - 1, &rv); + } + bufferedOutputStream->Write(cookieInList->Path().get(), cookieInList->Path().Length(), &rv); + if (cookieInList->IsSecure()) { + bufferedOutputStream->Write(kTrue, sizeof(kTrue) - 1, &rv); + } else { + bufferedOutputStream->Write(kFalse, sizeof(kFalse) - 1, &rv); + } + dateLen = PR_snprintf(dateString, sizeof(dateString), "%lld", PRInt64(cookieInList->Expiry())); + bufferedOutputStream->Write(dateString, dateLen, &rv); + bufferedOutputStream->Write(kTab, sizeof(kTab) - 1, &rv); + bufferedOutputStream->Write(cookieInList->Name().get(), cookieInList->Name().Length(), &rv); + bufferedOutputStream->Write(kTab, sizeof(kTab) - 1, &rv); + bufferedOutputStream->Write(cookieInList->Value().get(), cookieInList->Value().Length(), &rv); + bufferedOutputStream->Write(kNew, sizeof(kNew) - 1, &rv); + } + + mCookieChanged = PR_FALSE; + return NS_OK; +} + +/****************************************************************************** + * nsCookieService impl: + * private GetCookie/SetCookie helpers + ******************************************************************************/ + +// processes a single cookie, and returns PR_TRUE if there are more cookies +// to be processed +PRBool +nsCookieService::SetCookieInternal(nsIURI *aHostURI, + nsIChannel *aChannel, + nsDependentCString &aCookieHeader, + nsInt64 aServerTime, + nsCookieStatus aStatus, + nsCookiePolicy aPolicy) +{ + nsresult rv; + + // keep a |const char*| version of the unmodified aCookieHeader, + // for logging purposes + const char *cookieHeader = aCookieHeader.get(); + + // create a stack-based nsCookieAttributes, to store all the + // attributes parsed from the cookie + nsCookieAttributes cookieAttributes; + + // newCookie says whether there are multiple cookies in the header; so we can handle them separately. + // after this function, we don't need the cookieHeader string for processing this cookie anymore; + // so this function uses it as an outparam to point to the next cookie, if there is one. + const PRBool newCookie = ParseAttributes(aCookieHeader, cookieAttributes); + + // reject cookie if it's over the size limit, per RFC2109 + if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "cookie too big (> 4kb)"); + return newCookie; + } + + // calculate expiry time of cookie. we need to pass in cookieStatus, since + // the cookie may have been downgraded to a session cookie by p3p. + const nsInt64 currentTime = NOW_IN_SECONDS; + cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime, + currentTime, aStatus); + + // domain & path checks + if (!CheckDomain(cookieAttributes, aHostURI)) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "failed the domain tests"); + return newCookie; + } + if (!CheckPath(cookieAttributes, aHostURI)) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "failed the path tests"); + return newCookie; + } + + // create a new nsCookie and copy attributes + nsRefPtr cookie = + new nsCookie(cookieAttributes.name, + cookieAttributes.value, + cookieAttributes.host, + cookieAttributes.path, + cookieAttributes.expiryTime, + currentTime, + cookieAttributes.isSession, + cookieAttributes.isDomain, + cookieAttributes.isSecure, + aStatus, + aPolicy); + if (!cookie) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "unable to allocate memory for new cookie"); + return newCookie; + } + + // count the number of cookies from this host, and find whether a previous cookie + // has been set, for prompting purposes + PRUint32 countFromHost; + const PRBool foundCookie = FindCookiesFromHost(cookie, countFromHost, currentTime); + + // check if the cookie we're trying to set is already expired, and return. + // but we need to check there's no previous cookie first, because servers use + // this as a trick for deleting previous cookies. + if (!foundCookie && !cookie->IsSession() && cookie->Expiry() <= currentTime) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "cookie has already expired"); + return newCookie; + } + + // check permissions from site permission list, or ask the user, + // to determine if we can set the cookie + if (mPermissionService) { + PRBool permission; + // we need to think about prompters/parent windows here - TestPermission + // needs one to prompt, so right now it has to fend for itself to get one + mPermissionService->CanSetCookie(aHostURI, + aChannel, + NS_STATIC_CAST(nsICookie*, NS_STATIC_CAST(nsCookie*, cookie)), + countFromHost, foundCookie, + &permission); + if (!permission) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, cookieHeader, "cookie rejected by permission manager"); + return newCookie; + } + } + + // add the cookie to the list + rv = AddInternal(cookie, NOW_IN_SECONDS, aHostURI, cookieHeader); + if (NS_FAILED(rv)) { + // no need to log a failure here, AddInternal() does it for us + return newCookie; + } + + // notify observers if the cookie was downgraded or flagged (only for p3p + // at this point). this occurs only if the cookie was set successfully. + if (aStatus == nsICookie::STATUS_DOWNGRADED || + aStatus == nsICookie::STATUS_FLAGGED) { + UpdateCookieIcon(); + } + + COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, cookieHeader, cookie); + return newCookie; +} + +// this is a backend function for adding a cookie to the list, via SetCookie. +// also used in the cookie manager, for profile migration from IE. +// returns NS_OK if aCookie was added to the list; NS_ERROR otherwise. +nsresult +nsCookieService::AddInternal(nsCookie *aCookie, + nsInt64 aCurrentTime, + nsIURI *aHostURI, + const char *aCookieHeader) +{ + // find a position to insert the cookie at (and delete a cookie from, if necessary). + // also removes expired cookies from the list, for maintenance purposes. + PRInt32 insertPosition, deletePosition; + PRBool foundCookie = FindPosition(aCookie, insertPosition, deletePosition, aCurrentTime); + + // store the cookie + if (foundCookie) { + nsCookie *prevCookie = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(insertPosition)); + NS_ASSERTION(prevCookie, "corrupt cookie list"); + NS_RELEASE(prevCookie); + + // check if the server wants to delete the cookie + if (!aCookie->IsSession() && aCookie->Expiry() <= aCurrentTime) { + // delete previous cookie + mCookieList.RemoveElementAt(insertPosition); + + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie was deleted"); + mCookieChanged = PR_TRUE; + return NS_ERROR_FAILURE; + + } else { + // replace previous cookie + mCookieList.ReplaceElementAt(aCookie, insertPosition); + NS_ADDREF(aCookie); + } + + } else { + // check if cookie has already expired + if (!aCookie->IsSession() && aCookie->Expiry() <= aCurrentTime) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie has already expired"); + return NS_ERROR_FAILURE; + } + + // add the cookie, which means we might have to delete an old cookie + if (deletePosition != -1) { + nsCookie *deleteCookie = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(deletePosition)); + NS_ASSERTION(deleteCookie, "corrupt cookie list"); + + mCookieList.RemoveElementAt(deletePosition); + NS_RELEASE(deleteCookie); + + // adjust insertPosition if we removed a cookie before it + if (insertPosition > deletePosition) { + --insertPosition; + } + } + + mCookieList.InsertElementAt(aCookie, insertPosition); + NS_ADDREF(aCookie); + } + + mCookieChanged = PR_TRUE; + return NS_OK; +} + +/****************************************************************************** + * nsCookieService impl: + * private cookie header parsing functions + ******************************************************************************/ + +// The following comment block elucidates the function of ParseAttributes. +/****************************************************************************** + ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1 + ** please note: this BNF deviates from both specifications, and reflects this + ** implementation. indicates a reference to the defined grammer "bnf". + + ** Differences from RFC2109/2616 and explanations: + 1. implied *LWS + The grammar described by this specification is word-based. Except + where noted otherwise, linear white space () can be included + between any two adjacent words (token or quoted-string), and + between adjacent words and separators, without changing the + interpretation of a field. + according to spec is SP|HT|CR|LF, but here, we allow only SP | HT. + + 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in + common use inside values. + + 3. tokens and values have looser restrictions on allowed characters than + spec. This is also due to certain characters being in common use inside + values. We allow only '=' to separate token/value pairs, and ';' to + terminate tokens or values. is allowed within tokens and values + (see bug 206022). + + 4. where appropriate, full s are allowed, where the spec dictates to + reject control chars or non-ASCII chars. This is erring on the loose + side, since there's probably no good reason to enforce this strictness. + + 5. cookie is optional, where spec requires it. This is a fairly + trivial case, but allows the flexibility of setting only a cookie + with a blank and is required by some sites (see bug 169091). + + ** Begin BNF: + token = 1* + value = token-value | quoted-string + token-value = 1* + quoted-string = ( <"> *( qdtext | quoted-pair ) <"> ) + qdtext = > ; CR | LF removed by necko + quoted-pair = "\" ; CR | LF removed by necko + separators = ";" | "=" + value-sep = ";" + cookie-sep = CR | LF + allowed-chars = + OCTET = + LWS = SP | HT + NUL = + CR = + LF = + SP = + HT = + + set-cookie = "Set-Cookie:" cookies + cookies = cookie *( cookie-sep cookie ) + cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first + NAME = token ; cookie name + VALUE = value ; cookie value + cookie-av = token ["=" value] + + valid values for cookie-av (checked post-parsing) are: + cookie-av = "Path" "=" value + | "Domain" "=" value + | "Expires" "=" value + | "Max-Age" "=" value + | "Comment" "=" value + | "Version" "=" value + | "Secure" + +******************************************************************************/ + +// helper functions for GetTokenValue +static inline PRBool iswhitespace (char c) { return c == ' ' || c == '\t'; } +static inline PRBool isterminator (char c) { return c == '\n' || c == '\r'; } +static inline PRBool isquoteterminator(char c) { return isterminator(c) || c == '"'; } +static inline PRBool isvalueseparator (char c) { return isterminator(c) || c == ';'; } +static inline PRBool istokenseparator (char c) { return isvalueseparator(c) || c == '='; } + +// Parse a single token/value pair. +// Returns PR_TRUE if a cookie terminator is found, so caller can parse new cookie. +PRBool +nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, + nsASingleFragmentCString::const_char_iterator &aEndIter, + nsDependentSingleFragmentCSubstring &aTokenString, + nsDependentSingleFragmentCSubstring &aTokenValue, + PRBool &aEqualsFound) +{ + nsASingleFragmentCString::const_char_iterator start, lastSpace; + // initialize value string to clear garbage + aTokenValue.Rebind(aIter, aIter); + + // find , including any between the end-of-token and the + // token separator. we'll remove trailing next + while (aIter != aEndIter && iswhitespace(*aIter)) + ++aIter; + start = aIter; + while (aIter != aEndIter && !istokenseparator(*aIter)) + ++aIter; + + // remove trailing ; first check we're not at the beginning + lastSpace = aIter; + if (lastSpace != start) { + while (--lastSpace != start && iswhitespace(*lastSpace)); + ++lastSpace; + } + aTokenString.Rebind(start, lastSpace); + + aEqualsFound = (*aIter == '='); + if (aEqualsFound) { + // find + while (++aIter != aEndIter && iswhitespace(*aIter)); + + start = aIter; + + if (*aIter == '"') { + // process + // (note: cookie terminators, CR | LF, can't happen: + // they're removed by necko before the header gets here) + // assume value mangled if no terminating '"', return + while (++aIter != aEndIter && !isquoteterminator(*aIter)) { + // if (backwhacked char), skip over it. this allows '\"' in . + // we increment once over the backwhack, nullcheck, then continue to the 'while', + // which increments over the backwhacked char. one exception - we don't allow + // CR | LF here either (see above about necko) + if (*aIter == '\\' && (++aIter == aEndIter || isterminator(*aIter))) + break; + } + + if (aIter != aEndIter && !isterminator(*aIter)) { + // include terminating quote in attribute string + aTokenValue.Rebind(start, ++aIter); + // skip to next ';' + while (aIter != aEndIter && !isvalueseparator(*aIter)) + ++aIter; + } + } else { + // process + // just look for ';' to terminate ('=' allowed) + while (aIter != aEndIter && !isvalueseparator(*aIter)) + ++aIter; + + // remove trailing ; first check we're not at the beginning + if (aIter != start) { + lastSpace = aIter; + while (--lastSpace != start && iswhitespace(*lastSpace)); + aTokenValue.Rebind(start, ++lastSpace); + } + } + } + + // aIter is on ';', or terminator, or EOS + if (aIter != aEndIter) { + // if on terminator, increment past & return PR_TRUE to process new cookie + if (isterminator(*aIter)) { + ++aIter; + return PR_TRUE; + } + // fall-through: aIter is on ';', increment and return PR_FALSE + ++aIter; + } + return PR_FALSE; +} + +// Parses attributes from cookie header. expires/max-age attributes aren't folded into the +// cookie struct here, because we don't know which one to use until we've parsed the header. +PRBool +nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader, + nsCookieAttributes &aCookieAttributes) +{ + static NS_NAMED_LITERAL_CSTRING(kPath, "path" ); + static NS_NAMED_LITERAL_CSTRING(kDomain, "domain" ); + static NS_NAMED_LITERAL_CSTRING(kExpires, "expires"); + static NS_NAMED_LITERAL_CSTRING(kMaxage, "max-age"); + static NS_NAMED_LITERAL_CSTRING(kSecure, "secure" ); + + nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd; + nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd; + aCookieHeader.BeginReading(cookieStart); + aCookieHeader.EndReading(cookieEnd); + + aCookieAttributes.isSecure = PR_FALSE; + + nsDependentSingleFragmentCSubstring tokenString(cookieStart, cookieStart); + nsDependentSingleFragmentCSubstring tokenValue (cookieStart, cookieStart); + PRBool newCookie, equalsFound; + + // extract cookie & (first attribute), and copy the strings. + // if we find multiple cookies, return for processing + // note: if there's no '=', we assume token is NAME, not VALUE. + // the old code assumed VALUE instead. + // note: if there's no '=', we assume token is . this is required by + // some sites (see bug 169091). + // XXX fix the parser to parse according to grammar for this case + newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); + if (equalsFound) { + aCookieAttributes.name = tokenString; + aCookieAttributes.value = tokenValue; + } else { + aCookieAttributes.value = tokenString; + } + + // extract remaining attributes + while (cookieStart != cookieEnd && !newCookie) { + newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); + + if (!tokenValue.IsEmpty() && *tokenValue.BeginReading(tempBegin) == '"' + && *tokenValue.EndReading(tempEnd) == '"') { + // our parameter is a quoted-string; remove quotes for later parsing + tokenValue.Rebind(++tempBegin, --tempEnd); + } + + // decide which attribute we have, and copy the string + if (tokenString.Equals(kPath, nsCaseInsensitiveCStringComparator())) + aCookieAttributes.path = tokenValue; + + else if (tokenString.Equals(kDomain, nsCaseInsensitiveCStringComparator())) + aCookieAttributes.host = tokenValue; + + else if (tokenString.Equals(kExpires, nsCaseInsensitiveCStringComparator())) + aCookieAttributes.expires = tokenValue; + + else if (tokenString.Equals(kMaxage, nsCaseInsensitiveCStringComparator())) + aCookieAttributes.maxage = tokenValue; + + // ignore any tokenValue for isSecure; just set the boolean + else if (tokenString.Equals(kSecure, nsCaseInsensitiveCStringComparator())) + aCookieAttributes.isSecure = PR_TRUE; + } + + // rebind aCookieHeader, in case we need to process another cookie + aCookieHeader.Rebind(cookieStart, cookieEnd); + return newCookie; +} + +/****************************************************************************** + * nsCookieService impl: + * private domain & permission compliance enforcement functions + ******************************************************************************/ + +// returns PR_TRUE if aHost is an IP address +PRBool +nsCookieService::IsIPAddress(const nsAFlatCString &aHost) +{ + PRNetAddr addr; + return (PR_StringToNetAddr(aHost.get(), &addr) == PR_SUCCESS); +} + +PRBool +nsCookieService::IsInDomain(const nsACString &aDomain, + const nsACString &aHost, + PRBool aIsDomain) +{ + // if we have a non-domain cookie, require an exact match between domain and host. + // RFC2109 specifies this behavior; it allows a site to prevent its subdomains + // from accessing a cookie, for whatever reason. + if (!aIsDomain) { + return aDomain.Equals(aHost); + } + + // we have a domain cookie; test the following two cases: + /* + * normal case for hostName = x + * e.g., hostName = home.netscape.com + * domainName = .netscape.com + * + * special case for domainName = .hostName + * e.g., hostName = netscape.com + * domainName = .netscape.com + */ + // the lengthDifference tests are for efficiency, so we do only one .Equals() + PRUint32 domainLength = aDomain.Length(); + PRInt32 lengthDifference = aHost.Length() - domainLength; + // case for host & domain equal + // (e.g. .netscape.com & .netscape.com) + // this gives us slightly more efficiency, since we don't have + // to call up Substring(). + if (lengthDifference == 0) { + return aDomain.Equals(aHost); + } + // normal case + if (lengthDifference > 0) { + return aDomain.Equals(Substring(aHost, lengthDifference, domainLength)); + } + // special case + if (lengthDifference == -1) { + return Substring(aDomain, 1, domainLength - 1).Equals(aHost); + } + // no match + return PR_FALSE; +} + +PRBool +nsCookieService::IsForeign(nsIURI *aHostURI, + nsIURI *aFirstURI) +{ + // if aFirstURI is null, default to not foreign + if (!aFirstURI) { + return PR_FALSE; + } + + // chrome URLs are never foreign (otherwise sidebar cookies won't work). + // eventually we want to have a protocol whitelist here, + // _or_ do something smart with nsIProtocolHandler::protocolFlags. + PRBool isChrome = PR_FALSE; + nsresult rv = aFirstURI->SchemeIs("chrome", &isChrome); + if (NS_SUCCEEDED(rv) && isChrome) { + return PR_FALSE; + } + + // Get hosts + nsCAutoString currentHost, firstHost; + if (NS_FAILED(aHostURI->GetAsciiHost(currentHost)) || + NS_FAILED(aFirstURI->GetAsciiHost(firstHost))) { + return PR_TRUE; + } + // trim trailing dots + currentHost.Trim("."); + firstHost.Trim("."); + ToLowerCase(currentHost); + ToLowerCase(firstHost); + + // determine if it's foreign. we have a new algorithm for doing this, + // since the old behavior was broken: + + // first ensure we're not dealing with IP addresses; if we are, require an + // exact match. we can't avoid this, otherwise the algo below will allow two + // IP's such as 128.12.96.5 and 213.12.96.5 to match. + if (IsIPAddress(firstHost)) { + return !IsInDomain(firstHost, currentHost, PR_FALSE); + } + + // next, allow a one-subdomain-level "fuzz" in the comparison. first, we need + // to find how many subdomain levels each host has; we only do the looser + // comparison if they have the same number of levels. e.g. + // firstHost = weather.yahoo.com, currentHost = cookies.yahoo.com -> match + // firstHost = a.b.yahoo.com, currentHost = b.yahoo.com -> no match + // firstHost = yahoo.com, currentHost = weather.yahoo.com -> no match + // (since the normal test (next) will catch this case and give a match.) + // also, we can only do this if they have >=2 subdomain levels, to avoid + // matching yahoo.com with netscape.com (yes, this breaks for .co.nz etc...) + PRUint32 dotsInFirstHost = firstHost.CountChar('.'); + if (dotsInFirstHost == currentHost.CountChar('.') && + dotsInFirstHost >= 2) { + // we have enough dots - check IsInDomain(choppedFirstHost, currentHost) + PRInt32 dot1 = firstHost.FindChar('.'); + return !IsInDomain(Substring(firstHost, dot1, firstHost.Length() - dot1), currentHost); + } + + // don't have enough dots to chop firstHost, or the subdomain levels differ; + // so we just do the plain old check, IsInDomain(firstHost, currentHost). + return !IsInDomain(NS_LITERAL_CSTRING(".") + firstHost, currentHost); +} + +nsCookiePolicy +nsCookieService::GetP3PPolicy(PRInt32 aPolicy) +{ + switch (aPolicy) { + case P3P_NoPolicy: + return nsICookie::POLICY_NONE; + case P3P_NoConsent: + return nsICookie::POLICY_NO_CONSENT; + case P3P_ImplicitConsent: + return nsICookie::POLICY_IMPLICIT_CONSENT; + case P3P_ExplicitConsent: + return nsICookie::POLICY_EXPLICIT_CONSENT; + case P3P_NoIdentInfo: + return nsICookie::POLICY_NO_II; + default: + return nsICookie::POLICY_UNKNOWN; + } +} + +/* + * returns P3P_NoPolicy, P3P_NoConsent, P3P_ImplicitConsent, + * P3P_ExplicitConsent, or P3P_NoIdentInfo based on site + */ +PRInt32 +nsCookieService::SiteP3PPolicy(nsIURI *aCurrentURI, + nsIChannel *aChannel) +{ + // default to P3P_NoPolicy if anything fails + PRInt32 consent = P3P_NoPolicy; + + nsCOMPtr httpChannel = do_QueryInterface(aChannel); + if (mP3PService && httpChannel) { + nsCAutoString currentURISpec; + if (NS_SUCCEEDED(aCurrentURI->GetAsciiSpec(currentURISpec))) { + mP3PService->GetConsent(currentURISpec.get(), httpChannel, &consent); + } + } + return consent; +} + +nsCookieStatus +nsCookieService::P3PDecision(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIChannel *aChannel) +{ + // get the site policy from aHttpChannel + PRInt32 policy = SiteP3PPolicy(aHostURI, aChannel); + // check if the cookie is foreign; if aFirstURI is null, default to foreign + PRInt32 isForeign = IsForeign(aHostURI, aFirstURI) == PR_TRUE; + + // if site does not collect identifiable info, then treat it as if it did and + // asked for explicit consent. this check is required, since there is no entry + // in mCookiesP3PString for it. + if (policy == P3P_NoIdentInfo) { + policy = P3P_ExplicitConsent; + } + + // decide P3P_Accept, P3P_Downgrade, P3P_Flag, or P3P_Reject based on user's + // preferences. + // note: mCookiesP3PString can't be empty here, since we only execute this + // path if BEHAVIOR_P3P is set; this in turn can only occur + // if the p3p pref has been read (which is set to a default if the read + // fails). if cookie is foreign, [policy + 1] points to the appropriate + // pref; if cookie isn't foreign, [policy] points. + PRInt32 decision = mCookiesP3PString.CharAt(policy + isForeign); + + switch (decision) { + case P3P_Unknown: + return nsICookie::STATUS_UNKNOWN; + case P3P_Accept: + return nsICookie::STATUS_ACCEPTED; + case P3P_Downgrade: + return nsICookie::STATUS_DOWNGRADED; + case P3P_Flag: + return nsICookie::STATUS_FLAGGED; + case P3P_Reject: + return nsICookie::STATUS_REJECTED; + } + return nsICookie::STATUS_UNKNOWN; +} + +nsCookieStatus +nsCookieService::CheckPrefs(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIChannel *aChannel, + const char *aCookieHeader) +{ + // pref tree: + // 0) get the scheme strings from the two URI's + // 1) disallow ftp + // 2) disallow mailnews, if pref set + // 3) perform a permissionlist lookup to see if an entry exists for this host + // (a match here will override defaults in 4) + // 4) go through enumerated permissions to see which one we have: + // -> cookies disabled: return + // -> dontacceptforeign: check if cookie is foreign + // -> p3p: check p3p cookie data + + // we've extended the "nsCookieStatus" type to be used for all cases now + // (used to be only for p3p), so beware that its interpretation is not p3p- + // specific anymore. + + // first, get the URI scheme for further use + // if GetScheme fails on aHostURI, reject; aFirstURI is optional, so failing is ok + nsCAutoString currentURIScheme, firstURIScheme; + nsresult rv, rv2 = NS_OK; + rv = aHostURI->GetScheme(currentURIScheme); + if (aFirstURI) { + rv2 = aFirstURI->GetScheme(firstURIScheme); + } + if (NS_FAILED(rv) || NS_FAILED(rv2)) { + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "couldn't get scheme of host URI"); + return nsICookie::STATUS_REJECTED; + } + + // don't let ftp sites get/set cookies (could be a security issue) + if (currentURIScheme.Equals(NS_LITERAL_CSTRING("ftp"))) { + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies"); + return nsICookie::STATUS_REJECTED; + } + + // check the permission list first; if we find an entry, it overrides + // default prefs. see bug 184059. + if (mPermissionService) { + nsCookieAccess access; + mPermissionService->CanAccess(aHostURI, aFirstURI, aChannel, &access); + + // if we found an entry, use it + switch (access) { + case nsICookiePermission::ACCESS_DENY: + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are blocked for this site"); + return nsICookie::STATUS_REJECTED; + + case nsICookiePermission::ACCESS_ALLOW: + return nsICookie::STATUS_ACCEPTED; + } + } + + // check default prefs - go thru enumerated permissions + if (mCookiesPermissions == BEHAVIOR_REJECT) { + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled"); + return nsICookie::STATUS_REJECTED; + + } else if (mCookiesPermissions == BEHAVIOR_REJECTFOREIGN) { + // check if cookie is foreign. + // if aFirstURI is null, allow by default + + // note: this can be circumvented if we have http redirects within html, + // since the documentURI attribute isn't always correctly + // passed to the redirected channels. (or isn't correctly set in the first place) + if (IsForeign(aHostURI, aFirstURI)) { + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "originating server test failed"); + return nsICookie::STATUS_REJECTED; + } + + } else if (mCookiesPermissions == BEHAVIOR_P3P) { + // check to see if P3P conditions are satisfied. + // nsCookieStatus is an enumerated type, defined in nsCookie.idl (frozen interface): + // STATUS_UNKNOWN -- cookie collected in a previous session and this info no longer available + // STATUS_ACCEPTED -- cookie was accepted + // STATUS_DOWNGRADED -- cookie was accepted but downgraded to a session cookie + // STATUS_FLAGGED -- cookie was accepted with a warning being issued to the user + // STATUS_REJECTED + + // to do this, at the moment, we need a channel, but we can live without + // the two URI's (as long as no foreign checks are required). + // if the channel is null, we can fall back on "no p3p policy" prefs. + nsCookieStatus p3pStatus = P3PDecision(aHostURI, aFirstURI, aChannel); + if (p3pStatus == nsICookie::STATUS_REJECTED) { + COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "P3P test failed"); + } + return p3pStatus; + } + + // if nothing has complained, accept cookie + return nsICookie::STATUS_ACCEPTED; +} + +// processes domain attribute, and returns PR_TRUE if host has permission to set for this domain. +PRBool +nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes, + nsIURI *aHostURI) +{ + // get host from aHostURI + nsCAutoString hostFromURI; + if (NS_FAILED(aHostURI->GetAsciiHost(hostFromURI))) { + return PR_FALSE; + } + // trim trailing dots + hostFromURI.Trim("."); + ToLowerCase(hostFromURI); + + // if a domain is given, check the host has permission + if (!aCookieAttributes.host.IsEmpty()) { + // switch to lowercase now, to avoid case-insensitive compares everywhere + ToLowerCase(aCookieAttributes.host); + + // check whether the host is an IP address, and override isDomain to + // make the cookie a non-domain one. this will require an exact host + // match for the cookie, so we eliminate any chance of IP address + // funkiness (e.g. the alias 127.1 domain-matching 99.54.127.1). + // bug 105917 originally noted the requirement to deal with IP addresses. + if (IsIPAddress(aCookieAttributes.host)) { + aCookieAttributes.isDomain = PR_FALSE; + return IsInDomain(aCookieAttributes.host, hostFromURI, PR_FALSE); + } + + /* + * verify that this host has the authority to set for this domain. We do + * this by making sure that the host is in the domain. We also require + * that a domain have at least one embedded period to prevent domains of the form + * ".com" and ".edu" + */ + aCookieAttributes.host.Trim("."); + PRInt32 dot = aCookieAttributes.host.FindChar('.'); + if (dot == kNotFound) { + // fail dot test + return PR_FALSE; + } + + // prepend a dot, and check if the host is in the domain + aCookieAttributes.isDomain = PR_TRUE; + aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0); + if (!IsInDomain(aCookieAttributes.host, hostFromURI)) { + return PR_FALSE; + } + + /* + * check that portion of host not in domain does not contain a dot + * This satisfies the fourth requirement in section 4.3.2 of the cookie + * spec rfc 2109 (see www.cis.ohio-state.edu/htbin/rfc/rfc2109.html). + * It prevents host of the form x.y.co.nz from setting cookies in the + * entire .co.nz domain. Note that this doesn't really solve the problem, + * it justs makes it more unlikely. Sites such as y.co.nz can still set + * cookies for the entire .co.nz domain. + * + * Although this is the right thing to do(tm), it breaks too many sites. + * So only do it if the "network.cookie.strictDomains" pref is PR_TRUE. + * + */ + if (mCookiesStrictDomains) { + dot = hostFromURI.FindChar('.', 0, hostFromURI.Length() - aCookieAttributes.host.Length()); + if (dot != kNotFound) { + return PR_FALSE; + } + } + + // no domain specified, use hostFromURI + } else { + aCookieAttributes.isDomain = PR_FALSE; + aCookieAttributes.host = hostFromURI; + } + + return PR_TRUE; +} + +PRBool +nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes, + nsIURI *aHostURI) +{ + // if a path is given, check the host has permission + if (aCookieAttributes.path.IsEmpty()) { + // strip down everything after the last slash to get the path, + // ignoring slashes in the query string part. + // if we can QI to nsIURL, that'll take care of the query string portion. + // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash. + nsCOMPtr hostURL = do_QueryInterface(aHostURI); + if (hostURL) { + hostURL->GetDirectory(aCookieAttributes.path); + } else { + aHostURI->GetPath(aCookieAttributes.path); + PRInt32 slash = aCookieAttributes.path.RFindChar('/'); + if (slash != kNotFound) { + aCookieAttributes.path.Truncate(slash + 1); + } + } + +#if 0 + } else { + /** + * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site + * cannot set a cookie for a path that it is not on. See bug 155083. However this patch + * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has + * been disabled, unless we can evangelize these sites. + */ + // get path from aHostURI + nsCAutoString pathFromURI; + if (NS_FAILED(aHostURI->GetPath(pathFromURI)) || + !StringBeginsWith(pathFromURI, aCookieAttributes.path)) { + return PR_FALSE; + } +#endif + } + + return PR_TRUE; +} + +PRBool +nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes, + nsInt64 aServerTime, + nsInt64 aCurrentTime, + nsCookieStatus aStatus) +{ + /* Determine when the cookie should expire. This is done by taking the difference between + * the server time and the time the server wants the cookie to expire, and adding that + * difference to the client time. This localizes the client time regardless of whether or + * not the TZ environment variable was set on the client. + * + * Note: We need to consider accounting for network lag here, per RFC. + */ + nsInt64 delta; + + // check for max-age attribute first; this overrides expires attribute + if (!aCookieAttributes.maxage.IsEmpty()) { + // obtain numeric value of maxageAttribute + PRInt64 maxage; + PRInt32 numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage); + + // default to session cookie if the conversion failed + if (numInts != 1) { + return PR_TRUE; + } + + delta = nsInt64(maxage); + + // check for expires attribute + } else if (!aCookieAttributes.expires.IsEmpty()) { + nsInt64 expires; + PRTime tempExpires; + + // parse expiry time + if (PR_ParseTimeString(aCookieAttributes.expires.get(), PR_TRUE, &tempExpires) == PR_SUCCESS) { + expires = nsInt64(tempExpires) / USEC_PER_SEC; + } else { + return PR_TRUE; + } + + delta = expires - aServerTime; + + // default to session cookie if no attributes found + } else { + return PR_TRUE; + } + + if (delta > nsInt64(0)) { + // check cookie lifetime pref, and limit lifetime if required. + // we only want to do this if the cookie isn't going to be expired anyway. + if (mCookiesLifetimeEnabled) { + if (mCookiesLifetimeCurrentSession) { + // limit lifetime to session + return PR_TRUE; + } else if (delta > nsInt64(mCookiesLifetimeSec)) { + // limit lifetime to specified time + delta = mCookiesLifetimeSec; + } + } + } + + // if this addition overflows, expiryTime will be less than currentTime + // and the cookie will be expired - that's okay. + aCookieAttributes.expiryTime = aCurrentTime + delta; + + // we need to return whether the cookie is a session cookie or not: + // the cookie may have been previously downgraded by p3p prefs, + // so we take that into account here. only applies to non-expired cookies. + return aStatus == nsICookie::STATUS_DOWNGRADED && + aCookieAttributes.expiryTime > aCurrentTime; +} + +/****************************************************************************** + * nsCookieService impl: + * private cookielist management functions + ******************************************************************************/ + +void +nsCookieService::RemoveAllFromMemory() +{ + nsCookie *cookieInList; + for (PRInt32 i = mCookieList.Count(); i--;) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + NS_RELEASE(cookieInList); + } + mCookieList.SizeTo(0); + mCookieChanged = PR_TRUE; +} + +// removes any expired cookies from memory, and finds the oldest cookie in the list +void +nsCookieService::RemoveExpiredCookies(nsInt64 aCurrentTime, + PRInt32 &aOldestPosition) +{ + aOldestPosition = -1; + + nsCookie *cookieInList; + nsInt64 oldestTime = LL_MAXINT; + + for (PRInt32 i = mCookieList.Count(); i--;) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + if (!cookieInList->IsSession() && cookieInList->Expiry() <= aCurrentTime) { + mCookieList.RemoveElementAt(i); + NS_RELEASE(cookieInList); + mCookieChanged = PR_TRUE; + --aOldestPosition; + continue; + } + + if (oldestTime > cookieInList->LastAccessed()) { + oldestTime = cookieInList->LastAccessed(); + aOldestPosition = i; + } + } +} + +// count the number of cookies from this host, and find whether a previous cookie +// has been set, for prompting purposes. +PRBool +nsCookieService::FindCookiesFromHost(nsCookie *aCookie, + PRUint32 &aCountFromHost, + nsInt64 aCurrentTime) +{ + aCountFromHost = 0; + PRBool foundCookie = PR_FALSE; + + nsCookie *cookieInList; + const nsAFlatCString &host = aCookie->Host(); + const nsAFlatCString &path = aCookie->Path(); + const nsAFlatCString &name = aCookie->Name(); + + PRInt32 count = mCookieList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + // only count session or non-expired cookies + if (IsInDomain(cookieInList->Host(), host, cookieInList->IsDomain()) && + (cookieInList->IsSession() || cookieInList->Expiry() > aCurrentTime)) { + ++aCountFromHost; + + // check if we've found the previous cookie + if (path.Equals(cookieInList->Path()) && + host.Equals(cookieInList->Host()) && + name.Equals(cookieInList->Name())) { + foundCookie = PR_TRUE; + } + } + } + + return foundCookie; +} + +// find a position to store a cookie (either replacing an existing cookie, or adding +// to end of list), and a cookie to delete (if maximum number of cookies has been +// exceeded). also performs list maintenance by removing expired cookies. +// returns whether a previous cookie already exists, +// aInsertPosition is the position to insert the new cookie; +// aDeletePosition is the position to delete (-1 if not required). +PRBool +nsCookieService::FindPosition(nsCookie *aCookie, + PRInt32 &aInsertPosition, + PRInt32 &aDeletePosition, + nsInt64 aCurrentTime) +{ + aDeletePosition = -1; + aInsertPosition = -1; + PRBool foundCookie = PR_FALSE; + + // list maintenance: remove expired cookies, and find the position of the + // oldest cookie while we're at it - required if we have kMaxNumberOfCookies + // and need to remove one. + PRInt32 oldestPosition; + RemoveExpiredCookies(aCurrentTime, oldestPosition); + + nsCookie *cookieInList; + nsInt64 oldestTimeFromHost = LL_MAXINT; + PRInt32 oldestPositionFromHost; + PRInt32 countFromHost = 0; + const nsAFlatCString &host = aCookie->Host(); + const nsAFlatCString &path = aCookie->Path(); + const nsAFlatCString &name = aCookie->Name(); + + PRInt32 count = mCookieList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + cookieInList = NS_STATIC_CAST(nsCookie*, mCookieList.ElementAt(i)); + NS_ASSERTION(cookieInList, "corrupt cookie list"); + + // check if we've passed the location where we might find a previous cookie. + // mCookieList is sorted in order of descending path length, + // so since we're enumerating forwards, we look for aCookie path length + // to become greater than cookieInList path length. + // if we've found a position to insert the cookie at (either replacing a + // previous cookie, or inserting at a new position), we don't need to keep looking. + if (aInsertPosition == -1 && + path.Length() > cookieInList->Path().Length()) { + aInsertPosition = i; + } + + if (IsInDomain(cookieInList->Host(), host, cookieInList->IsDomain())) { + ++countFromHost; + + if (oldestTimeFromHost > cookieInList->LastAccessed()) { + oldestTimeFromHost = cookieInList->LastAccessed(); + oldestPositionFromHost = i; + } + + if (aInsertPosition == -1 && + path.Equals(cookieInList->Path()) && + host.Equals(cookieInList->Host()) && + name.Equals(cookieInList->Name())) { + aInsertPosition = i; + foundCookie = PR_TRUE; + } + } + } + + // if we didn't find a position to insert at, put it at the end of the list + if (aInsertPosition == -1) { + aInsertPosition = count; + } + + // choose which cookie to delete (oldest cookie, or oldest cookie from this host), + // if we have to. + if (countFromHost >= kMaxCookiesPerHost) { + aDeletePosition = oldestPositionFromHost; + } else if (count >= kMaxNumberOfCookies) { + aDeletePosition = oldestPosition; + } + + return foundCookie; +} diff --git a/netwerk/cookie/src/nsCookieService.h b/netwerk/cookie/src/nsCookieService.h new file mode 100644 index 00000000000..cfbb43e7af8 --- /dev/null +++ b/netwerk/cookie/src/nsCookieService.h @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Daniel Witte (dwitte@stanford.edu) + * Michiel van Leeuwen (mvl@exedo.nl) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsCookieService_h__ +#define nsCookieService_h__ + +#include "nsICookieService.h" +#include "nsICookieManager.h" +#include "nsICookieManager2.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" + +#include "nsCookie.h" +#include "nsString.h" +#include "nsVoidArray.h" + +struct nsCookieAttributes; +class nsICookieConsent; +class nsICookiePermission; +class nsIPrefBranch; +class nsIObserverService; +class nsIURI; +class nsIPrompt; +class nsIChannel; +class nsITimer; +class nsIFile; +class nsInt64; + +/****************************************************************************** + * nsCookieService: + * class declaration + ******************************************************************************/ + +class nsCookieService : public nsICookieService + , public nsICookieManager2 + , public nsIObserver + , public nsSupportsWeakReference +{ + public: + // nsISupports + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSICOOKIESERVICE + NS_DECL_NSICOOKIEMANAGER + NS_DECL_NSICOOKIEMANAGER2 + + nsCookieService(); + virtual ~nsCookieService(); + static nsCookieService* GetSingleton(); + + protected: + void InitPrefObservers(); + nsresult ReadPrefs(); + nsresult Read(); + nsresult Write(); + PRBool SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, nsDependentCString &aCookieHeader, nsInt64 aServerTime, nsCookieStatus aStatus, nsCookiePolicy aPolicy); + nsresult AddInternal(nsCookie *aCookie, nsInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader); + static PRBool GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentSingleFragmentCSubstring &aTokenString, nsDependentSingleFragmentCSubstring &aTokenValue, PRBool &aEqualsFound); + static PRBool ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie); + static PRBool IsIPAddress(const nsAFlatCString &aHost); + static PRBool IsFromMailNews(const nsAFlatCString &aScheme); + static PRBool IsInDomain(const nsACString &aDomain, const nsACString &aHost, PRBool aIsDomain = PR_TRUE); + static PRBool IsForeign(nsIURI *aHostURI, nsIURI *aFirstURI); + static nsCookiePolicy GetP3PPolicy(PRInt32 aPolicy); + PRInt32 SiteP3PPolicy(nsIURI *aCurrentURI, nsIChannel *aChannel); + nsCookieStatus P3PDecision(nsIURI *aHostURI, nsIURI *aFirstURI, nsIChannel *aChannel); + nsCookieStatus CheckPrefs(nsIURI *aHostURI, nsIURI *aFirstURI, nsIChannel *aChannel, const char *aCookieHeader); + PRBool CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI); + static PRBool CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI); + PRBool GetExpiry(nsCookieAttributes &aCookie, nsInt64 aServerTime, nsInt64 aCurrentTime, nsCookieStatus aStatus); + void RemoveAllFromMemory(); + void RemoveExpiredCookies(nsInt64 aCurrentTime, PRInt32 &aOldestPosition); + PRBool FindCookiesFromHost(nsCookie *aCookie, PRUint32 &aCountFromHost, nsInt64 aCurrentTime); + PRBool FindPosition(nsCookie *aCookie, PRInt32 &aInsertPosition, PRInt32 &aDeletePosition, nsInt64 aCurrentTime); + void UpdateCookieIcon(); + + // Use LazyWrite to save the cookies file on a timer. It will write + // the file only once if repeatedly hammered quickly. + void LazyWrite(); + static void DoLazyWrite(nsITimer *aTimer, void *aClosure); + + // Use LazyNotify to broadcast the "cookieChanged" notification at + // a reasonable frequency. + void LazyNotify(); + static void DoLazyNotify(nsITimer *aTimer, void *aClosure); + + protected: + // cached members + nsCOMPtr mPrefBranch; + nsCOMPtr mCookieFile; + nsCOMPtr mObserverService; + nsCOMPtr mP3PService; + nsCOMPtr mPermissionService; + + // impl members + nsCOMPtr mWriteTimer; + nsCOMPtr mNotifyTimer; + nsVoidArray mCookieList; + PRPackedBool mCookieChanged; + PRPackedBool mCookieIconVisible; + + // cached prefs +#ifdef MOZ_PHOENIX + // unfortunately, we require this #ifdef for now, since Phoenix uses different + // (more optimized) prefs to Mozilla. + // the following variables are Phoenix hacks to reduce ifdefs in the code. + PRPackedBool mCookiesEnabled_temp, // These two prefs are collapsed + mCookiesForDomainOnly_temp; // into mCookiesPermissions. +#endif + PRPackedBool mCookiesLifetimeEnabled, // Cookie lifetime limit enabled + mCookiesLifetimeCurrentSession, // Limit cookie lifetime to current session + mCookiesStrictDomains; // Optional pref to apply stricter domain checks + PRUint8 mCookiesPermissions; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, P3P} + PRInt32 mCookiesLifetimeSec; // Lifetime limit specified in seconds + + /* mCookiesP3PString (below) consists of 8 characters having the following interpretation: + * [0]: behavior for first-party cookies when site has no privacy policy + * [1]: behavior for third-party cookies when site has no privacy policy + * [2]: behavior for first-party cookies when site uses PII with no user consent + * [3]: behavior for third-party cookies when site uses PII with no user consent + * [4]: behavior for first-party cookies when site uses PII with implicit consent only + * [5]: behavior for third-party cookies when site uses PII with implicit consent only + * [6]: behavior for first-party cookies when site uses PII with explicit consent + * [7]: behavior for third-party cookies when site uses PII with explicit consent + * + * (note: PII = personally identifiable information) + * + * each of the eight characters can be one of the following: + * 'a': accept the cookie + * 'd': accept the cookie but downgrade it to a session cookie + * 'r': reject the cookie + */ + nsXPIDLCString mCookiesP3PString; + + // private static member, used to cache a ptr to nsCookieService, + // so we can make nsCookieService a singleton xpcom object. + static nsCookieService *gCookieService; +}; + +#define NS_COOKIEMANAGER_CID {0xaaab6710,0xf2c,0x11d5,{0xa5,0x3b,0x0,0x10,0xa4,0x1,0xeb,0x10}} + +#endif // nsCookieService_h__