Bug 1305162: Part 1a - Separate nsIMIMEInputStream headers from stream data. r=dragana draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 10 Jan 2017 14:22:03 -0800
changeset 458713 9a9e316b52c2659c638fcda7558382ae0e6f0b05
parent 447005 9ff9b1aa382fb49983785d9831b80100a36ecb71
child 458714 7b32b3e6d774f5272cacc9382cd0d17531d1a5eb
push id41026
push usermaglione.k@gmail.com
push dateTue, 10 Jan 2017 22:29:18 +0000
reviewersdragana
bugs1305162
milestone53.0a1
Bug 1305162: Part 1a - Separate nsIMIMEInputStream headers from stream data. r=dragana MozReview-Commit-ID: F1qZCBWUNRG
ipc/glue/InputStreamParams.ipdlh
netwerk/base/nsIMIMEInputStream.idl
netwerk/base/nsMIMEInputStream.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
toolkit/modules/addons/WebRequestUpload.jsm
--- a/ipc/glue/InputStreamParams.ipdlh
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -6,16 +6,22 @@ include protocol PBlob;
 include ProtocolTypes;
 
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace ipc {
 
+struct HeaderEntry
+{
+  nsCString name;
+  nsCString value;
+};
+
 struct StringInputStreamParams
 {
   nsCString data;
 };
 
 struct FileInputStreamParams
 {
   uint32_t fileDescriptorIndex;
@@ -81,16 +87,14 @@ struct BufferedInputStreamParams
 {
   OptionalInputStreamParams optionalStream;
   uint32_t bufferSize;
 };
 
 struct MIMEInputStreamParams
 {
   OptionalInputStreamParams optionalStream;
-  nsCString headers;
-  nsCString contentLength;
+  HeaderEntry[] headers;
   bool startedReading;
-  bool addContentLength;
 };
 
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/base/nsIMIMEInputStream.idl
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -1,41 +1,55 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "nsIHttpHeaderVisitor.idl"
 #include "nsIInputStream.idl"
 
 /**
  * The MIME stream separates headers and a datastream. It also allows
  * automatic creation of the content-length header.
  */
 
 [scriptable, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
 interface nsIMIMEInputStream : nsIInputStream
 {
     /**
      * When true a "Content-Length" header is automatically added to the
      * stream. The value of the content-length is automatically calculated
      * using the available() method on the data stream. The value is
      * recalculated every time the stream is rewinded to the start.
      * Not allowed to be changed once the stream has been started to be read.
+     *
+     * @deprecated A Content-Length header is automatically added when
+     * attaching the stream to a channel, so this setting no longer has any
+     * effect, and may not be set to false.
      */
     attribute boolean addContentLength;
 
     /**
      * Adds an additional header to the stream on the form "name: value". May
      * not be called once the stream has been started to be read.
      * @param name   name of the header
      * @param value  value of the header
      */
     void addHeader(in string name, in string value);
 
     /**
+     * Visits all headers which have been added via addHeader.  Calling
+     * addHeader while visiting request headers has undefined behavior.
+     *
+     * @param aVisitor
+     *        The header visitor instance.
+     */
+    void visitHeaders(in nsIHttpHeaderVisitor visitor);
+
+    /**
      * Sets data-stream. May not be called once the stream has been started
      * to be read.
      * The cursor of the new stream should be located at the beginning of the
      * stream if the implementation of the nsIMIMEInputStream also is used as
      * an nsISeekableStream.
      * @param stream  stream containing the data for the stream
      */
     void setData(in nsIInputStream stream);
--- a/netwerk/base/nsMIMEInputStream.cpp
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -7,68 +7,61 @@
  * The MIME stream separates headers and a datastream. It also allows
  * automatic creation of the content-length header.
  */
 
 #include "ipc/IPCMessageUtils.h"
 
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
-#include "nsIMultiplexInputStream.h"
+#include "nsIHttpHeaderVisitor.h"
 #include "nsIMIMEInputStream.h"
 #include "nsISeekableStream.h"
-#include "nsIStringStream.h"
 #include "nsString.h"
 #include "nsMIMEInputStream.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIIPCSerializableInputStream.h"
+#include "mozilla/Move.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 
 using namespace mozilla::ipc;
 using mozilla::Maybe;
+using mozilla::Move;
 
 class nsMIMEInputStream : public nsIMIMEInputStream,
                           public nsISeekableStream,
                           public nsIIPCSerializableInputStream
 {
     virtual ~nsMIMEInputStream();
 
 public:
     nsMIMEInputStream();
 
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
     NS_DECL_NSIMIMEINPUTSTREAM
     NS_DECL_NSISEEKABLESTREAM
     NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
 
-    nsresult Init();
-
 private:
 
     void InitStreams();
 
     struct MOZ_STACK_CLASS ReadSegmentsState {
         nsCOMPtr<nsIInputStream> mThisStream;
         nsWriteSegmentFun mWriter;
         void* mClosure;
     };
     static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
                               const char* aFromRawSegment, uint32_t aToOffset,
                               uint32_t aCount, uint32_t *aWriteCount);
 
-    nsCString mHeaders;
-    nsCOMPtr<nsIStringInputStream> mHeaderStream;
-    
-    nsCString mContentLength;
-    nsCOMPtr<nsIStringInputStream> mCLStream;
-    
-    nsCOMPtr<nsIInputStream> mData;
-    nsCOMPtr<nsIMultiplexInputStream> mStream;
-    bool mAddContentLength;
+    nsTArray<HeaderEntry> mHeaders;
+
+    nsCOMPtr<nsIInputStream> mStream;
     bool mStartedReading;
 };
 
 NS_IMPL_ADDREF(nsMIMEInputStream)
 NS_IMPL_RELEASE(nsMIMEInputStream)
 
 NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_MIMEINPUTSTREAM_CID)
@@ -78,140 +71,117 @@ NS_IMPL_QUERY_INTERFACE_CI(nsMIMEInputSt
                            nsIInputStream,
                            nsISeekableStream,
                            nsIIPCSerializableInputStream)
 NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
                             nsIMIMEInputStream,
                             nsIInputStream,
                             nsISeekableStream)
 
-nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false),
-                                         mStartedReading(false)
+nsMIMEInputStream::nsMIMEInputStream() : mStartedReading(false)
 {
 }
 
 nsMIMEInputStream::~nsMIMEInputStream()
 {
 }
 
