Bug 475156 - correcting cache behavior when using custom conditional request headers, r=bzbarsky

This commit is contained in:
Honza Bambas 2009-10-21 12:18:08 +02:00
parent ad4fd319b0
commit 6d9c76510f
5 changed files with 348 additions and 27 deletions

View File

@ -321,6 +321,8 @@ _TEST_FILES = test_bug5141.html \
test_classList.html \
test_bug514487.html \
test_range_bounds.html \
test_bug475156.html \
bug475156.sjs \
$(NULL)
# Disabled; see bug 492181

View File

@ -0,0 +1,27 @@
function handleRequest(request, response)
{
if (request.queryString == "")
{
var etag = request.hasHeader("If-Match") ? request.getHeader("If-Match") : null;
if (!etag || etag == getState("etag"))
{
response.setStatusLine(request.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/html");
response.setHeader("ETag", getState("etag"));
response.setHeader("Cache-control", "max-age=36000");
response.write(getState("etag"));
}
else if (etag)
{
response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
}
}
else
{
var etag = request.queryString.match(/^etag=(.*)$/);
if (etag)
setState("etag", etag[1]);
response.setStatusLine(request.httpVersion, 204, "No content");
}
}

View File

@ -0,0 +1,288 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=475156
-->
<head>
<title>Test for Bug 475156</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="drive(tests.shift());">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var path = "http://localhost:8888/tests/content/base/test/";
function fromCache(xhr)
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var ch = xhr.channel.QueryInterface(Components.interfaces.nsICachingChannel);
return ch.isFromCache();
}
var tests = [
// First just init the file with an ETag
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs?etag=a1");
},
loading: function(xhr)
{
},
done: function(xhr)
{
},
},
// Try to load the file the first time regularly, we have to get 200 OK
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
},
loading: function(xhr)
{
is(fromCache(xhr), false, "Not coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We get a fresh version of the file");
is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
is(xhr.responseText, "a1", "We got the expected file body");
},
},
// Try to load the file the second time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a1");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
is(xhr.responseText, "a1", "We got the expected file body");
},
},
// Try to load the file the third time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a1");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a1", "We got correct ETag");
is(xhr.responseText, "a1", "We got the expected file body");
},
},
// Now modify the ETag
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs?etag=a2");
},
loading: function(xhr)
{
},
done: function(xhr)
{
},
},
// Try to load the file, we have to get 200 OK with the new content
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a2");
},
loading: function(xhr)
{
is(fromCache(xhr), false, "Not coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We get a fresh version of the file");
is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
is(xhr.responseText, "a2", "We got the expected file body");
},
},
// Try to load the file the second time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a2");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
is(xhr.responseText, "a2", "We got the expected file body");
},
},
// Try to load the file the third time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a2");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a2", "We got correct ETag");
is(xhr.responseText, "a2", "We got the expected file body");
},
},
// Now modify the ETag ones more
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs?etag=a3");
},
loading: function(xhr)
{
},
done: function(xhr)
{
},
},
// Try to load the file, we have to get 200 OK with the new content
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a3");
},
loading: function(xhr)
{
is(fromCache(xhr), false, "Not coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We get a fresh version of the file");
is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
is(xhr.responseText, "a3", "We got the expected file body");
},
},
// Try to load the file the second time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a3");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
is(xhr.responseText, "a3", "We got the expected file body");
},
},
// Try to load the file the third time regularly, we have to get 304 Not Modified
{
init: function(xhr)
{
xhr.open("GET", path + "bug475156.sjs");
xhr.setRequestHeader("If-Match", "a3");
},
loading: function(xhr)
{
is(fromCache(xhr), true, "Coming from the cache");
},
done: function(xhr)
{
is(xhr.status, 200, "We got cached version");
is(xhr.getResponseHeader("Etag"), "a3", "We got correct ETag");
is(xhr.responseText, "a3", "We got the expected file body");
},
},
]
function drive(test)
{
var xhr = new XMLHttpRequest();
test.init(xhr);
xhr.onreadystatechange = function() {
if (this.readyState == 3) {
test.loading(this);
}
if (this.readyState == 4) {
test.done(this);
if (tests.length == 0)
SimpleTest.finish();
else
drive(tests.shift());
}
}
xhr.send();
}
</script>
</pre>
</body>
</html>

