Resolving bug 491201. Added ability for XMLHttpRequest.send() to accept an nsIDOMFile. r+sr=jonas@sicking.cc, r=cbiesinger@gmail.com
☠☠ backed out by 5260e85b49fc ☠ ☠
authorMatin Movassate <mmovassate@mozilla.com>
Mon, 31 Aug 2009 14:07:16 -0700
changeset 32112 7d5e1bcb47299ed50993f23880447b5a62fccc00
parent 32111 1b724a06a345bd91efa825e9e16cbce44e4d41c5
child 32113 105d89f1a33bd83bf4d5c935773c7a0afd269113
child 32114 5260e85b49fc4ba350d53d93f3e310ad462b11aa
push id8852
push userjst@mozilla.com
push dateMon, 31 Aug 2009 21:36:47 +0000
treeherderautoland@105d89f1a33b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscbiesinger
bugs491201
milestone1.9.3a1pre
Resolving bug 491201. Added ability for XMLHttpRequest.send() to accept an nsIDOMFile. r+sr=jonas@sicking.cc, r=cbiesinger@gmail.com
content/base/public/Makefile.in
content/base/public/nsDOMFile.h
content/base/public/nsIDOMFileInternal.idl
content/base/src/nsDOMFile.cpp
content/base/src/nsXMLHttpRequest.cpp
content/base/test/file_XHRSendData.sjs
content/base/test/test_XHRSendData.html
netwerk/base/public/Makefile.in
netwerk/base/public/nsIUploadChannel2.idl
netwerk/protocol/http/src/nsHttpChannel.cpp
netwerk/protocol/http/src/nsHttpChannel.h
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -88,16 +88,17 @@ SDK_XPIDLSRCS   = \
 		nsISelection.idl  \
 		$(NULL)
 
 XPIDLSRCS	= \
 		nsIChromeRegistry.idl       \
 		nsIContentPolicy.idl        \
 		nsIDocumentEncoder.idl      \
 		nsIDOMFile.idl \
+		nsIDOMFileInternal.idl \
 		nsIDOMFileList.idl \
 		nsIDOMFileException.idl \
 		nsIDOMParser.idl \
 		nsIDOMSerializer.idl \
 		nsISelection2.idl \
 		nsISelectionController.idl  \
 		nsISelectionDisplay.idl  \
 		nsISelectionListener.idl  \
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -36,32 +36,35 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDOMFile_h__
 #define nsDOMFile_h__
 
 #include "nsICharsetDetectionObserver.h"
 #include "nsIDOMFile.h"
+#include "nsIDOMFileInternal.h"
 #include "nsIDOMFileList.h"
 #include "nsIInputStream.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 
 class nsIDOMDocument;
 class nsIFile;
 class nsIInputStream;
 
 class nsDOMFile : public nsIDOMFile,
+                  public nsIDOMFileInternal,
                   public nsICharsetDetectionObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMFILE
+  NS_DECL_NSIDOMFILEINTERNAL
 
   nsDOMFile(nsIFile *aFile)
     : mFile(aFile)
   {}
   ~nsDOMFile() {}
 
   // from nsICharsetDetectionObserver
   NS_IMETHOD Notify(const char *aCharset, nsDetectionConfident aConf);
new file mode 100644
--- /dev/null
+++ b/content/base/public/nsIDOMFileInternal.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "domstubs.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(047CA6C4-52B3-46F1-8976-E198B724F72F)]
+interface nsIDOMFileInternal : nsISupports
+{
+  attribute nsIFile internalFile;
+};
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -63,16 +63,17 @@
 #include "plbase64.h"
 #include "prmem.h"
 
 // nsDOMFile implementation
 
 NS_INTERFACE_MAP_BEGIN(nsDOMFile)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFile)
   NS_INTERFACE_MAP_ENTRY(nsIDOMFile)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMFileInternal)
   NS_INTERFACE_MAP_ENTRY(nsICharsetDetectionObserver)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(File)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsDOMFile)
 NS_IMPL_RELEASE(nsDOMFile)
 
 static nsresult
