Bug 460335 - disallow redirects when updating an application cache, r=dcamp, sr=cbiesinger

This commit is contained in:
Honza Bambas 2009-02-27 15:09:06 +01:00
Родитель da8a84b61d
Коммит 2e9f407d6e
10 изменённых файлов: 349 добавлений и 17 удалений

Просмотреть файл

@ -66,6 +66,8 @@ _TEST_FILES = \
test_foreign.html \ test_foreign.html \
test_fallback.html \ test_fallback.html \
test_overlap.html \ test_overlap.html \
test_redirectManifest.html \
test_redirectUpdateItem.html \
overlap.cacheManifest \ overlap.cacheManifest \
overlap.cacheManifest^headers^ \ overlap.cacheManifest^headers^ \
test_updatingManifest.html \ test_updatingManifest.html \
@ -84,6 +86,8 @@ _TEST_FILES = \
bypass.cacheManifest \ bypass.cacheManifest \
bypass.cacheManifest^headers^ \ bypass.cacheManifest^headers^ \
bypass.html \ bypass.html \
dynamicRedirect.sjs \
explicitRedirect.sjs \
fallback.html \ fallback.html \
fallback2.html \ fallback2.html \
fallbackTop.html \ fallbackTop.html \
@ -99,8 +103,10 @@ _TEST_FILES = \
onwhitelist.html^headers^ \ onwhitelist.html^headers^ \
updatingIframe.sjs \ updatingIframe.sjs \
updatingImplicit.html \ updatingImplicit.html \
manifestRedirect.sjs \
missingFile.cacheManifest \ missingFile.cacheManifest \
missingFile.cacheManifest^headers^ \ missingFile.cacheManifest^headers^ \
redirects.sjs \
simpleManifest.cacheManifest \ simpleManifest.cacheManifest \
simpleManifest.cacheManifest^headers^ \ simpleManifest.cacheManifest^headers^ \
updatingManifest.sjs \ updatingManifest.sjs \

Просмотреть файл

