Bug 1277019: change header and status parsing methods to take nsACString instead of char array ; r?mayhemer draft
authorDecky Coss <coss@cosstropolis.com>
Tue, 31 May 2016 12:42:15 -0400
changeset 394483 e754dc8308221192a42c89471c88b34c0d7dfafc
parent 394256 2ea3d51ba1bb9f5c3b6921c43ea63f70b4fdf5d2
child 526824 d2aee23b877c130559dfbc7c68d7ebb10b8af548
push id24585
push usercoss@cosstropolis.com
push dateFri, 29 Jul 2016 20:13:23 +0000
reviewersmayhemer
bugs1277019
milestone50.0a1
Bug 1277019: change header and status parsing methods to take nsACString instead of char array ; r?mayhemer MozReview-Commit-ID: 7ZNwvnTMCzo
netwerk/protocol/http/InterceptedChannel.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChunkedDecoder.cpp
netwerk/protocol/http/nsHttpHeaderArray.cpp
netwerk/protocol/http/nsHttpHeaderArray.h
netwerk/protocol/http/nsHttpRequestHead.cpp
netwerk/protocol/http/nsHttpRequestHead.h
netwerk/protocol/http/nsHttpResponseHead.cpp
netwerk/protocol/http/nsHttpResponseHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/streamconv/converters/nsMultiMixedConv.cpp
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/cors/allow-headers.htm.ini
testing/web-platform/meta/cors/origin.htm.ini
testing/web-platform/meta/cors/remote-origin.htm.ini
testing/web-platform/tests/XMLHttpRequest/headers-normalize-response.htm
testing/web-platform/tests/XMLHttpRequest/resources/parse-headers.py
--- a/netwerk/protocol/http/InterceptedChannel.cpp
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -89,28 +89,28 @@ InterceptedChannelBase::DoSynthesizeStat
 
     // Always assume HTTP 1.1 for synthesized responses.
     nsAutoCString statusLine;
     statusLine.AppendLiteral("HTTP/1.1 ");
     statusLine.AppendInt(aStatus);
     statusLine.AppendLiteral(" ");
     statusLine.Append(aReason);
 
-    (*mSynthesizedResponseHead)->ParseStatusLine(statusLine.get());
+    (*mSynthesizedResponseHead)->ParseStatusLine(statusLine);
     return NS_OK;
 }
 
 nsresult
 InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
 {
     EnsureSynthesizedResponse();
 
     nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
     // Overwrite any existing header.
-    nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header.get());
+    nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header);
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedChannelBase::GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut)
 {
   MOZ_ASSERT(aCollectorOut);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -3554,17 +3554,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
     // line and headers in the form Firefox uses them internally (no dupicate
     // headers, etc.).
     rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Parse string stored in a "response-head" metadata element.
     // These response headers will be merged with the orignal headers (i.e. the
     // headers stored in a "original-response-headers" metadata element).
-    rv = mCachedResponseHead->ParseCachedHead((char *) buf.get());
+    rv = mCachedResponseHead->ParseCachedHead(buf.get());
     NS_ENSURE_SUCCESS(rv, rv);
     buf.Adopt(0);
 
     bool isCachedRedirect = WillRedirect(mCachedResponseHead);
 
     // Do not return 304 responses from the cache, and also do not return
     // any other non-redirect 3xx responses from the cache (see bug 759043).
     NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) ||
--- a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -108,17 +108,17 @@ nsHttpChunkedDecoder::ParseChunkRemainin
 
         if (mWaitEOF) {
             if (*buf) {
                 LOG(("got trailer: %s\n", buf));
                 // allocate a header array for the trailers on demand
                 if (!mTrailers) {
                     mTrailers = new nsHttpHeaderArray();
                 }
-                mTrailers->ParseHeaderLine(buf);
+                mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count));
             }
             else {
                 mWaitEOF = false;
                 mReachedEOF = true;
                 LOG(("reached end of chunked-body\n"));
             }
         }
         else if (*buf) {
--- a/netwerk/protocol/http/nsHttpHeaderArray.cpp
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -303,66 +303,68 @@ nsHttpHeaderArray::VisitHeaders(nsIHttpH
         if NS_FAILED(rv) {
             return rv;
         }
     }
     return NS_OK;
 }
 
 /*static*/ nsresult