@@ -142,16 +143,30 @@ nsDOMFile::GetAsText(const nsAString &aC
 
   rv = alias->GetPreferred(charsetGuess, charset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return ConvertStream(stream, charset.get(), aResult);
 }
 
 NS_IMETHODIMP
+nsDOMFile::GetInternalFile(nsIFile **aFile)
+{
+  NS_IF_ADDREF(*aFile = mFile);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMFile::SetInternalFile(nsIFile *aFile)
+{
+  mFile = aFile;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMFile::GetAsDataURL(nsAString &aResult)
 {
   aResult.AssignLiteral("data:");
 
   nsresult rv;
   nsCOMPtr<nsIMIMEService> mimeService =
     do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -40,34 +40,39 @@
 #include "nsIXPConnect.h"
 #include "nsICharsetConverterManager.h"
 #include "nsLayoutCID.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsIURI.h"
 #include "nsILoadGroup.h"
 #include "nsNetUtil.h"
+#include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
 #include "nsIUploadChannel.h"
+#include "nsIUploadChannel2.h"
 #include "nsIDOMSerializer.h"
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsGUIEvent.h"
 #include "nsIPrivateDOMEvent.h"
 #include "prprf.h"
 #include "nsIDOMEventListener.h"
 #include "nsIJSContextStack.h"
 #include "nsJSEnvironment.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsWeakPtr.h"
 #include "nsICharsetAlias.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMClassInfo.h"
 #include "nsIDOMElement.h"
+#include "nsIDOMFileInternal.h"
 #include "nsIDOMWindow.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
 #include "nsIVariant.h"
 #include "nsVariant.h"
 #include "nsIParser.h"
 #include "nsLoadListenerProxy.h"
 #include "nsStringStream.h"
 #include "nsIStreamConverterService.h"
 #include "nsICachingChannel.h"
 #include "nsContentUtils.h"
@@ -2353,16 +2358,47 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
             wstr->GetData(serial);
           } else {
             // stream?
             nsCOMPtr<nsIInputStream> stream(do_QueryInterface(supports));
             if (stream) {
               postDataStream = stream;
               charset.Truncate();
             }
+            else {
+              // nsIDOMFile?
+              nsCOMPtr<nsIDOMFileInternal> file(do_QueryInterface(supports));
+
+              if (file) {
+                nsCOMPtr<nsIFile> internalFile;
+                rv = file->GetInternalFile(getter_AddRefs(internalFile));
+                NS_ENSURE_SUCCESS(rv, rv);
+
+                nsCOMPtr<nsIInputStream> stream;
+                rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), internalFile); 
+                NS_ENSURE_SUCCESS(rv, rv);
+
+                // Feed local file input stream into our upload channel
+                if (stream) {
+                  postDataStream = stream;
+                  charset.Truncate();
+                  defaultContentType.Truncate();
+
+                  nsCOMPtr<nsIMIMEService> mimeService =
+                      do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+                  NS_ENSURE_SUCCESS(rv, rv);
+
+                  nsCAutoString mediaType;
+                  rv = mimeService->GetTypeFromFile(internalFile, mediaType);
+                  if (NS_SUCCEEDED(rv)) {
+                    defaultContentType = mediaType;
+                  }
+                }
+              }
+            }
           }
         }
       }
       break;
     case nsIDataType::VTYPE_VOID:
     case nsIDataType::VTYPE_EMPTY:
       // Makes us act as if !aBody, don't upload anything
       break;
@@ -2383,17 +2419,17 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = converter->ConvertToInputStream(serial,
                                            getter_AddRefs(postDataStream));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if (postDataStream) {
-      nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+      nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(httpChannel));
       NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
 
       // If no content type header was set by the client, we set it to
       // application/xml.
       nsCAutoString contentType;
       if (NS_FAILED(httpChannel->
                       GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
                                        contentType)) ||
@@ -2422,25 +2458,36 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
             nsCAutoString newCharset("; charset=");
             newCharset.Append(charset);
             contentType.Replace(charsetStart, charsetEnd - charsetStart,
                                 newCharset);
           }
         }
       }
 
+      // If necessary, wrap the stream in a buffered stream so as to guarantee
+      // support for our upload when calling ExplicitSetUploadStream.
+      if (!NS_InputStreamIsBuffered(postDataStream)) {
+        nsCOMPtr<nsIInputStream> bufferedStream;
+        rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+                                       postDataStream, 
+                                       4096);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        postDataStream = bufferedStream;
+      }
+
       mUploadComplete = PR_FALSE;
       PRUint32 uploadTotal = 0;
       postDataStream->Available(&uploadTotal);
       mUploadTotal = uploadTotal;
