Bug 669259 - Expose original header received from a peer. r=mcmanus

This commit is contained in:
Dragana Damjanovic 2016-05-12 14:33:00 -04:00
Родитель 730c3b339c
Коммит 7cf5152088
22 изменённых файлов: 736 добавлений и 138 удалений

Просмотреть файл

@ -1511,6 +1511,12 @@ pref("network.http.enable-packaged-apps", false);
// 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)

Просмотреть файл

@ -1660,9 +1660,38 @@ HttpBaseChannel::SetResponseHeader(const nsACString& header,
NS_IMETHODIMP
HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
{
if (!mResponseHead)
if (!mResponseHead) {
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->Headers().VisitHeaders(visitor);
}
return mResponseHead->Headers().VisitHeaders(visitor,
nsHttpHeaderArray::eFilterResponse);
}
NS_IMETHODIMP
HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
nsIHttpHeaderVisitor *aVisitor)
{
if (!mResponseHead) {
return NS_ERROR_NOT_AVAILABLE;
}
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

Просмотреть файл

@ -155,6 +155,9 @@ public:
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;

Просмотреть файл

@ -209,6 +209,19 @@ 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)
{

Просмотреть файл

@ -100,14 +100,58 @@ struct ParamTraits<mozilla::net::nsHttpHeaderArray::nsEntry>
{
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;
}
};

Просмотреть файл

@ -238,6 +238,7 @@ nsHttpHandler::nsHttpHandler()
, mTCPKeepaliveLongLivedEnabled(false)
, mTCPKeepaliveLongLivedIdleTimeS(600)
, mEnforceH1Framing(FRAMECHECK_BARELY)
, mKeepEmptyResponseHeadersAsEmtpyString(false)
{
LOG(("Creating nsHttpHandler [this=%p].\n", this));
@ -455,7 +456,7 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecu
// 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!
@ -463,7 +464,7 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecu
// 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
@ -471,31 +472,36 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecu
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;
@ -1695,6 +1701,14 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
}
}
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;

Просмотреть файл

@ -359,6 +359,11 @@ public:
void ShutdownConnectionManager();
bool KeepEmptyResponseHeadersAsEmtpyString() const
{
return mKeepEmptyResponseHeadersAsEmtpyString;
}
private:
virtual ~nsHttpHandler();
@ -572,6 +577,13 @@ private:
// 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.

Просмотреть файл

