2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is cookie manager code.
17 * The Initial Developer of the Original Code is
18 * Michiel van Leeuwen (mvl@exedo.nl).
19 * Portions created by the Initial Developer are Copyright (C) 2003
20 * the Initial Developer. All Rights Reserved.
23 * Darin Fisher <darin@meer.net>
24 * Daniel Witte <dwitte@stanford.edu>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 #include "nsCookiePermission.h"
42 #include "nsICookie2.h"
43 #include "nsIServiceManager.h"
44 #include "nsICookiePromptService.h"
45 #include "nsICookieManager2.h"
46 #include "nsNetUtil.h"
48 #include "nsIPrefService.h"
49 #include "nsIPrefBranch.h"
50 #include "nsIPrefBranch2.h"
51 #include "nsIDocShell.h"
52 #include "nsIDocShellTreeItem.h"
53 #include "nsIWebNavigation.h"
55 #include "nsIChannel.h"
56 #include "nsIDOMWindow.h"
57 #include "nsIDOMDocument.h"
58 #include "nsIPrincipal.h"
62 /****************************************************************
63 ************************ nsCookiePermission ********************
64 ****************************************************************/
66 // values for mCookiesLifetimePolicy
67 // 0 == accept normally
68 // 1 == ask before accepting
69 // 2 == downgrade to session
70 // 3 == limit lifetime to N days
71 static const PRUint32 ACCEPT_NORMALLY
= 0;
72 static const PRUint32 ASK_BEFORE_ACCEPT
= 1;
73 static const PRUint32 ACCEPT_SESSION
= 2;
74 static const PRUint32 ACCEPT_FOR_N_DAYS
= 3;
76 static const PRBool kDefaultPolicy
= PR_TRUE
;
77 static const char kCookiesLifetimePolicy
[] = "network.cookie.lifetimePolicy";
78 static const char kCookiesLifetimeDays
[] = "network.cookie.lifetime.days";
79 static const char kCookiesAlwaysAcceptSession
[] = "network.cookie.alwaysAcceptSessionCookies";
81 static const char kCookiesDisabledForMailNews
[] = "network.cookie.disableCookieForMailNews";
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";
90 static const char kPermissionType
[] = "cookie";
93 // returns PR_TRUE if URI appears to be the URI of a mailnews protocol
95 IsFromMailNews(nsIURI
*aURI
)
97 static const char *kMailNewsProtocols
[] =
98 { "imap", "news", "snews", "mailbox", nsnull
};
100 for (const char **p
= kMailNewsProtocols
; *p
; ++p
) {
101 if (NS_SUCCEEDED(aURI
->SchemeIs(*p
, &result
)) && result
)
108 NS_IMPL_ISUPPORTS2(nsCookiePermission
,
113 nsCookiePermission::Init()
116 mPermMgr
= do_GetService(NS_PERMISSIONMANAGER_CONTRACTID
, &rv
);
117 if (NS_FAILED(rv
)) return rv
;
119 // failure to access the pref service is non-fatal...
120 nsCOMPtr
<nsIPrefBranch2
> prefBranch
=
121 do_GetService(NS_PREFSERVICE_CONTRACTID
);
123 prefBranch
->AddObserver(kCookiesLifetimePolicy
, this, PR_FALSE
);
124 prefBranch
->AddObserver(kCookiesLifetimeDays
, this, PR_FALSE
);
125 prefBranch
->AddObserver(kCookiesAlwaysAcceptSession
, this, PR_FALSE
);
127 prefBranch
->AddObserver(kCookiesDisabledForMailNews
, this, PR_FALSE
);
129 PrefChanged(prefBranch
, nsnull
);
131 // migration code for original cookie prefs
133 rv
= prefBranch
->GetBoolPref(kCookiesPrefsMigrated
, &migrated
);
134 if (NS_FAILED(rv
) || !migrated
) {
135 PRBool warnAboutCookies
= PR_FALSE
;
136 prefBranch
->GetBoolPref(kCookiesAskPermission
, &warnAboutCookies
);
138 // if the user is using ask before accepting, we'll use that
139 if (warnAboutCookies
)
140 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ASK_BEFORE_ACCEPT
);
142 PRBool lifetimeEnabled
= PR_FALSE
;
143 prefBranch
->GetBoolPref(kCookiesLifetimeEnabled
, &lifetimeEnabled
);
145 // if they're limiting lifetime and not using the prompts, use the
146 // appropriate limited lifetime pref
147 if (lifetimeEnabled
&& !warnAboutCookies
) {
148 PRInt32 lifetimeBehavior
;
149 prefBranch
->GetIntPref(kCookiesLifetimeBehavior
, &lifetimeBehavior
);
150 if (lifetimeBehavior
)
151 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ACCEPT_FOR_N_DAYS
);
153 prefBranch
->SetIntPref(kCookiesLifetimePolicy
, ACCEPT_SESSION
);
155 prefBranch
->SetBoolPref(kCookiesPrefsMigrated
, PR_TRUE
);
163 nsCookiePermission::PrefChanged(nsIPrefBranch
*aPrefBranch
,
168 #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
170 if (PREF_CHANGED(kCookiesLifetimePolicy
) &&
171 NS_SUCCEEDED(aPrefBranch
->GetIntPref(kCookiesLifetimePolicy
, &val
)))
172 mCookiesLifetimePolicy
= val
;
174 if (PREF_CHANGED(kCookiesLifetimeDays
) &&
175 NS_SUCCEEDED(aPrefBranch
->GetIntPref(kCookiesLifetimeDays
, &val
)))
176 // save cookie lifetime in seconds instead of days
177 mCookiesLifetimeSec
= val
* 24 * 60 * 60;
179 if (PREF_CHANGED(kCookiesAlwaysAcceptSession
) &&
180 NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kCookiesAlwaysAcceptSession
, &val
)))
181 mCookiesAlwaysAcceptSession
= val
;
184 if (PREF_CHANGED(kCookiesDisabledForMailNews
) &&
185 NS_SUCCEEDED(aPrefBranch
->GetBoolPref(kCookiesDisabledForMailNews
, &val
)))
186 mCookiesDisabledForMailNews
= val
;
191 nsCookiePermission::SetAccess(nsIURI
*aURI
,
192 nsCookieAccess aAccess
)
195 // NOTE: nsCookieAccess values conveniently match up with
196 // the permission codes used by nsIPermissionManager.
197 // this is nice because it avoids conversion code.
199 return mPermMgr
->Add(aURI
, kPermissionType
, aAccess
);
203 nsCookiePermission::CanAccess(nsIURI
*aURI
,
204 nsIChannel
*aChannel
,
205 nsCookieAccess
*aResult
)
208 // disable cookies in mailnews if user's prefs say so
209 if (mCookiesDisabledForMailNews
) {
211 // try to examine the "app type" of the docshell owning this request. if
212 // we find a docshell in the heirarchy of type APP_TYPE_MAIL, then assume
213 // this URI is being loaded from within mailnews.
215 // XXX this is a pretty ugly hack at the moment since cookies really
216 // shouldn't have to talk to the docshell directly. ultimately, we want
217 // to talk to some more generic interface, which the docshell would also
218 // implement. but, the basic mechanism here of leveraging the channel's
219 // (or loadgroup's) notification callbacks attribute seems ideal as it
220 // avoids the problem of having to modify all places in the code which
221 // kick off network requests.
223 PRUint32 appType
= nsIDocShell::APP_TYPE_UNKNOWN
;
225 nsCOMPtr
<nsIDocShellTreeItem
> item
, parent
;
226 NS_QueryNotificationCallbacks(aChannel
, parent
);
230 nsCOMPtr
<nsIDocShell
> docshell
= do_QueryInterface(item
);
232 docshell
->GetAppType(&appType
);
233 } while (appType
!= nsIDocShell::APP_TYPE_MAIL
&&
234 NS_SUCCEEDED(item
->GetParent(getter_AddRefs(parent
))) &&
238 if ((appType
== nsIDocShell::APP_TYPE_MAIL
) ||
239 IsFromMailNews(aURI
)) {
240 *aResult
= ACCESS_DENY
;
244 #endif // MOZ_MAIL_NEWS
246 // finally, check with permission manager...
247 nsresult rv
= mPermMgr
->TestPermission(aURI
, kPermissionType
, (PRUint32
*) aResult
);
248 if (NS_SUCCEEDED(rv
)) {
250 // if we have one of the publicly-available values, just return it
251 case nsIPermissionManager::UNKNOWN_ACTION
: // ACCESS_DEFAULT
252 case nsIPermissionManager::ALLOW_ACTION
: // ACCESS_ALLOW
253 case nsIPermissionManager::DENY_ACTION
: // ACCESS_DENY
256 // ACCESS_SESSION means the cookie can be accepted; the session
257 // downgrade will occur in CanSetCookie().
258 case nsICookiePermission::ACCESS_SESSION
:
259 *aResult
= ACCESS_ALLOW
;
262 // ack, an unknown type! just use the defaults.
264 *aResult
= ACCESS_DEFAULT
;
272 nsCookiePermission::CanSetCookie(nsIURI
*aURI
,
273 nsIChannel
*aChannel
,
279 NS_ASSERTION(aURI
, "null uri");
281 *aResult
= kDefaultPolicy
;
284 mPermMgr
->TestPermission(aURI
, kPermissionType
, &perm
);
286 case nsICookiePermission::ACCESS_SESSION
:
287 *aIsSession
= PR_TRUE
;
289 case nsIPermissionManager::ALLOW_ACTION
: // ACCESS_ALLOW
293 case nsIPermissionManager::DENY_ACTION
: // ACCESS_DENY
298 // the permission manager has nothing to say about this cookie -
299 // so, we apply the default prefs to it.
300 NS_ASSERTION(perm
== nsIPermissionManager::UNKNOWN_ACTION
, "unknown permission");
302 // now we need to figure out what type of accept policy we're dealing with
303 // if we accept cookies normally, just bail and return
304 if (mCookiesLifetimePolicy
== ACCEPT_NORMALLY
) {
309 // declare this here since it'll be used in all of the remaining cases
310 PRInt64 currentTime
= PR_Now() / PR_USEC_PER_SEC
;
311 PRInt64 delta
= *aExpiry
- currentTime
;
313 // check whether the user wants to be prompted
314 if (mCookiesLifetimePolicy
== ASK_BEFORE_ACCEPT
) {
315 // if it's a session cookie and the user wants to accept these
316 // without asking, just accept the cookie and return
317 if (*aIsSession
&& mCookiesAlwaysAcceptSession
) {
322 // default to rejecting, in case the prompting process fails
325 nsCAutoString hostPort
;
326 aURI
->GetHostPort(hostPort
);
329 return NS_ERROR_UNEXPECTED
;
331 // If there is no host, use the scheme, and append "://",
332 // to make sure it isn't a host or something.
333 // This is done to make the dialog appear for javascript cookies from
334 // file:// urls, and make the text on it not too weird. (bug 209689)
335 if (hostPort
.IsEmpty()) {
336 aURI
->GetScheme(hostPort
);
337 if (hostPort
.IsEmpty()) {
338 // still empty. Just return the default.
341 hostPort
= hostPort
+ NS_LITERAL_CSTRING("://");
344 // we don't cache the cookiePromptService - it's not used often, so not
347 nsCOMPtr
<nsICookiePromptService
> cookiePromptService
=
348 do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID
, &rv
);
349 if (NS_FAILED(rv
)) return rv
;
351 // try to get a nsIDOMWindow from the channel...
352 nsCOMPtr
<nsIDOMWindow
> parent
;
354 NS_QueryNotificationCallbacks(aChannel
, parent
);
356 // get some useful information to present to the user:
357 // whether a previous cookie already exists, and how many cookies this host
359 PRBool foundCookie
= PR_FALSE
;
360 PRUint32 countFromHost
;
361 nsCOMPtr
<nsICookieManager2
> cookieManager
= do_GetService(NS_COOKIEMANAGER_CONTRACTID
, &rv
);
362 if (NS_SUCCEEDED(rv
)) {
363 nsCAutoString rawHost
;
364 aCookie
->GetRawHost(rawHost
);
365 rv
= cookieManager
->CountCookiesFromHost(rawHost
, &countFromHost
);
367 if (NS_SUCCEEDED(rv
) && countFromHost
> 0)
368 rv
= cookieManager
->CookieExists(aCookie
, &foundCookie
);
370 if (NS_FAILED(rv
)) return rv
;
372 // check if the cookie we're trying to set is already expired, and return;
373 // but only if there's no previous cookie, because then we need to delete the previous
374 // cookie. we need this check to avoid prompting the user for already-expired cookies.
375 if (!foundCookie
&& !*aIsSession
&& delta
<= 0) {
376 // the cookie has already expired. accept it, and let the backend figure
377 // out it's expired, so that we get correct logging & notifications.
382 PRBool rememberDecision
= PR_FALSE
;
383 rv
= cookiePromptService
->CookieDialog(parent
, aCookie
, hostPort
,
384 countFromHost
, foundCookie
,
385 &rememberDecision
, aResult
);
386 if (NS_FAILED(rv
)) return rv
;
388 if (*aResult
== nsICookiePromptService::ACCEPT_SESSION_COOKIE
)
389 *aIsSession
= PR_TRUE
;
391 if (rememberDecision
) {
393 case nsICookiePromptService::DENY_COOKIE
:
394 mPermMgr
->Add(aURI
, kPermissionType
, (PRUint32
) nsIPermissionManager::DENY_ACTION
);
396 case nsICookiePromptService::ACCEPT_COOKIE
:
397 mPermMgr
->Add(aURI
, kPermissionType
, (PRUint32
) nsIPermissionManager::ALLOW_ACTION
);
399 case nsICookiePromptService::ACCEPT_SESSION_COOKIE
:
400 mPermMgr
->Add(aURI
, kPermissionType
, nsICookiePermission::ACCESS_SESSION
);
407 // we're not prompting, so we must be limiting the lifetime somehow
408 // if it's a session cookie, we do nothing
409 if (!*aIsSession
&& delta
> 0) {
410 if (mCookiesLifetimePolicy
== ACCEPT_SESSION
) {
411 // limit lifetime to session
412 *aIsSession
= PR_TRUE
;
413 } else if (delta
> mCookiesLifetimeSec
) {
414 // limit lifetime to specified time
415 *aExpiry
= currentTime
+ mCookiesLifetimeSec
;
425 nsCookiePermission::GetOriginatingURI(nsIChannel
*aChannel
,
428 /* to find the originating URI, we use the loadgroup of the channel to obtain
429 * the docshell owning the load, and from there, we find the root content
430 * docshell and its URI. there are several possible cases:
432 * 1) no channel. this will occur for plugins using the nsICookieStorage
433 * interface, since they have none to provide. other consumers should
436 * 2) a channel, but no docshell. this can occur when the consumer kicking
437 * off the load doesn't provide one to the channel, and should be limited
438 * to loads of certain types of resources (e.g. favicons).
440 * 3) a non-content docshell. this occurs for loads kicked off from chrome,
441 * where no content docshell exists (favicons can also fall into this
444 * 4) a content docshell equal to the root content docshell, with channel
445 * loadflags LOAD_DOCUMENT_URI. this covers the case of a freshly kicked-
446 * off load (e.g. the user typing something in the location bar, or
447 * clicking on a bookmark), where the currentURI hasn't yet been set,
448 * and will be bogus. we return the channel URI in this case. note that
449 * we could also allow non-content docshells here, but that goes against
450 * the philosophy of having an audit trail back to a URI the user typed
453 * 5) a root content docshell. this covers most cases for an ordinary page
454 * load from the location bar, and will catch nested frames within
455 * a page, image loads, etc. we return the URI of the docshell's principal
464 return NS_ERROR_NULL_POINTER
;
466 // find the docshell and its root
467 nsCOMPtr
<nsIDocShellTreeItem
> docshell
, root
;
468 NS_QueryNotificationCallbacks(aChannel
, docshell
);
470 docshell
->GetSameTypeRootTreeItem(getter_AddRefs(root
));
474 root
->GetItemType(&type
);
477 if (!root
|| type
!= nsIDocShellTreeItem::typeContent
)
478 return NS_ERROR_INVALID_ARG
;
481 if (docshell
== root
) {
483 aChannel
->GetLoadFlags(&flags
);
485 if (flags
& nsIChannel::LOAD_DOCUMENT_URI
) {
486 // get the channel URI - the docshell's will be bogus
487 aChannel
->GetURI(aURI
);
489 return NS_ERROR_NULL_POINTER
;
495 // case 5) - get the originating URI from the docshell's principal
496 nsCOMPtr
<nsIWebNavigation
> webnav
= do_QueryInterface(root
);
498 nsCOMPtr
<nsIDOMDocument
> doc
;
499 webnav
->GetDocument(getter_AddRefs(doc
));
500 nsCOMPtr
<nsINode
> node
= do_QueryInterface(doc
);
502 node
->NodePrincipal()->GetURI(aURI
);
506 return NS_ERROR_NULL_POINTER
;
513 nsCookiePermission::Observe(nsISupports
*aSubject
,
515 const PRUnichar
*aData
)
517 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_QueryInterface(aSubject
);
518 NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
, aTopic
),
519 "unexpected topic - we only deal with pref changes!");
522 PrefChanged(prefBranch
, NS_LossyConvertUTF16toASCII(aData
).get());