/* -*- 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 //----------------------------------------------------------------------------- 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) || (variety == eVarietyRequestEnforceDefault), "Net original headers can only be set using SetHeader_internal()."); nsEntry* entry = nullptr; int32_t index = LookupEntry(header, &entry); // If an empty value is received and we aren't merging headers discard it if (value.IsEmpty() && header != nsHttp::X_Frame_Options) { if (!merge && entry) { if (entry->variety == eVarietyResponseNetOriginalAndResponse) { MOZ_ASSERT(variety == eVarietyResponse); entry->variety = eVarietyResponseNetOriginal; } else { mHeaders.RemoveElementAt(index); } } return NS_OK; } MOZ_ASSERT((variety == eVarietyRequestEnforceDefault) || (!entry || variety != eVarietyRequestDefault), "Cannot set default entry which overrides existing entry!"); // Set the variety to default if we are enforcing it. if (variety == eVarietyRequestEnforceDefault) { variety = eVarietyRequestDefault; } 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::index_type index = 0; do { index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader()); if (index != CopyableTArray::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::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; } const nsCString& hdr = entry.headerNameOriginal.IsEmpty() ? entry.header.val() : 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; } const nsCString& hdr = entry.headerNameOriginal.IsEmpty() ? entry.header.val() : 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 = // // 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.val()); } 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.val()); } 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