View File

@ -138,6 +138,7 @@ nsHttpChannel::nsHttpChannel()
, mLoadedFromApplicationCache(PR_FALSE)
, mTracingEnabled(PR_TRUE)
, mForceAllowThirdPartyCookie(PR_FALSE)
, mCustomConditionalRequest(PR_FALSE)
{
LOG(("Creating nsHttpChannel @%x\n", this));
@ -273,17 +274,6 @@ nsHttpChannel::AsyncCall(nsAsyncCallback funcPtr,
return rv;
}
PRBool
nsHttpChannel::RequestIsConditional()
{
// Is our consumer issuing a conditional request?
return mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Range);
}
nsresult
nsHttpChannel::Connect(PRBool firstTime)
{
@ -1501,6 +1491,9 @@ nsHttpChannel::ProcessNotModified()
LOG(("nsHttpChannel::ProcessNotModified [this=%x]\n", this));
if (mCustomConditionalRequest)
return NS_ERROR_FAILURE;
NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
@ -1682,12 +1675,6 @@ nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
if (IsSubRangeRequest(mRequestHead))
return NS_OK;
if (RequestIsConditional()) {
// don't use the cache if our consumer is making a conditional request
// (see bug 331825).
return NS_OK;
}
GenerateCacheKey(mPostID, cacheKey);
// Get a cache session with appropriate storage policy
@ -1882,12 +1869,6 @@ nsHttpChannel::OpenOfflineCacheEntryForWriting()
if (IsSubRangeRequest(mRequestHead))
return NS_OK;
if (RequestIsConditional()) {
// don't use the cache if our consumer is making a conditional request
// (see bug 331825).
return NS_OK;
}
nsCAutoString cacheKey;
GenerateCacheKey(mPostID, cacheKey);
@ -2108,9 +2089,12 @@ nsHttpChannel::CheckCache()
PRBool doValidation = PR_FALSE;
PRBool canAddImsHeader = PR_TRUE;
// Be optimistic: assume that we won't need to do validation
mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
mRequestHead.ClearHeader(nsHttp::If_None_Match);
mCustomConditionalRequest =
mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Range);
// If the LOAD_FROM_CACHE flag is set, any cached data can simply be used.
if (mLoadFlags & LOAD_FROM_CACHE) {
@ -2187,6 +2171,20 @@ nsHttpChannel::CheckCache()
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
}
if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) &&
(method == nsHttp::Get || method == nsHttp::Head)) {
const char *requestedETag, *cachedETag;
cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag);
requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match);
if (cachedETag && (!strncmp(cachedETag, "W/", 2) ||
strcmp(requestedETag, cachedETag))) {
// User has defined If-Match header, if the cached entry is not
// matching the provided header value or the cached ETag is weak,
// force validation.
doValidation = PR_TRUE;
}
}
if (!doValidation) {
//
// Check the authorization headers used to generate the cache entry.
@ -2226,9 +2224,11 @@ nsHttpChannel::CheckCache()
//
// the request method MUST be either GET or HEAD (see bug 175641).
//
// do not override conditional headers when consumer has defined its own
if (!mCachedResponseHead->NoStore() &&
(mRequestHead.Method() == nsHttp::Get ||
mRequestHead.Method() == nsHttp::Head)) {
mRequestHead.Method() == nsHttp::Head) &&
!mCustomConditionalRequest) {
const char *val;
// Add If-Modified-Since header if a Last-Modified was given
// and we are allowed to do this (see bugs 510359 and 269303)

View File

@ -385,6 +385,10 @@ private:
PRUint32 mLoadedFromApplicationCache : 1;
PRUint32 mTracingEnabled : 1;
PRUint32 mForceAllowThirdPartyCookie : 1;
// True if consumer added it's own If-None-Match or If-Modified-Since
// headers. In such a case we must not override them in the cache code
// and also we want to pass possible 304 code response through.
PRUint32 mCustomConditionalRequest : 1;
class nsContentEncodings : public nsIUTF8StringEnumerator
{