Bug 1255735 - "Firefox 45 does not send content-type in empty input[type=file] anymore". r=smaug, a=sylvestre GECKO4501esr_2016031618_RELBRANCH
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 18 Mar 2016 10:40:00 +0100
branchGECKO4501esr_2016031618_RELBRANCH
changeset 311922 cc9d03146a3992fa43c58559c698283710906020
parent 311921 b60e2c6c408610acc8099673347be9e9f4969b92
child 311923 dfb4ee5c0310c46f23b4ee4d480a0a434ea9edc6
push id72
push userkwierso@gmail.com
push dateThu, 07 Apr 2016 18:38:17 +0000
treeherdermozilla-esr45@cc9d03146a39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, sylvestre
bugs1255735
milestone45.0.1
Bug 1255735 - "Firefox 45 does not send content-type in empty input[type=file] anymore". r=smaug, a=sylvestre MozReview-Commit-ID: KR6QAauKLdp
dom/base/nsFormData.cpp
dom/base/nsFormData.h
dom/base/test/file_bug1250148.sjs
dom/base/test/mochitest.ini
dom/base/test/test_bug1250148.html
--- a/dom/base/nsFormData.cpp
+++ b/dom/base/nsFormData.cpp
@@ -272,16 +272,17 @@ nsFormData::SetNameFilePair(FormDataTupl
                             File* aFile)
 {
   MOZ_ASSERT(aData);
   aData->name = aName;
   if (aFile) {
     aData->value.SetAsFile() = aFile;
   } else {
     aData->value.SetAsUSVString() = EmptyString();
+    aData->wasNullBlob = true;
   }
 }
 
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 nsFormData::Append(const nsAString& aName, nsIVariant* aValue)
@@ -353,17 +354,20 @@ nsFormData::Constructor(const GlobalObje
 
 NS_IMETHODIMP
 nsFormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
                         nsACString& aContentType, nsACString& aCharset)
 {
   nsFSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
 
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
-    if (mFormData[i].value.IsFile()) {
+    if (mFormData[i].wasNullBlob) {
+      MOZ_ASSERT(mFormData[i].value.IsUSVString());
+      fs.AddNameFilePair(mFormData[i].name, nullptr);
+    } else if (mFormData[i].value.IsFile()) {
       fs.AddNameFilePair(mFormData[i].name, mFormData[i].value.GetAsFile());
     } else if (mFormData[i].value.IsUSVString()) {
       fs.AddNameValuePair(mFormData[i].name,
                           mFormData[i].value.GetAsUSVString());
     } else {
       fs.AddNameFilePair(mFormData[i].name, nullptr);
     }
   }
--- a/dom/base/nsFormData.h
+++ b/dom/base/nsFormData.h
@@ -36,18 +36,21 @@ private:
   ~nsFormData() {}
 
   typedef mozilla::dom::Blob Blob;
   typedef mozilla::dom::File File;
   typedef mozilla::dom::OwningFileOrUSVString OwningFileOrUSVString;
 
   struct FormDataTuple
   {
+    FormDataTuple() : wasNullBlob(false) {}
+
     nsString name;
     OwningFileOrUSVString value;
+    bool wasNullBlob;
   };
 
   // Returns the FormDataTuple to modify. This may be null, in which case
   // no element with aName was found.
   FormDataTuple*
   RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName);
 
   void SetNameValuePair(FormDataTuple* aData,
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_bug1250148.sjs
@@ -0,0 +1,60 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+
+function utf8decode(s) {
+  return decodeURIComponent(escape(s));
+}
+
+function utf8encode(s) {
+  return unescape(encodeURIComponent(s));
+}
+
+function handleRequest(request, response) {
+  var bodyStream = new BinaryInputStream(request.bodyInputStream);
+
+  var requestBody = "";
+  while ((bodyAvail = bodyStream.available()) > 0) {
+    requestBody += bodyStream.readBytes(bodyAvail);
+  }
+
+  var result = [];
+
+  if (request.method == "POST") {
+    var contentTypeParams = {};
+    request.getHeader("Content-Type").split(/\s*\;\s*/).forEach(function(s) {
+      if (s.indexOf('=') >= 0) {
+        let [name, value] = s.split('=');
+        contentTypeParams[name] = value;
+      }
+      else {
+        contentTypeParams[''] = s;
+      }
+    });
+
+    if (contentTypeParams[''] == "multipart/form-data" &&
+        request.queryString == "") {
+      requestBody.split("--" + contentTypeParams.boundary).slice(1, -1).forEach(function (s) {
+
+        let headers = {};
+        let headerEnd = s.indexOf("\r\n\r\n");
+        s.substr(2, headerEnd-2).split("\r\n").forEach(function(s) {
+          // We're assuming UTF8 for now
+          let [name, value] = s.split(': ');
+          headers[name] = utf8decode(value);
+        });
+
+        let body = s.substring(headerEnd + 4, s.length - 2);
+        if (!headers["Content-Type"] || headers["Content-Type"] == "text/plain") {
+          // We're assuming UTF8 for now
+          body = utf8decode(body);
+        }
+        result.push({ headers: headers, body: body});
+      });
+    }
+  }
+
+  response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+  response.write(utf8encode(JSON.stringify(result)));
+}
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -252,16 +252,17 @@ support-files =
   iframe_postMessages.html
   test_performance_observer.js
   performance_observer.html
   test_anonymousContent_style_csp.html^headers^
   file_explicit_user_agent.sjs
   referrer_change_server.sjs
   file_change_policy_redirect.html
   file_bug1198095.js
+  file_bug1250148.sjs
 
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_canvas.html]
 skip-if = buildapp == 'b2g' # Requires webgl support
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_anonymousContent_style_csp.html]
@@ -856,8 +857,9 @@ support-files = worker_postMessages.js
 [test_frameLoader_switchProcess.html]
 skip-if = e10s || os != 'linux' || buildapp != 'browser'
 [test_explicit_user_agent.html]
 [test_change_policy.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_document.all_iteration.html]
 [test_bug1198095.html]
 [test_bug1187157.html]