-nsresult nsMIMEInputStream::Init()
-{
-    nsresult rv = NS_OK;
-    mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1",
-                                &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
-                                      &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-    mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mStream->AppendStream(mHeaderStream);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mStream->AppendStream(mCLStream);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
-}
-
-
 NS_IMETHODIMP
 nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength)
 {
-    *aAddContentLength = mAddContentLength;
+    *aAddContentLength = true;
     return NS_OK;
 }
 NS_IMETHODIMP
 nsMIMEInputStream::SetAddContentLength(bool aAddContentLength)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
-    mAddContentLength = aAddContentLength;
+    if (!aAddContentLength) {
+      // Content-Length is automatically added by the channel when setting the
+      // upload stream, so setting this to false has no practical effect.
+      return NS_ERROR_FAILURE;
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInputStream::AddHeader(const char *aName, const char *aValue)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
-    mHeaders.Append(aName);
-    mHeaders.AppendLiteral(": ");
-    mHeaders.Append(aValue);
-    mHeaders.AppendLiteral("\r\n");
 
-    // Just in case someone somehow uses our stream, lets at least
-    // let the stream have a valid pointer. The stream will be properly
-    // initialized in nsMIMEInputStream::InitStreams
-    mHeaderStream->ShareData(mHeaders.get(), 0);
+    HeaderEntry* entry = mHeaders.AppendElement();
+    entry->name().Append(aName);
+    entry->value().Append(aValue);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor *visitor)
+{
+  nsresult rv;
+
+  for (auto& header : mHeaders) {
+    rv = visitor->VisitHeader(header.name(), header.value());
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsMIMEInputStream::SetData(nsIInputStream *aStream)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
-    // Remove the old stream if there is one
-    if (mData)
-        mStream->RemoveStream(2);
 
-    mData = aStream;
-    if (aStream)
-        mStream->AppendStream(mData);
+    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
+    if (!seekable) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    mStream = aStream;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInputStream::GetData(nsIInputStream **aStream)
 {
   NS_ENSURE_ARG_POINTER(aStream);
-  *aStream = mData;
+  *aStream = mStream;
   NS_IF_ADDREF(*aStream);
   return NS_OK;
 }
 
 // set up the internal streams
 void nsMIMEInputStream::InitStreams()
 {
     NS_ASSERTION(!mStartedReading,
                  "Don't call initStreams twice without rewinding");
 
     mStartedReading = true;
-
-    // We'll use the content-length stream to add the final \r\n
-    if (mAddContentLength) {
-        uint64_t cl = 0;
-        if (mData) {
-            mData->Available(&cl);
-        }
-        mContentLength.AssignLiteral("Content-Length: ");
-        mContentLength.AppendInt(cl);
-        mContentLength.AppendLiteral("\r\n\r\n");
-    }
-    else {
-        mContentLength.AssignLiteral("\r\n");
-    }
-    mCLStream->ShareData(mContentLength.get(), -1);
-    mHeaderStream->ShareData(mHeaders.get(), -1);
 }
 
 
 
 #define INITSTREAMS         \
 if (!mStartedReading) {     \
+    NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
     InitStreams();          \
 }
 
 // Reset mStartedReading when Seek-ing to start
 NS_IMETHODIMP
 nsMIMEInputStream::Seek(int32_t whence, int64_t offset)
 {
+    NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED);
+
     nsresult rv;
     nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+
     if (whence == NS_SEEK_SET && offset == 0) {
         rv = stream->Seek(whence, offset);
         if (NS_SUCCEEDED(rv))
             mStartedReading = false;
     }
     else {
         INITSTREAMS;
         rv = stream->Seek(whence, offset);
@@ -279,60 +249,44 @@ NS_IMETHODIMP nsMIMEInputStream::SetEOF(
 nsresult
 nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result)
 {
     *result = nullptr;
 
     if (outer)
         return NS_ERROR_NO_AGGREGATION;
 
-    nsMIMEInputStream *inst = new nsMIMEInputStream();
+    RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
     if (!inst)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    NS_ADDREF(inst);
-
-    nsresult rv = inst->Init();
-    if (NS_FAILED(rv)) {
-        NS_RELEASE(inst);
-        return rv;
-    }
-
-    rv = inst->QueryInterface(iid, result);
-    NS_RELEASE(inst);
-
-    return rv;
+    return inst->QueryInterface(iid, result);
 }
 
 void
 nsMIMEInputStream::Serialize(InputStreamParams& aParams,
                              FileDescriptorArray& aFileDescriptors)
 {
     MIMEInputStreamParams params;
 
-    if (mData) {
-        nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mData);
-        MOZ_ASSERT(stream);
-
+    if (mStream) {
         InputStreamParams wrappedParams;
-        SerializeInputStream(stream, wrappedParams, aFileDescriptors);
+        SerializeInputStream(mStream, wrappedParams, aFileDescriptors);
 
         NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
                      "Wrapped stream failed to serialize!");
 
         params.optionalStream() = wrappedParams;
     }
     else {
         params.optionalStream() = mozilla::void_t();
     }
 
     params.headers() = mHeaders;
-    params.contentLength() = mContentLength;
     params.startedReading() = mStartedReading;
-    params.addContentLength() = mAddContentLength;
 
     aParams = params;
 }
 
 bool
 nsMIMEInputStream::Deserialize(const InputStreamParams& aParams,
                                const FileDescriptorArray& aFileDescriptors)
 {
@@ -341,48 +295,34 @@ nsMIMEInputStream::Deserialize(const Inp
         return false;
     }
 
     const MIMEInputStreamParams& params =
         aParams.get_MIMEInputStreamParams();
     const OptionalInputStreamParams& wrappedParams = params.optionalStream();
 
     mHeaders = params.headers();
-    mContentLength = params.contentLength();
     mStartedReading = params.startedReading();
 
-    // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream
-    mHeaderStream->ShareData(mHeaders.get(),
-                             mStartedReading ? mHeaders.Length() : 0);
-    mCLStream->ShareData(mContentLength.get(),
-                         mStartedReading ? mContentLength.Length() : 0);
-
-    nsCOMPtr<nsIInputStream> stream;
     if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
+        nsCOMPtr<nsIInputStream> stream;
         stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
                                         aFileDescriptors);
         if (!stream) {
             NS_WARNING("Failed to deserialize wrapped stream!");
             return false;
         }
 
-        mData = stream;
-
-        if (NS_FAILED(mStream->AppendStream(mData))) {
-            NS_WARNING("Failed to append stream!");
-            return false;
-        }
+        mStream = stream;
     }
     else {
         NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
                      "Unknown type for OptionalInputStreamParams!");
     }
 
