Bug 669259 - Expose original header received from a peer. r=mcmanus
authorDragana Damjanovic <dd.mozilla@gmail.com>
Thu, 12 May 2016 14:33:00 -0400
changeset 297274 d10dd5841d7f41ae0613c587b5824f57b3385fa0
parent 297273 6664fb38c78954056e4a332ebe377964a0a66354
child 297275 2004a138081c3c47180ab36623ed2d97ddefdced
push id30253
push usercbook@mozilla.com
push dateFri, 13 May 2016 09:59:43 +0000
treeherdermozilla-central@5a2deb5a9b09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs669259
milestone49.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 669259 - Expose original header received from a peer. r=mcmanus
modules/libpref/init/all.js
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/NullHttpChannel.cpp
netwerk/protocol/http/PHttpChannelParams.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpHeaderArray.cpp
netwerk/protocol/http/nsHttpHeaderArray.h
netwerk/protocol/http/nsHttpRequestHead.cpp
netwerk/protocol/http/nsHttpResponseHead.cpp
netwerk/protocol/http/nsHttpResponseHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsIHttpChannel.idl
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
netwerk/streamconv/converters/nsMultiMixedConv.cpp
netwerk/test/httpserver/httpd.js
netwerk/test/httpserver/nsIHttpServer.idl
netwerk/test/unit/test_original_sent_received_head.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1506,16 +1506,22 @@ pref("network.http.enforce-framing.soft"
 // such as http://domain.com/package.pak!//resource.html
 // See http://www.w3.org/TR/web-packaging/#streamable-package-format
 pref("network.http.enable-packaged-apps", false);
 
 // Enable this to bring in the signature verification if the signature exists.
 // Set to false if you don't need the signed packaged web app support (i.e. NSec).
 pref("network.http.signed-packages.enabled", false);
 
+// If it is set to false, headers with empty value will not appear in the header
+// array - behavior as it used to be. If it is true: empty headers coming from
+// the network will exits in header array as empty string. Call SetHeader with
+// an empty value will still delete the header.(Bug 6699259)
+pref("network.http.keep_empty_response_headers_as_empty_string", false);
+
 // default values for FTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
 pref("network.ftp.control.qos", 0);
 
 // The max time to spend on xpcom events between two polls in ms.
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -1655,19 +1655,48 @@ HttpBaseChannel::SetResponseHeader(const
   mResponseHeadersModified = true;
 
   return mResponseHead->SetHeader(atom, value, merge);
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
 {
-  if (!mResponseHead)
+  if (!mResponseHead) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  return mResponseHead->Headers().VisitHeaders(visitor,
+    nsHttpHeaderArray::eFilterResponse);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+                                           nsIHttpHeaderVisitor *aVisitor)
+{
+  if (!mResponseHead) {
     return NS_ERROR_NOT_AVAILABLE;
-  return mResponseHead->Headers().VisitHeaders(visitor);
+  }
+
+  nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+  if (!atom) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mResponseHead->Headers().GetOriginalHeader(atom, aVisitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+  if (!mResponseHead) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mResponseHead->Headers().VisitHeaders(aVisitor,
+      nsHttpHeaderArray::eFilterResponseOriginal);
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetAllowPipelining(bool *value)
 {
   NS_ENSURE_ARG_POINTER(value);
   *value = mAllowPipelining;
   return NS_OK;
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -150,16 +150,19 @@ public:
                               const nsACString& aValue, bool aMerge) override;
   NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override;
   NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
   NS_IMETHOD VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
   NS_IMETHOD GetResponseHeader(const nsACString &header, nsACString &value) override;
   NS_IMETHOD SetResponseHeader(const nsACString& header,
                                const nsACString& value, bool merge) override;
   NS_IMETHOD VisitResponseHeaders(nsIHttpHeaderVisitor *visitor) override;
+  NS_IMETHOD GetOriginalResponseHeader(const nsACString &aHeader,
+                                       nsIHttpHeaderVisitor *aVisitor) override;
+  NS_IMETHOD VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor) override;
   NS_IMETHOD GetAllowPipelining(bool *value) override;
   NS_IMETHOD SetAllowPipelining(bool value) override;
   NS_IMETHOD GetAllowSTS(bool *value) override;
   NS_IMETHOD SetAllowSTS(bool value) override;
   NS_IMETHOD GetRedirectionLimit(uint32_t *value) override;
   NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
   NS_IMETHOD IsNoStoreResponse(bool *value) override;
   NS_IMETHOD IsNoCacheResponse(bool *value) override;
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -205,16 +205,29 @@ NullHttpChannel::SetResponseHeader(const
 
 NS_IMETHODIMP
 NullHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+NullHttpChannel::GetOriginalResponseHeader(const nsACString & header,
+                                            nsIHttpHeaderVisitor *aVisitor)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 NullHttpChannel::IsNoStoreResponse(bool *_retval)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 NullHttpChannel::IsNoCacheResponse(bool *_retval)
 {
--- a/netwerk/protocol/http/PHttpChannelParams.h
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -95,24 +95,68 @@ template<>
 struct ParamTraits<mozilla::net::nsHttpHeaderArray::nsEntry>
 {
   typedef mozilla::net::nsHttpHeaderArray::nsEntry paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.header);
     WriteParam(aMsg, aParam.value);
+    switch (aParam.variety) {
+      case mozilla::net::nsHttpHeaderArray::eVarietyUnknown:
+        WriteParam(aMsg, (uint8_t)0);
+        break;
+      case mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride:
+        WriteParam(aMsg, (uint8_t)1);
+        break;
+      case mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault:
+        WriteParam(aMsg, (uint8_t)2);
+        break;
+      case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginalAndResponse:
+        WriteParam(aMsg, (uint8_t)3);
+        break;
+      case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal:
+        WriteParam(aMsg, (uint8_t)4);
+        break;
+      case mozilla::net::nsHttpHeaderArray::eVarietyResponse:
+        WriteParam(aMsg, (uint8_t)5);
+    }
   }
 
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
+    uint8_t variety;
     if (!ReadParam(aMsg, aIter, &aResult->header) ||
-        !ReadParam(aMsg, aIter, &aResult->value))
+        !ReadParam(aMsg, aIter, &aResult->value)  ||
+        !ReadParam(aMsg, aIter, &variety))
       return false;
 
+    switch (variety) {
+      case 0:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyUnknown;
+        break;
+      case 1:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride;
+        break;
+      case 2:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault;
+        break;
+      case 3:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginalAndResponse;
+        break;
+      case 4:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal;
+        break;
+      case 5:
+        aResult->variety = mozilla::net::nsHttpHeaderArray::eVarietyResponse;
+        break;
+      default:
+        return false;
+    }
+
     return true;
   }
 };
 
 
 template<>
 struct ParamTraits<mozilla::net::nsHttpHeaderArray>
 {
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -233,16 +233,17 @@ nsHttpHandler::nsHttpHandler()
     , mRequestTokenBucketBurst(32)
     , mCriticalRequestPrioritization(true)
     , mTCPKeepaliveShortLivedEnabled(false)
     , mTCPKeepaliveShortLivedTimeS(60)
     , mTCPKeepaliveShortLivedIdleTimeS(10)
     , mTCPKeepaliveLongLivedEnabled(false)
     , mTCPKeepaliveLongLivedIdleTimeS(600)
     , mEnforceH1Framing(FRAMECHECK_BARELY)
+    , mKeepEmptyResponseHeadersAsEmtpyString(false)
 {
     LOG(("Creating nsHttpHandler [this=%p].\n", this));
 
     RegisterStrongMemoryReporter(new SpdyZlibReporter());
 
     MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
     gHttpHandler = this;
 }
@@ -450,57 +451,62 @@ nsHttpHandler::InitConnectionMgr()
 
 nsresult
 nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
 {
     nsresult rv;
 
     // Add the "User-Agent" header
     rv = request->SetHeader(nsHttp::User_Agent, UserAgent(),
-                            false, nsHttpHeaderArray::eVarietyDefault);
+                            false, nsHttpHeaderArray::eVarietyRequestDefault);
     if (NS_FAILED(rv)) return rv;
 
     // MIME based content negotiation lives!
     // Add the "Accept" header.  Note, this is set as an override because the
     // service worker expects to see it.  The other "default" headers are
     // hidden from service worker interception.
     rv = request->SetHeader(nsHttp::Accept, mAccept,
-                            false, nsHttpHeaderArray::eVarietyOverride);
+                            false, nsHttpHeaderArray::eVarietyRequestOverride);
     if (NS_FAILED(rv)) return rv;
 
     // Add the "Accept-Language" header.  This header is also exposed to the
     // service worker.
     if (!mAcceptLanguages.IsEmpty()) {
         // Add the "Accept-Language" header
         rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages,
-                                false, nsHttpHeaderArray::eVarietyOverride);
+                                false,
+                                nsHttpHeaderArray::eVarietyRequestOverride);
         if (NS_FAILED(rv)) return rv;
     }
 
     // Add the "Accept-Encoding" header
     if (isSecure) {
         rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings,
-                                false, nsHttpHeaderArray::eVarietyDefault);
+                                false,
+                                nsHttpHeaderArray::eVarietyRequestDefault);
     } else {
         rv = request->SetHeader(nsHttp::Accept_Encoding, mHttpAcceptEncodings,
-                                false, nsHttpHeaderArray::eVarietyDefault);
+                                false,
+                                nsHttpHeaderArray::eVarietyRequestDefault);
     }
     if (NS_FAILED(rv)) return rv;
 
     // Add the "Do-Not-Track" header
     if (mDoNotTrackEnabled) {
       rv = request->SetHeader(nsHttp::DoNotTrack, NS_LITERAL_CSTRING("1"),
-                              false, nsHttpHeaderArray::eVarietyDefault);
+                              false,
+                              nsHttpHeaderArray::eVarietyRequestDefault);
       if (NS_FAILED(rv)) return rv;
     }
 
     // add the "Send Hint" header
     if (mSafeHintEnabled || mParentalControlEnabled) {
       rv = request->SetHeader(nsHttp::Prefer, NS_LITERAL_CSTRING("safe"),
-                              false, nsHttpHeaderArray::eVarietyDefault);
+                              false,
+                              nsHttpHeaderArray::eVarietyRequestDefault);
       if (NS_FAILED(rv)) return rv;
     }
     return NS_OK;
 }
 
 nsresult
 nsHttpHandler::AddConnectionHeader(nsHttpRequestHead *request,
                                    uint32_t caps)
