Bug 1122258 - Fetch API: Set anonymous flag on channel if no credentials are to be passed.
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 15 Jan 2015 14:28:14 -0800
changeset 225497 1a680c6110633ba0aac904568584114df9720539
parent 225496 a1791566f721f1a14f515e7aec5f45890fe22a8f
child 225498 a76ced5f328cbe36bae13f80f4ad4659d40a8e6b
push id28163
push userphilringnalda@gmail.com
push dateSat, 24 Jan 2015 16:27:39 +0000
treeherdermozilla-central@1cf171c1a177 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1122258
milestone38.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 1122258 - Fetch API: Set anonymous flag on channel if no credentials are to be passed.
dom/fetch/FetchDriver.cpp
dom/tests/mochitest/fetch/worker_test_fetch_cors.js
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -311,40 +311,16 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
     return rv;
   }
 
   // Step 2 deals with letting ServiceWorkers intercept requests. This is
   // handled by Necko after the channel is opened.
   // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be
   // set based on the Request's flag.
 
-  // From here on we create a channel and set its properties with the
-  // information from the InternalRequest. This is an implementation detail.
-  MOZ_ASSERT(mLoadGroup);
-  nsCOMPtr<nsIChannel> chan;
-  rv = NS_NewChannel(getter_AddRefs(chan),
-                     uri,
-                     mPrincipal,
-                     nsILoadInfo::SEC_NORMAL,
-                     mRequest->GetContext(),
-                     mLoadGroup,
-                     nullptr, /* aCallbacks */
-                     nsIRequest::LOAD_NORMAL,
-                     ios);
-  mLoadGroup = nullptr;
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailWithNetworkError();
-    return rv;
-  }
-
-  // Insert ourselves into the notification callbacks chain so we can handle
-  // cross-origin redirects.
-  chan->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
-  chan->SetNotificationCallbacks(this);
-
   // Step 3.1 "If the CORS preflight flag is set and one of these conditions is
   // true..." is handled by the CORS proxy.
   //
   // Step 3.2 "Set request's skip service worker flag." This isn't required
   // since Necko will fall back to the network if the ServiceWorker does not
   // respond with a valid Response.
   //
   // NS_StartCORSPreflight() will automatically kick off the original request
@@ -355,16 +331,46 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
   // is include, or request's credentials mode is same-origin and the CORS flag
   // is unset, and unset otherwise."
   bool useCredentials = false;
   if (mRequest->GetCredentialsMode() == RequestCredentials::Include ||
       (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && !aCORSFlag)) {
     useCredentials = true;
   }
 
+  // This is effectivetly the opposite of the use credentials flag in "HTTP
+  // network or cache fetch" in the spec and decides whether to transmit
+  // cookies and other identifying information. LOAD_ANONYMOUS also prevents
+  // new cookies sent by the server from being stored.
+  const nsLoadFlags credentialsFlag = useCredentials ? 0 : nsIRequest::LOAD_ANONYMOUS;
+
+  // From here on we create a channel and set its properties with the
+  // information from the InternalRequest. This is an implementation detail.
+  MOZ_ASSERT(mLoadGroup);
+  nsCOMPtr<nsIChannel> chan;
+  rv = NS_NewChannel(getter_AddRefs(chan),
+                     uri,
+                     mPrincipal,
+                     nsILoadInfo::SEC_NORMAL,
+                     mRequest->GetContext(),
+                     mLoadGroup,
+                     nullptr, /* aCallbacks */
+                     nsIRequest::LOAD_NORMAL | credentialsFlag,
+                     ios);
+  mLoadGroup = nullptr;
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    FailWithNetworkError();
+    return rv;
+  }
+
+  // Insert ourselves into the notification callbacks chain so we can handle
+  // cross-origin redirects.
+  chan->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
+  chan->SetNotificationCallbacks(this);
+
   // FIXME(nsm): Bug 1120715.
   // Step 3.4 "If request's cache mode is default and request's header list
   // contains a header named `If-Modified-Since`, `If-None-Match`,
   // `If-Unmodified-Since`, `If-Match`, or `If-Range`, set request's cache mode
   // to no-store."
 
   // Step 3.5 begins "HTTP network or cache fetch".
   // HTTP network or cache fetch