-nsHttpHeaderArray::ParseHeaderLine(const char *line,
+nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
                                    nsHttpAtom *hdr,
-                                   char **val)
+                                   nsACString *val)
 {
     //
     // BNF from section 4.2 of RFC 2616:
     //
     //   message-header = field-name ":" [ field-value ]
     //   field-name     = token
     //   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));
+    int32_t split = line.FindChar(':');
+
+    if (split == kNotFound) {
+        LOG(("malformed header [%s]: no colon\n",
+            PromiseFlatCString(line).get()));
         return NS_ERROR_FAILURE;
     }
 
+    const nsACString& sub = Substring(line, 0, split);
+    const nsACString& sub2 = Substring(
+        line, split + 1, line.Length() - split - 1);
+
     // 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));
+    if (!nsHttp::IsValidToken(sub)) {
+        LOG(("malformed header [%s]: field-name not a token\n",
+            PromiseFlatCString(line).get()));
         return NS_ERROR_FAILURE;
     }
 
-    *p = 0; // null terminate field-name
-
-    nsHttpAtom atom = nsHttp::ResolveAtom(line);
+    nsHttpAtom atom = nsHttp::ResolveAtom(sub);
     if (!atom) {
-        LOG(("failed to resolve atom [%s]\n", line));
+        LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
         return NS_ERROR_FAILURE;
     }
 
     // skip over whitespace
-    p = net_FindCharNotInSet(++p, HTTP_LWS);
+    char *p = net_FindCharNotInSet(
+        sub2.BeginReading(), sub2.EndReading(), 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.
+    char *p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
 
     // assign return values
     if (hdr) *hdr = atom;
-    if (val) *val = p;
+    if (val) val->Assign(p, p2 - p + 1);
 
     return NS_OK;
 }
 
 void
 nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders,
                            bool pruneTransients)
 {
--- a/netwerk/protocol/http/nsHttpHeaderArray.h
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -91,19 +91,19 @@ public:
         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).
-    static nsresult ParseHeaderLine(const char *line,
+    static nsresult ParseHeaderLine(const nsACString& line,
                                     nsHttpAtom *header=nullptr,
-                                    char **value=nullptr);
+                                    nsACString* value=nullptr);
 
     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;
 
--- a/netwerk/protocol/http/nsHttpRequestHead.cpp
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -244,31 +244,32 @@ nsHttpRequestHead::ParsedMethod()
 bool
 nsHttpRequestHead::EqualsMethod(ParsedMethodType aType)
 {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return mParsedMethod == aType;
 }
 
 void
-nsHttpRequestHead::ParseHeaderSet(char *buffer)
+nsHttpRequestHead::ParseHeaderSet(const char *buffer)
 {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     nsHttpAtom hdr;
-    char *val;
+    nsAutoCString val;
     while (buffer) {
-        char *eof = strchr(buffer, '\r');
+        const char *eof = strchr(buffer, '\r');
         if (!eof) {
             break;
         }
-        *eof = '\0';
-        if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(buffer,
-                                                            &hdr,
-                                                            &val))) {
-            mHeaders.SetHeaderFromNet(hdr, nsDependentCString(val), false);
+        if (NS_SUCCEEDED(nsHttpHeaderArray::ParseHeaderLine(
+            nsDependentCSubstring(buffer, eof - buffer),
+            &hdr,
+            &val))) {
+
+            mHeaders.SetHeaderFromNet(hdr, val, false);
         }
         buffer = eof + 1;
         if (*buffer == '\n') {
             buffer++;
         }
     }
 }
 
--- a/netwerk/protocol/http/nsHttpRequestHead.h
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -93,34 +93,34 @@ public:
     bool EqualsMethod(ParsedMethodType aType);
     bool IsGet() { return EqualsMethod(kMethod_Get); }
     bool IsPost() { return EqualsMethod(kMethod_Post); }
     bool IsOptions() { return EqualsMethod(kMethod_Options); }
     bool IsConnect() { return EqualsMethod(kMethod_Connect); }
     bool IsHead() { return EqualsMethod(kMethod_Head); }
     bool IsPut() { return EqualsMethod(kMethod_Put); }
     bool IsTrace() { return EqualsMethod(kMethod_Trace); }