@@ -1690,16 +1696,24 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
             channel.EqualsLiteral("test2") ||
             channel.EqualsLiteral("dev")) {
             mNewTabContentSignaturesDisabled = true;
         } else {
             mNewTabContentSignaturesDisabled = false;
         }
     }
 
+    if (PREF_CHANGED(HTTP_PREF("keep_empty_response_headers_as_empty_string"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("keep_empty_response_headers_as_empty_string"),
+                                &cVar);
+        if (NS_SUCCEEDED(rv)) {
+            mKeepEmptyResponseHeadersAsEmtpyString = cVar;
+        }
+    }
+
     // Enable HTTP response timeout if TCP Keepalives are disabled.
     mResponseTimeoutEnabled = !mTCPKeepaliveShortLivedEnabled &&
                               !mTCPKeepaliveLongLivedEnabled;
 
 #undef PREF_CHANGED
 #undef MULTI_PREF_CHANGED
 }
 
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -354,16 +354,21 @@ public:
 
     nsIRequestContextService *GetRequestContextService()
     {
         return mRequestContextService.get();
     }
 
     void ShutdownConnectionManager();
 
+    bool KeepEmptyResponseHeadersAsEmtpyString() const
+    {
+        return mKeepEmptyResponseHeadersAsEmtpyString;
+    }
+
 private:
     virtual ~nsHttpHandler();
 
     //
     // Useragent/prefs helper methods
     //
     void     BuildUserAgent();
     void     InitUserAgentComponents();
@@ -567,16 +572,23 @@ private:
     // incorrect content lengths or malformed chunked encodings
     FrameCheckLevel mEnforceH1Framing;
 
     nsCOMPtr<nsIRequestContextService> mRequestContextService;
 
     // True if remote newtab content-signature disabled because of the channel.
     bool mNewTabContentSignaturesDisabled;
 
+    // If it is set to false, headers with empty value will not appear in the
+    // header array - behavior as it used to be. If it is true: empty headers
+    // coming from the network will exits in header array as empty string.
+    // Call SetHeader with an empty value will still delete the header.
+    // (Bug 6699259)
+    bool mKeepEmptyResponseHeadersAsEmtpyString;
+
 private:
     // For Rate Pacing Certain Network Events. Only assign this pointer on
     // socket thread.
     void MakeNewRequestTokenBucket();
     RefPtr<EventTokenBucket> mRequestTokenBucket;
 
 public:
     // Socket thread only
--- a/netwerk/protocol/http/nsHttpHeaderArray.cpp
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -18,109 +18,176 @@ namespace net {
 // nsHttpHeaderArray <public>
 //-----------------------------------------------------------------------------
 nsresult
 nsHttpHeaderArray::SetHeader(nsHttpAtom header,
                              const nsACString &value,
                              bool merge,
                              nsHttpHeaderArray::HeaderVariety variety)
 {
+    MOZ_ASSERT((variety == eVarietyResponse) ||
+               (variety == eVarietyRequestDefault) ||
+               (variety == eVarietyRequestOverride),
+               "Net original headers can only be set using SetHeader_internal().");
+
     nsEntry *entry = nullptr;
     int32_t index;
 
     index = LookupEntry(header, &entry);
 
     // If an empty value is passed in, then delete the header entry...
     // unless we are merging, in which case this function becomes a NOP.
     if (value.IsEmpty()) {
-        if (!merge && entry)
-            mHeaders.RemoveElementAt(index);
+        if (!merge && entry) {
+            if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+                MOZ_ASSERT(variety == eVarietyResponse);
+                entry->variety = eVarietyResponseNetOriginal;
+            } else {
+                mHeaders.RemoveElementAt(index);
+            }
+        }
         return NS_OK;
     }
 
-    MOZ_ASSERT(!entry || variety != eVarietyDefault,
+    MOZ_ASSERT(!entry || variety != eVarietyRequestDefault,
                "Cannot set default entry which overrides existing entry!");
     if (!entry) {
-        entry = mHeaders.AppendElement(); // new nsEntry()
-        if (!entry)
-            return NS_ERROR_OUT_OF_MEMORY;
-        entry->header = header;
-        entry->value = value;
-        entry->variety = variety;
+        return SetHeader_internal(header, value, variety);
     } else if (merge && !IsSingletonHeader(header)) {
-        MergeHeader(header, entry, value);
+        return MergeHeader(header, entry, value, variety);
     } else {
         // Replace the existing string with the new value
-        entry->value = value;
-        entry->variety = eVarietyOverride;
+        if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+            MOZ_ASSERT(variety == eVarietyResponse);
+            entry->variety = eVarietyResponseNetOriginal;
+            return SetHeader_internal(header, value, variety);
+        } else {
+            entry->value = value;
+            entry->variety = variety;
+        }
     }
 
     return NS_OK;
 }
 
 nsresult
