Bug 1305162: Part 1a - Separate nsIMIMEInputStream headers from stream data. r=dragana
☠☠ backed out by 81824fdf77fb ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Fri, 20 Jan 2017 15:43:07 -0800
changeset 375520 de131f7c1fc1f60db42509aea646ef4540e6c5fe
parent 375519 43f6ae2d0847b15fd9e37752926c5663119c293b
child 375521 1202edf6008d7c4da0976c78ed88cec7aa4f05d1
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1305162
milestone53.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 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,27 +47,108 @@
 #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 "nsIDOMWindowUtils.h"
 
 #include <algorithm>
 #include "HttpBaseChannel.h"
 
 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())
@@ -648,32 +729,48 @@ 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;
 
     // This method and ExplicitSetUploadStream mean different things by "empty
     // content type string".  This method means "no header", but
     // ExplicitSetUploadStream means "header with empty value".  So we have to
     // massage the contentType argument into the form ExplicitSetUploadStream
     // expects.
-    nsAutoCString contentType;
-    if (contentTypeArg.IsEmpty()) {
+    nsCOMPtr<nsIMIMEInputStream> mimeStream;
+    nsCString contentType(contentTypeArg);
+    if (contentType.IsEmpty()) {
+      contentType.SetIsVoid(true);
       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);
+
+        return ExplicitSetUploadStream(stream, contentType, contentLength,
+                                       method, hasHeaders);
+      }
+
       hasHeaders = true;
-      contentType.SetIsVoid(true);
     } else {
       method = NS_LITERAL_CSTRING("PUT");
-      hasHeaders = false;
-      contentType = contentTypeArg;
+
+      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;
@@ -805,16 +902,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;
     }
   }
 
@@ -2888,95 +2992,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]",
@@ -3262,17 +3287,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");