--- a/dom/tests/mochitest/fetch/worker_test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/worker_test_fetch_cors.js
@@ -7,20 +7,16 @@ if (typeof ok !== "function") {
 if (typeof is !== "function") {
   function is(a, b, msg) {
     postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
   }
 }
 
 var path = "/tests/dom/base/test/";
 
-function isNetworkError(response) {
-  return response.type == "error" && response.status === 0 && response.statusText === "";
-}
-
 function isOpaqueResponse(response) {
   return response.type == "opaque" && response.status === 0 && response.statusText === "";
 }
 
 function testModeSameOrigin() {
   // Fetch spec Section 4, step 4, "request's mode is same-origin".
   var req = new Request("http://example.com", { mode: "same-origin" });
   return fetch(req).then(function(res) {
@@ -63,16 +59,111 @@ function testModeNoCors() {
   // Fetch spec, section 4, step 4, response tainting should be set opaque, so
   // that fetching leads to an opaque filtered response in step 8.
   var r = new Request("http://example.com" + corsServerPath + "status=200&allowOrigin=*", { mode: "no-cors" });
   return fetch(r).then(function(res) {
     ok(isOpaqueResponse(res), "no-cors Request fetch should result in opaque response");
   });
 }
 
+function testSameOriginCredentials() {
+  var cookieStr = "type=chocolatechip";
+  var tests = [
+              {
+                // Initialize by setting a cookie.
+                pass: 1,
+                setCookie: cookieStr,
+                withCred: "same-origin",
+              },
+              {
+                // Default mode is "omit".
+                pass: 1,
+                noCookie: 1,
+              },
+              {
+                pass: 1,
+                noCookie: 1,
+                withCred: "omit",
+              },
+              {
+                pass: 1,
+                cookie: cookieStr,
+                withCred: "same-origin",
+              },
+              {
+                pass: 1,
+                cookie: cookieStr,
+                withCred: "include",
+              },
+              ];
+
+  var finalPromiseResolve, finalPromiseReject;
+  var finalPromise = new Promise(function(res, rej) {
+    finalPromiseResolve = res;
+    finalPromiseReject = rej;
+  });
+
+  function makeRequest(test) {
+    req = {
+      // Add a default query param just to make formatting the actual params
+      // easier.
+      url: corsServerPath + "a=b",
+      method: test.method,
+      headers: test.headers,
+      withCred: test.withCred,
+    };
+
+    if (test.setCookie)
+      req.url += "&setCookie=" + escape(test.setCookie);
+    if (test.cookie)
+      req.url += "&cookie=" + escape(test.cookie);
+    if (test.noCookie)
+      req.url += "&noCookie";
+
+    return new Request(req.url, { method: req.method,
+                                  headers: req.headers,
+                                  credentials: req.withCred });
+  }
+
+  function testResponse(res, test) {
+    ok(test.pass, "Expected test to pass " + test.toSource());
+    is(res.status, 200, "wrong status in test for " + test.toSource());
+    is(res.statusText, "OK", "wrong status text for " + test.toSource());
+    return res.text().then(function(v) {
+      is(v, "<res>hello pass</res>\n",
+       "wrong text in test for " + test.toSource());
+    });
+  }
+
+  function runATest(tests, i) {
+    var test = tests[i];
+    var request = makeRequest(test);
+    console.log(request.url);
+    fetch(request).then(function(res) {
+      testResponse(res, test);
+      if (i < tests.length-1) {
+        runATest(tests, i+1);
+      } else {
+        finalPromiseResolve();
+      }
+    }, function(e) {
+      ok(!test.pass, "Expected test to fail " + test.toSource());
+      ok(e instanceof TypeError, "Test should fail " + test.toSource());
+      if (i < tests.length-1) {
+        runATest(tests, i+1);
+      } else {
+        finalPromiseResolve();
+      }
+    });
+  }
+
+  runATest(tests, 0);
+  return finalPromise;
+}
+
 function testModeCors() {
   var tests = [// Plain request
                { pass: 1,
                  method: "GET",
                  noAllowPreflight: 1,
                },
 
                // undefined username
@@ -701,90 +792,108 @@ function testModeCors() {
           ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
       });
     })(test, request));
   }
 
   return Promise.all(fetches);
 }
 
