Bug 460335 - disallow redirects when updating an application cache, r=dcamp, sr=cbiesinger
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 05 Feb 2009 20:28:15 +0100
changeset 23147 8e8c7450183e4b716b2880f148e80292a3607b75
parent 23146 df442c0af61fa3a45fbefea201af00bbb265a149
child 23153 4873438d9f784d0ca5113c051cfb938e5bb35989
push id621
push userhonzab.moz@firemni.cz
push dateThu, 05 Feb 2009 19:31:27 +0000
reviewersdcamp, cbiesinger
bugs460335
milestone1.9.1b3pre
Bug 460335 - disallow redirects when updating an application cache, r=dcamp, sr=cbiesinger
dom/tests/mochitest/ajax/offline/Makefile.in
dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs
dom/tests/mochitest/ajax/offline/explicitRedirect.sjs
dom/tests/mochitest/ajax/offline/manifestRedirect.sjs
dom/tests/mochitest/ajax/offline/obsolete.html
dom/tests/mochitest/ajax/offline/redirects.cacheManifest
dom/tests/mochitest/ajax/offline/redirects.cacheManifest^headers^
dom/tests/mochitest/ajax/offline/test_redirectManifest.html
dom/tests/mochitest/ajax/offline/test_redirectUpdateItem.html
uriloader/prefetch/nsOfflineCacheUpdate.cpp
uriloader/prefetch/nsOfflineCacheUpdate.h
old mode 100644
new mode 100755
--- a/dom/tests/mochitest/ajax/offline/Makefile.in
+++ b/dom/tests/mochitest/ajax/offline/Makefile.in
@@ -59,16 +59,18 @@ include $(topsrcdir)/config/rules.mk
 	test_changingManifest.html \
 	test_refetchManifest.html \
 	test_offlineIFrame.html \
 	test_offlineMode.html \
 	test_bug445544.html \
 	test_foreign.html \
 	test_fallback.html \
 	test_updatingManifest.html \
+	test_redirectManifest.html \
+	test_redirectUpdateItem.html \
 	445544_part1.html \
 	445544_part2.html \
 	445544.cacheManifest \
 	445544.cacheManifest^headers^ \
 	test_bug460353.html \
 	460353_iframe_nomanifest.html \
 	460353_iframe_ownmanifest.html \
 	460353_iframe_samemanifest.html \
@@ -94,20 +96,25 @@ include $(topsrcdir)/config/rules.mk
 	onwhitelist.html^headers^ \
 	updatingIFrame.html \
 	updatingIFrame.html^headers^ \
 	updatingImplicit.html \
 	missingFile.cacheManifest \
 	missingFile.cacheManifest^headers^ \
 	simpleManifest.cacheManifest \
 	simpleManifest.cacheManifest^headers^ \
+	redirects.cacheManifest \
+	redirects.cacheManifest^headers^ \
 	updating.cacheManifest \
 	updating.cacheManifest^headers^ \
 	simpleManifest.notmanifest \
 	changing1Sec.sjs \
 	changing1Hour.sjs \
+	dynamicRedirect.sjs \
+	explicitRedirect.sjs \
+	manifestRedirect.sjs \
 	changingManifest.cacheManifest \
 	changingManifest.cacheManifest^headers^ \
 	offlineChild.html \
 	$(NULL)
 
 libs::	$(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response)
+{
+  response.setStatusLine(request.httpVersion, 307, "Moved temporarly");
+  response.setHeader("Location", "http://example.com/non-existing-dynamic.html");
+  response.setHeader("Content-Type", "text/html");
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response)
+{
+  response.setStatusLine(request.httpVersion, 307, "Moved temporarly");
+  response.setHeader("Location", "http://example.com/non-existing-explicit.html");
+  response.setHeader("Content-Type", "text/html");
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/manifestRedirect.sjs
@@ -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");
+}
--- a/dom/tests/mochitest/ajax/offline/obsolete.html
+++ b/dom/tests/mochitest/ajax/offline/obsolete.html
@@ -37,16 +37,18 @@ applicationCache.oncached = function() {
     applicationCache.swapCache();
     window.opener.todo(false, "We shouldn't have to swapCache in the oncached handler (bug 443023)");
   } catch(e) {
   }
 
   // Now delete the manifest and refresh, we should get an "obsolete" message.
   applicationCache.oncached = fail;
   applicationCache.onupdateready = fail;