@ -0,0 +1,27 @@
function handleRequest(request, response)
{
var match = request.queryString.match(/^state=(.*)$/);
if (match)
{
response.setStatusLine(request.httpVersion, 200, "No content");
setState("state", match[1]);
response.write("state='" + match[1] + "'");
}
if (request.queryString == "")
{
switch (getState("state"))
{
case "": // The default value
response.setStatusLine(request.httpVersion, 307, "Moved temporarly");
response.setHeader("Location", "http://example.com/non-existing-dynamic.html");
response.setHeader("Content-Type", "text/html");
break;
case "on":
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/html");
response.write("<html><body>Dynamic page</body></html>");
break;
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
function handleRequest(request, response)
{
var match = request.queryString.match(/^state=(.*)$/);
if (match)
{
response.setStatusLine(request.httpVersion, 200, "No content");
setState("state", match[1]);
response.write("state='" + match[1] + "'");
}
if (request.queryString == "")
{
switch (getState("state"))
{
case "": // The default value
response.setStatusLine(request.httpVersion, 307, "Moved temporarly");
response.setHeader("Location", "http://example.com/non-existing-explicit.html");
response.setHeader("Content-Type", "text/html");
break;
case "on":
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/html");
response.write("<html><body>Explicit page</body></html>");
break;
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 307, "Moved temporarly");
response.setHeader("Location", "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/updating.cacheManifest");
response.setHeader("Content-Type", "text/cache-manifest");
}

Просмотреть файл

@ -44,6 +44,8 @@ applicationCache.oncached = function() {
// Now delete the manifest and refresh, we should get an "obsolete" message. // Now delete the manifest and refresh, we should get an "obsolete" message.
applicationCache.oncached = fail; applicationCache.oncached = fail;
applicationCache.onupdateready = fail; applicationCache.onupdateready = fail;
applicationCache.onnoupdate = fail;
applicationCache.onerror = fail;
applicationCache.onobsolete = obsolete; applicationCache.onobsolete = obsolete;
// Make the obsoleting.sjs return 404 NOT FOUND code // Make the obsoleting.sjs return 404 NOT FOUND code

Просмотреть файл

@ -0,0 +1,55 @@
ver1manifest =
"CACHE MANIFEST\n" +
"# v1\n" +
"\n" +
"http://localhost:8888/tests/SimpleTest/SimpleTest.js\n" +
"http://localhost:8888/MochiKit/packed.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs";
ver2manifest =
"CACHE MANIFEST\n" +
"# v2\n" +
"\n" +
"http://localhost:8888/tests/SimpleTest/SimpleTest.js\n" +
"http://localhost:8888/MochiKit/packed.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs";
ver3manifest =
"CACHE MANIFEST\n" +
"# v3\n" +
"\n" +
"http://localhost:8888/tests/SimpleTest/SimpleTest.js\n" +
"http://localhost:8888/MochiKit/packed.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js\n" +
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs";
function handleRequest(request, response)
{
var match = request.queryString.match(/^state=(.*)$/);
if (match)
{
response.setStatusLine(request.httpVersion, 204, "No content");
setState("state", match[1]);
}
if (request.queryString == "")
{
response.setStatusLine(request.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/cache-manifest");
response.setHeader("Cache-Control", "no-cache");
switch (getState("state"))
{
case "": // The default value
response.write(ver1manifest + "\n#" + getState("state"));
break;
case "second":
response.write(ver2manifest + "\n#" + getState("state"));
break;
case "third":
response.write(ver3manifest + "\n#" + getState("state"));
break;
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/manifestRedirect.sjs">
<head>
<title>Fail update on manifest redirection test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script class="testbody" type="text/javascript">
/**
*/
function manifestCached()
{
OfflineTests.ok(false, "Manifest must not be cached");
finish();
}
function manifestError()
{
OfflineTest.ok(true, "Error expected");
finish();
}
function finish()
{
OfflineTest.teardown();
OfflineTest.finish();
}
SimpleTest.waitForExplicitFinish();
if (OfflineTest.setup()) {
applicationCache.onerror = OfflineTest.priv(manifestError);
applicationCache.oncached = OfflineTest.priv(manifestCached);
}
</script>
</head>
<body>
</body>
</html>

Просмотреть файл

@ -0,0 +1,128 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.sjs">
<head>
<title>Entries redirection handling during update test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script class="testbody" type="text/javascript">
var gCurrentManifestVersion = 1;
function manifestCached()
{
OfflineTest.checkCache(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs", false);
OfflineTest.checkCache(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs", true);
OfflineTest.is(gCurrentManifestVersion, 1, "Cached event for manifest version one");
// Now add one dynamic entry (now with content overriden redirect sjs)
applicationCache.mozAdd(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs");
// Wait for the dynamic entry be added to the cache...
OfflineTest.waitForAdd(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs",
function() {
// ...check it is there...
OfflineTest.checkCache(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs", true);
// ...revert state of the dynamic entry on the server, now we get the redirect...
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs",
"");
// ...update manifest to the new version on the server...
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.sjs",
"second");
gCurrentManifestVersion = 2;
// ...and finally invoke the cache update.
applicationCache.update();
});
}
function manifestUpdated()
{
switch (gCurrentManifestVersion)
{
case 2:
// Check the dynamic entry was removed from the cache (because of the redirect)...
OfflineTest.checkCache(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs", false);
// ...return back redirect for the explicit entry...
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs",
"");
// ...update the manifest to the third version...
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.sjs",
"third");
gCurrentManifestVersion = 3;
// ...and invoke the cache update, now we must get error.
applicationCache.update();
break;
case 3:
OfflineTest.ok(false, "Update didn't fail for third version of the manifest");
finish();
break;
}
}
function manifestError()
{
switch (gCurrentManifestVersion)
{
case 1:
OfflineTest.ok(false, "Error not expected when caching the first version of the manifest");
finish();
break;
case 2:
OfflineTest.ok(false, "Error not expected when updating to second version of the manifest");
finish();
break;
case 3:
OfflineTest.ok(true, "Error expected when updating to third version of the manifest");
finish();
break;
}
}
function finish()
{
OfflineTest.teardown();
OfflineTest.finish();
}
SimpleTest.waitForExplicitFinish();
if (OfflineTest.setup()) {
applicationCache.onerror = OfflineTest.priv(manifestError);
applicationCache.onupdateready = OfflineTest.priv(manifestUpdated);
applicationCache.oncached = OfflineTest.priv(manifestCached);
// Override sjs redirects on the server, it will now return 200 OK and the content
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs",
"on");
OfflineTest.setSJSState(
"http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs",
"on");
}
</script>
</head>
<body>
</body>
</html>

Просмотреть файл

@ -494,6 +494,13 @@ nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel, nsIChannel *aNewChannel,
PRUint32 aFlags) PRUint32 aFlags)
{ {
if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
// Don't allow redirect in case of non-internal redirect and cancel
// the channel to clean the cache entry.
aOldChannel->Cancel(NS_ERROR_ABORT);
return NS_ERROR_ABORT;
}
nsCOMPtr<nsIURI> newURI; nsCOMPtr<nsIURI> newURI;
nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI)); nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
if (NS_FAILED(rv)) if (NS_FAILED(rv))
@ -502,7 +509,7 @@ nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
nsCOMPtr<nsICachingChannel> oldCachingChannel = nsCOMPtr<nsICachingChannel> oldCachingChannel =
do_QueryInterface(aOldChannel); do_QueryInterface(aOldChannel);
nsCOMPtr<nsICachingChannel> newCachingChannel = nsCOMPtr<nsICachingChannel> newCachingChannel =
do_QueryInterface(aOldChannel); do_QueryInterface(aNewChannel);
if (newCachingChannel) { if (newCachingChannel) {
rv = newCachingChannel->SetCacheForOfflineUse(PR_TRUE); rv = newCachingChannel->SetCacheForOfflineUse(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -581,6 +588,42 @@ nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState)
return NS_OK; return NS_OK;
} }
nsresult
nsOfflineCacheUpdateItem::GetRequestSucceeded(PRBool * succeeded)
{
*succeeded = PR_FALSE;
if (!mChannel)
return NS_OK;
nsresult rv;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRBool reqSucceeded;
rv = httpChannel->GetRequestSucceeded(&reqSucceeded);
if (NS_ERROR_NOT_AVAILABLE == rv)
return NS_OK;
NS_ENSURE_SUCCESS(rv, rv);
if (!reqSucceeded) {
LOG(("Request failed"));
return NS_OK;
}
nsresult channelStatus;
rv = httpChannel->GetStatus(&channelStatus);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(channelStatus)) {
LOG(("Channel status=0x%08x", channelStatus));
return NS_OK;
}
*succeeded = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus) nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
{ {
@ -596,15 +639,6 @@ nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
PRUint32 httpStatus; PRUint32 httpStatus;
rv = httpChannel->GetResponseStatus(&httpStatus); rv = httpChannel->GetResponseStatus(&httpStatus);
if (rv == NS_ERROR_NOT_AVAILABLE) { if (rv == NS_ERROR_NOT_AVAILABLE) {
// Someone's calling this before we got a response... Check our
// ReadyState. If we're at RECEIVING or LOADED, then this means the
// connection errored before we got any data; return a somewhat
// sensible error code in that case.
if (mState >= nsIDOMLoadStatus::RECEIVING) {
*aStatus = NS_ERROR_NOT_AVAILABLE;
return NS_OK;
}
*aStatus = 0; *aStatus = 0;
return NS_OK; return NS_OK;
} }
@ -1241,11 +1275,11 @@ nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
// Be pessimistic // Be pessimistic
*aDoUpdate = PR_FALSE; *aDoUpdate = PR_FALSE;
PRUint16 status; PRBool succeeded;
nsresult rv = mManifestItem->GetStatus(&status); nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (status == 0 || status >= 400 || !mManifestItem->ParseSucceeded()) { if (!succeeded || !mManifestItem->ParseSucceeded()) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
@ -1375,12 +1409,12 @@ nsOfflineCacheUpdate::LoadCompleted()
nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem]; nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
mCurrentItem++; mCurrentItem++;
PRUint16 status; PRBool succeeded;
rv = item->GetStatus(&status); rv = item->GetRequestSucceeded(&succeeded);
// Check for failures. 4XX and 5XX errors on items explicitly // Check for failures. 3XX, 4XX and 5XX errors on items explicitly
// listed in the manifest will cause the update to fail. // listed in the manifest will cause the update to fail.
if (NS_FAILED(rv) || status == 0 || status >= 400) { if (NS_FAILED(rv) || !succeeded) {
if (item->mItemType & if (item->mItemType &
(nsIApplicationCache::ITEM_EXPLICIT | (nsIApplicationCache::ITEM_EXPLICIT |
nsIApplicationCache::ITEM_FALLBACK)) { nsIApplicationCache::ITEM_FALLBACK)) {

Просмотреть файл

@ -102,6 +102,7 @@ public:
nsresult OpenChannel(); nsresult OpenChannel();
nsresult Cancel(); nsresult Cancel();
nsresult GetRequestSucceeded(PRBool * succeeded);
private: private:
nsOfflineCacheUpdate* mUpdate; nsOfflineCacheUpdate* mUpdate;