-      rv = uploadChannel->SetUploadStream(postDataStream, contentType, -1);
-      // Reset the method to its original value
-      if (httpChannel) {
-        httpChannel->SetRequestMethod(method);
-      }
+
+      // We want to use a newer version of the upload channel that won't
+      // ignore the necessary headers for an empty Content-Type.
+      rv = uploadChannel->ExplicitSetUploadStream(postDataStream, contentType, -1, method, PR_FALSE);
     }
   }
 
   // Reset responseBody
   mResponseBody.Truncate();
 
   // Reset responseXML
   mResponseXML = nsnull;
--- a/content/base/test/file_XHRSendData.sjs
+++ b/content/base/test/file_XHRSendData.sjs
@@ -13,10 +13,18 @@ function handleRequest(request, response
 
   var body = new BinaryInputStream(request.bodyInputStream);
   var avail;
   var bytes = [];
   while ((avail = body.available()) > 0)
     Array.prototype.push.apply(bytes, body.readByteArray(avail));
 
   var data = String.fromCharCode.apply(null, bytes);
-  response.write(data);
-}
\ No newline at end of file
+  response.setHeader("Result-Content-Length", "" + data.length);
+  if (data.indexOf("TEST_REDIRECT_STR") >= 0) {
+    var newURL = "http://" + data.split("&url=")[1];
+    response.setStatusLine(null, 307, "redirect");
+    response.setHeader("Location", newURL, false);
+  }
+  else {
+    response.write(data);
+  }
+}
--- a/content/base/test/test_XHRSendData.html
+++ b/content/base/test/test_XHRSendData.html
@@ -8,16 +8,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank"
    href="https://bugzilla.mozilla.org/show_bug.cgi?id=464848">Mozilla Bug 464848</a>
 <p id="display">