-nsHttpHeaderArray::SetEmptyHeader(nsHttpAtom header)
+nsHttpHeaderArray::SetHeader_internal(nsHttpAtom header,
+                                      const nsACString &value,
+                                      nsHttpHeaderArray::HeaderVariety variety)
 {
+    nsEntry *entry =  mHeaders.AppendElement();
+    if (!entry) {
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
+    entry->header = header;
+    entry->value = value;
+    entry->variety = variety;
+    return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetEmptyHeader(nsHttpAtom header, HeaderVariety variety)
+{
+    MOZ_ASSERT((variety == eVarietyResponse) ||
+               (variety == eVarietyRequestDefault) ||
+               (variety == eVarietyRequestOverride),
+               "Original headers can only be set using SetHeader_internal().");
     nsEntry *entry = nullptr;
 
     LookupEntry(header, &entry);
 
-    if (!entry) {
-        entry = mHeaders.AppendElement(); // new nsEntry()
-        if (!entry)
-            return NS_ERROR_OUT_OF_MEMORY;
-        entry->header = header;
-    } else {
+    if (entry &&
+        entry->variety != eVarietyResponseNetOriginalAndResponse) {
         entry->value.Truncate();
+        return NS_OK;
+    } else if (entry) {
+        MOZ_ASSERT(variety == eVarietyResponse);
+        entry->variety = eVarietyResponseNetOriginal;
     }
 
-    return NS_OK;
+    return SetHeader_internal(header, EmptyCString(), variety);
 }
 
 nsresult
-nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header, const nsACString &value)
+nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header,
+                                    const nsACString &value,
+                                    bool response)
 {
+    // mHeader holds the consolidated (merged or updated) headers.
+    // mHeader for response header will keep the original heades as well.
     nsEntry *entry = nullptr;
 
     LookupEntry(header, &entry);
 
     if (!entry) {
         if (value.IsEmpty()) {
-            if (!TrackEmptyHeader(header)) {
+            if (!gHttpHandler->KeepEmptyResponseHeadersAsEmtpyString() &&
+                !TrackEmptyHeader(header)) {
                 LOG(("Ignoring Empty Header: %s\n", header.get()));
+                if (response) {
+                    // Set header as original but not as response header.
+                    return SetHeader_internal(header, value,
+                                              eVarietyResponseNetOriginal);
+                }
                 return NS_OK; // ignore empty headers by default
             }
         }
-        entry = mHeaders.AppendElement(); //new nsEntry(header, value);
-        if (!entry)
-            return NS_ERROR_OUT_OF_MEMORY;
-        entry->header = header;
-        entry->value = value;
+        HeaderVariety variety = eVarietyRequestOverride;
+        if (response) {
+            variety = eVarietyResponseNetOriginalAndResponse;
+        }
+        return SetHeader_internal(header, value, variety);
+
     } else if (!IsSingletonHeader(header)) {
-        MergeHeader(header, entry, value);
+        HeaderVariety variety = eVarietyRequestOverride;
+        if (response) {
+            variety = eVarietyResponse;
+        }
+        nsresult rv = MergeHeader(header, entry, value, variety);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+        if (response) {
+            rv = SetHeader_internal(header, value,
+                                    eVarietyResponseNetOriginal);
+        }
+        return rv;
     } else {
         // Multiple instances of non-mergeable header received from network
         // - ignore if same value
         if (!entry->value.Equals(value)) {
             if (IsSuspectDuplicateHeader(header)) {
                 // reply may be corrupt/hacked (ex: CLRF injection attacks)
                 return NS_ERROR_CORRUPTED_CONTENT;
             } // else silently drop value: keep value from 1st header seen
             LOG(("Header %s silently dropped as non mergeable header\n",
                  header.get()));
+
+        }
+        if (response) {
+            return SetHeader_internal(header, value,
+                                      eVarietyResponseNetOriginal);
         }
     }
 
     return NS_OK;
 }
 
 void
 nsHttpHeaderArray::ClearHeader(nsHttpAtom header)
 {
-    mHeaders.RemoveElement(header, nsEntry::MatchHeader());
+    nsEntry *entry = nullptr;
+    int32_t index = LookupEntry(header, &entry);
+    if (entry) {
+        if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+            entry->variety = eVarietyResponseNetOriginal;
+        } else {
+            mHeaders.RemoveElementAt(index);
+        }
+    }
 }
 
 const char *
 nsHttpHeaderArray::PeekHeader(nsHttpAtom header) const
 {
     const nsEntry *entry = nullptr;
     LookupEntry(header, &entry);
     return entry ? entry->value.get() : nullptr;
@@ -132,42 +199,81 @@ nsHttpHeaderArray::GetHeader(nsHttpAtom 
     const nsEntry *entry = nullptr;
     LookupEntry(header, &entry);
     if (!entry)
         return NS_ERROR_NOT_AVAILABLE;
     result = entry->value;
     return NS_OK;
 }
 
+nsresult
+nsHttpHeaderArray::GetOriginalHeader(nsHttpAtom aHeader,
+                                     nsIHttpHeaderVisitor *aVisitor)
+{
+    NS_ENSURE_ARG_POINTER(aVisitor);
+    uint32_t index = 0;
+    nsresult rv = NS_ERROR_NOT_AVAILABLE;
+    while (true) {
+        index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
+        if (index != UINT32_MAX) {
+            const nsEntry &entry = mHeaders[index];
+
+            MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
+                       (entry.variety == eVarietyResponseNetOriginal) ||
+                       (entry.variety == eVarietyResponse),
+                       "This must be a response header.");
+            index++;
+            if (entry.variety == eVarietyResponse) {
+                continue;
+            }
+            rv = NS_OK;
+            if (NS_FAILED(aVisitor->VisitHeader(nsDependentCString(entry.header),
+                                                entry.value))) {
+                break;
+            }
+        } else {
+            // if there is no such a header, it will return
+            // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
+            return rv;
+        }
+    }
+    return NS_OK;
+}
+
 bool
 nsHttpHeaderArray::HasHeader(nsHttpAtom header) const
 {
     const nsEntry *entry = nullptr;
     LookupEntry(header, &entry);
     return entry;
 }
 
 nsresult
 nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray::VisitorFilter filter)
 {
     NS_ENSURE_ARG_POINTER(visitor);
     uint32_t i, count = mHeaders.Length();
     for (i = 0; i < count; ++i) {
         const nsEntry &entry = mHeaders[i];
-        if (filter == eFilterSkipDefault && entry.variety == eVarietyDefault) {
+        if (filter == eFilterSkipDefault && entry.variety == eVarietyRequestDefault) {
+            continue;
+        } else if (filter == eFilterResponse && entry.variety == eVarietyResponseNetOriginal) {
+            continue;
+        } else if (filter == eFilterResponseOriginal && entry.variety == eVarietyResponse) {
             continue;
         }
         if (NS_FAILED(visitor->VisitHeader(nsDependentCString(entry.header),
-                                           entry.value)))
+                                           entry.value))) {
             break;
+        }
     }
     return NS_OK;
 }
 