-    void ParseHeaderSet(char *buffer);
+    void ParseHeaderSet(const char *buffer);
 private:
     // All members must be copy-constructable and assignable
     nsHttpHeaderArray mHeaders;
     nsCString         mMethod;
     nsHttpVersion     mVersion;
 
     // mRequestURI and mPath are strings instead of an nsIURI
     // because this is used off the main thread
     nsCString         mRequestURI;
     nsCString         mPath;
 
     nsCString         mOrigin;
     ParsedMethodType  mParsedMethod;
     bool              mHTTPS;
 
     // We are using ReentrantMonitor instead of a Mutex because VisitHeader
-    // function calls nsIHttpHeaderVisitor::VisitHeader while under lock. 
+    // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
     ReentrantMonitor  mReentrantMonitor;
 
     // During VisitHeader we sould not allow cal to SetHeader.
     bool mInVisitHeaders;
 };
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -265,43 +265,41 @@ nsHttpResponseHead::FlattenNetworkOrigin
     if (mVersion == NS_HTTP_VERSION_0_9) {
         return;
     }
 
     mHeaders.FlattenOriginalHeader(buf);
 }
 
 nsresult
-nsHttpResponseHead::ParseCachedHead(char *block)
+nsHttpResponseHead::ParseCachedHead(const char *block)
 {
     ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
     LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
 
     // this command works on a buffer as prepared by Flatten, as such it is
     // not very forgiving ;-)
 
     char *p = PL_strstr(block, "\r\n");
     if (!p)
         return NS_ERROR_UNEXPECTED;
 
-    *p = 0;
-    ParseStatusLine_locked(block);
+    ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
 
     do {
         block = p + 2;
 
         if (*block == 0)
             break;
 
         p = PL_strstr(block, "\r\n");
         if (!p)
             return NS_ERROR_UNEXPECTED;
 
-        *p = 0;
-        ParseHeaderLine_locked(block, false);
+        ParseHeaderLine_locked(nsDependentCSubstring(block, p - block), false);
 
     } while (1);
 
     return NS_OK;
 }
 
 nsresult
 nsHttpResponseHead::ParseCachedOriginalHeaders(char *block)
@@ -313,36 +311,38 @@ nsHttpResponseHead::ParseCachedOriginalH
     // as such it is not very forgiving ;-)
 
     if (!block) {
         return NS_ERROR_UNEXPECTED;
     }
 
     char *p = block;
     nsHttpAtom hdr = {0};
-    char *val;
+    nsAutoCString val;
     nsresult rv;
 
     do {
         block = p;
 
         if (*block == 0)
             break;
 
         p = PL_strstr(block, "\r\n");
         if (!p)
             return NS_ERROR_UNEXPECTED;
 
         *p = 0;
-        if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(block, &hdr, &val))) {
+        if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
+            nsDependentCString(block, p - block), &hdr, &val))) {
+
             return NS_OK;
         }
 
         rv = mHeaders.SetResponseHeaderFromCache(hdr,
-                                                 nsDependentCString(val),
+                                                 val,
                                                  nsHttpHeaderArray::eVarietyResponseNetOriginal);
 
         if (NS_FAILED(rv)) {
             return rv;
         }
 
         p = p + 2;
     } while (1);
@@ -500,111 +500,122 @@ nsHttpResponseHead::AssignDefaultStatusT
         break;
     default:
         mStatusText.AssignLiteral("No Reason Phrase");
         break;
     }
 }
 
 void
-nsHttpResponseHead::ParseStatusLine(const char *line)
+nsHttpResponseHead::ParseStatusLine(const nsACString &line)
 {
+
     ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
     ParseStatusLine_locked(line);
 }
 
 void
