2579 lines
94 KiB
C++
2579 lines
94 KiB
C++
/* -*- 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 "nsIGenericFactory.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsIDocumentLoader.h"
|
|
#include "nsIWebProgress.h"
|
|
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsIPrefBranchInternal.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIDocShellTreeItem.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 "nsCURILoader.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
|
|
/******************************************************************************
|
|
* nsCookieService impl:
|
|
* useful types & constants
|
|
******************************************************************************/
|
|
|
|
static NS_DEFINE_IID(kDocLoaderServiceCID, NS_DOCUMENTLOADER_SERVICE_CID);
|
|
|
|
static const char kCookieFileName[] = "cookies.txt";
|
|
|
|
static const PRUint32 kLazyWriteLoadingTimeout = 10000; //msec
|
|
static const PRUint32 kLazyWriteFinishedTimeout = 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 kCookiesDisabledForMailNews[] = "network.cookie.disableCookieForMailNews";
|
|
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 kCookiesAskPermission[] = "network.cookie.warnAboutCookies";
|
|
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_ISUPPORTS6(nsCookieService,
|
|
nsICookieService,
|
|
nsICookieManager,
|
|
nsICookieManager2,
|
|
nsIObserver,
|
|
nsIWebProgressListener,
|
|
nsISupportsWeakReference)
|
|
|
|
nsCookieService::nsCookieService()
|
|
: mLoadCount(0)
|
|
, mWritePending(PR_FALSE)
|
|
, 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);
|
|
|
|
// Register as an observer for the document loader
|
|
nsCOMPtr<nsIDocumentLoader> docLoaderService = do_GetService(kDocLoaderServiceCID);
|
|
nsCOMPtr<nsIWebProgress> progress = do_QueryInterface(docLoaderService);
|
|
if (progress) {
|
|
progress->AddProgressListener(this,
|
|
nsIWebProgress::NOTIFY_STATE_DOCUMENT |
|
|
nsIWebProgress::NOTIFY_STATE_NETWORK);
|
|
} else {
|
|
NS_ERROR("Couldn't get nsIDocumentLoader");
|
|
}
|
|
}
|
|
|
|
nsCookieService::~nsCookieService()
|
|
{
|
|
gCookieService = nsnull;
|
|
|
|
if (mWriteTimer)
|
|
mWriteTimer->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();
|
|
|
|
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(kCookiesDisabledForMailNews)) {
|
|
rv = mPrefBranch->GetBoolPref(kCookiesDisabledForMailNews, &tempPrefValue);
|
|
if (NS_FAILED(rv)) {
|
|
tempPrefValue = PR_TRUE;
|
|
}
|
|
mCookiesDisabledForMailNews = 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(kCookiesAskPermission)) {
|
|
rv = mPrefBranch->GetBoolPref(kCookiesAskPermission, &tempPrefValue);
|
|
if (NS_FAILED(rv)) {
|
|
tempPrefValue = PR_FALSE;
|
|
}
|
|
mCookiesAskPermission = tempPrefValue;
|
|
|
|
} 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<nsIURI> firstURI;
|
|
if (aChannel) {
|
|
nsCOMPtr<nsIHttpChannelInternal> 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<nsIURI> firstURI;
|
|
|
|
if (aChannel) {
|
|
nsCOMPtr<nsIHttpChannelInternal> 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,
|
|
cookieHeader, serverTime,
|
|
cookieStatus, cookiePolicy));
|
|
|
|
// write out the cookie file
|
|
LazyWrite(PR_TRUE);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsCookieService::LazyWrite(PRBool aForce)
|
|
{
|
|
// !aForce resets the timer at load end, but only if a write is pending
|
|
if (!aForce && !mWritePending)
|
|
return;
|
|
|
|
PRUint32 timeout = mLoadCount > 0 ? kLazyWriteLoadingTimeout :
|
|
kLazyWriteFinishedTimeout;
|
|
if (mWriteTimer) {
|
|
mWriteTimer->SetDelay(timeout);
|
|
mWritePending = PR_TRUE;
|
|
} else {
|
|
mWriteTimer = do_CreateInstance("@mozilla.org/timer;1");
|
|
if (mWriteTimer) {
|
|
mWriteTimer->InitWithFuncCallback(DoLazyWrite, this, timeout,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
mWritePending = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsCookieService::DoLazyWrite(nsITimer *aTimer,
|
|
void *aClosure)
|
|
{
|
|
nsCookieService *service = NS_REINTERPRET_CAST(nsCookieService*, aClosure);
|
|
service->mWritePending = PR_FALSE;
|
|
service->Write();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCookieService::OnStateChange(nsIWebProgress *aWebProgress,
|
|
nsIRequest *aRequest,
|
|
PRUint32 aProgressStateFlags,
|
|
nsresult aStatus)
|
|
{
|
|
if (aProgressStateFlags & STATE_IS_NETWORK) {
|
|
if (aProgressStateFlags & STATE_START)
|
|
++mLoadCount;
|
|
if (aProgressStateFlags & STATE_STOP) {
|
|
if (mLoadCount > 0) // needed because at startup we may miss initial STATE_START
|
|
--mLoadCount;
|
|
if (mLoadCount == 0)
|
|
LazyWrite(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
if (mObserverService &&
|
|
(aProgressStateFlags & STATE_IS_DOCUMENT) &&
|
|
(aProgressStateFlags & STATE_STOP)) {
|
|
mObserverService->NotifyObservers(nsnull, "cookieChanged", NS_LITERAL_STRING("cookies").get());
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// nsIWebProgressListener implementation
|
|
NS_IMETHODIMP
|
|
nsCookieService::OnProgressChange(nsIWebProgress *aProgress,
|
|
nsIRequest *aRequest,
|
|
PRInt32 aCurSelfProgress,
|
|
PRInt32 aMaxSelfProgress,
|
|
PRInt32 aCurTotalProgress,
|
|
PRInt32 aMaxTotalProgress)
|
|
{
|
|
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCookieService::OnLocationChange(nsIWebProgress *aWebProgress,
|
|
nsIRequest *aRequest,
|
|
nsIURI *aLocation)
|
|
{
|
|
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCookieService::OnStatusChange(nsIWebProgress *aWebProgress,
|
|
nsIRequest *aRequest,
|
|
nsresult aStatus,
|
|
const PRUnichar *aMessage)
|
|
{
|
|
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCookieService::OnSecurityChange(nsIWebProgress *aWebProgress,
|
|
nsIRequest *aRequest,
|
|
PRUint32 aState)
|
|
{
|
|
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
|
|
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<nsIPrefBranchInternal> 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(kCookiesDisabledForMailNews, 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(kCookiesAskPermission, this, PR_TRUE);
|
|
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.
|
|
#ifdef MOZ_PHOENIX
|
|
mCookiesDisabledForMailNews = PR_FALSE; // for efficiency
|
|
#else
|
|
mCookiesDisabledForMailNews = PR_TRUE;
|
|
mCookiesP3PString = NS_LITERAL_CSTRING(kCookiesP3PString_Default);
|
|
#endif
|
|
mCookiesPermissions = BEHAVIOR_REJECT;
|
|
mCookiesLifetimeEnabled = PR_FALSE;
|
|
mCookiesAskPermission = 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;
|
|
mCookiesDisabledForMailNews = PR_FALSE;
|
|
mCookiesLifetimeSec = 0;
|
|
|
|
#else
|
|
rv = mPrefBranch->GetIntPref(kCookiesPermissions, &tempPrefValue);
|
|
if (NS_FAILED(rv)) {
|
|
tempPrefValue = BEHAVIOR_REJECT;
|
|
rv2 = rv;
|
|
}
|
|
mCookiesPermissions = tempPrefValue;
|
|
|
|
rv = mPrefBranch->GetBoolPref(kCookiesDisabledForMailNews, &tempPrefValue);
|
|
if (NS_FAILED(rv)) {
|
|
tempPrefValue = PR_TRUE;
|
|
rv2 = rv;
|
|
}
|
|
mCookiesDisabledForMailNews = 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(kCookiesAskPermission, &tempPrefValue);
|
|
if (NS_FAILED(rv)) {
|
|
tempPrefValue = PR_FALSE;
|
|
rv2 = rv;
|
|
}
|
|
mCookiesAskPermission = tempPrefValue;
|
|
|
|
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<nsCookie> 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) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPermissionManager> permissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
static NS_NAMED_LITERAL_CSTRING(httpPrefix, "http://");
|
|
|
|
// remove leading dot from host
|
|
if (cookieInList->IsDomain()) {
|
|
rv = NS_NewURI(getter_AddRefs(uri), PromiseFlatCString(httpPrefix + Substring(cookieInList->Host(), 1, cookieInList->Host().Length() - 1)));
|
|
} else {
|
|
rv = NS_NewURI(getter_AddRefs(uri), PromiseFlatCString(httpPrefix + cookieInList->Host()));
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv))
|
|
permissionManager->Add(uri, "cookie", nsIPermissionManager::DENY_ACTION);
|
|
}
|
|
}
|
|
|
|
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<nsIInputStream> fileInputStream;
|
|
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), mCookieFile);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsILineInputStream> 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<nsIOutputStream> 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<nsIOutputStream> 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,
|
|
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<nsCookie> 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->TestPermission(aHostURI,
|
|
NS_STATIC_CAST(nsICookie*, NS_STATIC_CAST(nsCookie*, cookie)),
|
|
nsnull,
|
|
countFromHost, foundCookie,
|
|
mCookiesAskPermission,
|
|
&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. <bnf> 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 (<LWS>) 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.
|
|
<LWS> 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. <LWS> is allowed within tokens and values
|
|
(see bug 206022).
|
|
|
|
4. where appropriate, full <OCTET>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 <NAME> is optional, where spec requires it. This is a fairly
|
|
trivial case, but allows the flexibility of setting only a cookie <VALUE>
|
|
with a blank <NAME> and is required by some sites (see bug 169091).
|
|
|
|
** Begin BNF:
|
|
token = 1*<any allowed-chars except separators>
|
|
value = token-value | quoted-string
|
|
token-value = 1*<any allowed-chars except value-sep>
|
|
quoted-string = ( <"> *( qdtext | quoted-pair ) <"> )
|
|
qdtext = <any allowed-chars except <">> ; CR | LF removed by necko
|
|
quoted-pair = "\" <any OCTET except NUL or cookie-sep> ; CR | LF removed by necko
|
|
separators = ";" | "="
|
|
value-sep = ";"
|
|
cookie-sep = CR | LF
|
|
allowed-chars = <any OCTET except NUL or cookie-sep>
|
|
OCTET = <any 8-bit sequence of data>
|
|
LWS = SP | HT
|
|
NUL = <US-ASCII NUL, null control character (0)>
|
|
CR = <US-ASCII CR, carriage return (13)>
|
|
LF = <US-ASCII LF, linefeed (10)>
|
|
SP = <US-ASCII SP, space (32)>
|
|
HT = <US-ASCII HT, horizontal-tab (9)>
|
|
|
|
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 <token>, including any <LWS> between the end-of-token and the
|
|
// token separator. we'll remove trailing <LWS> next
|
|
while (aIter != aEndIter && iswhitespace(*aIter))
|
|
++aIter;
|
|
start = aIter;
|
|
while (aIter != aEndIter && !istokenseparator(*aIter))
|
|
++aIter;
|
|
|
|
// remove trailing <LWS>; 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 <value>
|
|
while (++aIter != aEndIter && iswhitespace(*aIter));
|
|
|
|
start = aIter;
|
|
|
|
if (*aIter == '"') {
|
|
// process <quoted-string>
|
|
// (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 <qdtext> (backwhacked char), skip over it. this allows '\"' in <quoted-string>.
|
|
// 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 <token-value>
|
|
// just look for ';' to terminate ('=' allowed)
|
|
while (aIter != aEndIter && !isvalueseparator(*aIter))
|
|
++aIter;
|
|
|
|
// remove trailing <LWS>; 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 <NAME> & <VALUE> (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 <VALUE>. this is required by
|
|
// some sites (see bug 169091).
|
|
// XXX fix the parser to parse according to <VALUE> 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);
|
|
}
|
|
|
|
// returns PR_TRUE if URI scheme is from mailnews
|
|
PRBool
|
|
nsCookieService::IsFromMailNews(const nsAFlatCString &aScheme)
|
|
{
|
|
return (aScheme.Equals(NS_LITERAL_CSTRING("imap")) ||
|
|
aScheme.Equals(NS_LITERAL_CSTRING("news")) ||
|
|
aScheme.Equals(NS_LITERAL_CSTRING("snews")) ||
|
|
aScheme.Equals(NS_LITERAL_CSTRING("mailbox")));
|
|
}
|
|
|
|
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<domainName>
|
|
* 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<nsIHttpChannel> 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;
|
|
}
|
|
|
|
// disable cookies in mailnews if user's prefs say so
|
|
if (mCookiesDisabledForMailNews) {
|
|
//
|
|
// try to examine the "app type" of the docshell owning this request. if
|
|
// we find a docshell in the heirarchy of type APP_TYPE_MAIL, then assume
|
|
// this URI is being loaded from within mailnews.
|
|
//
|
|
// XXX this is a pretty ugly hack at the moment since cookies really
|
|
// shouldn't have to talk to the docshell directly. ultimately, we want
|
|
// to talk to some more generic interface, which the docshell would also
|
|
// implement. but, the basic mechanism here of leveraging the channel's
|
|
// (or loadgroup's) notification callbacks attribute seems ideal as it
|
|
// avoids the problem of having to modify all places in the code which
|
|
// kick off network requests.
|
|
//
|
|
PRUint32 appType = nsIDocShell::APP_TYPE_UNKNOWN;
|
|
if (aChannel) {
|
|
nsCOMPtr<nsIInterfaceRequestor> req;
|
|
aChannel->GetNotificationCallbacks(getter_AddRefs(req));
|
|
if (!req) {
|
|
// check the load group's notification callbacks...
|
|
nsCOMPtr<nsILoadGroup> group;
|
|
aChannel->GetLoadGroup(getter_AddRefs(group));
|
|
if (group)
|
|
group->GetNotificationCallbacks(getter_AddRefs(req));
|
|
}
|
|
if (req) {
|
|
nsCOMPtr<nsIDocShellTreeItem> item, parent = do_GetInterface(req);
|
|
if (parent) {
|
|
do {
|
|
item = parent;
|
|
nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(item);
|
|
if (docshell)
|
|
docshell->GetAppType(&appType);
|
|
} while (appType != nsIDocShell::APP_TYPE_MAIL &&
|
|
NS_SUCCEEDED(item->GetParent(getter_AddRefs(parent))) &&
|
|
parent);
|
|
}
|
|
}
|
|
}
|
|
if ((appType == nsIDocShell::APP_TYPE_MAIL) ||
|
|
(aFirstURI && IsFromMailNews(firstURIScheme)) ||
|
|
IsFromMailNews(currentURIScheme)) {
|
|
COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies disabled for mailnews");
|
|
return nsICookie::STATUS_REJECTED;
|
|
}
|
|
}
|
|
|
|
// check the permission list first; if we find an entry, it overrides
|
|
// default prefs. see bug 184059.
|
|
nsCOMPtr<nsIPermissionManager> permissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
PRUint32 listPermission;
|
|
permissionManager->TestPermission(aHostURI, "cookie", &listPermission);
|
|
|
|
// if we found an entry, use it
|
|
switch (listPermission) {
|
|
case nsIPermissionManager::DENY_ACTION:
|
|
COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are blocked for this site");
|
|
return nsICookie::STATUS_REJECTED;
|
|
|
|
case nsIPermissionManager::ALLOW_ACTION:
|
|
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<nsIURL> 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;
|
|
}
|