-nsresult
+/*static*/ nsresult
 nsHttpHeaderArray::ParseHeaderLine(const char *line,
                                    nsHttpAtom *hdr,
                                    char **val)
 {
     //
     // BNF from section 4.2 of RFC 2616:
     //
     //   message-header = field-name ":" [ field-value ]
@@ -175,84 +281,104 @@ nsHttpHeaderArray::ParseHeaderLine(const
     //   field-value    = *( field-content | LWS )
     //   field-content  = <the OCTETs making up the field-value
     //                     and consisting of either *TEXT or combinations
     //                     of token, separators, and quoted-string>
     //
 
     // We skip over mal-formed headers in the hope that we'll still be able to
     // do something useful with the response.
-
     char *p = (char *) strchr(line, ':');
     if (!p) {
         LOG(("malformed header [%s]: no colon\n", line));
-        return NS_OK;
+        return NS_ERROR_FAILURE;
     }
 
     // make sure we have a valid token for the field-name
     if (!nsHttp::IsValidToken(line, p)) {
         LOG(("malformed header [%s]: field-name not a token\n", line));
-        return NS_OK;
+        return NS_ERROR_FAILURE;
     }
 
     *p = 0; // null terminate field-name
 
     nsHttpAtom atom = nsHttp::ResolveAtom(line);
     if (!atom) {
         LOG(("failed to resolve atom [%s]\n", line));
-        return NS_OK;
+        return NS_ERROR_FAILURE;
     }
 
     // skip over whitespace
     p = net_FindCharNotInSet(++p, HTTP_LWS);
 
     // trim trailing whitespace - bug 86608
     char *p2 = net_RFindCharNotInSet(p, HTTP_LWS);
 
     *++p2 = 0; // null terminate header value; if all chars starting at |p|
                // consisted of LWS, then p2 would have pointed at |p-1|, so
                // the prefix increment is always valid.
 
     // assign return values
     if (hdr) *hdr = atom;
     if (val) *val = p;
 
-    // assign response header
-    return SetHeaderFromNet(atom, nsDependentCString(p, p2 - p));
+    return NS_OK;
 }
 
 void
-nsHttpHeaderArray::ParseHeaderSet(char *buffer)
+nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders,
+                           bool pruneTransients)
 {
-    nsHttpAtom hdr;
-    char *val;
-    while (buffer) {
-        char *eof = strchr(buffer, '\r');
-        if (!eof) {
-            break;
+    uint32_t i, count = mHeaders.Length();
+    for (i = 0; i < count; ++i) {
+        const nsEntry &entry = mHeaders[i];
+        // Skip original header.
+        if (entry.variety == eVarietyResponseNetOriginal) {
+            continue;
+        }
+        // prune proxy headers if requested
+        if (pruneProxyHeaders &&
+            ((entry.header == nsHttp::Proxy_Authorization) ||
+             (entry.header == nsHttp::Proxy_Connection))) {
+            continue;
         }
-        *eof = '\0';
-        ParseHeaderLine(buffer, &hdr, &val);
-        buffer = eof + 1;
-        if (*buffer == '\n') {
-            buffer++;
+        if (pruneTransients &&
+            (entry.value.IsEmpty() ||
+             entry.header == nsHttp::Connection ||
+             entry.header == nsHttp::Proxy_Connection ||
+             entry.header == nsHttp::Keep_Alive ||
+             entry.header == nsHttp::WWW_Authenticate ||
+             entry.header == nsHttp::Proxy_Authenticate ||
+             entry.header == nsHttp::Trailer ||
+             entry.header == nsHttp::Transfer_Encoding ||
+             entry.header == nsHttp::Upgrade ||
+             // XXX this will cause problems when we start honoring
+             // Cache-Control: no-cache="set-cookie", what to do?
+             entry.header == nsHttp::Set_Cookie)) {
+            continue;
         }
+
+        buf.Append(entry.header);
+        buf.AppendLiteral(": ");
+        buf.Append(entry.value);
+        buf.AppendLiteral("\r\n");
     }
 }
 
 void
-nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders)
+nsHttpHeaderArray::FlattenOriginalHeader(nsACString &buf)
 {
     uint32_t i, count = mHeaders.Length();
     for (i = 0; i < count; ++i) {
         const nsEntry &entry = mHeaders[i];
-        // prune proxy headers if requested
-        if (pruneProxyHeaders && ((entry.header == nsHttp::Proxy_Authorization) ||
-                                  (entry.header == nsHttp::Proxy_Connection)))
+        // Skip changed header.
+        if (entry.variety == eVarietyResponse) {
             continue;
+        }
+
         buf.Append(entry.header);
         buf.AppendLiteral(": ");
         buf.Append(entry.value);
         buf.AppendLiteral("\r\n");
     }
 }
 
 const char *
--- a/netwerk/protocol/http/nsHttpHeaderArray.h
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -21,34 +21,54 @@ namespace IPC {
 
 namespace mozilla { namespace net {
 
 class nsHttpHeaderArray
 {
 public:
     const char *PeekHeader(nsHttpAtom header) const;
 
+    // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
+    // headers as they come from the network and the parse headers used in
+    // firefox.
+    // If the original and the firefox header are the same, we will keep just
+    // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
+    // If firefox header representation changes a header coming from the
+    // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
+    // header has been changed by SetHeader method, we will keep the original
+    // header as eVarietyResponseNetOriginal and make a copy for the new header
+    // and mark it as eVarietyResponse.
     enum HeaderVariety
     {
-        eVarietyOverride,
-        eVarietyDefault,
+        eVarietyUnknown,
+        // Used only for request header.
+        eVarietyRequestOverride,
+        eVarietyRequestDefault,
+        // Used only for response header.
+        eVarietyResponseNetOriginalAndResponse,
+        eVarietyResponseNetOriginal,
+        eVarietyResponse
     };
 
     // Used by internal setters: to set header from network use SetHeaderFromNet
     nsresult SetHeader(nsHttpAtom header, const nsACString &value,
-                       bool merge = false, HeaderVariety variety = eVarietyOverride);
+                       bool merge, HeaderVariety variety);
 
     // Used by internal setters to set an empty header
-    nsresult SetEmptyHeader(nsHttpAtom header);
+    nsresult SetEmptyHeader(nsHttpAtom header, HeaderVariety variety);
 
     // Merges supported headers. For other duplicate values, determines if error
     // needs to be thrown or 1st value kept.
-    nsresult SetHeaderFromNet(nsHttpAtom header, const nsACString &value);
+    // For the response header we keep the original headers as well.
+    nsresult SetHeaderFromNet(nsHttpAtom header, const nsACString &value,
+                              bool response);
 
     nsresult GetHeader(nsHttpAtom header, nsACString &value) const;
+    nsresult GetOriginalHeader(nsHttpAtom aHeader,
+                               nsIHttpHeaderVisitor *aVisitor);
     void     ClearHeader(nsHttpAtom h);
 
     // Find the location of the given header value, or null if none exists.
     const char *FindHeaderValue(nsHttpAtom header, const char *value) const
     {
         return nsHttp::FindToken(PeekHeader(header), value,
                                  HTTP_HEADER_VALUE_SEPS);
     }
@@ -60,42 +80,43 @@ public:
     }
 
     bool HasHeader(nsHttpAtom header) const;
 
     enum VisitorFilter
     {
         eFilterAll,
         eFilterSkipDefault,
+        eFilterResponse,
+        eFilterResponseOriginal
     };
 
     nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor, VisitorFilter filter = eFilterAll);
 
     // parse a header line, return the header atom and a pointer to the
     // header value (the substring of the header line -- do not free).
-    nsresult ParseHeaderLine(const char *line,
-                             nsHttpAtom *header=nullptr,
-                             char **value=nullptr);
+    static nsresult ParseHeaderLine(const char *line,
+                                    nsHttpAtom *header=nullptr,
+                                    char **value=nullptr);
 
-    void Flatten(nsACString &, bool pruneProxyHeaders=false);
-
-    void ParseHeaderSet(char *buffer);
+    void Flatten(nsACString &, bool pruneProxyHeaders, bool pruneTransients);
+    void FlattenOriginalHeader(nsACString &);
 
     uint32_t Count() const { return mHeaders.Length(); }
 
     const char *PeekHeaderAt(uint32_t i, nsHttpAtom &header) const;
 
     void Clear();
 
     // Must be copy-constructable and assignable
     struct nsEntry
     {
         nsHttpAtom header;
         nsCString value;
-        HeaderVariety variety = eVarietyOverride;
+        HeaderVariety variety = eVarietyUnknown;
 
         struct MatchHeader {
           bool Equals(const nsEntry &entry, const nsHttpAtom &header) const {
             return entry.header == header;
           }
         };
 
         bool operator==(const nsEntry& aOther) const
@@ -105,19 +126,24 @@ public:
     };
 
     bool operator==(const nsHttpHeaderArray& aOther) const
     {
         return mHeaders == aOther.mHeaders;
     }
 
 private:
+    // LookupEntry function will never return eVarietyResponseNetOriginal.
+    // It will ignore original headers from the network.
     int32_t LookupEntry(nsHttpAtom header, const nsEntry **) const;
     int32_t LookupEntry(nsHttpAtom header, nsEntry **);
-    void MergeHeader(nsHttpAtom header, nsEntry *entry, const nsACString &value);
+    nsresult MergeHeader(nsHttpAtom header, nsEntry *entry,
+                         const nsACString &value, HeaderVariety variety);
+    nsresult SetHeader_internal(nsHttpAtom header, const nsACString &value,
+                                HeaderVariety variety);
 
     // Header cannot be merged: only one value possible
     bool    IsSingletonHeader(nsHttpAtom header);
     // For some headers we want to track empty values to prevent them being
     // combined with non-empty ones as a CRLF attack vector
     bool    TrackEmptyHeader(nsHttpAtom header);
 
     // Subset of singleton headers: should never see multiple, different
@@ -134,28 +160,45 @@ private:
 
 //-----------------------------------------------------------------------------
 // nsHttpHeaderArray <private>: inline functions
 //-----------------------------------------------------------------------------
 
 inline int32_t
 nsHttpHeaderArray::LookupEntry(nsHttpAtom header, const nsEntry **entry) const
 {
-    uint32_t index = mHeaders.IndexOf(header, 0, nsEntry::MatchHeader());
-    if (index != UINT32_MAX)
-        *entry = &mHeaders[index];
+    uint32_t index = 0;
+    while (index != UINT32_MAX) {
+        index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+        if (index != UINT32_MAX) {
+            if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+                *entry = &mHeaders[index];
+                return index;
+            }
+            index++;
+        }
+    }
+
     return index;
 }
 
 inline int32_t
 nsHttpHeaderArray::LookupEntry(nsHttpAtom header, nsEntry **entry)
 {
-    uint32_t index = mHeaders.IndexOf(header, 0, nsEntry::MatchHeader());
-    if (index != UINT32_MAX)
-        *entry = &mHeaders[index];
+    uint32_t index = 0;
+    while (index != UINT32_MAX) {
+        index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
+        if (index != UINT32_MAX) {
+            if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
+                *entry = &mHeaders[index];
+                return index;
+            }
+            index++;
+        }
+    }
     return index;
 }
 
 inline bool
 nsHttpHeaderArray::IsSingletonHeader(nsHttpAtom header)
 {
     return header == nsHttp::Content_Type        ||
            header == nsHttp::Content_Disposition ||
@@ -174,41 +217,55 @@ nsHttpHeaderArray::IsSingletonHeader(nsH
 
 inline bool
 nsHttpHeaderArray::TrackEmptyHeader(nsHttpAtom header)
 {
     return header == nsHttp::Content_Length ||
            header == nsHttp::Location;
 }
 
-inline void
+inline nsresult
 nsHttpHeaderArray::MergeHeader(nsHttpAtom header,
                                nsEntry *entry,
-                               const nsACString &value)
+                               const nsACString &value,
+                               nsHttpHeaderArray::HeaderVariety variety)
 {
     if (value.IsEmpty())
-        return;   // merge of empty header = no-op
+        return NS_OK;   // merge of empty header = no-op
 
-    if (!entry->value.IsEmpty()) {
+    nsCString newValue = entry->value;
+    if (!newValue.IsEmpty()) {
         // Append the new value to the existing value
         if (header == nsHttp::Set_Cookie ||
             header == nsHttp::WWW_Authenticate ||
             header == nsHttp::Proxy_Authenticate)
         {
             // Special case these headers and use a newline delimiter to
             // delimit the values from one another as commas may appear
             // in the values of these headers contrary to what the spec says.
-            entry->value.Append('\n');
+            newValue.Append('\n');
         } else {
             // Delimit each value from the others using a comma (per HTTP spec)
-            entry->value.AppendLiteral(", ");
+            newValue.AppendLiteral(", ");
         }
     }
-    entry->value.Append(value);
-    entry->variety = eVarietyOverride;
+
+    newValue.Append(value);
+    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+        MOZ_ASSERT(variety == eVarietyResponse);
+        entry->variety = eVarietyResponseNetOriginal;
+        nsresult rv = SetHeader_internal(header, newValue, eVarietyResponse);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+    } else {
+        entry->value = newValue;
+        entry->variety = variety;
+    }
+    return NS_OK;
 }
 
 inline bool
 nsHttpHeaderArray::IsSuspectDuplicateHeader(nsHttpAtom header)
 {
     bool retval =  header == nsHttp::Content_Length         ||
                      header == nsHttp::Content_Disposition    ||
                      header == nsHttp::Location;
--- a/netwerk/protocol/http/nsHttpRequestHead.cpp
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -125,32 +125,34 @@ nsHttpRequestHead::Origin(nsACString &aO
     aOrigin = mOrigin;
 }
 
 nsresult
 nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v,
                              bool m /*= false*/)
 {
     MutexAutoLock lock(mLock);
-    return mHeaders.SetHeader(h, v, m);
+    return mHeaders.SetHeader(h, v, m,
+                              nsHttpHeaderArray::eVarietyRequestOverride);
 }
 
 nsresult
 nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, bool m,
                              nsHttpHeaderArray::HeaderVariety variety)
 {
     MutexAutoLock lock(mLock);
     return mHeaders.SetHeader(h, v, m, variety);
 }
 
 nsresult
 nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h)
 {
     MutexAutoLock lock(mLock);
-    return mHeaders.SetEmptyHeader(h);
+    return mHeaders.SetEmptyHeader(h,
+                                   nsHttpHeaderArray::eVarietyRequestOverride);
 }
 
 nsresult
 nsHttpRequestHead::GetHeader(nsHttpAtom h, nsACString &v)
 {
     v.Truncate();
     MutexAutoLock lock(mLock);
     return mHeaders.GetHeader(h, v);
@@ -185,17 +187,18 @@ nsHttpRequestHead::HasHeaderValue(nsHttp
 }
 
 nsresult
 nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v,
                                  bool merge /*= false */)
 {
     MutexAutoLock lock(mLock);
     if (!merge || !mHeaders.HasHeaderValue(h, v)) {
-        return mHeaders.SetHeader(h, nsDependentCString(v), merge);
+        return mHeaders.SetHeader(h, nsDependentCString(v), merge,
+                                  nsHttpHeaderArray::eVarietyRequestOverride);
     }
     return NS_OK;
 }
 
 nsHttpRequestHead::ParsedMethodType
 nsHttpRequestHead::ParsedMethod()
 {
     MutexAutoLock lock(mLock);
@@ -208,17 +211,34 @@ nsHttpRequestHead::EqualsMethod(ParsedMe
     MutexAutoLock lock(mLock);
     return mParsedMethod == aType;
 }
 
 void
 nsHttpRequestHead::ParseHeaderSet(char *buffer)
 {
     MutexAutoLock lock(mLock);
-    mHeaders.ParseHeaderSet(buffer);
+    nsHttpAtom hdr;
+    char *val;
+    while (buffer) {
+        char *eof = strchr(buffer, '\r');
+        if (!eof) {
+            break;
+        }
+        *eof = '\0';
+        if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(buffer,
+                                                            &hdr,
+                                                            &val))) {
+            mHeaders.SetHeaderFromNet(hdr, nsDependentCString(val), false);
+        }
+        buffer = eof + 1;
+        if (*buffer == '\n') {
+            buffer++;
+        }
+    }
 }
 
 bool
 nsHttpRequestHead::IsHTTPS()
 {
     MutexAutoLock lock(mLock);
     return mHTTPS;
 }