+[test_bug1250148.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1250148.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+       https://bugzilla.mozilla.org/show_bug.cgi?id=1250148
+     -->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1250148 - FormData and HTML submission compatibility</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body>
+    <form id="form" enctype="multipart/form-data"><input type="file" name="test" /></form>
+    <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var f = document.getElementById('form');
+var fd = new FormData(f);
+is(fd.get("test"), "", "We want an empty string.");
+
+var getAll = fd.getAll("test");
+ok(Array.isArray(getAll), "We want an array with 1 empty string.");
+is(getAll.length, 1, "We want an array with 1 empty string.");
+is(getAll[0], "", "We want an array with 1 empty string.");
+
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "file_bug1250148.sjs", true);
+xhr.onload = function() {
+  var obj = JSON.parse(xhr.responseText);
+
+  ok(Array.isArray(obj), "XHR response is an array.");
+  is(obj.length, 1, "XHR response array contains 1 result.");
+
+  ok("headers" in obj[0], "XHR response has an header value");
+
+  ok("Content-Disposition" in obj[0].headers, "XHR response.headers array has a Content-Desposition value");
+  is(obj[0].headers["Content-Disposition"], "form-data; name=\"test\"; filename=\"\"", "Correct Content-Disposition");
+
+  ok("Content-Type" in obj[0].headers, "XHR response.headers array has a Content-Type value");
+  is(obj[0].headers["Content-Type"], "application/octet-stream", "Correct Content-Type");
+
+  ok("body" in obj[0], "XHR response has a body value");
+  is(obj[0].body, "", "Correct body value");
+
+  SimpleTest.finish();
+}
+xhr.send(fd);
+
+    </script>
+  </body>
+</html>