tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHttpHeaderArray.cpp (16331B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=4 sw=2 sts=2 ci et: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // HttpLog.h should generally be included first
      8 #include "HttpLog.h"
      9 
     10 #include "nsHttpHeaderArray.h"
     11 #include "nsURLHelper.h"
     12 #include "nsIHttpHeaderVisitor.h"
     13 #include "nsHttpHandler.h"
     14 
     15 namespace mozilla {
     16 namespace net {
     17 
     18 //-----------------------------------------------------------------------------
     19 // nsHttpHeaderArray <public>
     20 //-----------------------------------------------------------------------------
     21 
     22 nsresult nsHttpHeaderArray::SetHeader(
     23    const nsACString& headerName, const nsACString& value, bool merge,
     24    nsHttpHeaderArray::HeaderVariety variety) {
     25  nsHttpAtom header = nsHttp::ResolveAtom(headerName);
     26  if (!header) {
     27    NS_WARNING("failed to resolve atom");
     28    return NS_ERROR_NOT_AVAILABLE;
     29  }
     30  return SetHeader(header, headerName, value, merge, variety);
     31 }
     32 
     33 nsresult nsHttpHeaderArray::SetHeader(
     34    const nsHttpAtom& header, const nsACString& value, bool merge,
     35    nsHttpHeaderArray::HeaderVariety variety) {
     36  return SetHeader(header, ""_ns, value, merge, variety);
     37 }
     38 
     39 nsresult nsHttpHeaderArray::SetHeader(
     40    const nsHttpAtom& header, const nsACString& headerName,
     41    const nsACString& value, bool merge,
     42    nsHttpHeaderArray::HeaderVariety variety) {
     43  MOZ_ASSERT(
     44      (variety == eVarietyResponse) || (variety == eVarietyRequestDefault) ||
     45          (variety == eVarietyRequestOverride) ||
     46          (variety == eVarietyResponseOverride) ||
     47          (variety == eVarietyRequestEnforceDefault),
     48      "Net original headers can only be set using SetHeader_internal().");
     49 
     50  nsEntry* entry = nullptr;
     51  int32_t index = LookupEntry(header, &entry);
     52 
     53  // If an empty value is received and we aren't merging headers discard it,
     54  // unless we're overriding
     55  if (value.IsEmpty() && header != nsHttp::X_Frame_Options &&
     56      variety != eVarietyResponseOverride) {
     57    if (!merge && entry) {
     58      if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
     59        MOZ_ASSERT(variety == eVarietyResponse);
     60        entry->variety = eVarietyResponseNetOriginal;
     61      } else {
     62        mHeaders.RemoveElementAt(index);
     63      }
     64    }
     65    return NS_OK;
     66  }
     67 
     68  MOZ_ASSERT((variety == eVarietyRequestEnforceDefault) ||
     69                 (!entry || variety != eVarietyRequestDefault),
     70             "Cannot set default entry which overrides existing entry!");
     71 
     72  // Set the variety to default if we are enforcing it.
     73  if (variety == eVarietyRequestEnforceDefault) {
     74    variety = eVarietyRequestDefault;
     75  }
     76  if (!entry) {
     77    return SetHeader_internal(header, headerName, value, variety);
     78  }
     79  if (merge && !IsSingletonHeader(header)) {
     80    return MergeHeader(header, entry, value, variety);
     81  }
     82  if (!IsIgnoreMultipleHeader(header)) {
     83    // Replace the existing string with the new value
     84    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
     85      MOZ_ASSERT(variety == eVarietyResponse ||
     86                 variety == eVarietyResponseOverride);
     87      entry->variety = eVarietyResponseNetOriginal;
     88      return SetHeader_internal(header, headerName, value, variety);
     89    }
     90    entry->value = value;
     91    entry->variety = variety;
     92  }
     93 
     94  return NS_OK;
     95 }
     96 
     97 nsresult nsHttpHeaderArray::SetHeader_internal(
     98    const nsHttpAtom& header, const nsACString& headerName,
     99    const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
    100  nsEntry* entry = mHeaders.AppendElement();
    101  if (!entry) {
    102    return NS_ERROR_OUT_OF_MEMORY;
    103  }
    104  entry->header = header;
    105  // Only save original form of a header if it is different than the header
    106  // atom string.
    107  if (!headerName.Equals(header.get())) {
    108    entry->headerNameOriginal = headerName;
    109  }
    110  entry->value = value;
    111  entry->variety = variety;
    112  return NS_OK;
    113 }
    114 
    115 nsresult nsHttpHeaderArray::SetEmptyHeader(const nsACString& headerName,
    116                                           HeaderVariety variety) {
    117  nsHttpAtom header = nsHttp::ResolveAtom(headerName);
    118  if (!header) {
    119    NS_WARNING("failed to resolve atom");
    120    return NS_ERROR_NOT_AVAILABLE;
    121  }
    122 
    123  MOZ_ASSERT((variety == eVarietyResponse) ||
    124                 (variety == eVarietyRequestDefault) ||
    125                 (variety == eVarietyRequestOverride),
    126             "Original headers can only be set using SetHeader_internal().");
    127  nsEntry* entry = nullptr;
    128 
    129  LookupEntry(header, &entry);
    130 
    131  if (entry && entry->variety != eVarietyResponseNetOriginalAndResponse) {
    132    entry->value.Truncate();
    133    return NS_OK;
    134  }
    135  if (entry) {
    136    MOZ_ASSERT(variety == eVarietyResponse);
    137    entry->variety = eVarietyResponseNetOriginal;
    138  }
    139 
    140  return SetHeader_internal(header, headerName, ""_ns, variety);
    141 }
    142 
    143 nsresult nsHttpHeaderArray::SetHeaderFromNet(
    144    const nsHttpAtom& header, const nsACString& headerNameOriginal,
    145    const nsACString& value, bool response) {
    146  // mHeader holds the consolidated (merged or updated) headers.
    147  // mHeader for response header will keep the original heades as well.
    148  nsEntry* entry = nullptr;
    149 
    150  LookupEntry(header, &entry);
    151 
    152  if (!entry) {
    153    HeaderVariety variety = eVarietyRequestOverride;
    154    if (response) {
    155      variety = eVarietyResponseNetOriginalAndResponse;
    156    }
    157    return SetHeader_internal(header, headerNameOriginal, value, variety);
    158  }
    159  if (!IsSingletonHeader(header)) {
    160    HeaderVariety variety = eVarietyRequestOverride;
    161    if (response) {
    162      variety = eVarietyResponse;
    163    }
    164    nsresult rv = MergeHeader(header, entry, value, variety);
    165    if (NS_FAILED(rv)) {
    166      return rv;
    167    }
    168    if (response) {
    169      rv = SetHeader_internal(header, headerNameOriginal, value,
    170                              eVarietyResponseNetOriginal);
    171    }
    172    return rv;
    173  }
    174  if (!IsIgnoreMultipleHeader(header)) {
    175    // Multiple instances of non-mergeable header received from network
    176    // - ignore if same value
    177    if (header == nsHttp::Content_Length) {
    178      // Content length header needs special handling.
    179      // For e.g. for CL all the below headers evaluates to value of X
    180      // Content-Length: X
    181      // Content-Length: X, X, X
    182      // Content-Length: X \n\r Content-Length: X
    183      // remove duplicate values from the header-values for comparison
    184 
    185      nsAutoCString headerValue;
    186      RemoveDuplicateHeaderValues(value, headerValue);
    187 
    188      nsAutoCString entryValue;
    189      RemoveDuplicateHeaderValues(entry->value, entryValue);
    190      if (entryValue != headerValue) {
    191        // reply may be corrupt/hacked (ex: CLRF injection attacks)
    192        return NS_ERROR_CORRUPTED_CONTENT;
    193      }
    194    } else if (!entry->value.Equals(value)) {  // compare remaining headers
    195      if (IsSuspectDuplicateHeader(header)) {
    196        // reply may be corrupt/hacked (ex: CLRF injection attacks)
    197        return NS_ERROR_CORRUPTED_CONTENT;
    198      }  // else silently drop value: keep value from 1st header seen
    199      LOG(("Header %s silently dropped as non mergeable header\n",
    200           header.get()));
    201    }
    202 
    203    if (response) {
    204      return SetHeader_internal(header, headerNameOriginal, value,
    205                                eVarietyResponseNetOriginal);
    206    }
    207  }
    208 
    209  return NS_OK;
    210 }
    211 
    212 nsresult nsHttpHeaderArray::SetResponseHeaderFromCache(
    213    const nsHttpAtom& header, const nsACString& headerNameOriginal,
    214    const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
    215  MOZ_ASSERT(
    216      (variety == eVarietyResponse) || (variety == eVarietyResponseNetOriginal),
    217      "Headers from cache can only be eVarietyResponse and "
    218      "eVarietyResponseNetOriginal");
    219 
    220  if (variety == eVarietyResponseNetOriginal) {
    221    return SetHeader_internal(header, headerNameOriginal, value,
    222                              eVarietyResponseNetOriginal);
    223  }
    224  nsTArray<nsEntry>::index_type index = 0;
    225  do {
    226    index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
    227    if (index !=
    228        CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex) {
    229      nsEntry& entry = mHeaders[index];
    230      if (value.Equals(entry.value)) {
    231        MOZ_ASSERT(
    232            (entry.variety == eVarietyResponseNetOriginal) ||
    233                (entry.variety == eVarietyResponseNetOriginalAndResponse),
    234            "This array must contain only eVarietyResponseNetOriginal"
    235            " and eVarietyResponseNetOriginalAndRespons headers!");
    236        entry.variety = eVarietyResponseNetOriginalAndResponse;
    237        return NS_OK;
    238      }
    239      index++;
    240    }
    241  } while (index !=
    242           CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex);
    243  // If we are here, we have not found an entry so add a new one.
    244  return SetHeader_internal(header, headerNameOriginal, value,
    245                            eVarietyResponse);
    246 }
    247 
    248 void nsHttpHeaderArray::ClearHeader(const nsHttpAtom& header) {
    249  nsEntry* entry = nullptr;
    250  int32_t index = LookupEntry(header, &entry);
    251  if (entry) {
    252    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
    253      entry->variety = eVarietyResponseNetOriginal;
    254    } else {
    255      mHeaders.RemoveElementAt(index);
    256    }
    257  }
    258 }
    259 
    260 const char* nsHttpHeaderArray::PeekHeader(const nsHttpAtom& header) const {
    261  const nsEntry* entry = nullptr;
    262  LookupEntry(header, &entry);
    263  return entry ? entry->value.get() : nullptr;
    264 }
    265 
    266 nsresult nsHttpHeaderArray::GetHeader(const nsHttpAtom& header,
    267                                      nsACString& result) const {
    268  const nsEntry* entry = nullptr;
    269  LookupEntry(header, &entry);
    270  if (!entry) return NS_ERROR_NOT_AVAILABLE;
    271  result = entry->value;
    272  return NS_OK;
    273 }
    274 
    275 nsresult nsHttpHeaderArray::GetOriginalHeader(const nsHttpAtom& aHeader,
    276                                              nsIHttpHeaderVisitor* aVisitor) {
    277  NS_ENSURE_ARG_POINTER(aVisitor);
    278  uint32_t index = 0;
    279  nsresult rv = NS_ERROR_NOT_AVAILABLE;
    280  while (true) {
    281    index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
    282    if (index != UINT32_MAX) {
    283      const nsEntry& entry = mHeaders[index];
    284 
    285      MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
    286                     (entry.variety == eVarietyResponseNetOriginal) ||
    287                     (entry.variety == eVarietyResponse) ||
    288                     (entry.variety == eVarietyResponseOverride),
    289                 "This must be a response header.");
    290      index++;
    291      if (entry.variety == eVarietyResponse ||
    292          entry.variety == eVarietyResponseOverride) {
    293        continue;
    294      }
    295 
    296      const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
    297                                 ? entry.header.val()
    298                                 : entry.headerNameOriginal;
    299 
    300      rv = NS_OK;
    301      if (NS_FAILED(aVisitor->VisitHeader(hdr, entry.value))) {
    302        break;
    303      }
    304    } else {
    305      // if there is no such a header, it will return
    306      // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
    307      return rv;
    308    }
    309  }
    310  return NS_OK;
    311 }
    312 
    313 bool nsHttpHeaderArray::HasHeader(const nsHttpAtom& header) const {
    314  const nsEntry* entry = nullptr;
    315  LookupEntry(header, &entry);
    316  return entry;
    317 }
    318 
    319 nsresult nsHttpHeaderArray::VisitHeaders(
    320    nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
    321  NS_ENSURE_ARG_POINTER(visitor);
    322  nsresult rv;
    323 
    324  uint32_t i, count = mHeaders.Length();
    325  for (i = 0; i < count; ++i) {
    326    const nsEntry& entry = mHeaders[i];
    327    if (filter == eFilterSkipDefault &&
    328        entry.variety == eVarietyRequestDefault) {
    329      continue;
    330    }
    331    if (filter == eFilterResponse &&
    332        entry.variety == eVarietyResponseNetOriginal) {
    333      continue;
    334    }
    335    if (filter == eFilterResponseOriginal &&
    336        entry.variety == eVarietyResponse) {
    337      continue;
    338    }
    339 
    340    const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
    341                               ? entry.header.val()
    342                               : entry.headerNameOriginal;
    343    rv = visitor->VisitHeader(hdr, entry.value);
    344    if (NS_FAILED(rv)) {
    345      return rv;
    346    }
    347  }
    348  return NS_OK;
    349 }
    350 
    351 /*static*/
    352 nsresult nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
    353                                            nsHttpAtom* hdr,
    354                                            nsACString* headerName,
    355                                            nsACString* val) {
    356  //
    357  // BNF from section 4.2 of RFC 2616:
    358  //
    359  //   message-header = field-name ":" [ field-value ]
    360  //   field-name     = token
    361  //   field-value    = *( field-content | LWS )
    362  //   field-content  = <the OCTETs making up the field-value
    363  //                     and consisting of either *TEXT or combinations
    364  //                     of token, separators, and quoted-string>
    365  //
    366 
    367  // We skip over mal-formed headers in the hope that we'll still be able to
    368  // do something useful with the response.
    369  int32_t split = line.FindChar(':');
    370 
    371  if (split == kNotFound) {
    372    LOG(("malformed header [%s]: no colon\n", PromiseFlatCString(line).get()));
    373    return NS_ERROR_FAILURE;
    374  }
    375 
    376  const nsACString& sub = Substring(line, 0, split);
    377  const nsACString& sub2 =
    378      Substring(line, split + 1, line.Length() - split - 1);
    379 
    380  // make sure we have a valid token for the field-name
    381  if (!nsHttp::IsValidToken(sub)) {
    382    LOG(("malformed header [%s]: field-name not a token\n",
    383         PromiseFlatCString(line).get()));
    384    return NS_ERROR_FAILURE;
    385  }
    386 
    387  nsHttpAtom atom = nsHttp::ResolveAtom(sub);
    388  if (!atom) {
    389    LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
    390    return NS_ERROR_FAILURE;
    391  }
    392 
    393  // skip over whitespace
    394  char* p =
    395      net_FindCharNotInSet(sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);
    396 
    397  // trim trailing whitespace - bug 86608
    398  char* p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);
    399 
    400  // assign return values
    401  if (hdr) *hdr = atom;
    402  if (val) val->Assign(p, p2 - p + 1);
    403  if (headerName) headerName->Assign(sub);
    404 
    405  return NS_OK;
    406 }
    407 
    408 void nsHttpHeaderArray::Flatten(nsACString& buf, bool pruneProxyHeaders,
    409                                bool pruneTransients) {
    410  uint32_t i, count = mHeaders.Length();
    411  for (i = 0; i < count; ++i) {
    412    const nsEntry& entry = mHeaders[i];
    413    // Skip original header.
    414    if (entry.variety == eVarietyResponseNetOriginal) {
    415      continue;
    416    }
    417    // prune proxy headers if requested
    418    if (pruneProxyHeaders && ((entry.header == nsHttp::Proxy_Authorization) ||
    419                              (entry.header == nsHttp::Proxy_Connection))) {
    420      continue;
    421    }
    422    if (pruneTransients &&
    423        (entry.value.IsEmpty() || entry.header == nsHttp::Connection ||
    424         entry.header == nsHttp::Proxy_Connection ||
    425         entry.header == nsHttp::Keep_Alive ||
    426         entry.header == nsHttp::WWW_Authenticate ||
    427         entry.header == nsHttp::Proxy_Authenticate ||
    428         entry.header == nsHttp::Trailer ||
    429         entry.header == nsHttp::Transfer_Encoding ||
    430         entry.header == nsHttp::Upgrade ||
    431         // XXX this will cause problems when we start honoring
    432         // Cache-Control: no-cache="set-cookie", what to do?
    433         entry.header == nsHttp::Set_Cookie)) {
    434      continue;
    435    }
    436 
    437    if (entry.headerNameOriginal.IsEmpty()) {
    438      buf.Append(entry.header.val());
    439    } else {
    440      buf.Append(entry.headerNameOriginal);
    441    }
    442    buf.AppendLiteral(": ");
    443    buf.Append(entry.value);
    444    buf.AppendLiteral("\r\n");
    445  }
    446 }
    447 
    448 void nsHttpHeaderArray::FlattenOriginalHeader(nsACString& buf) {
    449  uint32_t i, count = mHeaders.Length();
    450  for (i = 0; i < count; ++i) {
    451    const nsEntry& entry = mHeaders[i];
    452    // Skip changed header.
    453    if (entry.variety == eVarietyResponse) {
    454      continue;
    455    }
    456 
    457    if (entry.headerNameOriginal.IsEmpty()) {
    458      buf.Append(entry.header.val());
    459    } else {
    460      buf.Append(entry.headerNameOriginal);
    461    }
    462 
    463    buf.AppendLiteral(": ");
    464    buf.Append(entry.value);
    465    buf.AppendLiteral("\r\n");
    466  }
    467 }
    468 
    469 const char* nsHttpHeaderArray::PeekHeaderAt(
    470    uint32_t index, nsHttpAtom& header, nsACString& headerNameOriginal) const {
    471  const nsEntry& entry = mHeaders[index];
    472 
    473  header = entry.header;
    474  headerNameOriginal = entry.headerNameOriginal;
    475  return entry.value.get();
    476 }
    477 
    478 void nsHttpHeaderArray::Clear() { mHeaders.Clear(); }
    479 
    480 }  // namespace net
    481 }  // namespace mozilla