Bug 618835 - Cannot login to phpmyadmin if I made a typo during my first login attempt r=bz
authorbjarne@runitsoft.com
Sat, 11 Jun 2011 16:57:10 +0200
changeset 71432 b63b54f27418086d0f8fe103a5882ab4a889e9a5
parent 71431 568056abc91eb5f00194b29b4548dfe1daf06592
child 71433 51e2db1a55670b7d9497f22a148a52d5c0fccada
push id159
push usereakhgari@mozilla.com
push dateTue, 16 Aug 2011 17:53:11 +0000
treeherdermozilla-beta@8786e3e49240 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs618835
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 618835 - Cannot login to phpmyadmin if I made a typo during my first login attempt r=bz
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/unit/test_bug618835.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2344,43 +2344,52 @@ nsHttpChannel::OpenOfflineCacheEntryForW
     if (NS_SUCCEEDED(rv)) {
         mOfflineCacheEntry->GetAccessGranted(&mOfflineCacheAccess);
         LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess));
     }
 
     return rv;
 }
 
+// Generates the proper cache-key for this instance of nsHttpChannel
 nsresult
 nsHttpChannel::GenerateCacheKey(PRUint32 postID, nsACString &cacheKey)
 {
+    AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(),
+                     postID, cacheKey);
+    return NS_OK;
+}
+
+// Assembles a cache-key from the given pieces of information and |mLoadFlags|
+void
+nsHttpChannel::AssembleCacheKey(const char *spec, PRUint32 postID,
+                                nsACString &cacheKey)
+{
     cacheKey.Truncate();
 
     if (mLoadFlags & LOAD_ANONYMOUS) {
-      cacheKey.AssignLiteral("anon&");
+        cacheKey.AssignLiteral("anon&");
     }
 
     if (postID) {
         char buf[32];
         PR_snprintf(buf, sizeof(buf), "id=%x&", postID);
         cacheKey.Append(buf);
     }
 
     if (!cacheKey.IsEmpty()) {
-      cacheKey.AppendLiteral("uri=");
+        cacheKey.AppendLiteral("uri=");
     }
 
     // Strip any trailing #ref from the URL before using it as the key
-    const char *spec = mFallbackChannel ? mFallbackKey.get() : mSpec.get();
     const char *p = strchr(spec, '#');
     if (p)
         cacheKey.Append(spec, p - spec);
     else
         cacheKey.Append(spec);
-    return NS_OK;
 }
 
 // UpdateExpirationTime is called when a new response comes in from the server.
 // It updates the stored response-time and sets the expiration time on the
 // cache entry.  
 //
 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
 //
@@ -3312,34 +3321,18 @@ nsHttpChannel::AsyncProcessRedirection(P
         return NS_ERROR_REDIRECT_LOOP;
     }
 
     mRedirectType = redirectType;
 
     LOG(("redirecting to: %s [redirection-limit=%u]\n",
         location, PRUint32(mRedirectionLimit)));
 
-    nsresult rv;
-
-    // create a new URI using the location header and the current URL
-    // as a base...
-    nsCOMPtr<nsIIOService> ioService;
-    rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
-    if (NS_FAILED(rv)) return rv;
-
-    // the new uri should inherit the origin charset of the current uri
-    nsCAutoString originCharset;
-    rv = mURI->GetOriginCharset(originCharset);
-    if (NS_FAILED(rv))
-        originCharset.Truncate();
-
-    rv = ioService->NewURI(nsDependentCString(location),
-                           originCharset.get(),
-                           mURI,
-                           getter_AddRefs(mRedirectURI));
+    nsresult rv = CreateNewURI(location, getter_AddRefs(mRedirectURI));
+
     if (NS_FAILED(rv)) return rv;
 
     if (mApplicationCache) {
         // if we are redirected to a different origin check if there is a fallback
         // cache entry to fall back to. we don't care about file strict 
         // checking, at least mURI is not a file URI.
         if (!NS_SecurityCompareURIs(mURI, mRedirectURI, PR_FALSE)) {
             PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
@@ -3349,16 +3342,36 @@ nsHttpChannel::AsyncProcessRedirection(P
                 return NS_OK;
             PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
         }
     }
 
     return ContinueProcessRedirectionAfterFallback(NS_OK);
 }
 
