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>
25
* Alternatively, the contents of this file may be used under the terms of
26
* either the GNU General Public License Version 2 or later (the "GPL"), or
27
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
* in which case the provisions of the GPL or the LGPL are applicable instead
29
* of those above. If you wish to allow use of your version of this file only
30
* under the terms of either the GPL or the LGPL, and not to allow others to
31
* use your version of this file under the terms of the MPL, indicate your
32
* decision by deleting the provisions above and replace them with the notice
33
* and other provisions required by the GPL or the LGPL. If you do not delete
34
* the provisions above, a recipient may use your version of this file under
35
* the terms of any one of the MPL, the GPL or the LGPL.
37
* ***** END LICENSE BLOCK ***** */
40
#include "nsCookiePermission.h"
41
#include "nsICookie2.h"
42
#include "nsIServiceManager.h"
43
#include "nsICookiePromptService.h"
44
#include "nsICookieManager2.h"
47
#include "nsIPrefService.h"
48
#include "nsIPrefBranch.h"
49
#include "nsIPrefBranchInternal.h"
50
#include "nsIDocShell.h"
51
#include "nsIDocShellTreeItem.h"
52
#include "nsIInterfaceRequestor.h"
53
#include "nsIInterfaceRequestorUtils.h"
54
#include "nsILoadGroup.h"
55
#include "nsIChannel.h"
56
#include "nsIDOMWindow.h"
60
/****************************************************************
61
************************ nsCookiePermission ********************
62
****************************************************************/
64
// values for mCookiesLifetimePolicy
65
// 0 == accept normally
66
// 1 == ask before accepting
67
// 2 == downgrade to session
68
// 3 == limit lifetime to N days
69
static const PRUint32 ACCEPT_NORMALLY = 0;
70
static const PRUint32 ASK_BEFORE_ACCEPT = 1;
71
static const PRUint32 ACCEPT_SESSION = 2;
72
static const PRUint32 ACCEPT_FOR_N_DAYS = 3;
74
static const PRBool kDefaultPolicy = PR_TRUE;
75
static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
76
static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
77
static const char kCookiesAlwaysAcceptSession[] = "network.cookie.alwaysAcceptSessionCookies";
79
static const char kCookiesDisabledForMailNews[] = "network.cookie.disableCookieForMailNews";
82
static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
83
// obsolete pref names for migration
84
static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
85
static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior";
86
static const char kCookiesAskPermission[] = "network.cookie.warnAboutCookies";
88
static const char kPermissionType[] = "cookie";
90
// XXX these casts and constructs are horrible, but our nsInt64/nsTime
91
// classes are lacking so we need them for now. see bug 198694.
92
#define USEC_PER_SEC (nsInt64(1000000))
93
#define NOW_IN_SECONDS (nsInt64(PR_Now()) / USEC_PER_SEC)
96
// returns PR_TRUE if URI appears to be the URI of a mailnews protocol
98
IsFromMailNews(nsIURI *aURI)
100
static const char *kMailNewsProtocols[] =
101
{ "imap", "news", "snews", "mailbox", nsnull };
103
for (const char **p = kMailNewsProtocols; *p; ++p) {
104
if (NS_SUCCEEDED(aURI->SchemeIs(*p, &result)) && result)
112
GetInterfaceFromChannel(nsIChannel *aChannel,
117
return; // no context, no interface!
118
NS_ASSERTION(!*aResult, "result not initialized to null");
120
nsCOMPtr<nsIInterfaceRequestor> cbs;
121
aChannel->GetNotificationCallbacks(getter_AddRefs(cbs));
123
cbs->GetInterface(aIID, aResult);
125
// try load group's notification callbacks...
126
nsCOMPtr<nsILoadGroup> loadGroup;
127
aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
129
loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
131
cbs->GetInterface(aIID, aResult);
136
NS_IMPL_ISUPPORTS2(nsCookiePermission,
141
nsCookiePermission::Init()
144
mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
145
if (NS_FAILED(rv)) return rv;
147
// failure to access the pref service is non-fatal...
148
nsCOMPtr<nsIPrefBranchInternal> prefBranch =
149
do_GetService(NS_PREFSERVICE_CONTRACTID);
151
prefBranch->AddObserver(kCookiesLifetimePolicy, this, PR_FALSE);
152
prefBranch->AddObserver(kCookiesLifetimeDays, this, PR_FALSE);
153
prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, PR_FALSE);
155
prefBranch->AddObserver(kCookiesDisabledForMailNews, this, PR_FALSE);
157
PrefChanged(prefBranch, nsnull);
159
// migration code for original cookie prefs
161
rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated);
162
if (NS_FAILED(rv) || !migrated) {
163
PRBool warnAboutCookies = PR_FALSE;
164
prefBranch->GetBoolPref(kCookiesAskPermission, &warnAboutCookies);
166
// if the user is using ask before accepting, we'll use that
167
if (warnAboutCookies)
168
prefBranch->SetIntPref(kCookiesLifetimePolicy, ASK_BEFORE_ACCEPT);
170
PRBool lifetimeEnabled = PR_FALSE;
171
prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled);
173
// if they're limiting lifetime and not using the prompts, use the
174
// appropriate limited lifetime pref
175
if (lifetimeEnabled && !warnAboutCookies) {
176
PRInt32 lifetimeBehavior;
177
prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior);
178
if (lifetimeBehavior)
179
prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
181
prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
183
prefBranch->SetBoolPref(kCookiesPrefsMigrated, PR_TRUE);
191
nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
196
#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
198
if (PREF_CHANGED(kCookiesLifetimePolicy) &&
199
NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val)))
200
mCookiesLifetimePolicy = val;
202
if (PREF_CHANGED(kCookiesLifetimeDays) &&
203
NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
204
// save cookie lifetime in seconds instead of days
205
mCookiesLifetimeSec = val * 24 * 60 * 60;
207
if (PREF_CHANGED(kCookiesAlwaysAcceptSession) &&
208
NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &val)))
209
mCookiesAlwaysAcceptSession = val;
212
if (PREF_CHANGED(kCookiesDisabledForMailNews) &&
213
NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesDisabledForMailNews, &val)))
214
mCookiesDisabledForMailNews = val;
219
nsCookiePermission::SetAccess(nsIURI *aURI,
220
nsCookieAccess aAccess)
223
// NOTE: nsCookieAccess values conveniently match up with
224
// the permission codes used by nsIPermissionManager.
225
// this is nice because it avoids conversion code.
227
return mPermMgr->Add(aURI, kPermissionType, aAccess);
231
nsCookiePermission::CanAccess(nsIURI *aURI,
233
nsIChannel *aChannel,
234
nsCookieAccess *aResult)
237
// disable cookies in mailnews if user's prefs say so
238
if (mCookiesDisabledForMailNews) {
240
// try to examine the "app type" of the docshell owning this request. if
241
// we find a docshell in the heirarchy of type APP_TYPE_MAIL, then assume
242
// this URI is being loaded from within mailnews.
244
// XXX this is a pretty ugly hack at the moment since cookies really
245
// shouldn't have to talk to the docshell directly. ultimately, we want
246
// to talk to some more generic interface, which the docshell would also
247
// implement. but, the basic mechanism here of leveraging the channel's
248
// (or loadgroup's) notification callbacks attribute seems ideal as it
249
// avoids the problem of having to modify all places in the code which
250
// kick off network requests.
252
PRUint32 appType = nsIDocShell::APP_TYPE_UNKNOWN;
254
nsCOMPtr<nsIDocShellTreeItem> item, parent;
255
GetInterfaceFromChannel(aChannel, NS_GET_IID(nsIDocShellTreeItem),
256
getter_AddRefs(parent));
260
nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(item);
262
docshell->GetAppType(&appType);
263
} while (appType != nsIDocShell::APP_TYPE_MAIL &&
264
NS_SUCCEEDED(item->GetParent(getter_AddRefs(parent))) &&
268
if ((appType == nsIDocShell::APP_TYPE_MAIL) ||
269
(aFirstURI && IsFromMailNews(aFirstURI)) ||
270
IsFromMailNews(aURI)) {
271
*aResult = ACCESS_DENY;
275
#endif // MOZ_MAIL_NEWS
277
// finally, check with permission manager...
278
nsresult rv = mPermMgr->TestPermission(aURI, kPermissionType, (PRUint32 *) aResult);
279
if (NS_SUCCEEDED(rv)) {
281
// if we have one of the publicly-available values, just return it
282
case nsIPermissionManager::UNKNOWN_ACTION: // ACCESS_DEFAULT
283
case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
284
case nsIPermissionManager::DENY_ACTION: // ACCESS_DENY
287
// ACCESS_SESSION means the cookie can be accepted; the session
288
// downgrade will occur in CanSetCookie().
289
case nsICookiePermission::ACCESS_SESSION:
290
*aResult = ACCESS_ALLOW;
293
// ack, an unknown type! just use the defaults.
295
*aResult = ACCESS_DEFAULT;
303
nsCookiePermission::CanSetCookie(nsIURI *aURI,
304
nsIChannel *aChannel,
310
NS_ASSERTION(aURI, "null uri");
312
*aResult = kDefaultPolicy;
315
mPermMgr->TestPermission(aURI, kPermissionType, &perm);
317
case nsICookiePermission::ACCESS_SESSION:
318
*aIsSession = PR_TRUE;
320
case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
324
case nsIPermissionManager::DENY_ACTION: // ACCESS_DENY
329
// the permission manager has nothing to say about this cookie -
330
// so, we apply the default prefs to it.
331
NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
333
// now we need to figure out what type of accept policy we're dealing with
334
// if we accept cookies normally, just bail and return
335
if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
340
// declare this here since it'll be used in all of the remaining cases
341
nsInt64 currentTime = NOW_IN_SECONDS;
342
nsInt64 delta = nsInt64(*aExpiry) - currentTime;
344
// check whether the user wants to be prompted
345
if (mCookiesLifetimePolicy == ASK_BEFORE_ACCEPT) {
346
// if it's a session cookie and the user wants to accept these
347
// without asking, just accept the cookie and return
348
if (*aIsSession && mCookiesAlwaysAcceptSession) {
353
// default to rejecting, in case the prompting process fails
356
nsCAutoString hostPort;
357
aURI->GetHostPort(hostPort);
360
return NS_ERROR_UNEXPECTED;
362
// If there is no host, use the scheme, and append "://",
363
// to make sure it isn't a host or something.
364
// This is done to make the dialog appear for javascript cookies from
365
// file:// urls, and make the text on it not too weird. (bug 209689)
366
if (hostPort.IsEmpty()) {
367
aURI->GetScheme(hostPort);
368
if (hostPort.IsEmpty()) {
369
// still empty. Just return the default.
372
hostPort = hostPort + NS_LITERAL_CSTRING("://");
375
// we don't cache the cookiePromptService - it's not used often, so not
378
nsCOMPtr<nsICookiePromptService> cookiePromptService =
379
do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID, &rv);
380
if (NS_FAILED(rv)) return rv;
382
// try to get a nsIDOMWindow from the channel...
383
nsCOMPtr<nsIDOMWindow> parent;
384
GetInterfaceFromChannel(aChannel, NS_GET_IID(nsIDOMWindow),
385
getter_AddRefs(parent));
387
// get some useful information to present to the user:
388
// whether a previous cookie already exists, and how many cookies this host
391
PRUint32 countFromHost;
392
nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
393
if (NS_SUCCEEDED(rv))
394
rv = cookieManager->FindMatchingCookie(aCookie, &countFromHost, &foundCookie);
395
if (NS_FAILED(rv)) return rv;
397
// check if the cookie we're trying to set is already expired, and return;
398
// but only if there's no previous cookie, because then we need to delete the previous
399
// cookie. we need this check to avoid prompting the user for already-expired cookies.
400
if (!foundCookie && !*aIsSession && delta <= nsInt64(0)) {
401
// the cookie has already expired. accept it, and let the backend figure
402
// out it's expired, so that we get correct logging & notifications.
407
PRBool rememberDecision = PR_FALSE;
408
rv = cookiePromptService->CookieDialog(parent, aCookie, hostPort,
409
countFromHost, foundCookie,
410
&rememberDecision, aResult);
411
if (NS_FAILED(rv)) return rv;
413
if (*aResult == nsICookiePromptService::ACCEPT_SESSION_COOKIE)
414
*aIsSession = PR_TRUE;
416
if (rememberDecision) {
418
case nsICookiePromptService::DENY_COOKIE:
419
mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION);
421
case nsICookiePromptService::ACCEPT_COOKIE:
422
mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION);
424
case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
425
mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION);
432
// we're not prompting, so we must be limiting the lifetime somehow
433
// if it's a session cookie, we do nothing
434
if (!*aIsSession && delta > nsInt64(0)) {
435
if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
436
// limit lifetime to session
437
*aIsSession = PR_TRUE;
438
} else if (delta > mCookiesLifetimeSec) {
439
// limit lifetime to specified time
440
*aExpiry = currentTime + mCookiesLifetimeSec;
450
nsCookiePermission::Observe(nsISupports *aSubject,
452
const PRUnichar *aData)
454
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
455
NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
456
"unexpected topic - we only deal with pref changes!");
459
PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());