@ -23,6 +23,11 @@ nsHttpHeaderArray::SetHeader(nsHttpAtom header,
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;
@ -31,71 +36,120 @@ nsHttpHeaderArray::SetHeader(nsHttpAtom header,
// 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
@ -106,6 +160,11 @@ nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header, const nsACString &value)
} // 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);
}
}
@ -115,7 +174,15 @@ nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header, const nsACString &value)
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 *
@ -137,6 +204,40 @@ nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const
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
{
@ -152,17 +253,22 @@ nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray
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)
@ -180,17 +286,16 @@ nsHttpHeaderArray::ParseHeaderLine(const char *line,
// 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
@ -198,7 +303,7 @@ nsHttpHeaderArray::ParseHeaderLine(const char *line,
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
@ -215,39 +320,60 @@ nsHttpHeaderArray::ParseHeaderLine(const char *line,
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)
{
nsHttpAtom hdr;
char *val;
while (buffer) {
char *eof = strchr(buffer, '\r');
if (!eof) {
break;
}
*eof = '\0';
ParseHeaderLine(buffer, &hdr, &val);
buffer = eof + 1;
if (*buffer == '\n') {
buffer++;
}
}
}
void
nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders)
nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders,
bool pruneTransients)
{
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 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;
}
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::FlattenOriginalHeader(nsACString &buf)
{
uint32_t i, count = mHeaders.Length();
for (i = 0; i < count; ++i) {
const nsEntry &entry = mHeaders[i];
// Skip changed header.
if (entry.variety == eVarietyResponse) {
continue;
}
buf.Append(entry.header);
buf.AppendLiteral(": ");
buf.Append(entry.value);

Просмотреть файл

@ -26,24 +26,44 @@ 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.
@ -65,19 +85,20 @@ public:
{
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(); }
@ -90,7 +111,7 @@ public:
{
nsHttpAtom header;
nsCString value;
HeaderVariety variety = eVarietyOverride;
HeaderVariety variety = eVarietyUnknown;
struct MatchHeader {
bool Equals(const nsEntry &entry, const nsHttpAtom &header) const {
@ -110,9 +131,14 @@ public:
}
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);
@ -139,18 +165,35 @@ private:
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;
}
@ -179,15 +222,17 @@ nsHttpHeaderArray::TrackEmptyHeader(nsHttpAtom header)
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 ||
@ -196,14 +241,26 @@ nsHttpHeaderArray::MergeHeader(nsHttpAtom header,
// 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

Просмотреть файл

@ -130,7 +130,8 @@ 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
@ -145,7 +146,8 @@ nsresult
nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h)
{
MutexAutoLock lock(mLock);
return mHeaders.SetEmptyHeader(h);
return mHeaders.SetEmptyHeader(h,
nsHttpHeaderArray::eVarietyRequestOverride);
}
nsresult
@ -190,7 +192,8 @@ nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v,
{
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;
}
@ -213,7 +216,24 @@ 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
@ -305,7 +325,7 @@ nsHttpRequestHead::Flatten(nsACString &buf, bool pruneProxyHeaders)
buf.AppendLiteral("\r\n");
mHeaders.Flatten(buf, pruneProxyHeaders);
mHeaders.Flatten(buf, pruneProxyHeaders, false);
}
} // namespace net

Просмотреть файл

@ -26,7 +26,8 @@ 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
@ -46,7 +47,10 @@ nsHttpResponseHead::SetContentLength(int64_t 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
@ -68,37 +72,21 @@ nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients)
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
@ -120,8 +108,8 @@ nsHttpResponseHead::Parse(char *block)
do {
block = p + 2;
if (*block == 0)
break;
if (*block == 0)
break;
p = PL_strstr(block, "\r\n");
if (!p)
@ -328,11 +316,16 @@ 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|

Просмотреть файл

@ -78,7 +78,9 @@ public:
// 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.

Просмотреть файл

@ -1564,6 +1564,7 @@ nsHttpTransaction::HandleContentStart()
LOG3(("http response [\n"));
nsAutoCString headers;
mResponseHead->Flatten(headers, false);
mResponseHead->FlattenOriginalHeader(headers);
LogHeaders(headers.get());
LOG3(("]\n"));
}

Просмотреть файл

@ -344,6 +344,39 @@ interface nsIHttpChannel : nsIChannel
*/
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.

Просмотреть файл

@ -905,6 +905,25 @@ nsViewSourceChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
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)
{

Просмотреть файл

@ -19,6 +19,7 @@
#include "nsHttp.h"
#include "nsNetUtil.h"
#include "nsIURI.h"
#include "nsHttpHeaderArray.h"
//
// Helper function for determining the length of data bytes up to
@ -420,7 +421,8 @@ 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);
}
//

Просмотреть файл

@ -3701,6 +3701,15 @@ Response.prototype =
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
//
@ -4989,6 +4998,17 @@ nsHttpHeaders.prototype =
}
},
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.
*

Просмотреть файл

@ -529,6 +529,12 @@ interface nsIHttpResponse : nsISupports
*/
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.

Просмотреть файл

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

Просмотреть файл

@ -241,6 +241,7 @@ run-sequentially = node server exceptions dont replay well
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]

Просмотреть файл

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

Просмотреть файл

@ -93,3 +93,4 @@ skip-if = true
[test_app_offline_http.js]
[test_getHost_wrap.js]
[test_app_offline_notifications.js]
[test_original_sent_received_head_wrap.js]