diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/protocol/http/nsHttpHeaderArray.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/nsHttpHeaderArray.cpp | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp new file mode 100644 index 0000000000..944cf30f00 --- /dev/null +++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp @@ -0,0 +1,477 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 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(headerName); + if (!header) { + NS_WARNING("failed to resolve atom"); + return NS_ERROR_NOT_AVAILABLE; + } + return SetHeader(header, headerName, value, merge, variety); +} + +nsresult nsHttpHeaderArray::SetHeader( + const nsHttpAtom& header, const nsACString& value, bool merge, + nsHttpHeaderArray::HeaderVariety variety) { + return SetHeader(header, ""_ns, value, merge, variety); +} + +nsresult nsHttpHeaderArray::SetHeader( + const 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); + } + if (merge && !IsSingletonHeader(header)) { + return MergeHeader(header, entry, value, variety); + } + 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); + } + entry->value = value; + entry->variety = variety; + } + + return NS_OK; +} + +nsresult nsHttpHeaderArray::SetHeader_internal( + const 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(headerName); + 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; + } + if (entry) { + MOZ_ASSERT(variety == eVarietyResponse); + entry->variety = eVarietyResponseNetOriginal; + } + + return SetHeader_internal(header, headerName, ""_ns, variety); +} + +nsresult nsHttpHeaderArray::SetHeaderFromNet( + const 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) { + HeaderVariety variety = eVarietyRequestOverride; + if (response) { + variety = eVarietyResponseNetOriginalAndResponse; + } + return SetHeader_internal(header, headerNameOriginal, value, variety); + } + 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; + } + if (!IsIgnoreMultipleHeader(header)) { + // Multiple instances of non-mergeable header received from network + // - ignore if same value + if (header == nsHttp::Content_Length) { + // Content length header needs special handling. + // For e.g. for CL all the below headers evaluates to value of X + // Content-Length: X + // Content-Length: X, X, X + // Content-Length: X \n\r Content-Length: X + // remove duplicate values from the header-values for comparison + + nsAutoCString headerValue; + RemoveDuplicateHeaderValues(value, headerValue); + + nsAutoCString entryValue; + RemoveDuplicateHeaderValues(entry->value, entryValue); + if (entryValue != headerValue) { + // reply may be corrupt/hacked (ex: CLRF injection attacks) + return NS_ERROR_CORRUPTED_CONTENT; + } + } else if (!entry->value.Equals(value)) { // compare remaining headers + 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( + const 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); + } + nsTArray<nsEntry>::index_type index = 0; + do { + index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader()); + if (index != + CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::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 != + CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::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(const 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(const nsHttpAtom& header) const { + const nsEntry* entry = nullptr; + LookupEntry(header, &entry); + return entry ? entry->value.get() : nullptr; +} + +nsresult nsHttpHeaderArray::GetHeader(const 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(const 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(const 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; + } + if (filter == eFilterResponse && + entry.variety == eVarietyResponseNetOriginal) { + continue; + } + 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 |