Merge mozilla-b2g34 to 2.1s. a=merge
[gecko.git] / extensions / cookie / nsCookiePermission.cpp
blobb7dd54bfd59cf5b0eeb752ac211fcea71214280b
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"
15 #include "nsIURI.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"
22 #include "nsString.h"
23 #include "nsCRT.h"
24 #include "nsILoadContext.h"
25 #include "nsIScriptObjectPrincipal.h"
26 #include "nsNetCID.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,
56 nsICookiePermission,
57 nsIObserver)
59 bool
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
64 // lazily.
65 nsresult rv;
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);
74 if (prefBranch) {
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
81 bool migrated;
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
88 if (warnAboutCookies)
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);
99 if (lifetimeBehavior)
100 prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
101 else
102 prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
104 prefBranch->SetBoolPref(kCookiesPrefsMigrated, true);
108 return true;
111 void
112 nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
113 const char *aPref)
115 int32_t val;
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;
128 bool bval;
129 if (PREF_CHANGED(kCookiesAlwaysAcceptSession) &&
130 NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &bval)))
131 mCookiesAlwaysAcceptSession = bval;
134 NS_IMETHODIMP
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);
151 NS_IMETHODIMP
152 nsCookiePermission::CanAccess(nsIURI *aURI,
153 nsIChannel *aChannel,
154 nsCookieAccess *aResult)
156 // Check this protocol doesn't allow cookies
157 bool hasFlags;
158 nsresult rv =
159 NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
160 &hasFlags);
161 if (NS_FAILED(rv) || hasFlags) {
162 *aResult = ACCESS_DENY;
163 return NS_OK;
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;
178 return rv;
181 NS_IMETHODIMP
182 nsCookiePermission::CanSetCookie(nsIURI *aURI,
183 nsIChannel *aChannel,
184 nsICookie2 *aCookie,
185 bool *aIsSession,
186 int64_t *aExpiry,
187 bool *aResult)
189 NS_ASSERTION(aURI, "null uri");
191 *aResult = kDefaultPolicy;
193 // Lazily initialize ourselves
194 if (!EnsureInitialized())
195 return NS_ERROR_UNEXPECTED;
197 uint32_t perm;
198 mPermMgr->TestPermission(aURI, kPermissionType, &perm);
199 bool isThirdParty = false;
200 switch (perm) {
201 case nsICookiePermission::ACCESS_SESSION:
202 *aIsSession = true;
204 case nsICookiePermission::ACCESS_ALLOW:
205 *aResult = true;
206 break;
208 case nsICookiePermission::ACCESS_DENY:
209 *aResult = false;
210 break;
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
215 if (isThirdParty)
216 *aResult = false;
217 break;
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
222 if (isThirdParty) {
223 nsresult rv;
224 nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
225 if (NS_FAILED(rv)) {
226 *aResult = false;
227 break;
229 uint32_t priorCookieCount = 0;
230 nsAutoCString hostFromURI;
231 aURI->GetHost(hostFromURI);
232 cookieManager->CountCookiesFromHost(hostFromURI, &priorCookieCount);
233 *aResult = priorCookieCount != 0;
235 break;
237 default:
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) {
245 *aResult = true;
246 return NS_OK;
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))) {
260 *aResult = true;
261 return NS_OK;
264 // default to rejecting, in case the prompting process fails
265 *aResult = false;
267 nsAutoCString hostPort;
268 aURI->GetHostPort(hostPort);
270 if (!aCookie) {
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.
281 return NS_OK;
283 hostPort = hostPort + NS_LITERAL_CSTRING("://");
286 // we don't cache the cookiePromptService - it's not used often, so not
287 // worth the memory.
288 nsresult rv;
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
295 // has set
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.
315 *aResult = true;
316 return rv;
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)
328 *aIsSession = true;
330 if (rememberDecision) {
331 switch (dialogRes) {
332 case nsICookiePromptService::DENY_COOKIE:
333 mPermMgr->Add(aURI, kPermissionType, (uint32_t) nsIPermissionManager::DENY_ACTION,
334 nsIPermissionManager::EXPIRE_NEVER, 0);
335 break;
336 case nsICookiePromptService::ACCEPT_COOKIE:
337 mPermMgr->Add(aURI, kPermissionType, (uint32_t) nsIPermissionManager::ALLOW_ACTION,
338 nsIPermissionManager::EXPIRE_NEVER, 0);
339 break;
340 case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
341 mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION,
342 nsIPermissionManager::EXPIRE_NEVER, 0);
343 break;
344 default:
345 break;
348 } else {
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
354 *aIsSession = true;
355 } else if (delta > mCookiesLifetimeSec) {
356 // limit lifetime to specified time
357 *aExpiry = currentTime + mCookiesLifetimeSec;
363 return NS_OK;
366 NS_IMETHODIMP
367 nsCookiePermission::Observe(nsISupports *aSubject,
368 const char *aTopic,
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!");
375 if (prefBranch)
376 PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
377 return NS_OK;