diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index d85e9931702..5752d125b69 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -1326,6 +1326,29 @@ HttpBaseChannel::SetRequestHeader(const nsACString& aHeader, return mRequestHead.SetHeader(atom, flatValue, aMerge); } +NS_IMETHODIMP +HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) +{ + const nsCString &flatHeader = PromiseFlatCString(aHeader); + + LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", + this, flatHeader.get())); + + // Verify header names are valid HTTP tokens and header values are reasonably + // close to whats allowed in RFC 2616. + if (!nsHttp::IsValidToken(flatHeader)) { + return NS_ERROR_INVALID_ARG; + } + + nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get()); + if (!atom) { + NS_WARNING("failed to resolve atom"); + return NS_ERROR_NOT_AVAILABLE; + } + + return mRequestHead.SetEmptyHeader(atom); +} + NS_IMETHODIMP HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) { diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index e3e3b9d6571..69206cd9b22 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -138,6 +138,7 @@ public: NS_IMETHOD GetRequestHeader(const nsACString& aHeader, nsACString& aValue) override; NS_IMETHOD SetRequestHeader(const nsACString& aHeader, const nsACString& aValue, bool aMerge) override; + NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override; NS_IMETHOD VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) override; NS_IMETHOD GetResponseHeader(const nsACString &header, nsACString &value) override; NS_IMETHOD SetResponseHeader(const nsACString& header, diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 6599d7de128..b9931086210 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1769,6 +1769,25 @@ HttpChannelChild::SetRequestHeader(const nsACString& aHeader, tuple->mHeader = aHeader; tuple->mValue = aValue; tuple->mMerge = aMerge; + tuple->mEmpty = false; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) +{ + LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this)); + nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader); + if (NS_FAILED(rv)) + return rv; + + RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement(); + if (!tuple) + return NS_ERROR_OUT_OF_MEMORY; + + tuple->mHeader = aHeader; + tuple->mMerge = false; + tuple->mEmpty = true; return NS_OK; } diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index fdabaf345bb..79406a91c6a 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -80,6 +80,7 @@ public: NS_IMETHOD SetRequestHeader(const nsACString& aHeader, const nsACString& aValue, bool aMerge) override; + NS_IMETHOD SetEmptyRequestHeader(const nsACString& aHeader) override; NS_IMETHOD RedirectTo(nsIURI *newURI) override; // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override; diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 07c5dad138f..7cd690bf10f 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -361,9 +361,13 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, mChannel->SetLoadFlags(loadFlags); for (uint32_t i = 0; i < requestHeaders.Length(); i++) { - mChannel->SetRequestHeader(requestHeaders[i].mHeader, - requestHeaders[i].mValue, - requestHeaders[i].mMerge); + if (requestHeaders[i].mEmpty) { + mChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader); + } else { + mChannel->SetRequestHeader(requestHeaders[i].mHeader, + requestHeaders[i].mValue, + requestHeaders[i].mMerge); + } } mParentListener = new HttpChannelParentListener(this); @@ -636,9 +640,13 @@ HttpChannelParent::RecvRedirect2Verify(const nsresult& result, newHttpChannel->RedirectTo(apiRedirectUri); for (uint32_t i = 0; i < changedHeaders.Length(); i++) { - newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader, - changedHeaders[i].mValue, - changedHeaders[i].mMerge); + if (changedHeaders[i].mEmpty) { + newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader); + } else { + newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader, + changedHeaders[i].mValue, + changedHeaders[i].mMerge); + } } } } diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp index 1a159ef226d..01b55aed334 100644 --- a/netwerk/protocol/http/NullHttpChannel.cpp +++ b/netwerk/protocol/http/NullHttpChannel.cpp @@ -105,6 +105,12 @@ NullHttpChannel::SetRequestHeader(const nsACString & aHeader, const nsACString & return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +NullHttpChannel::SetEmptyRequestHeader(const nsACString & aHeader) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP NullHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *aVisitor) { diff --git a/netwerk/protocol/http/PHttpChannelParams.h b/netwerk/protocol/http/PHttpChannelParams.h index 4c1743775e4..cb28819dbea 100644 --- a/netwerk/protocol/http/PHttpChannelParams.h +++ b/netwerk/protocol/http/PHttpChannelParams.h @@ -24,11 +24,13 @@ struct RequestHeaderTuple { nsCString mHeader; nsCString mValue; bool mMerge; + bool mEmpty; bool operator ==(const RequestHeaderTuple &other) const { return mHeader.Equals(other.mHeader) && mValue.Equals(other.mValue) && - mMerge == other.mMerge; + mMerge == other.mMerge && + mEmpty == other.mEmpty; } }; @@ -49,13 +51,15 @@ struct ParamTraits WriteParam(aMsg, aParam.mHeader); WriteParam(aMsg, aParam.mValue); WriteParam(aMsg, aParam.mMerge); + WriteParam(aMsg, aParam.mEmpty); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { if (!ReadParam(aMsg, aIter, &aResult->mHeader) || !ReadParam(aMsg, aIter, &aResult->mValue) || - !ReadParam(aMsg, aIter, &aResult->mMerge)) + !ReadParam(aMsg, aIter, &aResult->mMerge) || + !ReadParam(aMsg, aIter, &aResult->mEmpty)) return false; return true; diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp index 380f8b33e9f..606e42777dd 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.cpp +++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp @@ -51,6 +51,25 @@ nsHttpHeaderArray::SetHeader(nsHttpAtom header, return NS_OK; } +nsresult +nsHttpHeaderArray::SetEmptyHeader(nsHttpAtom header) +{ + 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 { + entry->value.Truncate(); + } + + return NS_OK; +} + nsresult nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header, const nsACString &value) { diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h index ec1354b9909..9fb78faf34e 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.h +++ b/netwerk/protocol/http/nsHttpHeaderArray.h @@ -30,6 +30,9 @@ public: nsresult SetHeader(nsHttpAtom header, const nsACString &value, bool merge = false); + // Used by internal setters to set an empty header + nsresult SetEmptyHeader(nsHttpAtom header); + // 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); @@ -169,18 +172,20 @@ nsHttpHeaderArray::MergeHeader(nsHttpAtom header, if (value.IsEmpty()) return; // merge of empty header = no-op - // 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'); - } else { - // Delimit each value from the others using a comma (per HTTP spec) - entry->value.AppendLiteral(", "); + if (!entry->value.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'); + } else { + // Delimit each value from the others using a comma (per HTTP spec) + entry->value.AppendLiteral(", "); + } } entry->value.Append(value); } diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h index 64a66794f4d..755750e83d9 100644 --- a/netwerk/protocol/http/nsHttpRequestHead.h +++ b/netwerk/protocol/http/nsHttpRequestHead.h @@ -46,6 +46,7 @@ public: return mHeaders.PeekHeader(h); } nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false) { return mHeaders.SetHeader(h, v, m); } + nsresult SetEmptyHeader(nsHttpAtom h) { return mHeaders.SetEmptyHeader(h); } nsresult GetHeader(nsHttpAtom h, nsACString &v) const { return mHeaders.GetHeader(h, v); diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl index cf3a84bf740..1ece3a38667 100644 --- a/netwerk/protocol/http/nsIHttpChannel.idl +++ b/netwerk/protocol/http/nsIHttpChannel.idl @@ -14,7 +14,7 @@ interface nsIHttpHeaderVisitor; * the inspection of the resulting HTTP response status and headers when they * become available. */ -[builtinclass, scriptable, uuid(fdc70825-8ae1-4f81-bd9e-f1fd3f6307ad)] +[builtinclass, scriptable, uuid(182ee0b9-e3c1-4426-9db3-70814ea7ed24)] interface nsIHttpChannel : nsIChannel { /************************************************************************** @@ -127,6 +127,23 @@ interface nsIHttpChannel : nsIChannel in ACString aValue, in boolean aMerge); + /** + * Set a request header with empty value. + * + * This should be used with caution in the cases where the behavior of + * setRequestHeader ignoring empty header values is undesirable. + * + * This method may only be called before the channel is opened. + * + * @param aHeader + * The case-insensitive name of the request header to set (e.g., + * "Cookie"). + * + * @throws NS_ERROR_IN_PROGRESS if called after the channel has been + * opened. + */ + void setEmptyRequestHeader(in ACString aHeader); + /** * Call this method to visit all request headers. Calling setRequestHeader * while visiting request headers has undefined behavior. Don't do it! diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp index 3b0a46e14d0..e278de95f9d 100644 --- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp +++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp @@ -747,6 +747,13 @@ nsViewSourceChannel::SetRequestHeader(const nsACString & aHeader, mHttpChannel->SetRequestHeader(aHeader, aValue, aMerge); } +NS_IMETHODIMP +nsViewSourceChannel::SetEmptyRequestHeader(const nsACString & aHeader) +{ + return !mHttpChannel ? NS_ERROR_NULL_POINTER : + mHttpChannel->SetEmptyRequestHeader(aHeader); +} + NS_IMETHODIMP nsViewSourceChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *aVisitor) { diff --git a/netwerk/test/unit/test_head.js b/netwerk/test/unit/test_head.js index cf36a4e1100..02a905b04fd 100644 --- a/netwerk/test/unit/test_head.js +++ b/netwerk/test/unit/test_head.js @@ -46,6 +46,24 @@ function setup_test() { setOK = channel.getRequestHeader("MergeMe"); do_check_eq(setOK, "foo1, foo2, foo3"); + channel.setEmptyRequestHeader("Empty"); + setOK = channel.getRequestHeader("Empty"); + do_check_eq(setOK, ""); + + channel.setRequestHeader("ReplaceWithEmpty", "initial value", true); + setOK = channel.getRequestHeader("ReplaceWithEmpty"); + do_check_eq(setOK, "initial value"); + channel.setEmptyRequestHeader("ReplaceWithEmpty"); + setOK = channel.getRequestHeader("ReplaceWithEmpty"); + do_check_eq(setOK, ""); + + channel.setEmptyRequestHeader("MergeWithEmpty"); + setOK = channel.getRequestHeader("MergeWithEmpty"); + do_check_eq(setOK, ""); + channel.setRequestHeader("MergeWithEmpty", "foo", true); + setOK = channel.getRequestHeader("MergeWithEmpty"); + do_check_eq(setOK, "foo"); + var uri = ios.newURI("http://foo1.invalid:80", null, null); channel.referrer = uri; do_check_true(channel.referrer.equals(uri)); @@ -85,6 +103,12 @@ function serverHandler(metadata, response) { do_check_eq(setOK, "replaced"); setOK = metadata.getHeader("MergeMe"); do_check_eq(setOK, "foo1, foo2, foo3"); + setOK = metadata.getHeader("Empty"); + do_check_eq(setOK, ""); + setOK = metadata.getHeader("ReplaceWithEmpty"); + do_check_eq(setOK, ""); + setOK = metadata.getHeader("MergeWithEmpty"); + do_check_eq(setOK, "foo"); setOK = metadata.getHeader("Referer"); do_check_eq(setOK, "http://foo2.invalid:90/bar");