-nsHttpResponseHead::ParseStatusLine_locked(const char *line)
+nsHttpResponseHead::ParseStatusLine_locked(const nsACString &line)
 {
     //
     // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
     //
 
+    const char *start = line.BeginReading();
+    const char *end = line.EndReading();
+    const char *p = start;
+
     // HTTP-Version
-    ParseVersion(line);
+    ParseVersion(start);
 
-    if ((mVersion == NS_HTTP_VERSION_0_9) || !(line = PL_strchr(line, ' '))) {
+    int32_t index = line.FindChar(' ');
+
+    if ((mVersion == NS_HTTP_VERSION_0_9) || (index == -1)) {
         mStatus = 200;
         AssignDefaultStatusText();
     }
     else {
         // Status-Code
-        mStatus = (uint16_t) atoi(++line);
+        p += index + 1;
+        mStatus = (uint16_t) atoi(p);
         if (mStatus == 0) {
             LOG(("mal-formed response status; assuming status = 200\n"));
             mStatus = 200;
         }
 
         // Reason-Phrase is whatever is remaining of the line
-        if (!(line = PL_strchr(line, ' '))) {
+        index = line.FindChar(' ', p - start);
+        if (index == -1) {
             AssignDefaultStatusText();
         }
-        else
-            mStatusText = nsDependentCString(++line);
+        else {
+            p = start + index + 1;
+            mStatusText = nsDependentCSubstring(p, end - p);
+        }
     }
 
     LOG(("Have status line [version=%u status=%u statusText=%s]\n",
         unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
 }
 
 nsresult
-nsHttpResponseHead::ParseHeaderLine(const char *line)
+nsHttpResponseHead::ParseHeaderLine(const nsACString &line)
 {
     ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
     return ParseHeaderLine_locked(line, true);
 }
 
 nsresult
-nsHttpResponseHead::ParseHeaderLine_locked(const char *line, bool originalFromNetHeaders)
+nsHttpResponseHead::ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders)
 {
     nsHttpAtom hdr = {0};
-    char *val;
+    nsAutoCString val;
 
     if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(line, &hdr, &val))) {
         return NS_OK;
     }
     nsresult rv;
     if (originalFromNetHeaders) {
         rv = mHeaders.SetHeaderFromNet(hdr,
-                                       nsDependentCString(val),
+                                       val,
                                        true);
     } else {
         rv = mHeaders.SetResponseHeaderFromCache(hdr,
-                                                 nsDependentCString(val),
+                                                 val,
                                                  nsHttpHeaderArray::eVarietyResponse);
     }
     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.
-        if (nsHttp::ParseInt64(val, &ignored, &len)) {
+        if (nsHttp::ParseInt64(val.get(), &ignored, &len)) {
             mContentLength = len;
         }
         else {
             // If this is a negative content length then just ignore it
-            LOG(("invalid content-length! %s\n", val));
+            LOG(("invalid content-length! %s\n", val.get()));
         }
     }
     else if (hdr == nsHttp::Content_Type) {
-        LOG(("ParseContentType [type=%s]\n", val));
+        LOG(("ParseContentType [type=%s]\n", val.get()));
         bool dummy;
-        net_ParseContentType(nsDependentCString(val),
+        net_ParseContentType(val,
                              mContentType, mContentCharset, &dummy);
     }
     else if (hdr == nsHttp::Cache_Control)
-        ParseCacheControl(val);
+        ParseCacheControl(val.get());
     else if (hdr == nsHttp::Pragma)
-        ParsePragma(val);
+        ParsePragma(val.get());
     return NS_OK;
 }
 
 // From section 13.2.3 of RFC2616, we compute the current age of a cached
 // response as follows:
 //
 //    currentAge = max(max(0, responseTime - dateValue), ageValue)
 //               + now - requestTime
--- a/netwerk/protocol/http/nsHttpResponseHead.h
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -84,25 +84,25 @@ public:
     void FlattenNetworkOriginalHeaders(nsACString &buf);
 
     // The next 2 functions parse flattened response head and original net headers.
     // They are used when we are reading an entry from the cache.
     //
     // To keep proper order of the original headers we MUST call
     // ParseCachedOriginalHeaders FIRST and then ParseCachedHead.
     //
-    // block must be null terminated. parsing is destructive.
-    nsresult ParseCachedHead(char *block);
+    // block must be null terminated.
+    nsresult ParseCachedHead(const char *block);
     nsresult ParseCachedOriginalHeaders(char *block);
 
-    // parse the status line. line must be null terminated.
-    void ParseStatusLine(const char *line);
+    // parse the status line.
+    void ParseStatusLine(const nsACString &line);
 
-    // parse a header line. line must be null terminated. parsing is destructive.
-    nsresult ParseHeaderLine(const char *line);
+    // parse a header line.
+    nsresult ParseHeaderLine(const nsACString &line);
 
     // cache validation support methods
     nsresult ComputeFreshnessLifetime(uint32_t *);
     nsresult ComputeCurrentAge(uint32_t now, uint32_t requestTime,
                                uint32_t *result);
     bool MustValidate();
     bool MustValidateIfExpired();
 
@@ -139,18 +139,18 @@ public:
 private:
     nsresult SetHeader_locked(nsHttpAtom h, const nsACString &v,
                               bool m=false);
     void AssignDefaultStatusText();
     void ParseVersion(const char *);
     void ParseCacheControl(const char *);
     void ParsePragma(const char *);
 
-    void ParseStatusLine_locked(const char *line);
-    nsresult ParseHeaderLine_locked(const char *line, bool originalFromNetHeaders);
+    void ParseStatusLine_locked(const nsACString &line);
+    nsresult ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders);
 
     // these return failure if the header does not exist.
     nsresult ParseDateHeader(nsHttpAtom header, uint32_t *result) const;
 
     bool ExpiresInPast_locked() const;
     nsresult GetAgeValue_locked(uint32_t *result) const;
     nsresult GetExpiresValue_locked(uint32_t *result) const;
     nsresult GetMaxAgeValue_locked(uint32_t *result) const;
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -1361,19 +1361,19 @@ nsHttpTransaction::LocateHttpStart(char 
             firstByte = false;
         buf++;
         len--;
     }
     return 0;
 }
 
 nsresult
