dwitte%stanford.edu add httponly support for cookies. bugs 178993, 383181, and 387543; a=dveditz. patches by mkaply, Ronny Perinke, dveditz and dwitte. =================================================================== RCS file: /cvsroot/mozilla/netwerk/cookie/public nsICookie2.idl,v retrieving revision 1.3 retrieving revision 1.3.58.1 diff -u -r1.3 -r1.3.58.1 --- mozilla/netwerk/cookie/public/nsICookie2.idl 2003/10/22 06:53:19 1.3 +++ mozilla/netwerk/cookie/public/nsICookie2.idl 2007/07/11 03:18:15 1.3.58.1 @@ -72,3 +72,12 @@ readonly attribute PRInt64 expiry; }; + +[scriptable, uuid(40712890-6c9e-45fc-b77c-c8ea344f690e)] +interface nsICookie2_MOZILLA_1_8_BRANCH : nsICookie2 +{ + /** + * true if the cookie is an http only cookie + */ + readonly attribute boolean isHttpOnly; +}; =================================================================== RCS file: /cvsroot/mozilla/netwerk/cookie/src nsCookie.cpp,v retrieving revision 1.9 retrieving revision 1.9.28.1 diff -u -r1.9 -r1.9.28.1 --- mozilla/netwerk/cookie/src/nsCookie.cpp 2004/06/07 14:54:22 1.9 +++ mozilla/netwerk/cookie/src/nsCookie.cpp 2007/07/11 03:18:15 1.9.28.1 @@ -89,6 +89,7 @@ nsInt64 aLastAccessed, PRBool aIsSession, PRBool aIsSecure, + PRBool aIsHttpOnly, nsCookieStatus aStatus, nsCookiePolicy aPolicy) { @@ -111,7 +112,8 @@ // construct the cookie. placement new, oh yeah! return new (place) nsCookie(name, value, host, path, end, aExpiry, aLastAccessed, ++gLastCreationTime, - aIsSession, aIsSecure, aStatus, aPolicy); + aIsSession, aIsSecure, aIsHttpOnly, + aStatus, aPolicy); } /****************************************************************************** @@ -129,6 +131,7 @@ NS_IMETHODIMP nsCookie::GetIsSession(PRBool *aIsSession) { *aIsSession = IsSession(); return NS_OK; } NS_IMETHODIMP nsCookie::GetIsDomain(PRBool *aIsDomain) { *aIsDomain = IsDomain(); return NS_OK; } NS_IMETHODIMP nsCookie::GetIsSecure(PRBool *aIsSecure) { *aIsSecure = IsSecure(); return NS_OK; } +NS_IMETHODIMP nsCookie::GetIsHttpOnly(PRBool *aHttpOnly) { *aHttpOnly = IsHttpOnly(); return NS_OK; } NS_IMETHODIMP nsCookie::GetStatus(nsCookieStatus *aStatus) { *aStatus = Status(); return NS_OK; } NS_IMETHODIMP nsCookie::GetPolicy(nsCookiePolicy *aPolicy) { *aPolicy = Policy(); return NS_OK; } @@ -145,4 +148,4 @@ return NS_OK; } -NS_IMPL_ISUPPORTS2(nsCookie, nsICookie2, nsICookie) +NS_IMPL_ISUPPORTS3(nsCookie, nsICookie2, nsICookie, nsICookie2_MOZILLA_1_8_BRANCH) =================================================================== RCS file: /cvsroot/mozilla/netwerk/cookie/src nsCookie.h,v retrieving revision 1.11 retrieving revision 1.11.28.1 diff -u -r1.11 -r1.11.28.1 --- mozilla/netwerk/cookie/src/nsCookie.h 2004/06/07 14:54:22 1.11 +++ mozilla/netwerk/cookie/src/nsCookie.h 2007/07/11 03:18:15 1.11.28.1 @@ -55,7 +55,7 @@ * implementation ******************************************************************************/ -class nsCookie : public nsICookie2 +class nsCookie : public nsICookie2_MOZILLA_1_8_BRANCH { // break up the NS_DECL_ISUPPORTS macro, since we use a bitfield refcount member public: @@ -67,6 +67,7 @@ // nsISupports NS_DECL_NSICOOKIE NS_DECL_NSICOOKIE2 + NS_DECL_NSICOOKIE2_MOZILLA_1_8_BRANCH private: // for internal use only. see nsCookie::Create(). @@ -80,6 +81,7 @@ PRUint32 aCreationTime, PRBool aIsSession, PRBool aIsSecure, + PRBool aIsHttpOnly, nsCookieStatus aStatus, nsCookiePolicy aPolicy) : mNext(nsnull) @@ -94,6 +96,7 @@ , mRefCnt(0) , mIsSession(aIsSession != PR_FALSE) , mIsSecure(aIsSecure != PR_FALSE) + , mIsHttpOnly(aIsHttpOnly != PR_FALSE) , mStatus(aStatus) , mPolicy(aPolicy) { @@ -110,6 +113,7 @@ nsInt64 aLastAccessed, PRBool aIsSession, PRBool aIsSecure, + PRBool aIsHttpOnly, nsCookieStatus aStatus, nsCookiePolicy aPolicy); @@ -127,6 +131,7 @@ inline PRBool IsSession() const { return mIsSession; } inline PRBool IsDomain() const { return *mHost == '.'; } inline PRBool IsSecure() const { return mIsSecure; } + inline PRBool IsHttpOnly() const { return mIsHttpOnly; } inline nsCookieStatus Status() const { return mStatus; } inline nsCookiePolicy Policy() const { return mPolicy; } @@ -158,6 +163,7 @@ PRUint32 mRefCnt : 16; PRUint32 mIsSession : 1; PRUint32 mIsSecure : 1; + PRUint32 mIsHttpOnly: 1; PRUint32 mStatus : 3; PRUint32 mPolicy : 3; }; =================================================================== RCS file: /cvsroot/mozilla/netwerk/cookie/src nsCookieService.cpp,v retrieving revision 1.43.8.3 retrieving revision 1.43.8.4 diff -u -r1.43.8.3 -r1.43.8.4 --- mozilla/netwerk/cookie/src/nsCookieService.cpp 2007/05/07 11:14:01 1.43.8.3 +++ mozilla/netwerk/cookie/src/nsCookieService.cpp 2007/07/11 03:18:15 1.43.8.4 @@ -75,6 +75,12 @@ * useful types & constants ******************************************************************************/ +// XXX_hack. See bug 178993. +// This is a hack to hide HttpOnly cookies from older browsers +// + +static const char kHttpOnlyPrefix[] = "#HttpOnly_"; + static const char kCookieFileName[] = "cookies.txt"; static const PRUint32 kLazyWriteTimeout = 5000; //msec @@ -124,6 +130,7 @@ nsInt64 expiryTime; PRBool isSession; PRBool isSecure; + PRBool isHttpOnly; }; // stores linked list iteration state, and provides a rudimentary @@ -238,7 +245,8 @@ } nsCAutoString spec; - aHostURI->GetAsciiSpec(spec); + if (aHostURI) + aHostURI->GetAsciiSpec(spec); PR_LOG(sCookieLog, PR_LOG_DEBUG, ("%s%s%s\n", "===== ", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT", " =====")); @@ -259,14 +267,13 @@ PR_LOG(sCookieLog, PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get())); PR_LOG(sCookieLog, PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get())); - if (!aCookie->IsSession()) { - PR_ExplodeTime(aCookie->Expiry() * USEC_PER_SEC, PR_GMTParameters, &explodedTime); - PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); - } - + PR_ExplodeTime(aCookie->Expiry() * USEC_PER_SEC, PR_GMTParameters, &explodedTime); + PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); PR_LOG(sCookieLog, PR_LOG_DEBUG, - ("expires: %s", aCookie->IsSession() ? "at end of session" : timeString)); + ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : "")); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false")); + PR_LOG(sCookieLog, PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false")); } PR_LOG(sCookieLog, PR_LOG_DEBUG,("\n")); } @@ -482,30 +489,14 @@ return NS_OK; } -NS_IMETHODIMP -nsCookieService::GetCookieString(nsIURI *aHostURI, - nsIChannel *aChannel, - char **aCookie) -{ - // try to determine first party URI - nsCOMPtr firstURI; - if (aChannel) { - nsCOMPtr httpInternal = do_QueryInterface(aChannel); - if (httpInternal) - httpInternal->GetDocumentURI(getter_AddRefs(firstURI)); - } - - return GetCookieStringFromHttp(aHostURI, firstURI, aChannel, aCookie); -} - // helper function for GetCookieStringFromHttp static inline PRBool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; } -NS_IMETHODIMP -nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, - nsIURI *aFirstURI, - nsIChannel *aChannel, - char **aCookie) +nsresult nsCookieService::GetCookieInternal(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIChannel *aChannel, + PRBool aHttpBound, + char **aCookie) { *aCookie = nsnull; @@ -565,6 +556,12 @@ continue; } + // if the cookie is httpOnly and it's not going directly to the HTTP + // connection, don't send it + if (cookie->IsHttpOnly() && !aHttpBound) { + continue; + } + // calculate cookie path length, excluding trailing '/' PRUint32 cookiePathLen = cookie->Path().Length(); if (cookiePathLen > 0 && cookie->Path().Last() == '/') { @@ -644,6 +641,31 @@ } NS_IMETHODIMP +nsCookieService::GetCookieString(nsIURI *aHostURI, + nsIChannel *aChannel, + char **aCookie) +{ + // try to determine first party URI + nsCOMPtr firstURI; + if (aChannel) { + nsCOMPtr httpInternal = do_QueryInterface(aChannel); + if (httpInternal) + httpInternal->GetDocumentURI(getter_AddRefs(firstURI)); + } + + return GetCookieInternal(aHostURI, firstURI, aChannel, PR_FALSE, aCookie); +} + +NS_IMETHODIMP +nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIChannel *aChannel, + char **aCookie) +{ + return GetCookieInternal(aHostURI, aFirstURI, aChannel, PR_TRUE, aCookie); +} + +NS_IMETHODIMP nsCookieService::SetCookieString(nsIURI *aHostURI, nsIPrompt *aPrompt, const char *aCookieHeader, @@ -658,7 +680,7 @@ httpInternal->GetDocumentURI(getter_AddRefs(firstURI)); } - return SetCookieStringFromHttp(aHostURI, firstURI, aPrompt, aCookieHeader, nsnull, aChannel); + return SetCookieStringInternal(aHostURI, firstURI, aPrompt, aCookieHeader, nsnull, aChannel, PR_FALSE); } NS_IMETHODIMP @@ -669,6 +691,18 @@ const char *aServerTime, nsIChannel *aChannel) { + return SetCookieStringInternal(aHostURI, aFirstURI, aPrompt, aCookieHeader, aServerTime, aChannel, PR_TRUE); +} + +nsresult +nsCookieService::SetCookieStringInternal(nsIURI *aHostURI, + nsIURI *aFirstURI, + nsIPrompt *aPrompt, + const char *aCookieHeader, + const char *aServerTime, + nsIChannel *aChannel, + PRBool aFromHttp) +{ if (!aHostURI) { COOKIE_LOGFAILURE(SET_COOKIE, nsnull, aCookieHeader, "host URI is null"); return NS_OK; @@ -701,7 +735,7 @@ // switch to a nice string type now, and process each cookie in the header nsDependentCString cookieHeader(aCookieHeader); while (SetCookieInternal(aHostURI, aChannel, - cookieHeader, serverTime, + cookieHeader, serverTime, aFromHttp, cookieStatus, cookiePolicy)); // write out the cookie file @@ -852,13 +886,14 @@ currentTime, aIsSession, aIsSecure, + PR_FALSE, nsICookie::STATUS_UNKNOWN, nsICookie::POLICY_UNKNOWN); if (!cookie) { return NS_ERROR_OUT_OF_MEMORY; } - AddInternal(cookie, currentTime, nsnull, nsnull); + AddInternal(cookie, currentTime, nsnull, nsnull, PR_TRUE); return NS_OK; } @@ -916,11 +951,11 @@ nsCAutoString buffer; PRBool isMore = PR_TRUE; - PRInt32 hostIndex = 0, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex; + PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex; nsASingleFragmentCString::char_iterator iter; PRInt32 numInts; PRInt64 expires; - PRBool isDomain; + PRBool isDomain, isHttpOnly = PR_FALSE; nsInt64 currentTime = NOW_IN_SECONDS; // we use lastAccessedCounter to keep cookies in recently-used order, // so we start by initializing to currentTime (somewhat arbitrary) @@ -940,11 +975,26 @@ * most-recently used come first; least-recently-used come last. */ + /* + * ...but due to bug 178933, we hide HttpOnly cookies from older code + * in a comment, so they don't expose HttpOnly cookies to JS. + * + * The format for HttpOnly cookies is + * + * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie + * + */ + while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { - if (buffer.IsEmpty() || buffer.First() == '#') { + if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) { + isHttpOnly = PR_TRUE; + hostIndex = sizeof(kHttpOnlyPrefix) - 1; + } else if (buffer.IsEmpty() || buffer.First() == '#') { continue; + } else { + isHttpOnly = PR_FALSE; + hostIndex = 0; } - // this is a cheap, cheesy way of parsing a tab-delimited line into // string indexes, which can be lopped off into substrings. just for // purposes of obfuscation, it also checks that each token was found. @@ -987,6 +1037,7 @@ lastAccessedCounter, PR_FALSE, Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue), + isHttpOnly, nsICookie::STATUS_UNKNOWN, nsICookie::POLICY_UNKNOWN); if (!newCookie) { @@ -1082,6 +1133,11 @@ * note 2: cookies are written in order of lastAccessed time: * most-recently used come first; least-recently-used come last. */ + + /* + * XXX but see above in ::Read for the HttpOnly hack + */ + nsCookie *cookie; nsInt64 currentTime = NOW_IN_SECONDS; char dateString[22]; @@ -1094,6 +1150,10 @@ continue; } + // XXX hack for HttpOnly. see bug 178993. + if (cookie->IsHttpOnly()) { + bufferedOutputStream->Write(kHttpOnlyPrefix, sizeof(kHttpOnlyPrefix) - 1, &rv); + } bufferedOutputStream->Write(cookie->Host().get(), cookie->Host().Length(), &rv); if (cookie->IsDomain()) { bufferedOutputStream->Write(kTrue, sizeof(kTrue) - 1, &rv); @@ -1143,6 +1203,7 @@ nsIChannel *aChannel, nsDependentCString &aCookieHeader, nsInt64 aServerTime, + PRBool aFromHttp, nsCookieStatus aStatus, nsCookiePolicy aPolicy) { @@ -1199,6 +1260,7 @@ currentTime, cookieAttributes.isSession, cookieAttributes.isSecure, + cookieAttributes.isHttpOnly, aStatus, aPolicy); if (!cookie) { @@ -1229,7 +1291,7 @@ } // add the cookie to the list. AddInternal() takes care of logging. - AddInternal(cookie, NOW_IN_SECONDS, aHostURI, cookieHeader); + AddInternal(cookie, NOW_IN_SECONDS, aHostURI, cookieHeader, aFromHttp); return newCookie; } @@ -1242,8 +1304,15 @@ nsCookieService::AddInternal(nsCookie *aCookie, nsInt64 aCurrentTime, nsIURI *aHostURI, - const char *aCookieHeader) + const char *aCookieHeader, + PRBool aFromHttp) { + // if the new cookie is httponly, make sure we're not coming from script + if (!aFromHttp && aCookie->IsHttpOnly()) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script"); + return; + } + nsListIter matchIter; const PRBool foundCookie = FindCookie(aCookie->Host(), aCookie->Name(), aCookie->Path(), matchIter); @@ -1251,6 +1320,13 @@ nsRefPtr oldCookie; if (foundCookie) { oldCookie = matchIter.current; + + // if the old cookie is httponly, make sure we're not coming from script + if (!aFromHttp && oldCookie->IsHttpOnly()) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "previously stored cookie is httponly; coming from script"); + return; + } + RemoveCookieFromList(matchIter); // check if the cookie has expired @@ -1343,6 +1419,9 @@ trivial case, but allows the flexibility of setting only a cookie with a blank and is required by some sites (see bug 169091). + 6. Attribute "HttpOnly", not covered in the RFCs, is supported + (see bug 178993). + ** Begin BNF: token = 1* value = token-value | quoted-string @@ -1377,6 +1456,7 @@ | "Comment" "=" value | "Version" "=" value | "Secure" + | "HttpOnly" ******************************************************************************/ @@ -1483,6 +1563,7 @@ static const char kExpires[] = "expires"; static const char kMaxage[] = "max-age"; static const char kSecure[] = "secure"; + static const char kHttpOnly[] = "httponly"; nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd; nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd; @@ -1491,6 +1572,8 @@ aCookieAttributes.isSecure = PR_FALSE; + aCookieAttributes.isHttpOnly = PR_FALSE; + nsDependentCSubstring tokenString(cookieStart, cookieStart); nsDependentCSubstring tokenValue (cookieStart, cookieStart); PRBool newCookie, equalsFound; @@ -1537,6 +1620,11 @@ // ignore any tokenValue for isSecure; just set the boolean else if (tokenString.LowerCaseEqualsLiteral(kSecure)) aCookieAttributes.isSecure = PR_TRUE; + + // ignore any tokenValue for isHttpOnly (see bug 178993); + // just set the boolean + else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) + aCookieAttributes.isHttpOnly = PR_TRUE; } // rebind aCookieHeader, in case we need to process another cookie =================================================================== RCS file: /cvsroot/mozilla/netwerk/cookie/src nsCookieService.h,v retrieving revision 1.17 retrieving revision 1.17.24.1 diff -u -r1.17 -r1.17.24.1 --- mozilla/netwerk/cookie/src/nsCookieService.h 2004/06/26 18:47:01 1.17 +++ mozilla/netwerk/cookie/src/nsCookieService.h 2007/07/11 03:18:15 1.17.24.1 @@ -171,8 +171,10 @@ void PrefChanged(nsIPrefBranch *aPrefBranch); nsresult Read(); nsresult Write(); - PRBool SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, nsDependentCString &aCookieHeader, nsInt64 aServerTime, nsCookieStatus aStatus, nsCookiePolicy aPolicy); - void AddInternal(nsCookie *aCookie, nsInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader); + nsresult GetCookieInternal(nsIURI *aHostURI, nsIURI *aFirstURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie); + nsresult SetCookieStringInternal(nsIURI *aHostURI, nsIURI *aFirstURI, nsIPrompt *aPrompt, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, PRBool aFromHttp); + PRBool SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, nsDependentCString &aCookieHeader, nsInt64 aServerTime, PRBool aFromHttp, nsCookieStatus aStatus, nsCookiePolicy aPolicy); + void AddInternal(nsCookie *aCookie, nsInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp); void RemoveCookieFromList(nsListIter &aIter); PRBool AddCookieToList(nsCookie *aCookie); static PRBool GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, PRBool &aEqualsFound);