Bug 561276 - Cookie dependency on cache determination for image redirects r=bz

This commit is contained in:
bjarne@runitsoft.com 2011-03-23 23:36:53 -04:00
parent a119175fc3
commit 884a180af5
5 changed files with 146 additions and 7 deletions

View File

@ -76,6 +76,7 @@ HttpBaseChannel::HttpBaseChannel()
, mChooseApplicationCache(PR_FALSE) , mChooseApplicationCache(PR_FALSE)
, mLoadedFromApplicationCache(PR_FALSE) , mLoadedFromApplicationCache(PR_FALSE)
, mChannelIsForDownload(PR_FALSE) , mChannelIsForDownload(PR_FALSE)
, mRedirectedCachekeys(nsnull)
{ {
LOG(("Creating HttpBaseChannel @%x\n", this)); LOG(("Creating HttpBaseChannel @%x\n", this));
@ -87,6 +88,9 @@ HttpBaseChannel::~HttpBaseChannel()
{ {
LOG(("Destroying HttpBaseChannel @%x\n", this)); LOG(("Destroying HttpBaseChannel @%x\n", this));
// Make sure we don't leak
CleanRedirectCacheChainIfNecessary();
gHttpHandler->Release(); gHttpHandler->Release();
} }
@ -1166,6 +1170,13 @@ HttpBaseChannel::SetChannelIsForDownload(PRBool aChannelIsForDownload)
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys)
{
mRedirectedCachekeys = cacheKeys;
return NS_OK;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupportsPriority // HttpBaseChannel::nsISupportsPriority
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -1384,6 +1395,15 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI,
httpInternal->SetDocumentURI(newURI); httpInternal->SetDocumentURI(newURI);
else else
httpInternal->SetDocumentURI(mDocumentURI); httpInternal->SetDocumentURI(mDocumentURI);
// if there is a chain of keys for redirect-responses we transfer it to
// the new channel (see bug #561276)
if (mRedirectedCachekeys) {
LOG(("HttpBaseChannel::SetupReplacementChannel "
"[this=%p] transferring chain of redirect cache-keys", this));
httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys);
mRedirectedCachekeys = nsnull;
}
} }
// transfer application cache information // transfer application cache information

View File

@ -154,6 +154,14 @@ public:
NS_IMETHOD GetCanceled(PRBool *aCanceled); NS_IMETHOD GetCanceled(PRBool *aCanceled);
NS_IMETHOD GetChannelIsForDownload(PRBool *aChannelIsForDownload); NS_IMETHOD GetChannelIsForDownload(PRBool *aChannelIsForDownload);
NS_IMETHOD SetChannelIsForDownload(PRBool aChannelIsForDownload); NS_IMETHOD SetChannelIsForDownload(PRBool aChannelIsForDownload);
NS_IMETHOD SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys);
inline void CleanRedirectCacheChainIfNecessary()
{
if (mRedirectedCachekeys) {
delete mRedirectedCachekeys;
mRedirectedCachekeys = nsnull;
}
}
// nsISupportsPriority // nsISupportsPriority
NS_IMETHOD GetPriority(PRInt32 *value); NS_IMETHOD GetPriority(PRInt32 *value);
@ -250,6 +258,8 @@ protected:
PRUint32 mChooseApplicationCache : 1; PRUint32 mChooseApplicationCache : 1;
PRUint32 mLoadedFromApplicationCache : 1; PRUint32 mLoadedFromApplicationCache : 1;
PRUint32 mChannelIsForDownload : 1; PRUint32 mChannelIsForDownload : 1;
nsTArray<nsCString> *mRedirectedCachekeys;
}; };

View File

