Bug 815299 - Part 1: Add an API for setting an empty request header on an HTTP channel; r=dragana

This commit is contained in:
Ehsan Akhgari 2015-08-27 09:38:44 -04:00
parent faf0ed262f
commit 60ad7f6c40
13 changed files with 156 additions and 21 deletions

View File

@ -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)
{

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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)
{

View File

@ -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<mozilla::net::RequestHeaderTuple>
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;

View File

@ -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)
{

View File

@ -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);
}

View File

@ -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);

View File

@ -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!

View File

@ -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)
{

View File

@ -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");