Back to index

lightning-sunbird  0.9+nobinonly
nsCookiePermission.cpp
Go to the documentation of this file.
00001 // vim:ts=2:sw=2:et:
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is cookie manager code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Michiel van Leeuwen (mvl@exedo.nl).
00019  * Portions created by the Initial Developer are Copyright (C) 2003
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 
00040 #include "nsCookiePermission.h"
00041 #include "nsICookie2.h"
00042 #include "nsIServiceManager.h"
00043 #include "nsICookiePromptService.h"
00044 #include "nsICookieManager2.h"
00045 #include "nsNetUtil.h"
00046 #include "nsIURI.h"
00047 #include "nsIPrefService.h"
00048 #include "nsIPrefBranch.h"
00049 #include "nsIPrefBranch2.h"
00050 #include "nsIDocShell.h"
00051 #include "nsIDocShellTreeItem.h"
00052 #include "nsIInterfaceRequestor.h"
00053 #include "nsIInterfaceRequestorUtils.h"
00054 #include "nsILoadGroup.h"
00055 #include "nsIChannel.h"
00056 #include "nsIDOMWindow.h"
00057 #include "nsString.h"
00058 #include "nsCRT.h"
00059 
00060 /****************************************************************
00061  ************************ nsCookiePermission ********************
00062  ****************************************************************/
00063 
00064 // values for mCookiesLifetimePolicy
00065 // 0 == accept normally
00066 // 1 == ask before accepting
00067 // 2 == downgrade to session
00068 // 3 == limit lifetime to N days
00069 static const PRUint32 ACCEPT_NORMALLY = 0;
00070 static const PRUint32 ASK_BEFORE_ACCEPT = 1;
00071 static const PRUint32 ACCEPT_SESSION = 2;
00072 static const PRUint32 ACCEPT_FOR_N_DAYS = 3;
00073 
00074 static const PRBool kDefaultPolicy = PR_TRUE;
00075 static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
00076 static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
00077 static const char kCookiesAlwaysAcceptSession[] = "network.cookie.alwaysAcceptSessionCookies";
00078 #ifdef MOZ_MAIL_NEWS
00079 static const char kCookiesDisabledForMailNews[] = "network.cookie.disableCookieForMailNews";
00080 #endif
00081 
00082 static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
00083 // obsolete pref names for migration
00084 static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
00085 static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior";
00086 static const char kCookiesAskPermission[] = "network.cookie.warnAboutCookies";
00087 
00088 static const char kPermissionType[] = "cookie";
00089 
00090 // XXX these casts and constructs are horrible, but our nsInt64/nsTime
00091 // classes are lacking so we need them for now. see bug 198694.
00092 #define USEC_PER_SEC   (nsInt64(1000000))
00093 #define NOW_IN_SECONDS (nsInt64(PR_Now()) / USEC_PER_SEC)
00094 
00095 #ifdef MOZ_MAIL_NEWS
00096 // returns PR_TRUE if URI appears to be the URI of a mailnews protocol
00097 static PRBool
00098 IsFromMailNews(nsIURI *aURI)
00099 {
00100   static const char *kMailNewsProtocols[] =
00101       { "imap", "news", "snews", "mailbox", nsnull };
00102   PRBool result;
00103   for (const char **p = kMailNewsProtocols; *p; ++p) {
00104     if (NS_SUCCEEDED(aURI->SchemeIs(*p, &result)) && result)
00105       return PR_TRUE;
00106   }
00107   return PR_FALSE;
00108 }
00109 #endif
00110 
00111 NS_IMPL_ISUPPORTS2(nsCookiePermission,
00112                    nsICookiePermission,
00113                    nsIObserver)
00114 
00115 nsresult
00116 nsCookiePermission::Init()
00117 {
00118   nsresult rv;
00119   mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
00120   if (NS_FAILED(rv)) return rv;
00121 
00122   // failure to access the pref service is non-fatal...
00123   nsCOMPtr<nsIPrefBranch2> prefBranch =
00124       do_GetService(NS_PREFSERVICE_CONTRACTID);
00125   if (prefBranch) {
00126     prefBranch->AddObserver(kCookiesLifetimePolicy, this, PR_FALSE);
00127     prefBranch->AddObserver(kCookiesLifetimeDays, this, PR_FALSE);
00128     prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, PR_FALSE);
00129 #ifdef MOZ_MAIL_NEWS
00130     prefBranch->AddObserver(kCookiesDisabledForMailNews, this, PR_FALSE);
00131 #endif
00132     PrefChanged(prefBranch, nsnull);
00133 
00134     // migration code for original cookie prefs
00135     PRBool migrated;
00136     rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated);
00137     if (NS_FAILED(rv) || !migrated) {
00138       PRBool warnAboutCookies = PR_FALSE;
00139       prefBranch->GetBoolPref(kCookiesAskPermission, &warnAboutCookies);
00140 
00141       // if the user is using ask before accepting, we'll use that
00142       if (warnAboutCookies)
00143         prefBranch->SetIntPref(kCookiesLifetimePolicy, ASK_BEFORE_ACCEPT);
00144         
00145       PRBool lifetimeEnabled = PR_FALSE;
00146       prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled);
00147       
00148       // if they're limiting lifetime and not using the prompts, use the 
00149       // appropriate limited lifetime pref
00150       if (lifetimeEnabled && !warnAboutCookies) {
00151         PRInt32 lifetimeBehavior;
00152         prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior);
00153         if (lifetimeBehavior)
00154           prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
00155         else
00156           prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
00157       }
00158       prefBranch->SetBoolPref(kCookiesPrefsMigrated, PR_TRUE);
00159     }
00160   }
00161 
00162   return NS_OK;
00163 }
00164 
00165 void
00166 nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
00167                                 const char    *aPref)
00168 {
00169   PRBool val;
00170 
00171 #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
00172 
00173   if (PREF_CHANGED(kCookiesLifetimePolicy) &&
00174       NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val)))
00175     mCookiesLifetimePolicy = val;
00176 
00177   if (PREF_CHANGED(kCookiesLifetimeDays) &&
00178       NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
00179     // save cookie lifetime in seconds instead of days
00180     mCookiesLifetimeSec = val * 24 * 60 * 60;
00181 
00182   if (PREF_CHANGED(kCookiesAlwaysAcceptSession) &&
00183       NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &val)))
00184     mCookiesAlwaysAcceptSession = val;
00185 
00186 #ifdef MOZ_MAIL_NEWS
00187   if (PREF_CHANGED(kCookiesDisabledForMailNews) &&
00188       NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesDisabledForMailNews, &val)))
00189     mCookiesDisabledForMailNews = val;
00190 #endif
00191 }
00192 
00193 NS_IMETHODIMP
00194 nsCookiePermission::SetAccess(nsIURI         *aURI,
00195                               nsCookieAccess  aAccess)
00196 {
00197   //
00198   // NOTE: nsCookieAccess values conveniently match up with
00199   //       the permission codes used by nsIPermissionManager.
00200   //       this is nice because it avoids conversion code.
00201   //
00202   return mPermMgr->Add(aURI, kPermissionType, aAccess);
00203 }
00204 
00205 NS_IMETHODIMP
00206 nsCookiePermission::CanAccess(nsIURI         *aURI,
00207                               nsIURI         *aFirstURI,
00208                               nsIChannel     *aChannel,
00209                               nsCookieAccess *aResult)
00210 {
00211 #ifdef MOZ_MAIL_NEWS
00212   // disable cookies in mailnews if user's prefs say so
00213   if (mCookiesDisabledForMailNews) {
00214     //
00215     // try to examine the "app type" of the docshell owning this request.  if
00216     // we find a docshell in the heirarchy of type APP_TYPE_MAIL, then assume
00217     // this URI is being loaded from within mailnews.
00218     //
00219     // XXX this is a pretty ugly hack at the moment since cookies really
00220     // shouldn't have to talk to the docshell directly.  ultimately, we want
00221     // to talk to some more generic interface, which the docshell would also
00222     // implement.  but, the basic mechanism here of leveraging the channel's
00223     // (or loadgroup's) notification callbacks attribute seems ideal as it
00224     // avoids the problem of having to modify all places in the code which
00225     // kick off network requests.
00226     //
00227     PRUint32 appType = nsIDocShell::APP_TYPE_UNKNOWN;
00228     if (aChannel) {
00229       nsCOMPtr<nsIDocShellTreeItem> item, parent;
00230       NS_QueryNotificationCallbacks(aChannel, parent);
00231       if (parent) {
00232         do {
00233             item = parent;
00234             nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(item);
00235             if (docshell)
00236               docshell->GetAppType(&appType);
00237         } while (appType != nsIDocShell::APP_TYPE_MAIL &&
00238                  NS_SUCCEEDED(item->GetParent(getter_AddRefs(parent))) &&
00239                  parent);
00240       }
00241     }
00242     if ((appType == nsIDocShell::APP_TYPE_MAIL) ||
00243         (aFirstURI && IsFromMailNews(aFirstURI)) ||
00244         IsFromMailNews(aURI)) {
00245       *aResult = ACCESS_DENY;
00246       return NS_OK;
00247     }
00248   }
00249 #endif // MOZ_MAIL_NEWS
00250   
00251   // finally, check with permission manager...
00252   nsresult rv = mPermMgr->TestPermission(aURI, kPermissionType, (PRUint32 *) aResult);
00253   if (NS_SUCCEEDED(rv)) {
00254     switch (*aResult) {
00255     // if we have one of the publicly-available values, just return it
00256     case nsIPermissionManager::UNKNOWN_ACTION: // ACCESS_DEFAULT
00257     case nsIPermissionManager::ALLOW_ACTION:   // ACCESS_ALLOW
00258     case nsIPermissionManager::DENY_ACTION:    // ACCESS_DENY
00259       break;
00260 
00261     // ACCESS_SESSION means the cookie can be accepted; the session 
00262     // downgrade will occur in CanSetCookie().
00263     case nsICookiePermission::ACCESS_SESSION:
00264       *aResult = ACCESS_ALLOW;
00265       break;
00266 
00267     // ack, an unknown type! just use the defaults.
00268     default:
00269       *aResult = ACCESS_DEFAULT;
00270     }
00271   }
00272 
00273   return rv;
00274 }
00275 
00276 NS_IMETHODIMP 
00277 nsCookiePermission::CanSetCookie(nsIURI     *aURI,
00278                                  nsIChannel *aChannel,
00279                                  nsICookie2 *aCookie,
00280                                  PRBool     *aIsSession,
00281                                  PRInt64    *aExpiry,
00282                                  PRBool     *aResult)
00283 {
00284   NS_ASSERTION(aURI, "null uri");
00285 
00286   *aResult = kDefaultPolicy;
00287 
00288   PRUint32 perm;
00289   mPermMgr->TestPermission(aURI, kPermissionType, &perm);
00290   switch (perm) {
00291   case nsICookiePermission::ACCESS_SESSION:
00292     *aIsSession = PR_TRUE;
00293 
00294   case nsIPermissionManager::ALLOW_ACTION: // ACCESS_ALLOW
00295     *aResult = PR_TRUE;
00296     break;
00297 
00298   case nsIPermissionManager::DENY_ACTION:  // ACCESS_DENY
00299     *aResult = PR_FALSE;
00300     break;
00301 
00302   default:
00303     // the permission manager has nothing to say about this cookie -
00304     // so, we apply the default prefs to it.
00305     NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
00306     
00307     // now we need to figure out what type of accept policy we're dealing with
00308     // if we accept cookies normally, just bail and return
00309     if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
00310       *aResult = PR_TRUE;
00311       return NS_OK;
00312     }
00313     
00314     // declare this here since it'll be used in all of the remaining cases
00315     nsInt64 currentTime = NOW_IN_SECONDS;
00316     nsInt64 delta = nsInt64(*aExpiry) - currentTime;
00317     
00318     // check whether the user wants to be prompted
00319     if (mCookiesLifetimePolicy == ASK_BEFORE_ACCEPT) {
00320       // if it's a session cookie and the user wants to accept these 
00321       // without asking, just accept the cookie and return
00322       if (*aIsSession && mCookiesAlwaysAcceptSession) {
00323         *aResult = PR_TRUE;
00324         return NS_OK;
00325       }
00326       
00327       // default to rejecting, in case the prompting process fails
00328       *aResult = PR_FALSE;
00329 
00330       nsCAutoString hostPort;
00331       aURI->GetHostPort(hostPort);
00332 
00333       if (!aCookie) {
00334          return NS_ERROR_UNEXPECTED;
00335       }
00336       // If there is no host, use the scheme, and append "://",
00337       // to make sure it isn't a host or something.
00338       // This is done to make the dialog appear for javascript cookies from
00339       // file:// urls, and make the text on it not too weird. (bug 209689)
00340       if (hostPort.IsEmpty()) {
00341         aURI->GetScheme(hostPort);
00342         if (hostPort.IsEmpty()) {
00343           // still empty. Just return the default.
00344           return NS_OK;
00345         }
00346         hostPort = hostPort + NS_LITERAL_CSTRING("://");
00347       }
00348 
00349       // we don't cache the cookiePromptService - it's not used often, so not
00350       // worth the memory.
00351       nsresult rv;
00352       nsCOMPtr<nsICookiePromptService> cookiePromptService =
00353           do_GetService(NS_COOKIEPROMPTSERVICE_CONTRACTID, &rv);
00354       if (NS_FAILED(rv)) return rv;
00355 
00356       // try to get a nsIDOMWindow from the channel...
00357       nsCOMPtr<nsIDOMWindow> parent;
00358       if (aChannel)
00359         NS_QueryNotificationCallbacks(aChannel, parent);
00360 
00361       // get some useful information to present to the user:
00362       // whether a previous cookie already exists, and how many cookies this host
00363       // has set
00364       PRBool foundCookie;
00365       PRUint32 countFromHost;
00366       nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
00367       if (NS_SUCCEEDED(rv))
00368         rv = cookieManager->FindMatchingCookie(aCookie, &countFromHost, &foundCookie);
00369       if (NS_FAILED(rv)) return rv;
00370 
00371       // check if the cookie we're trying to set is already expired, and return;
00372       // but only if there's no previous cookie, because then we need to delete the previous
00373       // cookie. we need this check to avoid prompting the user for already-expired cookies.
00374       if (!foundCookie && !*aIsSession && delta <= nsInt64(0)) {
00375         // the cookie has already expired. accept it, and let the backend figure
00376         // out it's expired, so that we get correct logging & notifications.
00377         *aResult = PR_TRUE;
00378         return rv;
00379       }
00380 
00381       PRBool rememberDecision = PR_FALSE;
00382       rv = cookiePromptService->CookieDialog(parent, aCookie, hostPort, 
00383                                              countFromHost, foundCookie,
00384                                              &rememberDecision, aResult);
00385       if (NS_FAILED(rv)) return rv;
00386       
00387       if (*aResult == nsICookiePromptService::ACCEPT_SESSION_COOKIE)
00388         *aIsSession = PR_TRUE;
00389 
00390       if (rememberDecision) {
00391         switch (*aResult) {
00392           case nsICookiePromptService::DENY_COOKIE:
00393             mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION);
00394             break;
00395           case nsICookiePromptService::ACCEPT_COOKIE:
00396             mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION);
00397             break;
00398           case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
00399             mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION);
00400             break;
00401           default:
00402             break;
00403         }
00404       }
00405     } else {
00406       // we're not prompting, so we must be limiting the lifetime somehow
00407       // if it's a session cookie, we do nothing
00408       if (!*aIsSession && delta > nsInt64(0)) {
00409         if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
00410           // limit lifetime to session
00411           *aIsSession = PR_TRUE;
00412         } else if (delta > mCookiesLifetimeSec) {
00413           // limit lifetime to specified time
00414           *aExpiry = currentTime + mCookiesLifetimeSec;
00415         }
00416       }
00417     }
00418   }
00419 
00420   return NS_OK;
00421 }
00422 
00423 NS_IMETHODIMP
00424 nsCookiePermission::Observe(nsISupports     *aSubject,
00425                             const char      *aTopic,
00426                             const PRUnichar *aData)
00427 {
00428   nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
00429   NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
00430                "unexpected topic - we only deal with pref changes!");
00431 
00432   if (prefBranch)
00433     PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
00434   return NS_OK;
00435 }