michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsCookiePermission.h" michael@0: michael@0: #include "mozIThirdPartyUtil.h" michael@0: #include "nsICookie2.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsICookiePromptService.h" michael@0: #include "nsICookieManager2.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsString.h" michael@0: #include "nsCRT.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsNetCID.h" michael@0: michael@0: /**************************************************************** michael@0: ************************ nsCookiePermission ******************** michael@0: ****************************************************************/ michael@0: michael@0: // values for mCookiesLifetimePolicy michael@0: // 0 == accept normally michael@0: // 1 == ask before accepting michael@0: // 2 == downgrade to session michael@0: // 3 == limit lifetime to N days michael@0: static const uint32_t ACCEPT_NORMALLY = 0; michael@0: static const uint32_t ASK_BEFORE_ACCEPT = 1; michael@0: static const uint32_t ACCEPT_SESSION = 2; michael@0: static const uint32_t ACCEPT_FOR_N_DAYS = 3; michael@0: michael@0: static const bool kDefaultPolicy = true; michael@0: static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy"; michael@0: static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days"; michael@0: static const char kCookiesAlwaysAcceptSession[] = "network.cookie.alwaysAcceptSessionCookies"; michael@0: michael@0: static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated"; michael@0: // obsolete pref names for migration michael@0: static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled"; michael@0: static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior"; michael@0: static const char kCookiesAskPermission[] = "network.cookie.warnAboutCookies"; michael@0: michael@0: static const char kPermissionType[] = "cookie"; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCookiePermission, michael@0: nsICookiePermission, michael@0: nsIObserver) michael@0: michael@0: bool michael@0: nsCookiePermission::Init() michael@0: { michael@0: // Initialize nsIPermissionManager and fetch relevant prefs. This is only michael@0: // required for some methods on nsICookiePermission, so it should be done michael@0: // lazily. michael@0: nsresult rv; michael@0: mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return false; michael@0: mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return false; michael@0: michael@0: // failure to access the pref service is non-fatal... michael@0: nsCOMPtr prefBranch = michael@0: do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (prefBranch) { michael@0: prefBranch->AddObserver(kCookiesLifetimePolicy, this, false); michael@0: prefBranch->AddObserver(kCookiesLifetimeDays, this, false); michael@0: prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, false); michael@0: PrefChanged(prefBranch, nullptr); michael@0: michael@0: // migration code for original cookie prefs michael@0: bool migrated; michael@0: rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated); michael@0: if (NS_FAILED(rv) || !migrated) { michael@0: bool warnAboutCookies = false; michael@0: prefBranch->GetBoolPref(kCookiesAskPermission, &warnAboutCookies); michael@0: michael@0: // if the user is using ask before accepting, we'll use that michael@0: if (warnAboutCookies) michael@0: prefBranch->SetIntPref(kCookiesLifetimePolicy, ASK_BEFORE_ACCEPT); michael@0: michael@0: bool lifetimeEnabled = false; michael@0: prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled); michael@0: michael@0: // if they're limiting lifetime and not using the prompts, use the michael@0: // appropriate limited lifetime pref michael@0: if (lifetimeEnabled && !warnAboutCookies) { michael@0: int32_t lifetimeBehavior; michael@0: prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior); michael@0: if (lifetimeBehavior) michael@0: prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS); michael@0: else michael@0: prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION); michael@0: } michael@0: prefBranch->SetBoolPref(kCookiesPrefsMigrated, true); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch, michael@0: const char *aPref) michael@0: { michael@0: int32_t val; michael@0: michael@0: #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P)) michael@0: michael@0: if (PREF_CHANGED(kCookiesLifetimePolicy) && michael@0: NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val))) michael@0: mCookiesLifetimePolicy = val; michael@0: michael@0: if (PREF_CHANGED(kCookiesLifetimeDays) && michael@0: NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val))) michael@0: // save cookie lifetime in seconds instead of days michael@0: mCookiesLifetimeSec = val * 24 * 60 * 60; michael@0: michael@0: bool bval; michael@0: if (PREF_CHANGED(kCookiesAlwaysAcceptSession) && michael@0: NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &bval))) michael@0: mCookiesAlwaysAcceptSession = bval; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCookiePermission::SetAccess(nsIURI *aURI, michael@0: nsCookieAccess aAccess) michael@0: { michael@0: // Lazily initialize ourselves michael@0: if (!EnsureInitialized()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // michael@0: // NOTE: nsCookieAccess values conveniently match up with michael@0: // the permission codes used by nsIPermissionManager. michael@0: // this is nice because it avoids conversion code. michael@0: // michael@0: return mPermMgr->Add(aURI, kPermissionType, aAccess, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCookiePermission::CanAccess(nsIURI *aURI, michael@0: nsIChannel *aChannel, michael@0: nsCookieAccess *aResult) michael@0: { michael@0: // Check this protocol doesn't allow cookies michael@0: bool hasFlags; michael@0: nsresult rv = michael@0: NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS, michael@0: &hasFlags); michael@0: if (NS_FAILED(rv) || hasFlags) { michael@0: *aResult = ACCESS_DENY; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Lazily initialize ourselves michael@0: if (!EnsureInitialized()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // finally, check with permission manager... michael@0: rv = mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *) aResult); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (*aResult == nsICookiePermission::ACCESS_SESSION) { michael@0: *aResult = nsICookiePermission::ACCESS_ALLOW; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCookiePermission::CanSetCookie(nsIURI *aURI, michael@0: nsIChannel *aChannel, michael@0: nsICookie2 *aCookie, michael@0: bool *aIsSession, michael@0: int64_t *aExpiry, michael@0: bool *aResult) michael@0: { michael@0: NS_ASSERTION(aURI, "null uri"); michael@0: michael@0: *aResult = kDefaultPolicy; michael@0: michael@0: // Lazily initialize ourselves michael@0: if (!EnsureInitialized()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: uint32_t perm; michael@0: mPermMgr->TestPermission(aURI, kPermissionType, &perm); michael@0: bool isThirdParty = false; michael@0: switch (perm) { michael@0: case nsICookiePermission::ACCESS_SESSION: michael@0: *aIsSession = true; michael@0: michael@0: case nsICookiePermission::ACCESS_ALLOW: michael@0: *aResult = true; michael@0: break; michael@0: michael@0: case nsICookiePermission::ACCESS_DENY: michael@0: *aResult = false; michael@0: break; michael@0: michael@0: case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY: michael@0: mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty); michael@0: // If it's third party, we can't set the cookie michael@0: if (isThirdParty) michael@0: *aResult = false; michael@0: break; michael@0: michael@0: case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY: michael@0: mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty); michael@0: // If it's third party, check whether cookies are already set michael@0: if (isThirdParty) { michael@0: nsresult rv; michael@0: nsCOMPtr cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: *aResult = false; michael@0: break; michael@0: } michael@0: uint32_t priorCookieCount = 0; michael@0: nsAutoCString hostFromURI; michael@0: aURI->GetHost(hostFromURI); michael@0: cookieManager->CountCookiesFromHost(hostFromURI, &priorCookieCount); michael@0: *aResult = priorCookieCount != 0; michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: // the permission manager has nothing to say about this cookie - michael@0: // so, we apply the default prefs to it. michael@0: NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission"); michael@0: michael@0: // now we need to figure out what type of accept policy we're dealing with michael@0: // if we accept cookies normally, just bail and return michael@0: if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // declare this here since it'll be used in all of the remaining cases michael@0: int64_t currentTime = PR_Now() / PR_USEC_PER_SEC; michael@0: int64_t delta = *aExpiry - currentTime; michael@0: michael@0: // check whether the user wants to be prompted michael@0: if (mCookiesLifetimePolicy == ASK_BEFORE_ACCEPT) { michael@0: // if it's a session cookie and the user wants to accept these michael@0: // without asking, or if we are in private browsing mode, just michael@0: // accept the cookie and return michael@0: if ((*aIsSession && mCookiesAlwaysAcceptSession) || michael@0: (aChannel && NS_UsePrivateBrowsing(aChannel))) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // default to rejecting, in case the prompting process fails michael@0: *aResult = false; michael@0: michael@0: nsAutoCString hostPort; michael@0: aURI->GetHostPort(hostPort); michael@0: michael@0: if (!aCookie) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: // If there is no host, use the scheme, and append "://", michael@0: // to make sure it isn't a host or something. michael@0: // This is done to make the dialog appear for javascript cookies from michael@0: // file:// urls, and make the text on it not too weird. (bug 209689) michael@0: if (hostPort.IsEmpty()) { michael@0: aURI->GetScheme(hostPort); michael@0: if (hostPort.IsEmpty()) { michael@0: // still empty. Just return the default. michael@0: return NS_OK; michael@0: } michael@0: hostPort = hostPort + NS_LITERAL_CSTRING("://"); michael@0: } michael@0: michael@0: // we don't cache the cookiePromptService - it's not used often, so not michael@0: // worth the memory. michael@0: nsresult rv; michael@0: nsCOMPtr cookiePromptService = michael@0: do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // get some useful information to present to the user: michael@0: // whether a previous cookie already exists, and how many cookies this host michael@0: // has set michael@0: bool foundCookie = false; michael@0: uint32_t countFromHost; michael@0: nsCOMPtr cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAutoCString rawHost; michael@0: aCookie->GetRawHost(rawHost); michael@0: rv = cookieManager->CountCookiesFromHost(rawHost, &countFromHost); michael@0: michael@0: if (NS_SUCCEEDED(rv) && countFromHost > 0) michael@0: rv = cookieManager->CookieExists(aCookie, &foundCookie); michael@0: } michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // check if the cookie we're trying to set is already expired, and return; michael@0: // but only if there's no previous cookie, because then we need to delete the previous michael@0: // cookie. we need this check to avoid prompting the user for already-expired cookies. michael@0: if (!foundCookie && !*aIsSession && delta <= 0) { michael@0: // the cookie has already expired. accept it, and let the backend figure michael@0: // out it's expired, so that we get correct logging & notifications. michael@0: *aResult = true; michael@0: return rv; michael@0: } michael@0: michael@0: bool rememberDecision = false; michael@0: int32_t dialogRes = nsICookiePromptService::DENY_COOKIE; michael@0: rv = cookiePromptService->CookieDialog(nullptr, aCookie, hostPort, michael@0: countFromHost, foundCookie, michael@0: &rememberDecision, &dialogRes); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: *aResult = !!dialogRes; michael@0: if (dialogRes == nsICookiePromptService::ACCEPT_SESSION_COOKIE) michael@0: *aIsSession = true; michael@0: michael@0: if (rememberDecision) { michael@0: switch (dialogRes) { michael@0: case nsICookiePromptService::DENY_COOKIE: michael@0: mPermMgr->Add(aURI, kPermissionType, (uint32_t) nsIPermissionManager::DENY_ACTION, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: break; michael@0: case nsICookiePromptService::ACCEPT_COOKIE: michael@0: mPermMgr->Add(aURI, kPermissionType, (uint32_t) nsIPermissionManager::ALLOW_ACTION, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: break; michael@0: case nsICookiePromptService::ACCEPT_SESSION_COOKIE: michael@0: mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: // we're not prompting, so we must be limiting the lifetime somehow michael@0: // if it's a session cookie, we do nothing michael@0: if (!*aIsSession && delta > 0) { michael@0: if (mCookiesLifetimePolicy == ACCEPT_SESSION) { michael@0: // limit lifetime to session michael@0: *aIsSession = true; michael@0: } else if (delta > mCookiesLifetimeSec) { michael@0: // limit lifetime to specified time michael@0: *aExpiry = currentTime + mCookiesLifetimeSec; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // TODO: Why don't we just use this here: michael@0: // httpChannelInternal->GetDocumentURI(aURI); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCookiePermission::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: nsCOMPtr prefBranch = do_QueryInterface(aSubject); michael@0: NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic), michael@0: "unexpected topic - we only deal with pref changes!"); michael@0: michael@0: if (prefBranch) michael@0: PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get()); michael@0: return NS_OK; michael@0: }