Bug 454376 add -lCrun -lCstd for Solaris OS_LIBS, r=bsmedberg
[wine-gecko.git] / extensions / cookie / nsCookiePermission.cpp
blob0f8a64f02f7242a9faa034800408fc693e3e29e0
1 // vim:ts=2:sw=2:et:
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
13 * License.
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.
22 * Contributor(s):
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"
47 #include "nsIURI.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"
54 #include "nsINode.h"
55 #include "nsIChannel.h"
56 #include "nsIDOMWindow.h"
57 #include "nsIDOMDocument.h"
58 #include "nsIPrincipal.h"
59 #include "nsString.h"
60 #include "nsCRT.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";
80 #ifdef MOZ_MAIL_NEWS
81 static const char kCookiesDisabledForMailNews[] = "network.cookie.disableCookieForMailNews";
82 #endif
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";
92 #ifdef MOZ_MAIL_NEWS
93 // returns PR_TRUE if URI appears to be the URI of a mailnews protocol
94 static PRBool
95 IsFromMailNews(nsIURI *aURI)
97 static const char *kMailNewsProtocols[] =
98 { "imap", "news", "snews", "mailbox", nsnull };
99 PRBool result;
100 for (const char **p = kMailNewsProtocols; *p; ++p) {
101 if (NS_SUCCEEDED(aURI->SchemeIs(*p, &result)) && result)
102 return PR_TRUE;
104 return PR_FALSE;
106 #endif
108 NS_IMPL_ISUPPORTS2(nsCookiePermission,
109 nsICookiePermission,
110 nsIObserver)
112 nsresult
113 nsCookiePermission::Init()
115 nsresult rv;
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);
122 if (prefBranch) {
123 prefBranch->AddObserver(kCookiesLifetimePolicy, this, PR_FALSE);
124 prefBranch->AddObserver(kCookiesLifetimeDays, this, PR_FALSE);
125 prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, PR_FALSE);
126 #ifdef MOZ_MAIL_NEWS
127 prefBranch->AddObserver(kCookiesDisabledForMailNews, this, PR_FALSE);
128 #endif
129 PrefChanged(prefBranch, nsnull);
131 // migration code for original cookie prefs
132 PRBool migrated;
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);
152 else
153 prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
155 prefBranch->SetBoolPref(kCookiesPrefsMigrated, PR_TRUE);
159 return NS_OK;
162 void
163 nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
164 const char *aPref)
166 PRInt32 val;
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;
183 #ifdef MOZ_MAIL_NEWS
184 if (PREF_CHANGED(kCookiesDisabledForMailNews) &&
185 NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesDisabledForMailNews, &val)))
186 mCookiesDisabledForMailNews = val;
187 #endif
190 NS_IMETHODIMP
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);
202 NS_IMETHODIMP
203 nsCookiePermission::CanAccess(nsIURI *aURI,
204 nsIChannel *aChannel,
205 nsCookieAccess *aResult)
207 #ifdef MOZ_MAIL_NEWS
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;
224 if (aChannel) {
225 nsCOMPtr<nsIDocShellTreeItem> item, parent;
226 NS_QueryNotificationCallbacks(aChannel, parent);
227 if (parent) {
228 do {
229 item = parent;
230 nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(item);
231 if (docshell)
232 docshell->GetAppType(&appType);
233 } while (appType != nsIDocShell::APP_TYPE_MAIL &&
234 NS_SUCCEEDED(item->GetParent(getter_AddRefs(parent))) &&
235 parent);
238 if ((appType == nsIDocShell::APP_TYPE_MAIL) ||
239 IsFromMailNews(aURI)) {
240 *aResult = ACCESS_DENY;
241 return NS_OK;
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)) {
249 switch (*aResult) {
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
254 break;
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;
260 break;
262 // ack, an unknown type! just use the defaults.
263 default:
264 *aResult = ACCESS_DEFAULT;
268 return rv;
271 NS_IMETHODIMP
272 nsCookiePermission::CanSetCookie(nsIURI *aURI,
273 nsIChannel *aChannel,
274 nsICookie2 *aCookie,
275 PRBool *aIsSession,
276 PRInt64 *aExpiry,
277 PRBool *aResult)
279 NS_ASSERTION(aURI, "null uri");
281 *aResult = kDefaultPolicy;
283 PRUint32 perm;
284 mPermMgr->TestPermission(aURI, kPermissionType, &perm);
285 switch (perm) {
286 case nsICookiePermission::ACCESS_SESSION:
287 *aIsSession = PR_TRUE;
289 case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
290 *aResult = PR_TRUE;
291 break;
293 case nsIPermissionManager::DENY_ACTION: // ACCESS_DENY
294 *aResult = PR_FALSE;
295 break;
297 default:
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) {
305 *aResult = PR_TRUE;
306 return NS_OK;
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) {
318 *aResult = PR_TRUE;
319 return NS_OK;
322 // default to rejecting, in case the prompting process fails
323 *aResult = PR_FALSE;
325 nsCAutoString hostPort;
326 aURI->GetHostPort(hostPort);
328 if (!aCookie) {
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.
339 return NS_OK;
341 hostPort = hostPort + NS_LITERAL_CSTRING("://");
344 // we don't cache the cookiePromptService - it's not used often, so not
345 // worth the memory.
346 nsresult rv;
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;
353 if (aChannel)
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
358 // has set
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.
378 *aResult = PR_TRUE;
379 return rv;
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) {
392 switch (*aResult) {
393 case nsICookiePromptService::DENY_COOKIE:
394 mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION);
395 break;
396 case nsICookiePromptService::ACCEPT_COOKIE:
397 mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION);
398 break;
399 case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
400 mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION);
401 break;
402 default:
403 break;
406 } else {
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;
421 return NS_OK;
424 NS_IMETHODIMP
425 nsCookiePermission::GetOriginatingURI(nsIChannel *aChannel,
426 nsIURI **aURI)
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
434 * have a channel.
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
442 * category).
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
451 * or clicked on.
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
456 * in this case.
460 *aURI = nsnull;
462 // case 1)
463 if (!aChannel)
464 return NS_ERROR_NULL_POINTER;
466 // find the docshell and its root
467 nsCOMPtr<nsIDocShellTreeItem> docshell, root;
468 NS_QueryNotificationCallbacks(aChannel, docshell);
469 if (docshell)
470 docshell->GetSameTypeRootTreeItem(getter_AddRefs(root));
472 PRInt32 type;
473 if (root)
474 root->GetItemType(&type);
476 // cases 2) and 3)
477 if (!root || type != nsIDocShellTreeItem::typeContent)
478 return NS_ERROR_INVALID_ARG;
480 // case 4)
481 if (docshell == root) {
482 nsLoadFlags flags;
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);
488 if (!*aURI)
489 return NS_ERROR_NULL_POINTER;
491 return NS_OK;
495 // case 5) - get the originating URI from the docshell's principal
496 nsCOMPtr<nsIWebNavigation> webnav = do_QueryInterface(root);
497 if (webnav) {
498 nsCOMPtr<nsIDOMDocument> doc;
499 webnav->GetDocument(getter_AddRefs(doc));
500 nsCOMPtr<nsINode> node = do_QueryInterface(doc);
501 if (node)
502 node->NodePrincipal()->GetURI(aURI);
505 if (!*aURI)
506 return NS_ERROR_NULL_POINTER;
508 // all done!
509 return NS_OK;
512 NS_IMETHODIMP
513 nsCookiePermission::Observe(nsISupports *aSubject,
514 const char *aTopic,
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!");
521 if (prefBranch)
522 PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
523 return NS_OK;