@ -384,6 +384,9 @@ nsHttpChannel::DoNotifyListener()
// We have to make sure to drop the reference to the callbacks too // We have to make sure to drop the reference to the callbacks too
mCallbacks = nsnull; mCallbacks = nsnull;
mProgressSink = nsnull; mProgressSink = nsnull;
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
} }
void void
@ -2637,12 +2640,27 @@ nsHttpChannel::CheckCache()
(buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization)); (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
} }
if (!doValidation) { // Bug #561276: We maintain a chain of cache-keys which returns cached
// Sites redirect back to the original URI after setting a session/tracking // 3xx-responses (redirects) in order to detect cycles. If a cycle is
// cookie. In such cases, force revalidation so that we hit the net and do not // found, ignore the cached response and hit the net. Otherwise, use
// cycle thru cached responses. // the cached response and add the cache-key to the chain. Note that
if (isCachedRedirect && mRequestHead.PeekHeader(nsHttp::Cookie)) // a limited number of redirects (cached or not) is allowed and is
// enforced independently of this mechanism
if (!doValidation && isCachedRedirect) {
nsCAutoString cacheKey;
GenerateCacheKey(mPostID, cacheKey);
if (!mRedirectedCachekeys)
mRedirectedCachekeys = new nsTArray<nsCString>();
else if (mRedirectedCachekeys->Contains(cacheKey))
doValidation = PR_TRUE; doValidation = PR_TRUE;
LOG(("Redirection-chain %s key %s\n",
doValidation ? "contains" : "does not contain", cacheKey.get()));
// Append cacheKey if not in the chain already
if (!doValidation)
mRedirectedCachekeys->AppendElement(cacheKey);
} }
mCachedContentIsValid = !doValidation; mCachedContentIsValid = !doValidation;
@ -2678,6 +2696,9 @@ nsHttpChannel::CheckCache()
mRequestHead.SetHeader(nsHttp::If_None_Match, mRequestHead.SetHeader(nsHttp::If_None_Match,
nsDependentCString(val)); nsDependentCString(val));
} }
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
} }
LOG(("nsHTTPChannel::CheckCache exit [this=%p doValidation=%d]\n", this, doValidation)); LOG(("nsHTTPChannel::CheckCache exit [this=%p doValidation=%d]\n", this, doValidation));
@ -4043,6 +4064,9 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
if (mLoadGroup) if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, status); mLoadGroup->RemoveRequest(this, nsnull, status);
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
mCallbacks = nsnull; mCallbacks = nsnull;
mProgressSink = nsnull; mProgressSink = nsnull;

View File

@ -37,6 +37,12 @@
#include "nsISupports.idl" #include "nsISupports.idl"
%{C++
#include "nsTArray.h"
class nsCString;
%}
[ptr] native StringArray(nsTArray<nsCString>);
interface nsIURI; interface nsIURI;
interface nsIProxyInfo; interface nsIProxyInfo;
@ -45,7 +51,7 @@ interface nsIProxyInfo;
* using any feature exposed by this interface, be aware that this interface * using any feature exposed by this interface, be aware that this interface
* will change and you will be broken. You have been warned. * will change and you will be broken. You have been warned.
*/ */
[scriptable, uuid(9fb2a161-d075-4bf2-b07a-26bac650cc81)] [scriptable, uuid(44e35ead-6656-4f9e-b51d-ebaf1bee6e2e)]
interface nsIHttpChannelInternal : nsISupports interface nsIHttpChannelInternal : nsISupports
{ {
/** /**
@ -93,4 +99,9 @@ interface nsIHttpChannelInternal : nsISupports
* Lets externalhandler tell the channel it is open on behalf of a download * Lets externalhandler tell the channel it is open on behalf of a download
*/ */
attribute boolean channelIsForDownload; attribute boolean channelIsForDownload;
/**
* Transfer chain of redirected cache-keys
*/
[noscript] void setCacheKeysRedirectChain(in StringArray cacheKeys);
}; };

View File

@ -0,0 +1,74 @@
//
// Verify that we hit the net if we discover a cycle of redirects
// coming from cache.
//
do_load_httpd_js();
var httpserver = new nsHttpServer();
var iteration = 0;
function getCacheService()
{
return Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
}
function setupChannel(suffix)
{
var ios =
Components.classes["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var chan = ios.newChannel("http://localhost:4444" + suffix, "", null);
var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
httpChan.requestMethod = "GET";
return httpChan;
}
function checkValueAndTrigger(request, data, ctx)
{
do_check_eq("Ok", data);
httpserver.stop(do_test_finished);
}
function run_test()
{
httpserver.registerPathHandler("/redirect1", redirectHandler1);
httpserver.registerPathHandler("/redirect2", redirectHandler2);
httpserver.start(4444);
// clear cache
getCacheService().
evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
// load first time
var channel = setupChannel("/redirect1");
channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null), null);
do_test_pending();
}
function redirectHandler1(metadata, response)
{
// first time we return a cacheable 302 pointing to next redirect
if (iteration < 1) {
response.setStatusLine(metadata.httpVersion, 302, "Found");
response.setHeader("Cache-Control", "max-age=600", false);
response.setHeader("Location", "/redirect2", false);
// next time called we return 200
} else {
response.setStatusLine(metadata.httpVersion, 200, "Ok");
response.setHeader("Cache-Control", "max-age=600", false);
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write("Ok", "Ok".length);
}
iteration += 1;
}
function redirectHandler2(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 302, "Found");
response.setHeader("Cache-Control", "max-age=600", false);
response.setHeader("Location", "/redirect1", false);
}