summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/nsHttpHeaderArray.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/nsHttpHeaderArray.cpp')
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp441
1 files changed, 441 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp
new file mode 100644
index 000000000..670300dea
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,441 @@
+/* -*- 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(nsHttpAtom header,
+ 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, value, variety);
+ } else if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ } else {
+ // Replace the existing string with the new value
+ 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::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->variety != eVarietyResponseNetOriginalAndResponse) {
+ entry->value.Truncate();
+ return NS_OK;
+ } else if (entry) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ }
+
+ return SetHeader_internal(header, EmptyCString(), variety);
+}
+
+nsresult
+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 (!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
+ }
+ }
+ HeaderVariety variety = eVarietyRequestOverride;
+ if (response) {
+ variety = eVarietyResponseNetOriginalAndResponse;
+ }
+ return SetHeader_internal(header, 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, value,
+ eVarietyResponseNetOriginal);
+ }
+ return rv;
+ } else {
+ // 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, value,
+ eVarietyResponseNetOriginal);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsHttpHeaderArray::SetResponseHeaderFromCache(nsHttpAtom header,
+ 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, 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, 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;
+ }
+ 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
+{
+ 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;
+ }
+ rv = visitor->VisitHeader(
+ nsDependentCString(entry.header), entry.value);
+ if NS_FAILED(rv) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/*static*/ nsresult
+nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
+ nsHttpAtom *hdr,
+ 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);
+
+ 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;
+ }
+
+ 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);
+ buf.AppendLiteral("\r\n");
+ }
+}
+
+const char *
+nsHttpHeaderArray::PeekHeaderAt(uint32_t index, nsHttpAtom &header) const
+{
+ const nsEntry &entry = mHeaders[index];
+
+ header = entry.header;
+ return entry.value.get();
+}
+
+void
+nsHttpHeaderArray::Clear()
+{
+ mHeaders.Clear();
+}
+
+} // namespace net
+} // namespace mozilla