-    mAddContentLength = params.addContentLength();
-
     return true;
 }
 
 Maybe<uint64_t>
 nsMIMEInputStream::ExpectedSerializedLength()
 {
     nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
     return serializable ? serializable->ExpectedSerializedLength() : Nothing();
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -47,25 +47,106 @@
 #include "nsILoadGroupChild.h"
 #include "mozilla/ConsoleReportCollector.h"
 #include "LoadInfo.h"
 #include "nsISSLSocketControl.h"
 #include "mozilla/Telemetry.h"
 #include "nsIURL.h"
 #include "nsIConsoleService.h"
 #include "mozilla/BinarySearch.h"
+#include "mozilla/DebugOnly.h"
 #include "nsIHttpHeaderVisitor.h"
+#include "nsIMIMEInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsICacheInfoChannel.h"
 
 #include <algorithm>
 
 namespace mozilla {
 namespace net {
 
+static
+bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
+{
+  // IMPORTANT: keep this list ASCII-code sorted
+  static nsHttpAtom const* blackList[] = {
+    &nsHttp::Accept,
+    &nsHttp::Accept_Encoding,
+    &nsHttp::Accept_Language,
+    &nsHttp::Authentication,
+    &nsHttp::Authorization,
+    &nsHttp::Connection,
+    &nsHttp::Content_Length,
+    &nsHttp::Cookie,
+    &nsHttp::Host,
+    &nsHttp::If,
+    &nsHttp::If_Match,
+    &nsHttp::If_Modified_Since,
+    &nsHttp::If_None_Match,
+    &nsHttp::If_None_Match_Any,
+    &nsHttp::If_Range,
+    &nsHttp::If_Unmodified_Since,
+    &nsHttp::Proxy_Authenticate,
+    &nsHttp::Proxy_Authorization,
+    &nsHttp::Range,
+    &nsHttp::TE,
+    &nsHttp::Transfer_Encoding,
+    &nsHttp::Upgrade,
+    &nsHttp::User_Agent,
+    &nsHttp::WWW_Authenticate
+  };
+
+  class HttpAtomComparator
+  {
+    nsHttpAtom const& mTarget;
+  public:
+    explicit HttpAtomComparator(nsHttpAtom const& aTarget)
+      : mTarget(aTarget) {}
+    int operator()(nsHttpAtom const* aVal) const {
+      if (mTarget == *aVal) {
+        return 0;
+      }
+      return strcmp(mTarget._val, aVal->_val);
+    }
+  };
+
+  size_t unused;
+  return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+                        HttpAtomComparator(aHeader), &unused);
+}
+
+class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit AddHeadersToChannelVisitor(nsIHttpChannel *aChannel)
+    : mChannel(aChannel)
+  {
+  }
+
+  NS_IMETHOD VisitHeader(const nsACString& aHeader,
+                         const nsACString& aValue) override
+  {
+    nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+    if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+      mChannel->SetRequestHeader(aHeader, aValue, false);
+    }
+    return NS_OK;
+  }
+private:
+  ~AddHeadersToChannelVisitor()
+  {
+  }
+
+  nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
+
 HttpBaseChannel::HttpBaseChannel()
   : mStartPos(UINT64_MAX)
   , mStatus(NS_OK)
   , mLoadFlags(LOAD_NORMAL)
   , mCaps(0)
   , mClassOfService(0)
   , mPriority(PRIORITY_NORMAL)
   , mRedirectionLimit(gHttpHandler->RedirectionLimit())
@@ -640,24 +721,46 @@ HttpBaseChannel::SetUploadStream(nsIInpu
   // 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.
 
   if (stream) {
     nsAutoCString method;
-    bool hasHeaders;
-
+    bool hasHeaders = false;
+
+    nsCOMPtr<nsIMIMEInputStream> mimeStream;
     if (contentType.IsEmpty()) {
       method = NS_LITERAL_CSTRING("POST");
+
+      // MIME streams are a special case, and include headers which need to be
+      // copied to the channel.
+      mimeStream = do_QueryInterface(stream);
+      if (mimeStream) {
+        // Copy non-origin related headers to the channel.
+        nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+          new AddHeadersToChannelVisitor(this);
+        mimeStream->VisitHeaders(visitor);
+
+        // Pass a void string for the content type so that we don't override the
+        // content type set by the stream.
+        nsCString voidContentType;
+        voidContentType.SetIsVoid(true);
+
+        return ExplicitSetUploadStream(stream, voidContentType, contentLength,
+                                       method, hasHeaders);
+      }
+
       hasHeaders = true;
     } else {
       method = NS_LITERAL_CSTRING("PUT");
-      hasHeaders = false;
+
+      MOZ_ASSERT(NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
+                 "nsIMIMEInputStream should not be set with an explicit content type");
     }
     return ExplicitSetUploadStream(stream, contentType, contentLength,
                                    method, hasHeaders);
   }
 
   // if stream is null, ExplicitSetUploadStream returns error.
   // So we need special case for GET method.
   mUploadStreamHasHeaders = false;
@@ -789,16 +892,23 @@ HttpBaseChannel::ExplicitSetUploadStream
                                        const nsACString &aContentType,
                                        int64_t aContentLength,
                                        const nsACString &aMethod,
                                        bool aStreamHasHeaders)
 {
   // Ensure stream is set and method is valid
   NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
 
+  {
+    DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
+    MOZ_ASSERT(!aStreamHasHeaders ||
+               NS_FAILED(CallQueryInterface(aStream, getter_AddRefs(mimeStream.value))),
+               "nsIMIMEInputStream should not include headers");
+  }
+
   if (aContentLength < 0 && !aStreamHasHeaders) {
     nsresult rv = aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength));
     if (NS_FAILED(rv) || aContentLength < 0) {
       NS_ERROR("unable to determine content length");
       return NS_ERROR_FAILURE;
     }
   }
 
