1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:ts=2:sw=2:et: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is cookie manager code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Michiel van Leeuwen (mvl@exedo.nl).
20 : * Portions created by the Initial Developer are Copyright (C) 2003
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Darin Fisher <darin@meer.net>
25 : * Daniel Witte <dwitte@stanford.edu>
26 : * Ehsan Akhgari <ehsan.akhgari@gmail.com>
27 : * Kathleen Brade <brade@pearlcrescent.com>
28 : * Mark Smith <mcs@pearlcrescent.com>
29 : *
30 : * Alternatively, the contents of this file may be used under the terms of
31 : * either the GNU General Public License Version 2 or later (the "GPL"), or
32 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 : * in which case the provisions of the GPL or the LGPL are applicable instead
34 : * of those above. If you wish to allow use of your version of this file only
35 : * under the terms of either the GPL or the LGPL, and not to allow others to
36 : * use your version of this file under the terms of the MPL, indicate your
37 : * decision by deleting the provisions above and replace them with the notice
38 : * and other provisions required by the GPL or the LGPL. If you do not delete
39 : * the provisions above, a recipient may use your version of this file under
40 : * the terms of any one of the MPL, the GPL or the LGPL.
41 : *
42 : * ***** END LICENSE BLOCK ***** */
43 :
44 :
45 : #include "nsCookiePermission.h"
46 : #include "nsICookie2.h"
47 : #include "nsIServiceManager.h"
48 : #include "nsICookiePromptService.h"
49 : #include "nsICookieManager2.h"
50 : #include "nsNetUtil.h"
51 : #include "nsIURI.h"
52 : #include "nsIPrefService.h"
53 : #include "nsIPrefBranch.h"
54 : #include "nsIChannel.h"
55 : #include "nsIHttpChannelInternal.h"
56 : #include "nsIDOMWindow.h"
57 : #include "nsIDOMDocument.h"
58 : #include "nsIPrincipal.h"
59 : #include "nsString.h"
60 : #include "nsCRT.h"
61 : #include "nsILoadContext.h"
62 : #include "nsIScriptObjectPrincipal.h"
63 : #include "nsNetCID.h"
64 :
65 : /****************************************************************
66 : ************************ nsCookiePermission ********************
67 : ****************************************************************/
68 :
69 : // values for mCookiesLifetimePolicy
70 : // 0 == accept normally
71 : // 1 == ask before accepting
72 : // 2 == downgrade to session
73 : // 3 == limit lifetime to N days
74 : static const PRUint32 ACCEPT_NORMALLY = 0;
75 : static const PRUint32 ASK_BEFORE_ACCEPT = 1;
76 : static const PRUint32 ACCEPT_SESSION = 2;
77 : static const PRUint32 ACCEPT_FOR_N_DAYS = 3;
78 :
79 : static const bool kDefaultPolicy = true;
80 : static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
81 : static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
82 : static const char kCookiesAlwaysAcceptSession[] = "network.cookie.alwaysAcceptSessionCookies";
83 :
84 : static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
85 : // obsolete pref names for migration
86 : static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
87 : static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior";
88 : static const char kCookiesAskPermission[] = "network.cookie.warnAboutCookies";
89 :
90 : static const char kPermissionType[] = "cookie";
91 :
92 11129 : NS_IMPL_ISUPPORTS2(nsCookiePermission,
93 : nsICookiePermission,
94 : nsIObserver)
95 :
96 : bool
97 282 : nsCookiePermission::Init()
98 : {
99 : // Initialize nsIPermissionManager and fetch relevant prefs. This is only
100 : // required for some methods on nsICookiePermission, so it should be done
101 : // lazily.
102 : nsresult rv;
103 282 : mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
104 282 : if (NS_FAILED(rv)) return false;
105 :
106 : // failure to access the pref service is non-fatal...
107 : nsCOMPtr<nsIPrefBranch> prefBranch =
108 564 : do_GetService(NS_PREFSERVICE_CONTRACTID);
109 282 : if (prefBranch) {
110 282 : prefBranch->AddObserver(kCookiesLifetimePolicy, this, false);
111 282 : prefBranch->AddObserver(kCookiesLifetimeDays, this, false);
112 282 : prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, false);
113 282 : PrefChanged(prefBranch, nsnull);
114 :
115 : // migration code for original cookie prefs
116 : bool migrated;
117 282 : rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated);
118 282 : if (NS_FAILED(rv) || !migrated) {
119 282 : bool warnAboutCookies = false;
120 282 : prefBranch->GetBoolPref(kCookiesAskPermission, &warnAboutCookies);
121 :
122 : // if the user is using ask before accepting, we'll use that
123 282 : if (warnAboutCookies)
124 0 : prefBranch->SetIntPref(kCookiesLifetimePolicy, ASK_BEFORE_ACCEPT);
125 :
126 282 : bool lifetimeEnabled = false;
127 282 : prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled);
128 :
129 : // if they're limiting lifetime and not using the prompts, use the
130 : // appropriate limited lifetime pref
131 282 : if (lifetimeEnabled && !warnAboutCookies) {
132 : PRInt32 lifetimeBehavior;
133 1 : prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior);
134 1 : if (lifetimeBehavior)
135 0 : prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
136 : else
137 1 : prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
138 : }
139 282 : prefBranch->SetBoolPref(kCookiesPrefsMigrated, true);
140 : }
141 : }
142 :
143 282 : return true;
144 : }
145 :
146 : void
147 287 : nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
148 : const char *aPref)
149 : {
150 : PRInt32 val;
151 :
152 : #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
153 :
154 574 : if (PREF_CHANGED(kCookiesLifetimePolicy) &&
155 287 : NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val)))
156 287 : mCookiesLifetimePolicy = val;
157 :
158 569 : if (PREF_CHANGED(kCookiesLifetimeDays) &&
159 282 : NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
160 : // save cookie lifetime in seconds instead of days
161 282 : mCookiesLifetimeSec = val * 24 * 60 * 60;
162 :
163 : bool bval;
164 569 : if (PREF_CHANGED(kCookiesAlwaysAcceptSession) &&
165 282 : NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &bval)))
166 282 : mCookiesAlwaysAcceptSession = bval;
167 287 : }
168 :
169 : NS_IMETHODIMP
170 1 : nsCookiePermission::SetAccess(nsIURI *aURI,
171 : nsCookieAccess aAccess)
172 : {
173 : // Lazily initialize ourselves
174 1 : if (!EnsureInitialized())
175 0 : return NS_ERROR_UNEXPECTED;
176 :
177 : //
178 : // NOTE: nsCookieAccess values conveniently match up with
179 : // the permission codes used by nsIPermissionManager.
180 : // this is nice because it avoids conversion code.
181 : //
182 1 : return mPermMgr->Add(aURI, kPermissionType, aAccess,
183 1 : nsIPermissionManager::EXPIRE_NEVER, 0);
184 : }
185 :
186 : NS_IMETHODIMP
187 18831 : nsCookiePermission::CanAccess(nsIURI *aURI,
188 : nsIChannel *aChannel,
189 : nsCookieAccess *aResult)
190 : {
191 : // Check this protocol doesn't allow cookies
192 : bool hasFlags;
193 : nsresult rv =
194 : NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
195 18831 : &hasFlags);
196 18831 : if (NS_FAILED(rv) || hasFlags) {
197 0 : *aResult = ACCESS_DENY;
198 0 : return NS_OK;
199 : }
200 :
201 : // Lazily initialize ourselves
202 18831 : if (!EnsureInitialized())
203 0 : return NS_ERROR_UNEXPECTED;
204 :
205 : // finally, check with permission manager...
206 18831 : rv = mPermMgr->TestPermission(aURI, kPermissionType, (PRUint32 *) aResult);
207 18831 : if (NS_SUCCEEDED(rv)) {
208 18831 : switch (*aResult) {
209 : // if we have one of the publicly-available values, just return it
210 : case nsIPermissionManager::UNKNOWN_ACTION: // ACCESS_DEFAULT
211 : case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
212 : case nsIPermissionManager::DENY_ACTION: // ACCESS_DENY
213 18831 : break;
214 :
215 : // ACCESS_SESSION means the cookie can be accepted; the session
216 : // downgrade will occur in CanSetCookie().
217 : case nsICookiePermission::ACCESS_SESSION:
218 0 : *aResult = ACCESS_ALLOW;
219 0 : break;
220 :
221 : // ack, an unknown type! just use the defaults.
222 : default:
223 0 : *aResult = ACCESS_DEFAULT;
224 : }
225 : }
226 :
227 18831 : return rv;
228 : }
229 :
230 : NS_IMETHODIMP
231 15215 : nsCookiePermission::CanSetCookie(nsIURI *aURI,
232 : nsIChannel *aChannel,
233 : nsICookie2 *aCookie,
234 : bool *aIsSession,
235 : PRInt64 *aExpiry,
236 : bool *aResult)
237 : {
238 15215 : NS_ASSERTION(aURI, "null uri");
239 :
240 15215 : *aResult = kDefaultPolicy;
241 :
242 : // Lazily initialize ourselves
243 15215 : if (!EnsureInitialized())
244 0 : return NS_ERROR_UNEXPECTED;
245 :
246 : PRUint32 perm;
247 15215 : mPermMgr->TestPermission(aURI, kPermissionType, &perm);
248 15215 : switch (perm) {
249 : case nsICookiePermission::ACCESS_SESSION:
250 0 : *aIsSession = true;
251 :
252 : case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
253 0 : *aResult = true;
254 0 : break;
255 :
256 : case nsIPermissionManager::DENY_ACTION: // ACCESS_DENY
257 0 : *aResult = false;
258 0 : break;
259 :
260 : default:
261 : // the permission manager has nothing to say about this cookie -
262 : // so, we apply the default prefs to it.
263 15215 : NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
264 :
265 : // now we need to figure out what type of accept policy we're dealing with
266 : // if we accept cookies normally, just bail and return
267 15215 : if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
268 15073 : *aResult = true;
269 15073 : return NS_OK;
270 : }
271 :
272 : // declare this here since it'll be used in all of the remaining cases
273 142 : PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC;
274 142 : PRInt64 delta = *aExpiry - currentTime;
275 :
276 : // check whether the user wants to be prompted
277 142 : if (mCookiesLifetimePolicy == ASK_BEFORE_ACCEPT) {
278 : // if it's a session cookie and the user wants to accept these
279 : // without asking, or if we are in private browsing mode, just
280 : // accept the cookie and return
281 4 : if ((*aIsSession && mCookiesAlwaysAcceptSession) ||
282 2 : InPrivateBrowsing()) {
283 1 : *aResult = true;
284 1 : return NS_OK;
285 : }
286 :
287 : // default to rejecting, in case the prompting process fails
288 1 : *aResult = false;
289 :
290 2 : nsCAutoString hostPort;
291 1 : aURI->GetHostPort(hostPort);
292 :
293 1 : if (!aCookie) {
294 0 : return NS_ERROR_UNEXPECTED;
295 : }
296 : // If there is no host, use the scheme, and append "://",
297 : // to make sure it isn't a host or something.
298 : // This is done to make the dialog appear for javascript cookies from
299 : // file:// urls, and make the text on it not too weird. (bug 209689)
300 1 : if (hostPort.IsEmpty()) {
301 0 : aURI->GetScheme(hostPort);
302 0 : if (hostPort.IsEmpty()) {
303 : // still empty. Just return the default.
304 0 : return NS_OK;
305 : }
306 0 : hostPort = hostPort + NS_LITERAL_CSTRING("://");
307 : }
308 :
309 : // we don't cache the cookiePromptService - it's not used often, so not
310 : // worth the memory.
311 : nsresult rv;
312 : nsCOMPtr<nsICookiePromptService> cookiePromptService =
313 2 : do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID, &rv);
314 1 : if (NS_FAILED(rv)) return rv;
315 :
316 : // try to get a nsIDOMWindow from the channel...
317 2 : nsCOMPtr<nsIDOMWindow> parent;
318 1 : if (aChannel) {
319 0 : nsCOMPtr<nsILoadContext> ctx;
320 0 : NS_QueryNotificationCallbacks(aChannel, ctx);
321 0 : if (ctx) {
322 0 : ctx->GetAssociatedWindow(getter_AddRefs(parent));
323 : }
324 : }
325 :
326 : // get some useful information to present to the user:
327 : // whether a previous cookie already exists, and how many cookies this host
328 : // has set
329 1 : bool foundCookie = false;
330 : PRUint32 countFromHost;
331 2 : nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
332 1 : if (NS_SUCCEEDED(rv)) {
333 2 : nsCAutoString rawHost;
334 1 : aCookie->GetRawHost(rawHost);
335 1 : rv = cookieManager->CountCookiesFromHost(rawHost, &countFromHost);
336 :
337 1 : if (NS_SUCCEEDED(rv) && countFromHost > 0)
338 1 : rv = cookieManager->CookieExists(aCookie, &foundCookie);
339 : }
340 1 : if (NS_FAILED(rv)) return rv;
341 :
342 : // check if the cookie we're trying to set is already expired, and return;
343 : // but only if there's no previous cookie, because then we need to delete the previous
344 : // cookie. we need this check to avoid prompting the user for already-expired cookies.
345 1 : if (!foundCookie && !*aIsSession && delta <= 0) {
346 : // the cookie has already expired. accept it, and let the backend figure
347 : // out it's expired, so that we get correct logging & notifications.
348 0 : *aResult = true;
349 0 : return rv;
350 : }
351 :
352 1 : bool rememberDecision = false;
353 1 : PRInt32 dialogRes = nsICookiePromptService::DENY_COOKIE;
354 1 : rv = cookiePromptService->CookieDialog(parent, aCookie, hostPort,
355 : countFromHost, foundCookie,
356 1 : &rememberDecision, &dialogRes);
357 1 : if (NS_FAILED(rv)) return rv;
358 :
359 1 : *aResult = !!dialogRes;
360 1 : if (dialogRes == nsICookiePromptService::ACCEPT_SESSION_COOKIE)
361 0 : *aIsSession = true;
362 :
363 1 : if (rememberDecision) {
364 0 : switch (dialogRes) {
365 : case nsICookiePromptService::DENY_COOKIE:
366 0 : mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION,
367 0 : nsIPermissionManager::EXPIRE_NEVER, 0);
368 0 : break;
369 : case nsICookiePromptService::ACCEPT_COOKIE:
370 0 : mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION,
371 0 : nsIPermissionManager::EXPIRE_NEVER, 0);
372 0 : break;
373 : case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
374 0 : mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION,
375 0 : nsIPermissionManager::EXPIRE_NEVER, 0);
376 0 : break;
377 : default:
378 0 : break;
379 : }
380 : }
381 : } else {
382 : // we're not prompting, so we must be limiting the lifetime somehow
383 : // if it's a session cookie, we do nothing
384 140 : if (!*aIsSession && delta > 0) {
385 11 : if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
386 : // limit lifetime to session
387 11 : *aIsSession = true;
388 0 : } else if (delta > mCookiesLifetimeSec) {
389 : // limit lifetime to specified time
390 0 : *aExpiry = currentTime + mCookiesLifetimeSec;
391 : }
392 : }
393 : }
394 : }
395 :
396 141 : return NS_OK;
397 : }
398 :
399 : NS_IMETHODIMP
400 0 : nsCookiePermission::GetOriginatingURI(nsIChannel *aChannel,
401 : nsIURI **aURI)
402 : {
403 : /* to find the originating URI, we use the loadgroup of the channel to obtain
404 : * the window owning the load, and from there, we find the top same-type
405 : * window and its URI. there are several possible cases:
406 : *
407 : * 1) no channel.
408 : *
409 : * 2) a channel with the "force allow third party cookies" option set.
410 : * since we may not have a window, we return the channel URI in this case.
411 : *
412 : * 3) a channel, but no window. this can occur when the consumer kicking
413 : * off the load doesn't provide one to the channel, and should be limited
414 : * to loads of certain types of resources.
415 : *
416 : * 4) a window equal to the top window of same type, with the channel its
417 : * document channel. this covers the case of a freshly kicked-off load
418 : * (e.g. the user typing something in the location bar, or clicking on a
419 : * bookmark), where the window's URI hasn't yet been set, and will be
420 : * bogus. we return the channel URI in this case.
421 : *
422 : * 5) Anything else. this covers most cases for an ordinary page load from
423 : * the location bar, and will catch nested frames within a page, image
424 : * loads, etc. we return the URI of the root window's document's principal
425 : * in this case.
426 : */
427 :
428 0 : *aURI = nsnull;
429 :
430 : // case 1)
431 0 : if (!aChannel)
432 0 : return NS_ERROR_NULL_POINTER;
433 :
434 : // case 2)
435 0 : nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(aChannel);
436 0 : if (httpChannelInternal)
437 : {
438 0 : bool doForce = false;
439 0 : if (NS_SUCCEEDED(httpChannelInternal->GetForceAllowThirdPartyCookie(&doForce)) && doForce)
440 : {
441 : // return the channel's URI (we may not have a window)
442 0 : aChannel->GetURI(aURI);
443 0 : if (!*aURI)
444 0 : return NS_ERROR_NULL_POINTER;
445 :
446 0 : return NS_OK;
447 : }
448 : }
449 :
450 : // find the associated window and its top window
451 0 : nsCOMPtr<nsILoadContext> ctx;
452 0 : NS_QueryNotificationCallbacks(aChannel, ctx);
453 0 : nsCOMPtr<nsIDOMWindow> topWin, ourWin;
454 0 : if (ctx) {
455 0 : ctx->GetTopWindow(getter_AddRefs(topWin));
456 0 : ctx->GetAssociatedWindow(getter_AddRefs(ourWin));
457 : }
458 :
459 : // case 3)
460 0 : if (!topWin)
461 0 : return NS_ERROR_INVALID_ARG;
462 :
463 : // case 4)
464 0 : if (ourWin == topWin) {
465 : // Check whether this is the document channel for this window (representing
466 : // a load of a new page). This is a bit of a nasty hack, but we will
467 : // hopefully flag these channels better later.
468 : nsLoadFlags flags;
469 0 : aChannel->GetLoadFlags(&flags);
470 :
471 0 : if (flags & nsIChannel::LOAD_DOCUMENT_URI) {
472 : // get the channel URI - the window's will be bogus
473 0 : aChannel->GetURI(aURI);
474 0 : if (!*aURI)
475 0 : return NS_ERROR_NULL_POINTER;
476 :
477 0 : return NS_OK;
478 : }
479 : }
480 :
481 : // case 5) - get the originating URI from the top window's principal
482 0 : nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(topWin);
483 0 : NS_ENSURE_TRUE(scriptObjPrin, NS_ERROR_UNEXPECTED);
484 :
485 0 : nsIPrincipal* prin = scriptObjPrin->GetPrincipal();
486 0 : NS_ENSURE_TRUE(prin, NS_ERROR_UNEXPECTED);
487 :
488 0 : prin->GetURI(aURI);
489 :
490 0 : if (!*aURI)
491 0 : return NS_ERROR_NULL_POINTER;
492 :
493 : // all done!
494 0 : return NS_OK;
495 : }
496 :
497 : NS_IMETHODIMP
498 5 : nsCookiePermission::Observe(nsISupports *aSubject,
499 : const char *aTopic,
500 : const PRUnichar *aData)
501 : {
502 10 : nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
503 5 : NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
504 : "unexpected topic - we only deal with pref changes!");
505 :
506 5 : if (prefBranch)
507 5 : PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
508 5 : return NS_OK;
509 : }
510 :
511 : bool
512 2 : nsCookiePermission::InPrivateBrowsing()
513 : {
514 2 : bool inPrivateBrowsingMode = false;
515 2 : if (!mPBService)
516 1 : mPBService = do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
517 2 : if (mPBService)
518 2 : mPBService->GetPrivateBrowsingEnabled(&inPrivateBrowsingMode);
519 2 : return inPrivateBrowsingMode;
520 : }
|