+  <input id="fileList" type="file"></input>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 xhr = new XMLHttpRequest();
@@ -27,16 +28,42 @@ testDoc1 = xhr.responseXML;
 is(testDoc1.inputEncoding, "ISO-8859-1", "wrong encoding");
 
 testDoc2 = document.implementation.createDocument("", "", null);
 testDoc2.appendChild(testDoc2.createComment(" doc 2 "));
 testDoc2.appendChild(testDoc2.createElement("res"));
 testDoc2.documentElement.appendChild(testDoc2.createTextNode("text"));
 is(testDoc2.inputEncoding, null, "wrong encoding");
 
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+var testData = "blahblahblahblahblahblahblaaaaaaaah. blah.";
+testFileTxt = createFileWithDataExt(testData, ".txt");
+testFilePng = createFileWithDataExt(testData, ".png");
+testFileJpg = createFileWithDataExt(testData, ".jpg");
+testFileGif = createFileWithDataExt(testData, ".gif");
+testFilePdf = createFileWithDataExt(testData, ".pdf");
+testFileXml = createFileWithDataExt(testData, ".xml");
+testFileUnknown = createFileWithDataExt(testData, "noext");
+
+function createFileWithDataExt(fileData, extension) {
+  var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
+  var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
+  testFile.append("testfile" + extension);
+  var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+  outStream.init(testFile, 0x02 | 0x08 | 0x20, 0666, 0);
+  outStream.write(fileData, fileData.length);
+  outStream.close();
+
+  var fileList = document.getElementById('fileList');
+  fileList.value = testFile.path;
+
+  return fileList.files[0];
+}
+
 tests = [{ body: null,
            resBody: "",
          },
          { body: undefined,
            resBody: "",
          },
          { body: "hi",
            resBody: "hi",
@@ -104,37 +131,112 @@ tests = [{ body: null,
            resBody: "<!-- doc 2 -->\n<res>text</res>",
            resContentType: "foo/bar; charset=UTF-8; baz=bin",
          },
          { body: testDoc2,
            contentType: "foo/bar; charset=uTf-8",
            resBody: "<!-- doc 2 -->\n<res>text</res>",
            resContentType: "foo/bar; charset=uTf-8",
          },
+         { body: testFileTxt,
+           resBody: testData,
+           resContentType: testFileTxt.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFilePng,
+           resBody: testData,
+           resContentType: testFilePng.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFileJpg,
+           resBody: testData,
+           resContentType: testFileJpg.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFileGif,
+           resBody: testData,
+           resContentType: testFileGif.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFilePdf,
+           resBody: testData,
+           resContentType: testFilePdf.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFileXml,
+           resBody: testData,
+           resContentType: testFileXml.mediaType,
+           resContentLength: testData.length,
+         },
+         { body: testFileUnknown,
+           resBody: testData,
+           resContentType: "",
+           resContentLength: testData.length,
+         },
+         { //will trigger a redirect test server-side
+           body: ("TEST_REDIRECT_STR&url=" + window.location.host + window.location.pathname),
+           redirect: true,
+         },
          ];
 
-for each(test in tests) {
-  xhr = new XMLHttpRequest;
-  xhr.open("POST", "file_XHRSendData.sjs", false);
-  if (test.contentType)
-    xhr.setRequestHeader("Content-Type", test.contentType);
-  xhr.send(test.body);
+try {
+  for each(test in tests) {
+    xhr = new XMLHttpRequest;
+    xhr.open("POST", "file_XHRSendData.sjs", false);
+    if (test.contentType)
+      xhr.setRequestHeader("Content-Type", test.contentType);
+    xhr.send(test.body);
+
+    if (test.resContentType) {
+      is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
+         "Wrong Content-Type sent");
+    }
+    else {
+      is(xhr.getResponseHeader("Result-Content-Type"), null);
+    }
 
-  if (test.resContentType) {
-    is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
-       "Wrong Content-Type sent");
-  }
-  else {
-    is(xhr.getResponseHeader("Result-Content-Type"), null);
-  }
+    if (test.resContentLength) {
+      is(xhr.getResponseHeader("Result-Content-Length"), test.resContentLength, "Wrong Content-Length sent");
+    }
 
-  if (test.body instanceof Document) {
-    is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
-  }
-  else {
-    is(xhr.responseText, test.resBody, "Wrong body");
+    if (test.body instanceof Document) {
+      is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
+    }
+    else if (!test.redirect) {
+      is(xhr.responseText, test.resBody, "Wrong body");
+    }
+    else {
+      // If we're testing redirect, determine whether the body is
+      // this document by looking for the relevant bug url
+      is(xhr.responseText.indexOf("https://bugzilla.mozilla.org/show_bug.cgi?id=464848") >= 0, true,
+                                  "Wrong page for redirect");
+    }
   }
 }
+finally {
+  cleanUpData();
+}
+
+function cleanUpData() {
+  removeFileWithExt(".txt");
+  removeFileWithExt(".png");
+  removeFileWithExt(".jpg");
+  removeFileWithExt(".gif");
+  removeFileWithExt(".pdf");
+  removeFileWithExt(".xml");
+  removeFileWithExt("noext");
+}
+
+function removeFileWithExt(extension)
+{
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");    
+    var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
+    var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
+
+    testFile.append("testfile" + extension);
+    testFile.remove(false);
+}
+
 
 </script>
 </pre>
 </body>
 </html>
--- a/netwerk/base/public/Makefile.in
+++ b/netwerk/base/public/Makefile.in
@@ -106,16 +106,17 @@ XPIDLSRCS	= \
 		nsISecurityInfoProvider.idl \
 		nsIStreamListenerTee.idl \
 		nsISimpleStreamListener.idl \
 		nsIStreamTransportService.idl \
 		nsIStreamLoader.idl \
 		nsISyncStreamListener.idl \
 		nsISystemProxySettings.idl \
 		nsIUnicharStreamLoader.idl \
+		nsIUploadChannel2.idl \
 		nsIStandardURL.idl \
 		nsINestedURI.idl \
 		nsIURLParser.idl \
 		nsIURIChecker.idl \
 		nsISecurityEventSink.idl \
 		nsISecretDecoderRing.idl \
 		nsISecureBrowserUI.idl \
 		nsICryptoFIPSInfo.idl \
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIUploadChannel2.idl
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+[scriptable, uuid(8821E259-7252-4464-B874-A55D8EF6B222)]
+interface nsIUploadChannel2 : nsISupports
+{
+    /**
+     * Sets a stream to be uploaded by this channel with the specified
+     * Content-Type and Content-Length header values.
+     *
+     * Most implementations of this interface require that the stream:
+     *   (1) implement threadsafe addRef and release
+     *   (2) implement nsIInputStream::readSegments
+     *   (3) implement nsISeekableStream::seek
+     *
+     * @param aStream
+     *        The stream to be uploaded by this channel.
+     * @param aContentType
+     *        This value will replace any existing Content-Type
+     *        header on the HTTP request, regardless of whether
+     *        or not its empty.
+     * @param aContentLength
+     *        A value of -1 indicates that the length of the stream should be
+     *        determined by calling the stream's |available| method.
+     * @param aMethod
+     *        The HTTP request method to set on the stream.
+     * @param aStreamHasHeaders
+     *        True if the stream already contains headers for the HTTP request.
+     */
+    void explicitSetUploadStream(in nsIInputStream aStream,
+                                 in ACString aContentType,
+                                 in long long aContentLength,
+                                 in ACString aMethod,
+                                 in boolean aStreamHasHeaders);
+};
--- a/netwerk/protocol/http/src/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/src/nsHttpChannel.cpp
@@ -2766,38 +2766,36 @@ nsHttpChannel::SetupReplacementChannel(n
     newChannel->SetNotificationCallbacks(mCallbacks);
     newChannel->SetLoadFlags(newLoadFlags);
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
     if (!httpChannel)
         return NS_OK; // no other options to set
 
     if (preserveMethod) {
-        nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
+        nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
         if (mUploadStream && uploadChannel) {
             // rewind upload stream
             nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
             if (seekable)
                 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
 
             // replicate original call to SetUploadStream...
-            if (mUploadStreamHasHeaders)
-                uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
-            else {
-                const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
-                const char *clen  = mRequestHead.PeekHeader(nsHttp::Content_Length);
-                if (ctype && clen)
-                    uploadChannel->SetUploadStream(mUploadStream,
-                                                   nsDependentCString(ctype),
-                                                   atoi(clen));
-            }
+            const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
+            if (!ctype)
+              ctype = "";
+            const char *clen  = mRequestHead.PeekHeader(nsHttp::Content_Length);
+            if (clen)
+                uploadChannel->ExplicitSetUploadStream(
+                    mUploadStream,
+                    nsDependentCString(ctype),
+                    nsCRT::atoll(clen),
+                    nsDependentCString(mRequestHead.Method()),
+                    mUploadStreamHasHeaders);
         }
-        // must happen after setting upload stream since SetUploadStream
-        // may change the request method.
-        httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
     }
     // convey the referrer if one was used for this channel to the next one
     if (mReferrer)
         httpChannel->SetReferrer(mReferrer);
     // convey the mAllowPipelining flag
     httpChannel->SetAllowPipelining(mAllowPipelining);
     // convey the new redirection limit
     httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
@@ -4073,16 +4071,17 @@ NS_IMPL_RELEASE_INHERITED(nsHttpChannel,
 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsIRequest)
     NS_INTERFACE_MAP_ENTRY(nsIChannel)
     NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
     NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
     NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
     NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+    NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
     NS_INTERFACE_MAP_ENTRY(nsICacheListener)
     NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
     NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
     NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
     NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
     NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
     NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
     NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
@@ -4680,17 +4679,19 @@ nsHttpChannel::GetUploadStream(nsIInputS
 {
     NS_ENSURE_ARG_POINTER(stream);
     *stream = mUploadStream;
     NS_IF_ADDREF(*stream);
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &contentType, PRInt32 contentLength)
+nsHttpChannel::SetUploadStream(nsIInputStream *stream,
+                               const nsACString &contentType,
+                               PRInt32 contentLength)
 {
     // NOTE: for backwards compatibility and for compatibility with old style
     // plugins, |stream| may include headers, specifically Content-Type and
     // Content-Length headers.  in this case, |contentType| and |contentLength|
     // would be unspecified.  this is traditionally the case of a POST request,
     // and so we select POST as the request method if contentType and
     // contentLength are unspecified.
     
@@ -4698,17 +4699,18 @@ nsHttpChannel::SetUploadStream(nsIInputS
         if (!contentType.IsEmpty()) {
             if (contentLength < 0) {
                 stream->Available((PRUint32 *) &contentLength);
                 if (contentLength < 0) {
                     NS_ERROR("unable to determine content length");
                     return NS_ERROR_FAILURE;
                 }
             }
-            mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", contentLength));
+            mRequestHead.SetHeader(nsHttp::Content_Length,
+                                   nsPrintfCString("%d", contentLength));
             mRequestHead.SetHeader(nsHttp::Content_Type, contentType);
             mUploadStreamHasHeaders = PR_FALSE;
             mRequestHead.SetMethod(nsHttp::Put); // PUT request
         }
         else {
             mUploadStreamHasHeaders = PR_TRUE;
             mRequestHead.SetMethod(nsHttp::Post); // POST request
         }
@@ -4717,16 +4719,47 @@ nsHttpChannel::SetUploadStream(nsIInputS
         mUploadStreamHasHeaders = PR_FALSE;
         mRequestHead.SetMethod(nsHttp::Get); // revert to GET request
     }
     mUploadStream = stream;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsHttpChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
+                                       const nsACString &aContentType,
+                                       PRInt64 aContentLength,
+                                       const nsACString &aMethod,
+                                       PRBool aStreamHasHeaders)
+{
+    // Ensure stream is set and method is valid 
+    NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
+
+    if (aContentLength < 0) {
+        PRUint32 streamLength;
+        aStream->Available(&streamLength);
+        aContentLength = streamLength;
+        if (aContentLength < 0) {
+            NS_ERROR("unable to determine content length");
+            return NS_ERROR_FAILURE;
+        }
+    }
+
+    nsresult rv = SetRequestMethod(aMethod);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%lld", aContentLength));
+    mRequestHead.SetHeader(nsHttp::Content_Type, aContentType);
+
+    mUploadStreamHasHeaders = aStreamHasHeaders;
+    mUploadStream = aStream;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsHttpChannel::GetResponseStatus(PRUint32 *value)
 {
     NS_ENSURE_ARG_POINTER(value);
     if (!mResponseHead)
         return NS_ERROR_NOT_AVAILABLE;
     *value = mResponseHead->Status();
     return NS_OK;
 }
--- a/netwerk/protocol/http/src/nsHttpChannel.h
+++ b/netwerk/protocol/http/src/nsHttpChannel.h
@@ -69,16 +69,17 @@
 #include "nsICacheSession.h"
 #include "nsICacheEntryDescriptor.h"
 #include "nsICacheListener.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsIEncodedChannel.h"
 #include "nsITransport.h"
 #include "nsIUploadChannel.h"
+#include "nsIUploadChannel2.h"
 #include "nsIStringEnumerator.h"
 #include "nsIOutputStream.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIPrompt.h"
 #include "nsIResumableChannel.h"
 #include "nsISupportsPriority.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsICancelable.h"
@@ -96,16 +97,17 @@ class nsProxyInfo;
 //-----------------------------------------------------------------------------
 
 class nsHttpChannel : public nsHashPropertyBag
                     , public nsIHttpChannel
                     , public nsIHttpChannelInternal
                     , public nsIStreamListener
                     , public nsICachingChannel
                     , public nsIUploadChannel
+                    , public nsIUploadChannel2
                     , public nsICacheListener
                     , public nsIEncodedChannel
                     , public nsITransportEventSink
                     , public nsIResumableChannel
                     , public nsISupportsPriority
                     , public nsIProtocolProxyCallback
                     , public nsIProxiedChannel
                     , public nsITraceableChannel
@@ -116,16 +118,17 @@ public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUEST
     NS_DECL_NSICHANNEL
     NS_DECL_NSIHTTPCHANNEL
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSICACHINGCHANNEL
     NS_DECL_NSIUPLOADCHANNEL
+    NS_DECL_NSIUPLOADCHANNEL2
     NS_DECL_NSICACHELISTENER
     NS_DECL_NSIENCODEDCHANNEL
     NS_DECL_NSIHTTPCHANNELINTERNAL
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIRESUMABLECHANNEL
     NS_DECL_NSISUPPORTSPRIORITY
     NS_DECL_NSIPROTOCOLPROXYCALLBACK
     NS_DECL_NSIPROXIEDCHANNEL