@@ -2811,95 +2921,16 @@ HttpBaseChannel::ShouldRewriteRedirectTo
   // rewrite for 303 unless it was HEAD
   if (httpStatus == 303)
     return method != nsHttpRequestHead::kMethod_Head;
 
   // otherwise, such as for 307, do not rewrite
   return false;
 }
 
-static
-bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
-{
-  // IMPORTANT: keep this list ASCII-code sorted
-  static nsHttpAtom const* blackList[] = {
-    &nsHttp::Accept,
-    &nsHttp::Accept_Encoding,
-    &nsHttp::Accept_Language,
-    &nsHttp::Authentication,
-    &nsHttp::Authorization,
-    &nsHttp::Connection,
-    &nsHttp::Content_Length,
-    &nsHttp::Cookie,
-    &nsHttp::Host,
-    &nsHttp::If,
-    &nsHttp::If_Match,
-    &nsHttp::If_Modified_Since,
-    &nsHttp::If_None_Match,
-    &nsHttp::If_None_Match_Any,
-    &nsHttp::If_Range,
-    &nsHttp::If_Unmodified_Since,
-    &nsHttp::Proxy_Authenticate,
-    &nsHttp::Proxy_Authorization,
-    &nsHttp::Range,
-    &nsHttp::TE,
-    &nsHttp::Transfer_Encoding,
-    &nsHttp::Upgrade,
-    &nsHttp::User_Agent,
-    &nsHttp::WWW_Authenticate
-  };
-
-  class HttpAtomComparator
-  {
-    nsHttpAtom const& mTarget;
-  public:
-    explicit HttpAtomComparator(nsHttpAtom const& aTarget)
-      : mTarget(aTarget) {}
-    int operator()(nsHttpAtom const* aVal) const {
-      if (mTarget == *aVal) {
-        return 0;
-      }
-      return strcmp(mTarget._val, aVal->_val);
-    }
-  };
-
-  size_t unused;
-  return BinarySearchIf(blackList, 0, ArrayLength(blackList),
-                        HttpAtomComparator(aHeader), &unused);
-}
-
-class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel)
-    : mChannel(aChannel)
-  {
-  }
-
-  NS_IMETHOD VisitHeader(const nsACString& aHeader,
-                         const nsACString& aValue) override
-  {
-    nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
-    if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
-      mChannel->SetRequestHeader(aHeader, aValue, false);
-    }
-    return NS_OK;
-  }
-private:
-  ~SetupReplacementChannelHeaderVisitor()
-  {
-  }
-
-  nsCOMPtr<nsIHttpChannel> mChannel;
-};
-
-NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor)
-
 nsresult
 HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
                                          nsIChannel   *newChannel,
                                          bool          preserveMethod,
                                          uint32_t      redirectFlags)
 {
   LOG(("HttpBaseChannel::SetupReplacementChannel "
      "[this=%p newChannel=%p preserveMethod=%d]",
@@ -3178,17 +3209,17 @@ HttpBaseChannel::SetupReplacementChannel
   if (cacheInfoChan) {
     cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
   }
 
   if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                        nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
     // Copy non-origin related headers to the new channel.
     nsCOMPtr<nsIHttpHeaderVisitor> visitor =
-      new SetupReplacementChannelHeaderVisitor(httpChannel);
+      new AddHeadersToChannelVisitor(httpChannel);
     mRequestHead.VisitHeaders(visitor);
   }
 
   // This channel has been redirected. Don't report timing info.
   mTimingEnabled = false;
   return NS_OK;
 }
 
--- a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -144,25 +144,33 @@ CustomChannel.prototype = {
 <script type="text/javascript">
 <!--
 document.getElementById('form').submit();
 //-->
 </script>
 `;
     } else if (this.uri.spec.startsWith(ACTION_BASE)) {
       var postData = "";
+      var headers = {};
       if (this._uploadStream) {
         var bstream = Cc["@mozilla.org/binaryinputstream;1"]
             .createInstance(Ci.nsIBinaryInputStream);
         bstream.setInputStream(this._uploadStream);
         postData = bstream.readBytes(bstream.available());
+
+        if (this._uploadStream instanceof Ci.nsIMIMEInputStream) {
+          this._uploadStream.visitHeaders((name, value) => {
+            headers[name] = value;
+          });
+        }
       }
       data += `
 <input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
 <input id="post_data" value="${btoa(postData)}">
+<input id="upload_headers" value='${JSON.stringify(headers)}'>
 `;
     }
 
     data += `
 </body>
 </html>
 `;
 
@@ -209,18 +217,19 @@ document.getElementById('form').submit()
 function frameScript() {
   addMessageListener("Test:WaitForIFrame", function() {
     var check = function() {
       if (content) {
         var frame = content.document.getElementById("frame");
         if (frame) {
           var upload_stream = frame.contentDocument.getElementById("upload_stream");
           var post_data = frame.contentDocument.getElementById("post_data");
-          if (upload_stream && post_data) {
-            sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]);
+          var headers = frame.contentDocument.getElementById("upload_headers");
+          if (upload_stream && post_data && headers) {
+            sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value, headers.value]);
             return;
           }
         }
       }
 
       setTimeout(check, 100);
     };
 
@@ -231,19 +240,19 @@ function frameScript() {
 function loadTestTab(uri) {
   gBrowser.selectedTab = gBrowser.addTab(uri);
   var browser = gBrowser.selectedBrowser;
 
   let manager = browser.messageManager;
   browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
 
   return new Promise(resolve => {
-    function listener({ data: [hasUploadStream, postData] }) {
+    function listener({ data: [hasUploadStream, postData, headers] }) {
       manager.removeMessageListener("Test:IFrameLoaded", listener);
-      resolve([hasUploadStream, atob(postData)]);
+      resolve([hasUploadStream, atob(postData), JSON.parse(headers)]);
     }
 
     manager.addMessageListener("Test:IFrameLoaded", listener);
     manager.sendAsyncMessage("Test:WaitForIFrame");
   });
 }
 
 add_task(function*() {
@@ -267,18 +276,19 @@ add_task(function*() {
 add_task(function*() {
   var [hasUploadStream, postData] = yield loadTestTab(UPLOAD_FORM_URI);
   is(hasUploadStream, "no", "upload action should not have uploadStream");
 
   gBrowser.removeCurrentTab();
 });
 
 add_task(function*() {
-  var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI);
+  var [hasUploadStream, postData, headers] = yield loadTestTab(POST_FORM_URI);
+
   is(hasUploadStream, "yes", "post action should have uploadStream");
-  is(postData,
-     "Content-Type: text/plain\r\n" +
-     "Content-Length: 9\r\n" +
-     "\r\n" +
-     "foo=bar\r\n", "POST data is received correctly");
+  is(postData, "foo=bar\r\n",
+     "POST data is received correctly");
+
+  is(headers["Content-Type"], "text/plain", "Content-Type header is correct");
+  is(headers["Content-Length"], undefined, "Content-Length header is correct");
 
   gBrowser.removeCurrentTab();
 });
--- a/toolkit/modules/addons/WebRequestUpload.jsm
+++ b/toolkit/modules/addons/WebRequestUpload.jsm
@@ -365,28 +365,30 @@ function parseFormData(stream, channel, 
     }
 
     return formData;
   }
 
   try {
     let headers;
     if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
-      // MIME input streams encode additional headers as a block at the
-      // beginning of their stream. The actual request data comes from a
-      // sub-stream, which is accessible via their `data` member. The
-      // difference in available bytes between the outer stream and the
-      // inner data stream tells us the size of that header block.
-      //
-      // Since we need to know at least the value of the Content-Type
-      // header to properly parse the request body, we need to read and
-      // parse the header block in order to extract it.
+      if (channel instanceof Ci.nsIUploadChannel2 && channel.uploadStreamHasHeaders) {
+        // MIME input streams encode additional headers as a block at the
+        // beginning of their stream. The actual request data comes from a
+        // sub-stream, which is accessible via their `data` member. The
+        // difference in available bytes between the outer stream and the
+        // inner data stream tells us the size of that header block.
+        //
+        // Since we need to know at least the value of the Content-Type
+        // header to properly parse the request body, we need to read and
+        // parse the header block in order to extract it.
 
-      headers = readString(createTextStream(stream),
-                           stream.available() - stream.data.available());
+        headers = readString(createTextStream(stream),
+                             stream.available() - stream.data.available());
+      }
 
       rewind(stream);
       stream = stream.data;
     }
 
     let contentType;
     try {
       contentType = channel.getRequestHeader("Content-Type");