+// Creates an URI to the given location using current URI for base and charset
+nsresult
+nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
+{
+    nsCOMPtr<nsIIOService> ioService;
+    nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+    if (NS_FAILED(rv)) return rv;
+
+    // the new uri should inherit the origin charset of the current uri
+    nsCAutoString originCharset;
+    rv = mURI->GetOriginCharset(originCharset);
+    if (NS_FAILED(rv))
+        originCharset.Truncate();
+
+    return ioService->NewURI(nsDependentCString(loc),
+                             originCharset.get(),
+                             mURI,
+                             newURI);
+}
+
 nsresult
 nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
 {
     if (NS_SUCCEEDED(rv) && mFallingBack) {
         // do not continue with redirect processing, fallback is in
         // progress now.
         return NS_OK;
     }
@@ -4970,47 +4983,92 @@ nsHttpChannel::MaybeInvalidateCacheEntry
     // other method not listed here will potentially invalidate
     // any cached copy of the resource
     if (mRequestHead.Method() == nsHttp::Options ||
        mRequestHead.Method() == nsHttp::Get ||
        mRequestHead.Method() == nsHttp::Head ||
        mRequestHead.Method() == nsHttp::Trace ||
        mRequestHead.Method() == nsHttp::Connect)
         return;
-        
+
+
+    // Invalidate the request-uri.
+    // Pass 0 in first param to get the cache-key for a GET-request.
+    nsCAutoString tmpCacheKey;
+    GenerateCacheKey(0, tmpCacheKey);
+    LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", 
+        this, tmpCacheKey.get()));
+    DoInvalidateCacheEntry(tmpCacheKey);
+
+    // Invalidate Location-header if set
+    const char *location = mResponseHead->PeekHeader(nsHttp::Location);
+    if (location) {
+        LOG(("  Location-header=%s\n", location));
+        InvalidateCacheEntryForLocation(location);
+    }
+
+    // Invalidate Content-Location-header if set
+    location = mResponseHead->PeekHeader(nsHttp::Content_Location);
+    if (location) {
+        LOG(("  Content-Location-header=%s\n", location));
+        InvalidateCacheEntryForLocation(location);
+    }
+}
+
+void
+nsHttpChannel::InvalidateCacheEntryForLocation(const char *location)
+{
+    nsCAutoString tmpCacheKey, tmpSpec;
+    nsCOMPtr<nsIURI> resultingURI;
+    nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
+    if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
+        if (NS_SUCCEEDED(resultingURI->GetAsciiSpec(tmpSpec))) {
+            location = tmpSpec.get();  //reusing |location|
+
+            // key for a GET-request to |location| with current load-flags
+            AssembleCacheKey(location, 0, tmpCacheKey);
+            DoInvalidateCacheEntry(tmpCacheKey);
+        } else
+            NS_WARNING(("  failed getting ascii-spec\n"));
+    } else {
+        LOG(("  hosts not matching\n"));
+    }
+}
+
+void
+nsHttpChannel::DoInvalidateCacheEntry(nsACString &key)
+{
     // NOTE:
     // Following comments 24,32 and 33 in bug #327765, we only care about
-    // the cache in the protocol-handler.
+    // the cache in the protocol-handler, not the application cache.
     // The logic below deviates from the original logic in OpenCacheEntry on
     // one point by using only READ_ONLY access-policy. I think this is safe.
-    LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p]\n", this));
-
-    nsCAutoString tmpCacheKey;
-    // passing 0 in first param gives the cache-key for a GET to my resource
-    GenerateCacheKey(0, tmpCacheKey);
-
-    // Now, find the session holding the cache-entry
+
+    // First, find session holding the cache-entry - use current storage-policy
     nsCOMPtr<nsICacheSession> session;
     nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
 
