1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCookiePermission.h"
9 #include "mozIThirdPartyUtil.h"
10 #include "nsICookie2.h"
11 #include "nsIServiceManager.h"
12 #include "nsICookiePromptService.h"
13 #include "nsICookieManager2.h"
14 #include "nsNetUtil.h"
16 #include "nsIPrefService.h"
17 #include "nsIPrefBranch.h"
18 #include "nsIChannel.h"
19 #include "nsIHttpChannelInternal.h"
20 #include "nsIDOMWindow.h"
21 #include "nsIPrincipal.h"
24 #include "nsILoadContext.h"
25 #include "nsIScriptObjectPrincipal.h"
28 /****************************************************************
29 ************************ nsCookiePermission ********************
30 ****************************************************************/
32 // values for mCookiesLifetimePolicy
33 // 0 == accept normally
34 // 1 == ask before accepting
35 // 2 == downgrade to session
36 // 3 == limit lifetime to N days
37 static const uint32_t ACCEPT_NORMALLY
= 0;
38 static const uint32_t ASK_BEFORE_ACCEPT
= 1;
39 static const uint32_t ACCEPT_SESSION
= 2;
40 static const uint32_t ACCEPT_FOR_N_DAYS
= 3;
42 static const bool kDefaultPolicy
= true;
43 static const char kCookiesLifetimePolicy
[] = "network.cookie.lifetimePolicy";
44 static const char kCookiesLifetimeDays
[] = "network.cookie.lifetime.days";
45 static const char kCookiesAlwaysAcceptSession
[] = "network.cookie.alwaysAcceptSessionCookies";
47 static const char kCookiesPrefsMigrated
[] = "network.cookie.prefsMigrated";
48 // obsolete pref names for migration
49 static const char kCookiesLifetimeEnabled
[] = "network.cookie.lifetime.enabled";
50 static const char kCookiesLifetimeBehavior
[] = "network.cookie.lifetime.behavior";
51 static const char kCookiesAskPermission
[] = "network.cookie.warnAboutCookies";
53 static const char kPermissionType
[] = "cookie";
55 NS_IMPL_ISUPPORTS(nsCookiePermission
,
60 nsCookiePermission::Init()
62 // Initialize nsIPermissionManager and fetch relevant prefs. This is only
63 // required for some methods on nsICookiePermission, so it should be done
66 mPermMgr
= do_GetService(NS_PERMISSIONMANAGER_CONTRACTID
, &rv
);
67 if (NS_FAILED(rv
)) return false;
68 mThirdPartyUtil
= do_GetService(THIRDPARTYUTIL_CONTRACTID
, &rv
);
69 if (NS_FAILED(rv
)) return false;
71 // failure to access the pref service is non-fatal...
72 nsCOMPtr
<nsIPrefBranch
> prefBranch
=
73 do_GetService(NS_PREFSERVICE_CONTRACTID
);
75 prefBranch
->AddObserver(kCookiesLifetimePolicy
, this, false);
76 prefBranch
->AddObserver(kCookiesLifetimeDays
, this, false);
77 prefBranch
->AddObserver(kCookiesAlwaysAcceptSession
, this, false);
78 PrefChanged(prefBranch
, nullptr);
80 // migration code for original cookie prefs
82 rv
= prefBranch
->GetBoolPref(kCookiesPrefsMigrated
, &migrated
);
83 if (NS_FAILED(rv
) || !migrated
) {
84 bool warnAboutCookies
= false;
85 prefBranch
->GetBoolPref(kCookiesAskPermission
, &warnAboutCookies
);
87 // if the user is using ask before accepting, we'll use that
89 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ASK_BEFORE_ACCEPT
);
91 bool lifetimeEnabled
= false;
92 prefBranch
->GetBoolPref(kCookiesLifetimeEnabled
, &lifetimeEnabled
);
94 // if they're limiting lifetime and not using the prompts, use the
95 // appropriate limited lifetime pref
96 if (lifetimeEnabled
&& !warnAboutCookies
) {
97 int32_t lifetimeBehavior
;
98 prefBranch
->GetIntPref(kCookiesLifetimeBehavior
, &lifetimeBehavior
);
100 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ACCEPT_FOR_N_DAYS
);
102 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ACCEPT_SESSION
);
104 prefBranch
->SetBoolPref(kCookiesPrefsMigrated
, true);
112 nsCookiePermission::PrefChanged(nsIPrefBranch
*aPrefBranch
,
117 #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
119 if (PREF_CHANGED(kCookiesLifetimePolicy
) &&
120 NS_SUCCEEDED(aPrefBranch
->GetIntPref(kCookiesLifetimePolicy
, &val
)))
121 mCookiesLifetimePolicy
= val
;
123 if (PREF_CHANGED(kCookiesLifetimeDays
) &&
124 NS_SUCCEEDED(aPrefBranch
->GetIntPref(kCookiesLifetimeDays
, &val
)))
125 // save cookie lifetime in seconds instead of days
126 mCookiesLifetimeSec
= val
* 24 * 60 * 60;
129 if (PREF_CHANGED(kCookiesAlwaysAcceptSession
) &&
130 NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kCookiesAlwaysAcceptSession
, &bval
)))
131 mCookiesAlwaysAcceptSession
= bval
;
135 nsCookiePermission::SetAccess(nsIURI
*aURI
,
136 nsCookieAccess aAccess
)
138 // Lazily initialize ourselves
139 if (!EnsureInitialized())
140 return NS_ERROR_UNEXPECTED
;
143 // NOTE: nsCookieAccess values conveniently match up with
144 // the permission codes used by nsIPermissionManager.
145 // this is nice because it avoids conversion code.
147 return mPermMgr
->Add(aURI
, kPermissionType
, aAccess
,
148 nsIPermissionManager::EXPIRE_NEVER
, 0);
152 nsCookiePermission::CanAccess(nsIURI
*aURI
,
153 nsIChannel
*aChannel
,
154 nsCookieAccess
*aResult
)
156 // Check this protocol doesn't allow cookies
159 NS_URIChainHasFlags(aURI
, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS
,
161 if (NS_FAILED(rv
) || hasFlags
) {
162 *aResult
= ACCESS_DENY
;
166 // Lazily initialize ourselves
167 if (!EnsureInitialized())
168 return NS_ERROR_UNEXPECTED
;
170 // finally, check with permission manager...
171 rv
= mPermMgr
->TestPermission(aURI
, kPermissionType
, (uint32_t *) aResult
);
172 if (NS_SUCCEEDED(rv
)) {
173 if (*aResult
== nsICookiePermission::ACCESS_SESSION
) {
174 *aResult
= nsICookiePermission::ACCESS_ALLOW
;
182 nsCookiePermission::CanSetCookie(nsIURI
*aURI
,
183 nsIChannel
*aChannel
,
189 NS_ASSERTION(aURI
, "null uri");
191 *aResult
= kDefaultPolicy
;
193 // Lazily initialize ourselves
194 if (!EnsureInitialized())
195 return NS_ERROR_UNEXPECTED
;
198 mPermMgr
->TestPermission(aURI
, kPermissionType
, &perm
);
199 bool isThirdParty
= false;
201 case nsICookiePermission::ACCESS_SESSION
:
204 case nsICookiePermission::ACCESS_ALLOW
:
208 case nsICookiePermission::ACCESS_DENY
:
212 case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY
:
213 mThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aURI
, &isThirdParty
);
214 // If it's third party, we can't set the cookie
219 case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY
:
220 mThirdPartyUtil
->IsThirdPartyChannel(aChannel
, aURI
, &isThirdParty
);
221 // If it's third party, check whether cookies are already set
224 nsCOMPtr
<nsICookieManager2
> cookieManager
= do_GetService(NS_COOKIEMANAGER_CONTRACTID
, &rv
);
229 uint32_t priorCookieCount
= 0;
230 nsAutoCString hostFromURI
;
231 aURI
->GetHost(hostFromURI
);
232 cookieManager
->CountCookiesFromHost(hostFromURI
, &priorCookieCount
);
233 *aResult
= priorCookieCount
!= 0;
238 // the permission manager has nothing to say about this cookie -
239 // so, we apply the default prefs to it.
240 NS_ASSERTION(perm
== nsIPermissionManager::UNKNOWN_ACTION
, "unknown permission");
242 // now we need to figure out what type of accept policy we're dealing with
243 // if we accept cookies normally, just bail and return
244 if (mCookiesLifetimePolicy
== ACCEPT_NORMALLY
) {
249 // declare this here since it'll be used in all of the remaining cases
250 int64_t currentTime
= PR_Now() / PR_USEC_PER_SEC
;
251 int64_t delta
= *aExpiry
- currentTime
;
253 // check whether the user wants to be prompted
254 if (mCookiesLifetimePolicy
== ASK_BEFORE_ACCEPT
) {
255 // if it's a session cookie and the user wants to accept these
256 // without asking, or if we are in private browsing mode, just
257 // accept the cookie and return
258 if ((*aIsSession
&& mCookiesAlwaysAcceptSession
) ||
259 (aChannel
&& NS_UsePrivateBrowsing(aChannel
))) {
264 // default to rejecting, in case the prompting process fails
267 nsAutoCString hostPort
;
268 aURI
->GetHostPort(hostPort
);
271 return NS_ERROR_UNEXPECTED
;
273 // If there is no host, use the scheme, and append "://",
274 // to make sure it isn't a host or something.
275 // This is done to make the dialog appear for javascript cookies from
276 // file:// urls, and make the text on it not too weird. (bug 209689)
277 if (hostPort
.IsEmpty()) {
278 aURI
->GetScheme(hostPort
);
279 if (hostPort
.IsEmpty()) {
280 // still empty. Just return the default.
283 hostPort
= hostPort
+ NS_LITERAL_CSTRING("://");
286 // we don't cache the cookiePromptService - it's not used often, so not
289 nsCOMPtr
<nsICookiePromptService
> cookiePromptService
=
290 do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID
, &rv
);
291 if (NS_FAILED(rv
)) return rv
;
293 // get some useful information to present to the user:
294 // whether a previous cookie already exists, and how many cookies this host
296 bool foundCookie
= false;
297 uint32_t countFromHost
;
298 nsCOMPtr
<nsICookieManager2
> cookieManager
= do_GetService(NS_COOKIEMANAGER_CONTRACTID
, &rv
);
299 if (NS_SUCCEEDED(rv
)) {
300 nsAutoCString rawHost
;
301 aCookie
->GetRawHost(rawHost
);
302 rv
= cookieManager
->CountCookiesFromHost(rawHost
, &countFromHost
);
304 if (NS_SUCCEEDED(rv
) && countFromHost
> 0)
305 rv
= cookieManager
->CookieExists(aCookie
, &foundCookie
);
307 if (NS_FAILED(rv
)) return rv
;
309 // check if the cookie we're trying to set is already expired, and return;
310 // but only if there's no previous cookie, because then we need to delete the previous
311 // cookie. we need this check to avoid prompting the user for already-expired cookies.
312 if (!foundCookie
&& !*aIsSession
&& delta
<= 0) {
313 // the cookie has already expired. accept it, and let the backend figure
314 // out it's expired, so that we get correct logging & notifications.
319 bool rememberDecision
= false;
320 int32_t dialogRes
= nsICookiePromptService::DENY_COOKIE
;
321 rv
= cookiePromptService
->CookieDialog(nullptr, aCookie
, hostPort
,
322 countFromHost
, foundCookie
,
323 &rememberDecision
, &dialogRes
);
324 if (NS_FAILED(rv
)) return rv
;
326 *aResult
= !!dialogRes
;
327 if (dialogRes
== nsICookiePromptService::ACCEPT_SESSION_COOKIE
)
330 if (rememberDecision
) {
332 case nsICookiePromptService::DENY_COOKIE
:
333 mPermMgr
->Add(aURI
, kPermissionType
, (uint32_t) nsIPermissionManager::DENY_ACTION
,
334 nsIPermissionManager::EXPIRE_NEVER
, 0);
336 case nsICookiePromptService::ACCEPT_COOKIE
:
337 mPermMgr
->Add(aURI
, kPermissionType
, (uint32_t) nsIPermissionManager::ALLOW_ACTION
,
338 nsIPermissionManager::EXPIRE_NEVER
, 0);
340 case nsICookiePromptService::ACCEPT_SESSION_COOKIE
:
341 mPermMgr
->Add(aURI
, kPermissionType
, nsICookiePermission::ACCESS_SESSION
,
342 nsIPermissionManager::EXPIRE_NEVER
, 0);
349 // we're not prompting, so we must be limiting the lifetime somehow
350 // if it's a session cookie, we do nothing
351 if (!*aIsSession
&& delta
> 0) {
352 if (mCookiesLifetimePolicy
== ACCEPT_SESSION
) {
353 // limit lifetime to session
355 } else if (delta
> mCookiesLifetimeSec
) {
356 // limit lifetime to specified time
357 *aExpiry
= currentTime
+ mCookiesLifetimeSec
;
367 nsCookiePermission::Observe(nsISupports
*aSubject
,
369 const char16_t
*aData
)
371 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
372 NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
, aTopic
),
373 "unexpected topic - we only deal with pref changes!");
376 PrefChanged(prefBranch
, NS_LossyConvertUTF16toASCII(aData
).get());