Bug 815299 - Part 3: Accept empty HTTP headers in XHR; r=jdm
☠☠ backed out by 0f5defda5bf2 ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 27 Aug 2015 10:58:50 -0400
changeset 294052 0a2a9972b4c74b55a8d19ca8add2ff4baf84ace0
parent 294051 3555d4b1ab1ec066327e69e509b8800af4c8903a
child 294053 8758ef717d4277ecb684d989385d4f4fc35c32a6
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs815299
milestone43.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 815299 - Part 3: Accept empty HTTP headers in XHR; r=jdm
dom/base/nsXMLHttpRequest.cpp
dom/workers/test/serviceworkers/fetch/fetch_tests.js
dom/workers/test/test_xhr_headers.html
dom/workers/test/xhr_headers_server.sjs
testing/web-platform/meta/XMLHttpRequest/setrequestheader-allow-empty-value.htm.ini
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -3099,18 +3099,23 @@ nsXMLHttpRequest::SetRequestHeader(const
     }
   }
 
   if (!mAlreadySetHeaders.Contains(header)) {
     // Case 2 above
     mergeHeaders = false;
   }
 
-  // Merge headers depending on what we decided above.
-  nsresult rv = httpChannel->SetRequestHeader(header, value, mergeHeaders);
+  nsresult rv;
+  if (value.IsEmpty()) {
+    rv = httpChannel->SetEmptyRequestHeader(header);
+  } else {
+    // Merge headers depending on what we decided above.
+    rv = httpChannel->SetRequestHeader(header, value, mergeHeaders);
+  }
   if (rv == NS_ERROR_INVALID_ARG) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
   if (NS_SUCCEEDED(rv)) {
     // Remember that we've set this header, so subsequent set operations will merge values.
     mAlreadySetHeaders.PutEntry(nsCString(header));
 
     // We'll want to duplicate this header for any replacement channels (eg. on redirect)
@@ -3441,19 +3446,23 @@ nsXMLHttpRequest::OnRedirectVerifyCallba
   if (NS_SUCCEEDED(result)) {
     mChannel = mNewRedirectChannel;
 
     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
     if (httpChannel) {
       // Ensure all original headers are duplicated for the new channel (bug #553888)
       for (uint32_t i = mModifiedRequestHeaders.Length(); i > 0; ) {
         --i;
-        httpChannel->SetRequestHeader(mModifiedRequestHeaders[i].header,
-                                      mModifiedRequestHeaders[i].value,
-                                      false);
+        if (mModifiedRequestHeaders[i].value.IsEmpty()) {
+          httpChannel->SetEmptyRequestHeader(mModifiedRequestHeaders[i].header);
+        } else {
+          httpChannel->SetRequestHeader(mModifiedRequestHeaders[i].header,
+                                        mModifiedRequestHeaders[i].value,
+                                        false);
+        }
       }
     }
   } else {
     mErrorLoad = true;
   }
 
   mNewRedirectChannel = nullptr;
 
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -180,16 +180,22 @@ fetchXHR('something.txt', function(xhr) 
 // file that force a redirect. The redirect location fetch does not go through
 // the SW.
 fetchXHR('redirect_serviceworker.sjs', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "// empty worker, always succeed!\n", "load should have redirection content");
   finish();
 });
 
+fetchXHR('empty-header', function(xhr) {
+  my_ok(xhr.status == 200, "load should be successful");
+  my_ok(xhr.responseText == "emptyheader", "load should have the expected content");
+  finish();
+}, null, [["emptyheader", ""]]);
+
 expectAsyncResult();
 fetch('http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
 .then(function(res) {
   my_ok(res.ok, "Valid CORS request should receive valid response");
   my_ok(res.type == "cors", "Response type should be CORS");
   res.text().then(function(body) {
     my_ok(body === "<res>hello pass</res>\n", "cors response body should match");
     finish();
--- a/dom/workers/test/test_xhr_headers.html
+++ b/dom/workers/test/test_xhr_headers.html
@@ -26,16 +26,17 @@ var serverFilename = filenamePrefix + "s
 var workerFilename = filenamePrefix + "worker.js";
 var otherHost = "example.com";
 
 info("Informing server about the current host");
 
 var xhr = new XMLHttpRequest();
 xhr.open("POST", path + serverFilename);
 xhr.setRequestHeader("options-host", otherHost);
+xhr.setRequestHeader("empty", "");
 xhr.onreadystatechange = function() {
   if (xhr.readyState == 4) {
     info("Launching worker");
 
     var worker = new Worker(path + workerFilename);
     worker.postMessage("http://" + otherHost + path + serverFilename);
 
     worker.onmessage = function(event) {
--- a/dom/workers/test/xhr_headers_server.sjs
+++ b/dom/workers/test/xhr_headers_server.sjs
@@ -9,35 +9,43 @@ function handleRequest(request, response
     case "POST":
       try {
         var optionsHost = request.getHeader("options-host");
       } catch(e) { }
 
       if (optionsHost) {
         setState("postHost", request.host);
         setState("optionsHost", optionsHost);
-        return;
       }
-      break;
+
+      try {
+        var emptyHeader = "nada" + request.getHeader("empty");
+      } catch(e) { }
+
+      if (emptyHeader && emptyHeader == "nada") {
+        setState("emptyHeader", "nada");
+      }
+      return;
 
     case "OPTIONS":
       if (getState("optionsHost") == request.host) {
         try {
           var optionsHeader =
             request.getHeader("Access-Control-Request-Headers");
         } catch(e) { }
         setState("optionsHeader", "'" + optionsHeader + "'");
       }
       break;
 
     case "GET":
       response.setHeader("Cache-Control", "no-cache", false);
       response.setHeader("Content-Type", "text/plain", false);
 
-      if (getState("postHost") == request.host) {
+      if (getState("postHost") == request.host &&
+          getState("emptyHeader") == "nada") {
         var result = getState("optionsHeader");
         if (result) {
           response.write("Success: expected OPTIONS request with " + result +
                          " header");
         } else if (getState("badGet") == 1) {
           response.write("Error: unexpected GET request");
         }
       } else {
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/setrequestheader-allow-empty-value.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[setrequestheader-allow-empty-value.htm]
-  type: testharness
-  [XMLHttpRequest: setRequestHeader() - empty header ()]
-    expected: FAIL
-