-function testCredentials() {
+function testCrossOriginCredentials() {
   var tests = [
            { pass: 1,
              method: "GET",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 0,
              method: "GET",
-             withCred: 1,
+             withCred: "include",
              allowCred: 0,
            },
            { pass: 0,
              method: "GET",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
              origin: "*",
            },
            { pass: 1,
              method: "GET",
-             withCred: 0,
+             withCred: "omit",
              allowCred: 1,
              origin: "*",
            },
            { pass: 1,
              method: "GET",
              setCookie: "a=1",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              cookie: "a=1",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              noCookie: 1,
-             withCred: 0,
+             withCred: "omit",
              allowCred: 1,
            },
            { pass: 0,
              method: "GET",
              noCookie: 1,
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              setCookie: "a=2",
-             withCred: 0,
+             withCred: "omit",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              cookie: "a=1",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              setCookie: "a=2",
-             withCred: 1,
+             withCred: "include",
              allowCred: 1,
            },
            { pass: 1,
              method: "GET",
              cookie: "a=2",
-             withCred: 1,
+             withCred: "include",
+             allowCred: 1,
+           },
+           {
+             // When credentials mode is same-origin, but mode is cors, no
+             // cookie should be sent cross origin.
+             pass: 0,
+             method: "GET",
+             cookie: "a=2",
+             withCred: "same-origin",
              allowCred: 1,
            },
+           {
+             // When credentials mode is same-origin, but mode is cors, no
+             // cookie should be sent cross origin. This test checks the same
+             // thing as above, but uses the noCookie check on the server
+             // instead, and expects a valid response.
+             pass: 1,
+             method: "GET",
+             noCookie: 1,
+             withCred: "same-origin",
+           },
            ];
-           // FIXME(nsm): Add "same-origin" credentials test
 
   var baseURL = "http://example.org" + corsServerPath;
   var origin = "http://mochi.test:8888";
 
   var finalPromiseResolve, finalPromiseReject;
   var finalPromise = new Promise(function(res, rej) {
     finalPromiseResolve = res;
     finalPromiseReject = rej;
@@ -810,51 +919,51 @@ function testCredentials() {
 
     if ("allowHeaders" in test)
       req.url += "&allowHeaders=" + escape(test.allowHeaders);
     if ("allowMethods" in test)
       req.url += "&allowMethods=" + escape(test.allowMethods);
 
     return new Request(req.url, { method: req.method,
                                   headers: req.headers,
-                                  credentials: req.withCred ? "include" : "omit" });
+                                  credentials: req.withCred });
   }
 
   function testResponse(res, test) {
     ok(test.pass, "Expected test to pass for " + test.toSource());
     is(res.status, 200, "wrong status in test for " + test.toSource());
     is(res.statusText, "OK", "wrong status text for " + test.toSource());
     return res.text().then(function(v) {
       is(v, "<res>hello pass</res>\n",
        "wrong text in test for " + test.toSource());
     });
   }
 
-  function runATest(i) {
+  function runATest(tests, i) {
     var test = tests[i];
     var request = makeRequest(test);
     fetch(request).then(function(res) {
       testResponse(res, test);
       if (i < tests.length-1) {
-        runATest(i+1);
+        runATest(tests, i+1);
       } else {
         finalPromiseResolve();
       }
     }, function(e) {
       ok(!test.pass, "Expected test failure for " + test.toSource());
       ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
       if (i < tests.length-1) {
-        runATest(i+1);
+        runATest(tests, i+1);
       } else {
         finalPromiseResolve();
       }
     });
   }
 
-  runATest(0);
+  runATest(tests, 0);
   return finalPromise;
 }
 
 function testRedirects() {
   var origin = "http://mochi.test:8888";
 
   var tests = [
            { pass: 1,
@@ -1129,18 +1238,16 @@ function testRedirects() {
     }
 
     var request = new Request(req.url, { method: req.method,
                                          headers: req.headers,
                                          body: req.body });
     fetches.push((function(request, test) {
       return fetch(request).then(function(res) {
         ok(test.pass, "Expected test to pass for " + test.toSource());
-        is(isNetworkError(res), false,
-          "shouldn't have failed in test for " + test.toSource());
         is(res.status, 200, "wrong status in test for " + test.toSource());
         is(res.statusText, "OK", "wrong status text for " + test.toSource());
         is((new URL(res.url)).host, (new URL(test.hops[test.hops.length-1].server)).host, "Response URL should be redirected URL");
         return res.text().then(function(v) {
           is(v, "<res>hello pass</res>\n",
              "wrong responseText in test for " + test.toSource());
         });
       }, function(e) {
@@ -1163,17 +1270,18 @@ function runTest() {
   }
 
   testNoCorsCtor();
 
   Promise.resolve()
     .then(testModeSameOrigin)
     .then(testModeNoCors)
     .then(testModeCors)
-    .then(testCredentials)
+    .then(testSameOriginCredentials)
+    .then(testCrossOriginCredentials)
     .then(testRedirects)
     // Put more promise based tests here.
     .then(done)
     .catch(function(e) {
       ok(false, "Some test failed " + e);
       done();
     });
 }