1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00
Files
UXP/netwerk/protocol/http/nsHttpHeaderArray.cpp
T
Moonchild c95a802078 Issue #2070 - When multiple HSTS headers are received, only consider the first.
This implements a plain interpretations of RFC 6797, which says to only consider
the first HSTS header.
This slightly conflicts with RFC 7230, which says that sending multiple headers
which can't be merged is illegal (except for a specific whitelist which HSTS isn't in),
so this situation should never occur in the first place (and would therefore not need
the explicit entry in RFC 6797).

It improves HSTS robustness dealing with non-compliant servers.

Resolves #2070
2022-12-30 09:21:48 +08:00

509 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 ci et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first
#include "HttpLog.h"
#include "nsHttpHeaderArray.h"
#include "nsURLHelper.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsHttpHandler.h"
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// nsHttpHeaderArray <public>
//-----------------------------------------------------------------------------
nsresult
nsHttpHeaderArray::SetHeader(const nsACString &headerName,
const nsACString &value,
bool merge,
nsHttpHeaderArray::HeaderVariety variety)
{
nsHttpAtom header = nsHttp::ResolveAtom(PromiseFlatCString(headerName).get());
if (!header) {
NS_WARNING("failed to resolve atom");
return NS_ERROR_NOT_AVAILABLE;
}
return SetHeader(header, headerName, value, merge, variety);
}
nsresult
nsHttpHeaderArray::SetHeader(nsHttpAtom header,
const nsACString &value,
bool merge,
nsHttpHeaderArray::HeaderVariety variety)
{
return SetHeader(header, EmptyCString(), value, merge, variety);
}
nsresult
nsHttpHeaderArray::SetHeader(nsHttpAtom header,
const nsACString &headerName,
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) {
if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
MOZ_ASSERT(variety == eVarietyResponse);
entry->variety = eVarietyResponseNetOriginal;
} else {
mHeaders.RemoveElementAt(index);
}
}
return NS_OK;
}
MOZ_ASSERT(!entry || variety != eVarietyRequestDefault,
"Cannot set default entry which overrides existing entry!");
if (!entry) {
return SetHeader_internal(header, headerName, value, variety);
} else if (merge && !IsSingletonHeader(header)) {
return MergeHeader(header, entry, value, variety);
} else if (!IsIgnoreMultipleHeader(header)) {
// Replace the existing string with the new value
if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
MOZ_ASSERT(variety == eVarietyResponse);
entry->variety = eVarietyResponseNetOriginal;
return SetHeader_internal(header, headerName, value, variety);
} else {
entry->value = value;
entry->variety = variety;
}
}
return NS_OK;
}
nsresult
nsHttpHeaderArray::SetHeader_internal(nsHttpAtom header,
const nsACString &headerName,
const nsACString &value,
nsHttpHeaderArray::HeaderVariety variety)
{
nsEntry *entry = mHeaders.AppendElement();
if (!entry) {
return NS_ERROR_OUT_OF_MEMORY;
}
entry->header = header;
// Only save original form of a header if it is different than the header
// atom string.
if (!headerName.Equals(header.get())) {
entry->headerNameOriginal = headerName;
}
entry->value = value;
entry->variety = variety;
return NS_OK;
}
nsresult
nsHttpHeaderArray::SetEmptyHeader(const nsACString &headerName,
HeaderVariety variety)
{
nsHttpAtom header = nsHttp::ResolveAtom(PromiseFlatCString(headerName).get());
if (!header) {
NS_WARNING("failed to resolve atom");
return NS_ERROR_NOT_AVAILABLE;
}
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->variety != eVarietyResponseNetOriginalAndResponse) {
entry->value.Truncate();
return NS_OK;
} else if (entry) {
MOZ_ASSERT(variety == eVarietyResponse);
entry->variety = eVarietyResponseNetOriginal;
}
return SetHeader_internal(header, headerName, EmptyCString(), variety);
}
nsresult
nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header,
const nsACString &headerNameOriginal,
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 (!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, headerNameOriginal, value,
eVarietyResponseNetOriginal);
}
return NS_OK; // ignore empty headers by default
}
}
HeaderVariety variety = eVarietyRequestOverride;
if (response) {
variety = eVarietyResponseNetOriginalAndResponse;
}
return SetHeader_internal(header, headerNameOriginal, value, variety);
} else if (!IsSingletonHeader(header)) {
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, headerNameOriginal, value,
eVarietyResponseNetOriginal);
}
return rv;
} else if (!IsIgnoreMultipleHeader(header)) {
// 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, headerNameOriginal, value,
eVarietyResponseNetOriginal);
}
}
return NS_OK;
}
nsresult
nsHttpHeaderArray::SetResponseHeaderFromCache(nsHttpAtom header,
const nsACString &headerNameOriginal,
const nsACString &value,
nsHttpHeaderArray::HeaderVariety variety)
{
MOZ_ASSERT((variety == eVarietyResponse) ||
(variety == eVarietyResponseNetOriginal),
"Headers from cache can only be eVarietyResponse and "
"eVarietyResponseNetOriginal");
if (variety == eVarietyResponseNetOriginal) {
return SetHeader_internal(header, headerNameOriginal, value,
eVarietyResponseNetOriginal);
} else {
nsTArray<nsEntry>::index_type index = 0;
do {
index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
if (index != mHeaders.NoIndex) {
nsEntry &entry = mHeaders[index];
if (value.Equals(entry.value)) {
MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginal) ||
(entry.variety == eVarietyResponseNetOriginalAndResponse),
"This array must contain only eVarietyResponseNetOriginal"
" and eVarietyResponseNetOriginalAndRespons headers!");
entry.variety = eVarietyResponseNetOriginalAndResponse;
return NS_OK;
}
index++;
}
} while (index != mHeaders.NoIndex);
// If we are here, we have not found an entry so add a new one.
return SetHeader_internal(header, headerNameOriginal, value,
eVarietyResponse);
}
}
void
nsHttpHeaderArray::ClearHeader(nsHttpAtom header)
{
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;
}
nsresult
nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const
{
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;
}
nsAutoCString hdr;
if (entry.headerNameOriginal.IsEmpty()) {
hdr = nsDependentCString(entry.header);
} else {
hdr = entry.headerNameOriginal;
}
rv = NS_OK;
if (NS_FAILED(aVisitor->VisitHeader(hdr,
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);
nsresult rv;
uint32_t i, count = mHeaders.Length();
for (i = 0; i < count; ++i) {
const nsEntry &entry = mHeaders[i];
if (filter == eFilterSkipDefault && entry.variety == eVarietyRequestDefault) {
continue;
} else if (filter == eFilterResponse && entry.variety == eVarietyResponseNetOriginal) {
continue;
} else if (filter == eFilterResponseOriginal && entry.variety == eVarietyResponse) {
continue;
}
nsAutoCString hdr;
if (entry.headerNameOriginal.IsEmpty()) {
hdr = nsDependentCString(entry.header);
} else {
hdr = entry.headerNameOriginal;
}
rv = visitor->VisitHeader(hdr, entry.value);
if NS_FAILED(rv) {
return rv;
}
}
return NS_OK;
}
/*static*/ nsresult
nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
nsHttpAtom *hdr,
nsACString *headerName,
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.
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(sub)) {
LOG(("malformed header [%s]: field-name not a token\n",
PromiseFlatCString(line).get()));
return NS_ERROR_FAILURE;
}
nsHttpAtom atom = nsHttp::ResolveAtom(sub);
if (!atom) {
LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
return NS_ERROR_FAILURE;
}
// skip over whitespace
char *p = net_FindCharNotInSet(
sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);
// trim trailing whitespace - bug 86608
char *p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
// assign return values
if (hdr) *hdr = atom;
if (val) val->Assign(p, p2 - p + 1);
if (headerName) headerName->Assign(sub);
return NS_OK;
}
void
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];
// 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;
}
if (entry.headerNameOriginal.IsEmpty()) {
buf.Append(entry.header);
} else {
buf.Append(entry.headerNameOriginal);
}
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;
}
if (entry.headerNameOriginal.IsEmpty()) {
buf.Append(entry.header);
} else {
buf.Append(entry.headerNameOriginal);
}
buf.AppendLiteral(": ");
buf.Append(entry.value);
buf.AppendLiteral("\r\n");
}
}
const char *
nsHttpHeaderArray::PeekHeaderAt(uint32_t index, nsHttpAtom &header,
nsACString &headerNameOriginal) const
{
const nsEntry &entry = mHeaders[index];
header = entry.header;
headerNameOriginal = entry.headerNameOriginal;
return entry.value.get();
}
void
nsHttpHeaderArray::Clear()
{
mHeaders.Clear();
}
} // namespace net
} // namespace mozilla