-    nsresult rv;
-    rv = gHttpHandler->GetCacheSession(storagePolicy,
-                                       getter_AddRefs(session));
-
-    if (NS_FAILED(rv)) return;
-
-    // Finally, find the actual cache-entry
+    nsresult rv = gHttpHandler->GetCacheSession(storagePolicy,
+                                                getter_AddRefs(session));
+
+    if (NS_FAILED(rv))
+        return;
+
+    // Now, find the actual cache-entry
     nsCOMPtr<nsICacheEntryDescriptor> tmpCacheEntry;
-    rv = session->OpenCacheEntry(tmpCacheKey, nsICache::ACCESS_READ,
+    rv = session->OpenCacheEntry(key, nsICache::ACCESS_READ,
                                  PR_FALSE,
                                  getter_AddRefs(tmpCacheEntry));
-    
+
     // If entry was found, set its expiration-time = 0
     if(NS_SUCCEEDED(rv)) {
-       tmpCacheEntry->SetExpirationTime(0);
+        tmpCacheEntry->SetExpirationTime(0);
+        LOG(("  cache-entry invalidated [key=%s]\n", key.Data()));
+    } else {
+        LOG(("  cache-entry not found [key=%s]\n", key.Data()));
     }
 }
 
 nsCacheStoragePolicy
 nsHttpChannel::DetermineStoragePolicy()
 {
     nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
     if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -268,16 +268,30 @@ private:
     nsresult ProcessSTSHeader();
 
     /**
      * Computes and returns a 64 bit encoded string holding a hash of the
      * input buffer. Input buffer must be a null-terminated string.
      */
     nsresult Hash(const char *buf, nsACString &hash);
 
+    void InvalidateCacheEntryForLocation(const char *location);
+    void AssembleCacheKey(const char *spec, PRUint32 postID, nsACString &key);
+    nsresult CreateNewURI(const char *loc, nsIURI **newURI);
+    void DoInvalidateCacheEntry(nsACString &key);
+
+    // Ref RFC2616 13.10: "invalidation... MUST only be performed if
+    // the host part is the same as in the Request-URI"
+    inline PRBool HostPartIsTheSame(nsIURI *uri) {
+        nsCAutoString tmpHost1, tmpHost2;
+        return (NS_SUCCEEDED(mURI->GetAsciiHost(tmpHost1)) &&
+                NS_SUCCEEDED(uri->GetAsciiHost(tmpHost2)) &&
+                (tmpHost1 == tmpHost2));
+    }
+
 private:
     nsCOMPtr<nsISupports>             mSecurityInfo;
     nsCOMPtr<nsICancelable>           mProxyRequest;
 
     nsRefPtr<nsInputStreamPump>       mTransactionPump;
     nsRefPtr<nsHttpTransaction>       mTransaction;
 
     PRUint64                          mLogicalOffset;
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_bug618835.js
@@ -0,0 +1,117 @@
+//
+// If a response to a non-safe HTTP request-method contains the Location- or
+// Content-Location header, we must make sure to invalidate any cached entry
+// representing the URIs pointed to by either header. RFC 2616 section 13.10
+//
+// This test uses 3 URIs: "/post" is the target of a POST-request and always
+// redirects (301) to "/redirect". The URIs "/redirect" and "/cl" both counts
+// the number of loads from the server (handler). The response from "/post"
+// always contains the headers "Location: /redirect" and "Content-Location:
+// /cl", whose cached entries are to be invalidated. The tests verifies that
+// "/redirect" and "/cl" are loaded from server the expected number of times.
+//
+do_load_httpd_js();
+
+var httpserv;
+
+function getCacheService() {
+    return Components.classes["@mozilla.org/network/cache-service;1"]
+            .getService(Components.interfaces.nsICacheService);
+}
+
+function setupChannel(path) {
+    var ios =
+        Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+    return chan = ios.newChannel(path, "", null)
+                                .QueryInterface(Ci.nsIHttpChannel);
+}
+
+// Verify that Content-Location-URI has been loaded once, load post_target
+function InitialListener() { }
+InitialListener.prototype = {
+    onStartRequest: function(request, context) { },
+    onStopRequest: function(request, context, status) {
+        do_check_eq(1, numberOfCLHandlerCalls);
+        do_execute_soon(function() {
+            var channel = setupChannel("http://localhost:4444/post");
+            channel.requestMethod = "post";
+            channel.asyncOpen(new RedirectingListener(), null);
+        });
+    }
+};
+
+// Verify that Location-URI has been loaded once, reload post_target
+function RedirectingListener() { }
+RedirectingListener.prototype = {
+    onStartRequest: function(request, context) { },
+    onStopRequest: function(request, context, status) {
+        do_check_eq(1, numberOfHandlerCalls);
+        do_execute_soon(function() {
+            var channel = setupChannel("http://localhost:4444/post");
+            channel.requestMethod = "post";
+            channel.asyncOpen(new VerifyingListener(), null);
+        });
+    }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// reload Content-Location-URI
+function VerifyingListener() { }
+VerifyingListener.prototype = {
+    onStartRequest: function(request, context) { },
+    onStopRequest: function(request, context, status) {
+        do_check_eq(2, numberOfHandlerCalls);
+        var channel = setupChannel("http://localhost:4444/cl");
+        channel.asyncOpen(new FinalListener(), null);
+    }
+};
+
+// Verify that Location-URI has been loaded twice (cached entry invalidated),
+// stop test
+function FinalListener() { }
+FinalListener.prototype = {
+    onStartRequest: function(request, context) { },
+    onStopRequest: function(request, context, status) {
+        do_check_eq(2, numberOfCLHandlerCalls);
+        httpserv.stop(do_test_finished);
+    }
+};
+
+function run_test() {
+  httpserv = new nsHttpServer();
+  httpserv.registerPathHandler("/cl", content_location);
+  httpserv.registerPathHandler("/post", post_target);
+  httpserv.registerPathHandler("/redirect", redirect_target);
+  httpserv.start(4444);
+
+  // Clear cache
+  getCacheService().evictEntries(
+          Components.interfaces.nsICache.STORE_ANYWHERE);
+
+  // Load Content-Location URI into cache and start the chain of loads
+  var channel = setupChannel("http://localhost:4444/cl");
+  channel.asyncOpen(new InitialListener(), null);
+
+  do_test_pending();
+}
+
+var numberOfCLHandlerCalls = 0;
+function content_location(metadata, response) {
+    numberOfCLHandlerCalls++;
+    response.setStatusLine(metadata.httpVersion, 200, "Ok");
+    response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+function post_target(metadata, response) {
+    response.setStatusLine(metadata.httpVersion, 301, "Moved Permanently");
+    response.setHeader("Location", "/redirect", false);
+    response.setHeader("Content-Location", "/cl", false);
+    response.setHeader("Cache-Control", "max-age=360000", false);
+}
+
+var numberOfHandlerCalls = 0;
+function redirect_target(metadata, response) {
+    numberOfHandlerCalls++;
+    response.setStatusLine(metadata.httpVersion, 200, "Ok");
+    response.setHeader("Cache-Control", "max-age=360000", false);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -51,16 +51,17 @@ tail =
 [test_bug540566.js]
 [test_bug543805.js]
 [test_bug553970.js]
 [test_bug561276.js]
 [test_bug580508.js]
 [test_bug586908.js]
 [test_bug588389.js]
 [test_bug596443.js]
+[test_bug618835.js]
 [test_bug633743.js]
 [test_bug652761.js]
 [test_bug651100.js]
 [test_bug659569.js]
 [test_bug660066.js]
 [test_cacheflags.js]
 [test_channel_close.js]
 [test_compareURIs.js]