Bug 815299 - Part 3: Accept empty HTTP headers in XHR; r=jdm
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 27 Aug 2015 10:58:50 -0400
changeset 294171 55d57fcbc791dc1341063c3d1ead6d832fdf150a
parent 294170 a9a32b845ba884ad926d2bbac572245dcb92b298
child 294172 93ffb8305933e2aed8046a45286807a35edae09d
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
@@ -6,38 +6,53 @@
 
 function handleRequest(request, response) {
   switch (request.method) {
     case "POST":
       try {
         var optionsHost = request.getHeader("options-host");
       } catch(e) { }
 
+      var headerFound = false;
       if (optionsHost) {
         setState("postHost", request.host);
         setState("optionsHost", optionsHost);
-        return;
+        headerFound = true;
       }
-      break;
+
+      try {
+        var emptyHeader = "nada" + request.getHeader("empty");
+      } catch(e) { }
+
+      if (emptyHeader && emptyHeader == "nada") {
+        setState("emptyHeader", "nada");
+        headerFound = true;
+      }
+      if (headerFound) {
+        return;
+      } else {
+        break;
+      }
 
     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
-