Bug 761479 - XMLHttpRequest with mozAnon=true should still send explicitly passed username + password, r=cbiesinger
authorHonza Bambas <honzab.moz@firemni.cz>
Tue, 23 Oct 2012 17:03:49 +0200
changeset 111286 6f1121e69ee9736920f37167aae5d0d257f61ca9
parent 111285 4dc26e9f31899456fcbee2758db97e1def607264
child 111287 d2ad385bd84bf4012cc09714939e0c953bf6b641
child 111289 22effc39c25074d1996f980d725f154dab1471df
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerscbiesinger
bugs761479
milestone19.0a1
Bug 761479 - XMLHttpRequest with mozAnon=true should still send explicitly passed username + password, r=cbiesinger
content/base/test/file_XHR_anon.sjs
content/base/test/test_XHR_anon.html
netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
--- a/content/base/test/file_XHR_anon.sjs
+++ b/content/base/test/file_XHR_anon.sjs
@@ -1,17 +1,18 @@
 function handleRequest(request, response) {
   let invalidHeaders = ["Cookie"];
   let headers = {};
 
   if (request.queryString == "expectAuth=true") {
     if (request.hasHeader("Authorization")) {
       headers["authorization"] = request.getHeader("Authorization");
     } else {
-      response.setStatusLine(null, 500, "Server Error");
+      response.setStatusLine(null, 401, "Authentication required");
+      response.setHeader("WWW-Authenticate", "basic realm=\"testrealm\"", true);
     }
   } else {
     invalidHeaders.push("Authorization");
   }
 
   for each (let header in invalidHeaders) {
     if (request.hasHeader(header)) {
       response.setStatusLine(null, 500, "Server Error");
--- a/content/base/test/test_XHR_anon.html
+++ b/content/base/test/test_XHR_anon.html
@@ -11,77 +11,168 @@
 <iframe id="loader"></iframe>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
+// An XHR with the anon flag set will not send cookie and auth information.
+const TEST_URL = "http://example.com/tests/content/base/test/file_XHR_anon.sjs";
+document.cookie = "foo=bar";
+
+let am = {
+  authMgr: null,
+
+  init: function() {
+    const {classes: Cc, interfaces: Ci} = SpecialPowers.wrap(Components);
+
+    this.authMgr = Cc["@mozilla.org/network/http-auth-manager;1"]
+                     .getService(Components.interfaces.nsIHttpAuthManager)
+  },
+
+  addIdentity: function() {
+    this.authMgr.setAuthIdentity("http", "example.com", -1, "basic", "testrealm",
+                                 "", "example.com", "user1", "password1");
+  },
+
+  tearDown: function() {
+    this.authMgr.clearAll();
+  },
+}
+
+var tests = [ test1, test2, test2a, test3, test3, test3, test4, test4, test4, test5, test5, test5 ];
 
 function runTests() {
-  let tearDown = (function setUp() {
-    SimpleTest.waitForExplicitFinish();
-
-    const {classes: Cc, interfaces: Ci} = SpecialPowers.wrap(SpecialPowers.Components);
-
-    let authMgr = Cc["@mozilla.org/network/http-auth-manager;1"]
-                    .getService(SpecialPowers.Ci.nsIHttpAuthManager)
-    authMgr.setAuthIdentity("http", "example.com", 80, "basic", "testrealm",
-                            "", "example.com", "user1", "password1");
-
-    SpecialPowers.addPermission("systemXHR", true, document);
-
-    return function tearDown() {
-      authMgr.clearAll();
-      SpecialPowers.removePermission("systemXHR", document);
-      SimpleTest.finish();
-    }
-  }());
-
-  // An XHR with the anon flag set will not send cookie and auth information.
-
-  const TEST_URL = "http://example.com/tests/content/base/test/file_XHR_anon.sjs";
-
-  document.cookie = "foo=bar";
-
-
-  function withoutCredentials() {
-    let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
-    is(xhr.mozAnon, true, "withoutCredentials: .mozAnon == true");
-    xhr.open("GET", TEST_URL);
-    xhr.onload = function onload() {
-      is(xhr.status, 200, "withoutCredentials: " + xhr.responseText);
-      withCredentials();
-    };
-    xhr.onerror = function onerror() {
-      ok(false, "Got an error event!");
-      tearDown();
-    }
-    xhr.send();
+  if (!tests.length) {
+    am.tearDown();
+    SpecialPowers.removePermission("systemXHR", document);
+    SimpleTest.finish();
+    return;
   }
 
-  function withCredentials() {
-    // TODO: this currently does not work as expected, see bug 761479
-    let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
-    is(xhr.mozAnon, true, "withCredentials: .mozAnon == true");
-    xhr.open("GET", TEST_URL + "?expectAuth=true", true,
-             "user2name", "pass2word");
-    xhr.onload = function onload() {
-      todo_is(xhr.status, 200, "withCredentials: " + xhr.responseText);
-      let response = JSON.parse(xhr.responseText);
-      todo_is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
-      tearDown();
-    };
-    xhr.onerror = function onerror() {
-      ok(false, "Got an error event!");
-      tearDown();
-    }
-    xhr.send();
+  var test = tests.shift();
+  test();
+}
+
+function test1() {
+  am.addIdentity();
+
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test1: .mozAnon == true");
+  xhr.open("GET", TEST_URL);
+  xhr.onload = function onload() {
+    is(xhr.status, 200, "test1: " + xhr.responseText);
+    am.tearDown();
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    am.tearDown();
+    runTests();
   }
+  xhr.send();
+}
 
-  withoutCredentials();
+function test2() {
+  am.addIdentity();
+
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test2: .mozAnon == true");
+  xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+           "user2name", "pass2word");
+  xhr.onload = function onload() {
+    is(xhr.status, 200, "test2: " + xhr.responseText);
+    let response = JSON.parse(xhr.responseText);
+    is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
+    am.tearDown();
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    am.tearDown();
+    runTests();
+  }
+  xhr.send();
 }
 
+function test2a() {
+  am.addIdentity();
+
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test2: .mozAnon == true");
+  xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+           "user1", "pass2word");
+  xhr.onload = function onload() {
+    is(xhr.status, 200, "test2: " + xhr.responseText);
+    let response = JSON.parse(xhr.responseText);
+    is(response.authorization, "Basic dXNlcjE6cGFzczJ3b3Jk");
+    am.tearDown();
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    am.tearDown();
+    runTests();
+  }
+  xhr.send();
+}
+
+function test3() {
+  am.addIdentity();
+
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test3: .mozAnon == true");
+  xhr.open("GET", TEST_URL + "?expectAuth=true", true);
+  xhr.onload = function onload() {
+    is(xhr.status, 401, "test3: " + xhr.responseText);
+    am.tearDown();
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    am.tearDown();
+    runTests();
+  }
+  xhr.send();
+}
+
+function test4() {
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test4: .mozAnon == true");
+  xhr.open("GET", TEST_URL + "?expectAuth=true", true);
+  xhr.onload = function onload() {
+    is(xhr.status, 401, "test4: " + xhr.responseText);
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    runTests();
+  }
+  xhr.send();
+}
+
+function test5() {
+  let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+  is(xhr.mozAnon, true, "test5: .mozAnon == true");
+  xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+           "user2name", "pass2word");
+  xhr.onload = function onload() {
+    is(xhr.status, 200, "test5: " + xhr.responseText);
+    let response = JSON.parse(xhr.responseText);
+    is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
+    runTests();
+  };
+  xhr.onerror = function onerror() {
+    ok(false, "Got an error event!");
+    runTests();
+  }
+  xhr.send();
+}
+
+am.init();
+SpecialPowers.addPermission("systemXHR", true, document);
+SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -87,30 +87,19 @@ nsHttpChannelAuthProvider::ProcessAuthen
     nsCOMPtr<nsIProxyInfo> proxyInfo;
     nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
     if (NS_FAILED(rv)) return rv;
     if (proxyInfo) {
         mProxyInfo = do_QueryInterface(proxyInfo);
         if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
     }
 
-    uint32_t loadFlags;
-    rv = mAuthChannel->GetLoadFlags(&loadFlags);
-    if (NS_FAILED(rv)) return rv;
-
     nsAutoCString challenges;
     mProxyAuth = (httpStatus == 407);
 
-    // Do proxy auth even if we're LOAD_ANONYMOUS
-    if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) &&
-        (!mProxyAuth || !UsingHttpProxy())) {
-        LOG(("Skipping authentication for anonymous non-proxy request\n"));
-        return NS_ERROR_NOT_AVAILABLE;
-    }
-
     rv = PrepareForAuthentication(mProxyAuth);
     if (NS_FAILED(rv))
         return rv;
 
     if (mProxyAuth) {
         // only allow a proxy challenge if we have a proxy server configured.
         // otherwise, we could inadvertently expose the user's proxy
         // credentials to an origin server.  We could attempt to proceed as
@@ -671,23 +660,39 @@ nsHttpChannelAuthProvider::GetCredential
     nsAutoCString path, scheme;
     bool identFromURI = false;
     nsISupports **continuationState;
 
     rv = GetAuthorizationMembers(proxyAuth, scheme, host, port,
                                  path, ident, continuationState);
     if (NS_FAILED(rv)) return rv;
 
+    uint32_t loadFlags;
+    rv = mAuthChannel->GetLoadFlags(&loadFlags);
+    if (NS_FAILED(rv)) return rv;
+
     if (!proxyAuth) {
         // if this is the first challenge, then try using the identity
         // specified in the URL.
         if (mIdent.IsEmpty()) {
             GetIdentityFromURI(authFlags, mIdent);
             identFromURI = !mIdent.IsEmpty();
         }
+
+        if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
+            LOG(("Skipping authentication for anonymous non-proxy request\n"));
+            return NS_ERROR_NOT_AVAILABLE;
+        }
+
+        // Let explicit URL credentials pass
+        // regardless of the LOAD_ANONYMOUS flag
+    }
+    else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
+        LOG(("Skipping authentication for anonymous non-proxy request\n"));
+        return NS_ERROR_NOT_AVAILABLE;
     }
 
     //
     // if we already tried some credentials for this transaction, then
     // we need to possibly clear them from the cache, unless the credentials
     // in the cache have changed, in which case we'd want to give them a
     // try instead.
     //
@@ -724,18 +729,19 @@ nsHttpChannelAuthProvider::GetCredential
                     // corresponding entry from the auth cache.
                     authCache->ClearAuthEntry(scheme.get(), host,
                                               port, realm.get());
                     entry = nullptr;
                     ident->Clear();
                 }
             }
             else if (!identFromURI ||
-                     nsCRT::strcmp(ident->User(),
-                                   entry->Identity().User()) == 0) {
+                     (nsCRT::strcmp(ident->User(),
+                                    entry->Identity().User()) == 0 &&
+                     !(loadFlags && nsIChannel::LOAD_ANONYMOUS))) {
                 LOG(("  taking identity from auth cache\n"));
                 // the password from the auth cache is more likely to be
                 // correct than the one in the URL.  at least, we know that it
                 // works with the given username.  it is possible for a server
                 // to distinguish logons based on the supplied password alone,
                 // but that would be quite unusual... and i don't think we need
                 // to worry about such unorthodox cases.
                 ident->Set(entry->Identity());