+  applicationCache.onnoupdate = fail;
+  applicationCache.onerror = fail;
   applicationCache.onobsolete = obsolete;
 
   var req = new XMLHttpRequest();
   req.open("DELETE", "obsolete.cacheManifest");
   req.send("");
   req.onreadystatechange = function() {
     if (req.readyState == 4) {
       applicationCache.update();
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/redirects.cacheManifest
@@ -0,0 +1,8 @@
+CACHE MANIFEST
+# v1
+
+http://localhost:8888/tests/SimpleTest/SimpleTest.js
+http://localhost:8888/MochiKit/packed.js
+http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
+
+http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/redirects.cacheManifest^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Type: text/cache-manifest
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/test_redirectManifest.html
@@ -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>
new file mode 100755
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/test_redirectUpdateItem.html
@@ -0,0 +1,151 @@
+<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.cacheManifest">
+<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">
+
+/**
+ */
+
+// The manifest is always the same, we just update its version comment
+var manifestVersion2Content =
+  "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";
+
+var manifestVersion3Content =
+  "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";
+
+
+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);
+
+      // ...delete content of the dynamic entry from the server, now we get the redirect...
+      OfflineTest.deleteData(
+        "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs");
+
+      // ...update manifest to the new version on the server...
+      OfflineTest.putData(
+        "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.cacheManifest",
+        "text/cache-manifest", manifestVersion2Content);
+      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.deleteData(
+        "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs");
+
+      // ...update the manifest to the third version...
+      OfflineTest.putData(
+        "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/redirects.cacheManifest",
+        "text/cache-manifest", manifestVersion3Content);
+      gCurrentManifestVersion = 3;
+
+      // ...and invoke the cache update, now we must get error.
+      applicationCache.update();
+      break;
+
+    case 3:
+      OfflineTest.ok(false, "Update failed 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.putData(
+      "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/explicitRedirect.sjs",
+      "text/html",
+      "<html><body>Explicit page</body></html>");
+  OfflineTest.putData(
+      "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/dynamicRedirect.sjs",
+      "text/html",
+      "<html><body>Dynamic page</body></html>");
+}
+
+</script>
+
+</head>
+
+<body>
+</body>
+</html>
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -489,25 +489,32 @@ nsOfflineCacheUpdateItem::GetInterface(c
 // nsOfflineCacheUpdateItem::nsIChannelEventSink
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
                                             nsIChannel *aNewChannel,
                                             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;
     nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsICachingChannel> oldCachingChannel =
         do_QueryInterface(aOldChannel);
     nsCOMPtr<nsICachingChannel> newCachingChannel =
-      do_QueryInterface(aOldChannel);
+        do_QueryInterface(aNewChannel);
     if (newCachingChannel) {
         rv = newCachingChannel->SetCacheForOfflineUse(PR_TRUE);
         NS_ENSURE_SUCCESS(rv, rv);
         if (!mClientID.IsEmpty()) {
             rv = newCachingChannel->SetOfflineCacheClientID(mClientID);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
@@ -576,40 +583,67 @@ nsOfflineCacheUpdateItem::GetLoadedSize(
 
 NS_IMETHODIMP
 nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState)
 {
     *aReadyState = mState;
     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
 nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
 {
     if (!mChannel) {
         *aStatus = 0;
         return NS_OK;
     }
 
     nsresult rv;
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     PRUint32 httpStatus;
     rv = httpChannel->GetResponseStatus(&httpStatus);
     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;
         return NS_OK;
     }
 
     NS_ENSURE_SUCCESS(rv, rv);
     *aStatus = PRUint16(httpStatus);
     return NS_OK;
 }
@@ -1235,21 +1269,21 @@ nsOfflineCacheUpdate::InitPartial(nsIURI
 }
 
 nsresult
 nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
 {
     // Be pessimistic
     *aDoUpdate = PR_FALSE;
 
-    PRUint16 status;
-    nsresult rv = mManifestItem->GetStatus(&status);
+    PRBool succeeded;
+    nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (status == 0 || status >= 400 || !mManifestItem->ParseSucceeded()) {
+    if (!succeeded || !mManifestItem->ParseSucceeded()) {
         return NS_ERROR_FAILURE;
     }
 
     if (!mManifestItem->NeedsUpdate()) {
         return NS_OK;
     }
 
     // Add items requested by the manifest.
@@ -1354,22 +1388,22 @@ nsOfflineCacheUpdate::LoadCompleted()
         return;
     }
 
     // Normal load finished.
 
     nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
     mCurrentItem++;
 
-    PRUint16 status;
-    rv = item->GetStatus(&status);
-
-    // Check for failures.  4XX and 5XX errors on items explicitly
+    PRBool succeeded;
+    rv = item->GetRequestSucceeded(&succeeded);
+
+    // Check for failures.  3XX, 4XX and 5XX errors on items explicitly
     // 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 &
             (nsIApplicationCache::ITEM_EXPLICIT |
              nsIApplicationCache::ITEM_FALLBACK)) {
             mSucceeded = PR_FALSE;
         }
     } else {
         rv = mApplicationCache->MarkEntry(item->mCacheKey, item->mItemType);
         if (NS_FAILED(rv)) {
--- a/uriloader/prefetch/nsOfflineCacheUpdate.h
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -97,16 +97,17 @@ public:
     nsCOMPtr<nsIURI>           mReferrerURI;
     nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
     nsCString                  mClientID;
     nsCString                  mCacheKey;
     PRUint32                   mItemType;
 
     nsresult OpenChannel();
     nsresult Cancel();
+    nsresult GetRequestSucceeded(PRBool * succeeded);
 
 private:
     nsOfflineCacheUpdate*          mUpdate;
     nsCOMPtr<nsIChannel>           mChannel;
     PRUint16                       mState;
 
 protected:
     PRInt32                        mBytesRead;