@@ -300,13 +320,13 @@ nsHttpRequestHead::Flatten(nsACString &b
         buf.AppendLiteral("0.9");
         break;
     default:
         buf.AppendLiteral("1.0");
     }
 
     buf.AppendLiteral("\r\n");
 
-    mHeaders.Flatten(buf, pruneProxyHeaders);
+    mHeaders.Flatten(buf, pruneProxyHeaders, false);
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -21,17 +21,18 @@ namespace net {
 // nsHttpResponseHead <public>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
                               const nsACString &val,
                               bool merge)
 {
-    nsresult rv = mHeaders.SetHeader(hdr, val, merge);
+    nsresult rv = mHeaders.SetHeader(hdr, val, merge,
+                                     nsHttpHeaderArray::eVarietyResponse);
     if (NS_FAILED(rv)) return rv;
 
     // respond to changes in these headers.  we need to reparse the entire
     // header since the change may have merged in additional values.
     if (hdr == nsHttp::Cache_Control)
         ParseCacheControl(mHeaders.PeekHeader(hdr));
     else if (hdr == nsHttp::Pragma)
         ParsePragma(mHeaders.PeekHeader(hdr));
@@ -41,17 +42,20 @@ nsHttpResponseHead::SetHeader(nsHttpAtom
 
 void
 nsHttpResponseHead::SetContentLength(int64_t len)
 {
     mContentLength = len;
     if (len < 0)
         mHeaders.ClearHeader(nsHttp::Content_Length);
     else
-        mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString("%lld", len));
+        mHeaders.SetHeader(nsHttp::Content_Length,
+                           nsPrintfCString("%lld", len),
+                           false,
+                           nsHttpHeaderArray::eVarietyResponse);
 }
 
 void
 nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients)
 {
     if (mVersion == NS_HTTP_VERSION_0_9)
         return;
 
@@ -63,47 +67,31 @@ nsHttpResponseHead::Flatten(nsACString &
     else
         buf.AppendLiteral("1.0 ");
 
     buf.Append(nsPrintfCString("%u", unsigned(mStatus)) +
                NS_LITERAL_CSTRING(" ") +
                mStatusText +
                NS_LITERAL_CSTRING("\r\n"));
 
-    if (!pruneTransients) {
-        mHeaders.Flatten(buf, false);
+
+    mHeaders.Flatten(buf, false, pruneTransients);
+}
+
+void
+nsHttpResponseHead::FlattenOriginalHeader(nsACString &buf)
+{
+    if (mVersion == NS_HTTP_VERSION_0_9) {
         return;
     }
 
-    // otherwise, we need to iterate over the headers and only flatten
-    // those that are appropriate.
-    uint32_t i, count = mHeaders.Count();
-    for (i=0; i<count; ++i) {
-        nsHttpAtom header;
-        const char *value = mHeaders.PeekHeaderAt(i, header);
+    buf.AppendLiteral("  OriginalHeaders");
+    buf.AppendLiteral("\r\n");
 
-        if (!value || header == nsHttp::Connection
-                   || header == nsHttp::Proxy_Connection
-                   || header == nsHttp::Keep_Alive
-                   || header == nsHttp::WWW_Authenticate
-                   || header == nsHttp::Proxy_Authenticate
-                   || header == nsHttp::Trailer
-                   || header == nsHttp::Transfer_Encoding
-                   || header == nsHttp::Upgrade
-                   // XXX this will cause problems when we start honoring
-                   // Cache-Control: no-cache="set-cookie", what to do?
-                   || header == nsHttp::Set_Cookie)
-            continue;
-
-        // otherwise, write out the "header: value\r\n" line
-        buf.Append(nsDependentCString(header.get()) +
-                   NS_LITERAL_CSTRING(": ") +
-                   nsDependentCString(value) +
-                   NS_LITERAL_CSTRING("\r\n"));
-    }
+    mHeaders.FlattenOriginalHeader(buf);
 }
 
 nsresult
 nsHttpResponseHead::Parse(char *block)
 {
 
     LOG(("nsHttpResponseHead::Parse [this=%p]\n", this));
 
@@ -115,18 +103,18 @@ nsHttpResponseHead::Parse(char *block)
         return NS_ERROR_UNEXPECTED;
 
     *p = 0;
     ParseStatusLine(block);
 
     do {
         block = p + 2;
 
-		if (*block == 0)
-			break;
+        if (*block == 0)
+            break;
 
         p = PL_strstr(block, "\r\n");
         if (!p)
             return NS_ERROR_UNEXPECTED;
 
         *p = 0;
         ParseHeaderLine(block);
 
@@ -323,21 +311,26 @@ nsHttpResponseHead::ParseStatusLine(cons
         unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
 }
 
 nsresult
 nsHttpResponseHead::ParseHeaderLine(const char *line)
 {
     nsHttpAtom hdr = {0};
     char *val;
-    nsresult rv;
 
-    rv = mHeaders.ParseHeaderLine(line, &hdr, &val);
-    if (NS_FAILED(rv))
+    if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(line, &hdr, &val))) {
+        return NS_OK;
+    } 
+    nsresult rv = mHeaders.SetHeaderFromNet(hdr,
+                                            nsDependentCString(val),
+                                            true);
+    if (NS_FAILED(rv)) {
         return rv;
+    }
 
     // leading and trailing LWS has been removed from |val|
 
     // handle some special case headers...
     if (hdr == nsHttp::Content_Length) {
         int64_t len;
         const char *ignored;
         // permit only a single value here.
--- a/netwerk/protocol/http/nsHttpResponseHead.h
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -73,17 +73,19 @@ public:
 
     void     SetContentType(const nsACString &s)    { mContentType = s; }
     void     SetContentCharset(const nsACString &s) { mContentCharset = s; }
     void     SetContentLength(int64_t);
 
     // write out the response status line and headers as a single text block,
     // optionally pruning out transient headers (ie. headers that only make
     // sense the first time the response is handled).
+    // Both functions append to the string supplied string.
     void     Flatten(nsACString &, bool pruneTransients);
+    void     FlattenOriginalHeader(nsACString &buf);
 
     // parse flattened response head. block must be null terminated. parsing is
     // destructive.
     nsresult Parse(char *block);
 
     // parse the status line. line must be null terminated.
     void     ParseStatusLine(const char *line);
 
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -1559,16 +1559,17 @@ nsHttpTransaction::HandleContentStart()
     LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     if (mResponseHead) {
         if (LOG3_ENABLED()) {
             LOG3(("http response [\n"));
             nsAutoCString headers;
             mResponseHead->Flatten(headers, false);
+            mResponseHead->FlattenOriginalHeader(headers);
             LogHeaders(headers.get());
             LOG3(("]\n"));
         }
 
         // Save http version, mResponseHead isn't available anymore after
         // TakeResponseHead() is called
         mHttpVersion = mResponseHead->Version();
         mHttpResponseCode = mResponseHead->Status();
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -339,16 +339,49 @@ interface nsIHttpChannel : nsIChannel
      * @param aVisitor
      *        the header visitor instance.
      *
      * @throws NS_ERROR_NOT_AVAILABLE if called before the response
      *         has been received (before onStartRequest).
      */
     void visitResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
 
+     /**
+     * Get the value(s) of a particular response header in the form and order
+     * it has been received from the remote peer. There can be multiple headers
+     * with the same name.
+     *
+     * @param aHeader
+     *        The case-insensitive name of the response header to query (e.g.,
+     *        "Set-Cookie").
+     *
+     * @param aVisitor
+     *        the header visitor instance.
+     *
+     * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+     *         has been received (before onStartRequest) or if the header is
+     *         not set in the response.
+     */
+    void getOriginalResponseHeader(in ACString aHeader,
+                                   in nsIHttpHeaderVisitor aVisitor);
+
+    /**
+     * Call this method to visit all response headers in the form and order as
+     * they have been received from the remote peer.
+     * Calling setResponseHeader while visiting response headers has undefined
+     * behavior.  Don't do it!
+     *
+     * @param aVisitor
+     *        the header visitor instance.
+     *
+     * @throws NS_ERROR_NOT_AVAILABLE if called before the response
+     *         has been received (before onStartRequest).
+     */
+    void visitOriginalResponseHeaders(in nsIHttpHeaderVisitor aVisitor);
+
     /**
      * Returns true if the server sent a "Cache-Control: no-store" response
      * header.
      *
      * @throws NS_ERROR_NOT_AVAILABLE if called before the response
      *         has been received (before onStartRequest).
      */
     boolean isNoStoreResponse();
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -901,16 +901,35 @@ nsViewSourceChannel::VisitResponseHeader
     nsresult rv =
         mHttpChannel->GetResponseHeader(contentTypeStr, contentType);
     if (NS_SUCCEEDED(rv))
         aVisitor->VisitHeader(contentTypeStr, contentType);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsViewSourceChannel::GetOriginalResponseHeader(const nsACString & aHeader,
+                                               nsIHttpHeaderVisitor *aVisitor)
+{
+    nsAutoCString value;
+    nsresult rv = GetResponseHeader(aHeader, value);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+    aVisitor->VisitHeader(aHeader, value);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
+{
+    return VisitResponseHeaders(aVisitor);
+}
+
+NS_IMETHODIMP
 nsViewSourceChannel::IsNoStoreResponse(bool *_retval)
 {
     return !mHttpChannel ? NS_ERROR_NULL_POINTER :
         mHttpChannel->IsNoStoreResponse(_retval);
 }
 
 NS_IMETHODIMP
 nsViewSourceChannel::IsNoCacheResponse(bool *_retval)
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -14,16 +14,17 @@
 #include "nsURLHelper.h"
 #include "nsIStreamConverterService.h"
 #include "nsICacheInfoChannel.h"
 #include <algorithm>
 #include "nsContentSecurityManager.h"
 #include "nsHttp.h"
 #include "nsNetUtil.h"
 #include "nsIURI.h"
+#include "nsHttpHeaderArray.h"
 
 //
 // Helper function for determining the length of data bytes up to
 // the next multipart token.  A token is usually preceded by a LF
 // or CRLF delimiter.
 // 
 static uint32_t
 LengthToToken(const char *cursor, const char *token)
@@ -415,17 +416,18 @@ nsPartChannel::GetResponseHead()
     return mResponseHead;
 }
 
 NS_IMETHODIMP
 nsPartChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
 {
     if (!mResponseHead)
         return NS_ERROR_NOT_AVAILABLE;
-    return mResponseHead->Headers().VisitHeaders(visitor);
+    return mResponseHead->Headers().VisitHeaders(visitor,
+        mozilla::net::nsHttpHeaderArray::eFilterResponse);
 }
 
 //
 // nsIByteRangeRequest implementation...
 //
 
 NS_IMETHODIMP 
 nsPartChannel::GetIsByteRangeRequest(bool *aIsByteRangeRequest)
--- a/netwerk/test/httpserver/httpd.js
+++ b/netwerk/test/httpserver/httpd.js
@@ -3696,16 +3696,25 @@ Response.prototype =
   {
     if (!this._headers || this._finished || this._powerSeized)
       throw Cr.NS_ERROR_NOT_AVAILABLE;
     this._ensureAlive();
 
     this._headers.setHeader(name, value, merge);
   },
 
+  setHeaderNoCheck: function(name, value)
+  {
+    if (!this._headers || this._finished || this._powerSeized)
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    this._ensureAlive();
+
+    this._headers.setHeaderNoCheck(name, value);
+  },
+
   //
   // see nsIHttpResponse.processAsync
   //
   processAsync: function()
   {
     if (this._finished)
       throw Cr.NS_ERROR_UNEXPECTED;
     if (this._powerSeized)
@@ -4984,16 +4993,27 @@ nsHttpHeaders.prototype =
       }
     }
     else
     {
       this._headers[name] = [value];
     }
   },
 
+  setHeaderNoCheck: function(fieldName, fieldValue)
+  {
+    var name = headerUtils.normalizeFieldName(fieldName);
+    var value = headerUtils.normalizeFieldValue(fieldValue);
+    if (name in this._headers) {
+      this._headers[name].push(fieldValue);
+    } else {
+      this._headers[name] = [fieldValue];
+    }
+  },
+
   /**
    * Returns the value for the header specified by this.
    *
    * @throws NS_ERROR_INVALID_ARG
    *   if fieldName does not constitute a valid header field name
    * @throws NS_ERROR_NOT_AVAILABLE
    *   if the given header does not exist in this
    * @returns string
--- a/netwerk/test/httpserver/nsIHttpServer.idl
+++ b/netwerk/test/httpserver/nsIHttpServer.idl
@@ -525,16 +525,22 @@ interface nsIHttpResponse : nsISupports
    * @throws NS_ERROR_NOT_AVAILABLE
    *   if this response is being processed asynchronously and data has been
    *   written to this response's body, or if seizePower() has been called on
    *   this
    */
   void setHeader(in string name, in string value, in boolean merge);
 
   /**
+   * This is used for testing our header handling, so header will be sent out
+   * without transformation. There can be multiple headers.
+   */
+  void setHeaderNoCheck(in string name, in string value);
+
+  /**
    * A stream to which data appearing in the body of this response (or in the
    * totality of the response if seizePower() is called) should be written.
    * After this response has been designated as being processed asynchronously,
    * or after seizePower() has been called on this, subsequent writes will no
    * longer be buffered and will be written to the underlying transport without
    * delaying until the entire response is constructed.  Write-through may or
    * may not be synchronous in the implementation, and in any case particular
    * behavior may not be observable to the HTTP client as intermediate buffers
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_original_sent_received_head.js
@@ -0,0 +1,189 @@
+//
+//  HTTP headers test
+//  Response headers can be changed after they have been received, e.g. empty
+//  headers are deleted, some duplicate header are merged (if no error is
+//  thrown), etc.
+//
+//  The "original header" is introduced to hold the header array in the order
+//  and the form as they have been received from the network.
+//  Here, the "original headers" are tested.
+//
+
+// Note: sets Cc and Ci variables
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+  return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var channel;
+var ios;
+
+var dbg=1
+if (dbg) { print("============== START =========="); }
+
+function run_test() {
+  setup_test();
+  do_test_pending();
+}
+
+function setup_test() {
+  if (dbg) { print("============== setup_test: in"); }
+
+  httpserver.registerPathHandler(testpath, serverHandler);
+  httpserver.start(-1);
+
+  channel = setupChannel(testpath);
+
+  // ChannelListener defined in head_channels.js
+  channel.asyncOpen(new ChannelListener(checkResponse, channel), null);
+
+  if (dbg) { print("============== setup_test: out"); }
+}
+
+function setupChannel(path) {
+  var chan = NetUtil.newChannel ({
+    uri: URL + path,
+    loadUsingSystemPrincipal: true
+  }).QueryInterface(Components.interfaces.nsIHttpChannel);
+  chan.requestMethod = "GET";
+  return chan;
+}
+
+function serverHandler(metadata, response) {
+  if (dbg) { print("============== serverHandler: in"); }
+
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setStatusLine("1.1", 200, "OK");
+
+  // Set a empty header. A empty link header will not appear in header list,
+  // but in the "original headers", it will be still exactly as received.
+  response.setHeaderNoCheck("Link", "", true);
+  response.setHeaderNoCheck("Link", "value1");
+  response.setHeaderNoCheck("Link", "value2");
+  response.setHeaderNoCheck("Location", "loc");
+  response.bodyOutputStream.write(httpbody, httpbody.length);
+
+  if (dbg) { print("============== serverHandler: out"); }
+}
+
+function checkResponse(request, data, context) {
+  if (dbg) { print("============== checkResponse: in"); }
+
+  do_check_eq(channel.responseStatus, 200);
+  do_check_eq(channel.responseStatusText, "OK");
+  do_check_true(channel.requestSucceeded);
+
+  // Response header have only one link header.
+  var linkHeaderFound = 0;
+  var locationHeaderFound = 0;
+  channel.visitResponseHeaders({
+    visitHeader: function visit(aName, aValue) {
+      if (aName == "Link") {
+        linkHeaderFound++;
+        do_check_eq(aValue, "value1, value2");
+      }
+      if (aName == "Location") {
+        locationHeaderFound++;
+        do_check_eq(aValue, "loc");
+      }
+    }
+  });
+  do_check_eq(linkHeaderFound, 1);
+  do_check_eq(locationHeaderFound, 1);
+
+  // The "original header" still contains 3 link headers.
+  var linkOrgHeaderFound = 0;
+  var locationOrgHeaderFound = 0;
+  channel.visitOriginalResponseHeaders({
+    visitHeader: function visitOrg(aName, aValue) {
+      if (aName == "Link") {
+        if (linkOrgHeaderFound == 0) {
+          do_check_eq(aValue, "");
+        } else if (linkOrgHeaderFound == 1 ) {
+          do_check_eq(aValue, "value1");
+        } else {
+          do_check_eq(aValue, "value2");
+        }
+        linkOrgHeaderFound++;
+      }
+      if (aName == "Location") {
+          locationOrgHeaderFound++;
+          do_check_eq(aValue, "loc");
+      }
+    }
+  });
+  do_check_eq(linkOrgHeaderFound, 3);
+  do_check_eq(locationOrgHeaderFound, 1);
+
+  if (dbg) { print("============== Remove headers"); }
+  // Remove header.
+  channel.setResponseHeader("Link", "", false);
+  channel.setResponseHeader("Location", "", false);
+
+  var linkHeaderFound2 = false;
+  var locationHeaderFound2 = 0;
+  channel.visitResponseHeaders({
+    visitHeader: function visit(aName, aValue) {
+      if (aName == "Link") {
+        linkHeaderFound2 = true;
+      }
+      if (aName == "Location") {
+        locationHeaderFound2 = true;
+      }
+    }
+  });
+  do_check_false(linkHeaderFound2, "There should be no link header");
+  do_check_false(locationHeaderFound2, "There should be no location headers.");
+
+  // The "original header" still contains the empty header.
+  var linkOrgHeaderFound2 = 0;
+  var locationOrgHeaderFound2 = 0;
+  channel.visitOriginalResponseHeaders({
+    visitHeader: function visitOrg(aName, aValue) {
+      if (aName == "Link") {
+        if (linkOrgHeaderFound2 == 0) {
+          do_check_eq(aValue, "");
+        } else if (linkOrgHeaderFound2 == 1 ) {
+          do_check_eq(aValue, "value1");
+        } else {
+          do_check_eq(aValue, "value2");
+        }
+        linkOrgHeaderFound2++;
+      }
+      if (aName == "Location") {
+        locationOrgHeaderFound2++;
+        do_check_eq(aValue, "loc");
+      }
+    }
+  });
+  do_check_true(linkOrgHeaderFound2 == 3,
+                "Original link header still here.");
+  do_check_true(locationOrgHeaderFound2 == 1,
+                "Original location header still here.");
+
+  if (dbg) { print("============== Test GetResponseHeader"); }
+  var linkOrgHeaderFound3 = 0;
+  channel.getOriginalResponseHeader("Link",{
+    visitHeader: function visitOrg(aName, aValue) {
+      if (linkOrgHeaderFound3 == 0) {
+        do_check_eq(aValue, "");
+      } else if (linkOrgHeaderFound3 == 1 ) {
+        do_check_eq(aValue, "value1");
+      } else {
+        do_check_eq(aValue, "value2");
+      }
+      linkOrgHeaderFound3++;
+    }
+  });
+  do_check_true(linkOrgHeaderFound2 == 3, 
+                "Original link header still here.");
+
+  httpserver.stop(do_test_finished);
+  if (dbg) { print("============== checkResponse: out"); }
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -236,16 +236,17 @@ run-sequentially = node server exception
 [test_multipart_streamconv.js]
 [test_multipart_streamconv_missing_lead_boundary.js]
 [test_nestedabout_serialize.js]
 [test_net_addr.js]
 # Bug 732363: test fails on windows for unknown reasons.
 skip-if = os == "win"
 [test_nojsredir.js]
 [test_offline_status.js]
+[test_original_sent_received_head.js]
 [test_parse_content_type.js]
 [test_permmgr.js]
 [test_plaintext_sniff.js]
 [test_post.js]
 [test_private_necko_channel.js]
 [test_private_cookie_changed.js]
 [test_progress.js]
 [test_protocolproxyservice.js]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_original_sent_received_head_wrap.js
@@ -0,0 +1,7 @@
+//
+// Run test script in content process instead of chrome (xpcshell's default)
+//
+
+function run_test() {
+  run_test_in_child("../unit/test_original_sent_received_head.js");
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -88,8 +88,9 @@ skip-if = true
 [test_synthesized_response_wrap.js]
 [test_xmlhttprequest_wrap.js]
 [test_XHR_redirects.js]
 [test_redirect_history_wrap.js]
 [test_reply_without_content_type_wrap.js]
 [test_app_offline_http.js]
 [test_getHost_wrap.js]
 [test_app_offline_notifications.js]
+[test_original_sent_received_head_wrap.js]