-nsHttpTransaction::ParseLine(char *line)
+nsHttpTransaction::ParseLine(nsACString &line)
 {
-    LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
+    LOG(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get()));
     nsresult rv = NS_OK;
 
     if (!mHaveStatusLine) {
         mResponseHead->ParseStatusLine(line);
         mHaveStatusLine = true;
         // XXX this should probably never happen
         if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
             mHaveAllHeaders = true;
@@ -1391,17 +1391,17 @@ nsHttpTransaction::ParseLineSegment(char
 
     if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
         // trim off the new line char, and if this segment is
         // not a continuation of the previous or if we haven't
         // parsed the status line yet, then parse the contents
         // of mLineBuf.
         mLineBuf.Truncate(mLineBuf.Length() - 1);
         if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
-            nsresult rv = ParseLine(mLineBuf.BeginWriting());
+            nsresult rv = ParseLine(mLineBuf);
             mLineBuf.Truncate();
             if (NS_FAILED(rv)) {
                 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
                     mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
                     nullptr, 0);
                 return rv;
             }
         }
@@ -1470,17 +1470,17 @@ nsHttpTransaction::ParseHead(char *buf,
             // tolerate only minor junk before the status line
             mHttpResponseMatched = true;
             char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
             if (!p) {
                 // Treat any 0.9 style response of a put as a failure.
                 if (mRequestHead->IsPut())
                     return NS_ERROR_ABORT;
 
-                mResponseHead->ParseStatusLine("");
+                mResponseHead->ParseStatusLine(EmptyCString());
                 mHaveStatusLine = true;
                 mHaveAllHeaders = true;
                 return NS_OK;
             }
             if (p > buf) {
                 // skip over the junk
                 mInvalidResponseBytesRead += p - buf;
                 *countRead = p - buf;
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -169,17 +169,17 @@ public:
 private:
     friend class DeleteHttpTransaction;
     virtual ~nsHttpTransaction();
 
     nsresult Restart();
     nsresult RestartInProgress();
     char    *LocateHttpStart(char *buf, uint32_t len,
                              bool aAllowPartialMatch);
-    nsresult ParseLine(char *line);
+    nsresult ParseLine(nsACString &line);
     nsresult ParseLineSegment(char *seg, uint32_t len);
     nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
     nsresult ProcessData(char *, uint32_t, uint32_t *);
     void     DeleteSelfOnConsumerThread();
     void     ReleaseBlockingTransaction();
 
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -1210,19 +1210,18 @@ nsMultiMixedConv::ParseHeaders(nsIChanne
             done = true;
             break;
         }
 
         char tmpChar = *newLine;
         *newLine = '\0'; // cursor is now null terminated
 
         if (mResponseHead) {
-            // ParseHeaderLine is destructive. We create a copy
             nsAutoCString tmpHeader(cursor);
-            mResponseHead->ParseHeaderLine(tmpHeader.get());
+            mResponseHead->ParseHeaderLine(tmpHeader);
         }
 
         char *colon = (char *) strchr(cursor, ':');
         if (colon) {
             *colon = '\0';
             nsAutoCString headerStr(cursor);
             headerStr.CompressWhitespace();
             *colon = ':';
@@ -1345,9 +1344,8 @@ NS_NewMultiMixedConv(nsMultiMixedConv** 
 
     *aMultiMixedConv = new nsMultiMixedConv();
     if (! *aMultiMixedConv)
         return NS_ERROR_OUT_OF_MEMORY;
 
     NS_ADDREF(*aMultiMixedConv);
     return NS_OK;
 }
-
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -12527,16 +12527,20 @@
         "path": "XMLHttpRequest/getresponseheader-special-characters.htm",
         "url": "/XMLHttpRequest/getresponseheader-special-characters.htm"
       },
       {
         "path": "XMLHttpRequest/getresponseheader-unsent-opened-state.htm",
         "url": "/XMLHttpRequest/getresponseheader-unsent-opened-state.htm"
       },
       {
+        "path": "XMLHttpRequest/headers-normalize-response.htm",
+        "url": "/XMLHttpRequest/headers-normalize-response.htm"
+      },
+      {
         "path": "XMLHttpRequest/interfaces.html",
         "url": "/XMLHttpRequest/interfaces.html"
       },
       {
         "path": "XMLHttpRequest/open-after-abort.htm",
         "url": "/XMLHttpRequest/open-after-abort.htm"
       },
       {
deleted file mode 100644
--- a/testing/web-platform/meta/cors/allow-headers.htm.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[allow-headers.htm]
-  type: testharness
-  [Disallow origin: http://web-platform.test:8000\\0]
-    expected: FAIL
-
-  [Disallow origin: *\\0]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/cors/origin.htm.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[origin.htm]
-  type: testharness
-  [Disallow origin: http://web-platform.test:8000\\0]
-    expected: FAIL
-
-  [Disallow origin: *\\0]
-    expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/cors/remote-origin.htm.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[remote-origin.htm]
-  type: testharness
-  [Disallow origin: http://www1.web-platform.test:8000\\0]
-    expected: FAIL
-
-  [Disallow origin: *\\0]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/headers-normalize-response.htm
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Whitespace and null in header values</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=support.js?pipe=sub></script>
+
+<h1>Whitespace and null in response header values</h1>
+
+<div id=log></div>
+
+<script>
+
+function matchHeaderValue(val) {
+    test(function () {
+        var client = new XMLHttpRequest();
+        var trimmed = val.trim();
+        client.open("GET", "resources/parse-headers.py?my-custom-header="+encodeURIComponent(val), false);
+        client.send();
+        var r = client.getResponseHeader("My-Custom-Header");
+
+        assert_equals(r, trimmed);
+    }, "Header value: " + val.replace(/\t/g, "[tab]").replace(/ /g, "_").replace("\0", "\\0"));
+}
+
+matchHeaderValue("hello world\0");
+matchHeaderValue("\0hello world");
+matchHeaderValue("hello\0world");
+matchHeaderValue("  hello world");
+matchHeaderValue("hello world  ");
+matchHeaderValue("  hello world  ");
+matchHeaderValue("\thello world");
+matchHeaderValue("hello world\t");
+matchHeaderValue("\thello world\t");
+matchHeaderValue("hello      world");
+matchHeaderValue("hello\tworld");
+matchHeaderValue("\0");
+matchHeaderValue("   ");
+matchHeaderValue("\t");
+matchHeaderValue("");
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/parse-headers.py
@@ -0,0 +1,12 @@
+import json
+
+def main(request, response):
+
+    content = ""
+    if "my-custom-header" in request.GET:
+        val = request.GET.first("my-custom-header")
+        print "header is about to be set to '{}'".format(val)
+        response.headers.set("My-Custom-Header", val)
+        print "header was set"
+
+    return content