tor-browser

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

InternalHeaders.cpp (17245B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 #include "mozilla/dom/InternalHeaders.h"
      8 
      9 #include "FetchUtil.h"
     10 #include "mozilla/ErrorResult.h"
     11 #include "mozilla/dom/FetchTypes.h"
     12 #include "nsCharSeparatedTokenizer.h"
     13 #include "nsContentUtils.h"
     14 #include "nsIHttpChannel.h"
     15 #include "nsIHttpHeaderVisitor.h"
     16 #include "nsNetUtil.h"
     17 #include "nsReadableUtils.h"
     18 
     19 namespace mozilla::dom {
     20 
     21 InternalHeaders::InternalHeaders(nsTArray<Entry>&& aHeaders,
     22                                 HeadersGuardEnum aGuard)
     23    : mGuard(aGuard), mList(std::move(aHeaders)), mListDirty(true) {}
     24 
     25 InternalHeaders::InternalHeaders(
     26    const nsTArray<HeadersEntry>& aHeadersEntryList, HeadersGuardEnum aGuard)
     27    : mGuard(aGuard), mListDirty(true) {
     28  for (const HeadersEntry& headersEntry : aHeadersEntryList) {
     29    mList.AppendElement(Entry(headersEntry.name(), headersEntry.value()));
     30  }
     31 }
     32 
     33 void InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
     34                            HeadersGuardEnum& aGuard) {
     35  aGuard = mGuard;
     36 
     37  aIPCHeaders.Clear();
     38  for (Entry& entry : mList) {
     39    aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
     40  }
     41 }
     42 
     43 bool InternalHeaders::IsValidHeaderValue(const nsCString& aLowerName,
     44                                         const nsCString& aNormalizedValue,
     45                                         ErrorResult& aRv) {
     46  // Steps 2 to 6 for ::Set() and ::Append() in the spec.
     47 
     48  // Step 2
     49  if (IsInvalidName(aLowerName, aRv) || IsInvalidValue(aNormalizedValue, aRv)) {
     50    return false;
     51  }
     52 
     53  // Step 3
     54  if (IsImmutable(aRv)) {
     55    return false;
     56  }
     57 
     58  // Step 4
     59  if (mGuard == HeadersGuardEnum::Request) {
     60    if (IsForbiddenRequestHeader(aLowerName, aNormalizedValue)) {
     61      return false;
     62    }
     63  }
     64  // Step 5
     65  if (mGuard == HeadersGuardEnum::Request_no_cors) {
     66    nsAutoCString tempValue;
     67    Get(aLowerName, tempValue, aRv);
     68 
     69    if (tempValue.IsVoid()) {
     70      tempValue = aNormalizedValue;
     71    } else {
     72      tempValue.Append(", ");
     73      tempValue.Append(aNormalizedValue);
     74    }
     75 
     76    if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName, tempValue)) {
     77      return false;
     78    }
     79  }
     80 
     81  // Step 6
     82  else if (IsForbiddenResponseHeader(aLowerName)) {
     83    return false;
     84  }
     85 
     86  return true;
     87 }
     88 
     89 void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
     90                             ErrorResult& aRv) {
     91  // Step 1
     92  nsAutoCString trimValue;
     93  NS_TrimHTTPWhitespace(aValue, trimValue);
     94 
     95  // Steps 2 to 6
     96  nsAutoCString lowerName;
     97  ToLowerCase(aName, lowerName);
     98  if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
     99    return;
    100  }
    101 
    102  // Step 7
    103  nsAutoCString name(aName);
    104  ReuseExistingNameIfExists(name);
    105  SetListDirty();
    106  mList.AppendElement(Entry(name, trimValue));
    107 
    108  // Step 8
    109  if (mGuard == HeadersGuardEnum::Request_no_cors) {
    110    RemovePrivilegedNoCorsRequestHeaders();
    111  }
    112 }
    113 
    114 void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() {
    115  bool dirty = false;
    116 
    117  // remove in reverse order to minimize copying
    118  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
    119    if (IsPrivilegedNoCorsRequestHeaderName(mList[i].mName)) {
    120      mList.RemoveElementAt(i);
    121      dirty = true;
    122    }
    123  }
    124 
    125  if (dirty) {
    126    SetListDirty();
    127  }
    128 }
    129 
    130 bool InternalHeaders::DeleteInternal(const nsCString& aLowerName,
    131                                     ErrorResult& aRv) {
    132  bool dirty = false;
    133 
    134  // remove in reverse order to minimize copying
    135  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
    136    if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
    137      mList.RemoveElementAt(i);
    138      dirty = true;
    139    }
    140  }
    141 
    142  if (dirty) {
    143    SetListDirty();
    144  }
    145 
    146  return dirty;
    147 }
    148 
    149 void InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) {
    150  // See https://fetch.spec.whatwg.org/#dom-headers-delete
    151  nsAutoCString lowerName;
    152  ToLowerCase(aName, lowerName);
    153 
    154  // Step 1
    155  if (IsInvalidName(lowerName, aRv)) {
    156    return;
    157  }
    158 
    159  if (IsImmutable(aRv)) {
    160    return;
    161  }
    162 
    163  nsAutoCString value;
    164  GetInternal(lowerName, value, aRv);
    165  if (IsForbiddenRequestHeader(lowerName, value)) {
    166    return;
    167  }
    168 
    169  // Step 2
    170  if (mGuard == HeadersGuardEnum::Request_no_cors &&
    171      !IsNoCorsSafelistedRequestHeaderName(lowerName) &&
    172      !IsPrivilegedNoCorsRequestHeaderName(lowerName)) {
    173    return;
    174  }
    175 
    176  if (IsForbiddenResponseHeader(lowerName)) {
    177    return;
    178  }
    179 
    180  // Steps 3, 4, and 5
    181  if (!DeleteInternal(lowerName, aRv)) {
    182    return;
    183  }
    184 
    185  // Step 6
    186  if (mGuard == HeadersGuardEnum::Request_no_cors) {
    187    RemovePrivilegedNoCorsRequestHeaders();
    188  }
    189 }
    190 
    191 void InternalHeaders::Get(const nsACString& aName, nsACString& aValue,
    192                          ErrorResult& aRv) const {
    193  nsAutoCString lowerName;
    194  ToLowerCase(aName, lowerName);
    195 
    196  if (IsInvalidName(lowerName, aRv)) {
    197    return;
    198  }
    199 
    200  GetInternal(lowerName, aValue, aRv);
    201 }
    202 
    203 void InternalHeaders::GetInternal(const nsCString& aLowerName,
    204                                  nsACString& aValue, ErrorResult& aRv) const {
    205  const char* delimiter = ", ";
    206  bool firstValueFound = false;
    207 
    208  for (uint32_t i = 0; i < mList.Length(); ++i) {
    209    if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
    210      if (firstValueFound) {
    211        aValue += delimiter;
    212      }
    213      aValue += mList[i].mValue;
    214      firstValueFound = true;
    215    }
    216  }
    217 
    218  // No value found, so return null to content
    219  if (!firstValueFound) {
    220    aValue.SetIsVoid(true);
    221  }
    222 }
    223 
    224 void InternalHeaders::GetSetCookie(nsTArray<nsCString>& aValues) const {
    225  for (uint32_t i = 0; i < mList.Length(); ++i) {
    226    if (mList[i].mName.EqualsIgnoreCase("Set-Cookie")) {
    227      aValues.AppendElement(mList[i].mValue);
    228    }
    229  }
    230 }
    231 
    232 void InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue,
    233                               ErrorResult& aRv) const {
    234  nsAutoCString lowerName;
    235  ToLowerCase(aName, lowerName);
    236 
    237  if (IsInvalidName(lowerName, aRv)) {
    238    return;
    239  }
    240 
    241  for (uint32_t i = 0; i < mList.Length(); ++i) {
    242    if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
    243      aValue = mList[i].mValue;
    244      return;
    245    }
    246  }
    247 
    248  // No value found, so return null to content
    249  aValue.SetIsVoid(true);
    250 }
    251 
    252 bool InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const {
    253  nsAutoCString lowerName;
    254  ToLowerCase(aName, lowerName);
    255 
    256  if (IsInvalidName(lowerName, aRv)) {
    257    return false;
    258  }
    259 
    260  for (uint32_t i = 0; i < mList.Length(); ++i) {
    261    if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
    262      return true;
    263    }
    264  }
    265  return false;
    266 }
    267 
    268 void InternalHeaders::Set(const nsACString& aName, const nsACString& aValue,
    269                          ErrorResult& aRv) {
    270  // Step 1
    271  nsAutoCString trimValue;
    272  NS_TrimHTTPWhitespace(aValue, trimValue);
    273 
    274  // Steps 2 to 6
    275  nsAutoCString lowerName;
    276  ToLowerCase(aName, lowerName);
    277  if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
    278    return;
    279  }
    280 
    281  // Step 7
    282  SetListDirty();
    283 
    284  int32_t firstIndex = INT32_MAX;
    285 
    286  // remove in reverse order to minimize copying
    287  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
    288    if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
    289      firstIndex = std::min(firstIndex, i);
    290      mList.RemoveElementAt(i);
    291    }
    292  }
    293 
    294  if (firstIndex < INT32_MAX) {
    295    Entry* entry = mList.InsertElementAt(firstIndex);
    296    entry->mName = aName;
    297    entry->mValue = trimValue;
    298  } else {
    299    mList.AppendElement(Entry(aName, trimValue));
    300  }
    301 
    302  // Step 8
    303  if (mGuard == HeadersGuardEnum::Request_no_cors) {
    304    RemovePrivilegedNoCorsRequestHeaders();
    305  }
    306 }
    307 
    308 void InternalHeaders::Clear() {
    309  SetListDirty();
    310  mList.Clear();
    311 }
    312 
    313 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) {
    314  // The guard is only checked during ::Set() and ::Append() in the spec.  It
    315  // does not require revalidating headers already set.
    316  mGuard = aGuard;
    317 }
    318 
    319 InternalHeaders::~InternalHeaders() = default;
    320 
    321 // static
    322 bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName(
    323    const nsCString& aName) {
    324  return aName.EqualsIgnoreCase("accept") ||
    325         aName.EqualsIgnoreCase("accept-language") ||
    326         aName.EqualsIgnoreCase("content-language") ||
    327         aName.EqualsIgnoreCase("content-type");
    328 }
    329 
    330 // static
    331 bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName(
    332    const nsCString& aName) {
    333  return aName.EqualsIgnoreCase("range");
    334 }
    335 
    336 // static
    337 bool InternalHeaders::IsRevalidationHeader(const nsCString& aName) {
    338  return aName.EqualsIgnoreCase("if-modified-since") ||
    339         aName.EqualsIgnoreCase("if-none-match") ||
    340         aName.EqualsIgnoreCase("if-unmodified-since") ||
    341         aName.EqualsIgnoreCase("if-match") ||
    342         aName.EqualsIgnoreCase("if-range");
    343 }
    344 
    345 // static
    346 bool InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv) {
    347  if (!NS_IsValidHTTPToken(aName)) {
    348    aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(aName);
    349    return true;
    350  }
    351 
    352  return false;
    353 }
    354 
    355 // static
    356 bool InternalHeaders::IsInvalidValue(const nsACString& aValue,
    357                                     ErrorResult& aRv) {
    358  if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
    359    aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(aValue);
    360    return true;
    361  }
    362  return false;
    363 }
    364 
    365 bool InternalHeaders::IsImmutable(ErrorResult& aRv) const {
    366  if (mGuard == HeadersGuardEnum::Immutable) {
    367    aRv.ThrowTypeError("Headers are immutable and cannot be modified.");
    368    return true;
    369  }
    370  return false;
    371 }
    372 
    373 bool InternalHeaders::IsForbiddenRequestHeader(const nsCString& aName,
    374                                               const nsACString& aValue) const {
    375  return mGuard == HeadersGuardEnum::Request &&
    376         nsContentUtils::IsForbiddenRequestHeader(aName, aValue);
    377 }
    378 
    379 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
    380    const nsCString& aName) const {
    381  return mGuard == HeadersGuardEnum::Request_no_cors &&
    382         !nsContentUtils::IsCORSSafelistedRequestHeader(aName, ""_ns);
    383 }
    384 
    385 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
    386    const nsCString& aName, const nsACString& aValue) const {
    387  return mGuard == HeadersGuardEnum::Request_no_cors &&
    388         !nsContentUtils::IsCORSSafelistedRequestHeader(aName, aValue);
    389 }
    390 
    391 bool InternalHeaders::IsForbiddenResponseHeader(const nsCString& aName) const {
    392  return mGuard == HeadersGuardEnum::Response &&
    393         nsContentUtils::IsForbiddenResponseHeader(aName);
    394 }
    395 
    396 void InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv) {
    397  const nsTArray<Entry>& list = aInit.mList;
    398  for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
    399    const Entry& entry = list[i];
    400    Append(entry.mName, entry.mValue, aRv);
    401  }
    402 }
    403 
    404 void InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit,
    405                           ErrorResult& aRv) {
    406  for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
    407    const Sequence<nsCString>& tuple = aInit[i];
    408    if (tuple.Length() != 2) {
    409      aRv.ThrowTypeError(
    410          "Headers require name/value tuples when being initialized by a "
    411          "sequence.");
    412      return;
    413    }
    414    Append(tuple[0], tuple[1], aRv);
    415  }
    416 }
    417 
    418 void InternalHeaders::Fill(const Record<nsCString, nsCString>& aInit,
    419                           ErrorResult& aRv) {
    420  for (auto& entry : aInit.Entries()) {
    421    Append(entry.mKey, entry.mValue, aRv);
    422    if (aRv.Failed()) {
    423      return;
    424    }
    425  }
    426 }
    427 
    428 namespace {
    429 
    430 class FillHeaders final : public nsIHttpHeaderVisitor {
    431  RefPtr<InternalHeaders> mInternalHeaders;
    432 
    433  ~FillHeaders() = default;
    434 
    435 public:
    436  NS_DECL_ISUPPORTS
    437 
    438  explicit FillHeaders(InternalHeaders* aInternalHeaders)
    439      : mInternalHeaders(aInternalHeaders) {
    440    MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders);
    441  }
    442 
    443  NS_IMETHOD
    444  VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
    445    mInternalHeaders->Append(aHeader, aValue, IgnoreErrors());
    446    return NS_OK;
    447  }
    448 };
    449 
    450 NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor)
    451 
    452 }  // namespace
    453 
    454 void InternalHeaders::FillResponseHeaders(nsIRequest* aRequest) {
    455  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
    456  if (!httpChannel) {
    457    return;
    458  }
    459 
    460  RefPtr<FillHeaders> visitor = new FillHeaders(this);
    461  nsresult rv = httpChannel->VisitResponseHeaders(visitor);
    462  if (NS_FAILED(rv)) {
    463    NS_WARNING("failed to fill headers");
    464  }
    465 }
    466 
    467 bool InternalHeaders::HasOnlySimpleHeaders() const {
    468  for (uint32_t i = 0; i < mList.Length(); ++i) {
    469    if (!nsContentUtils::IsCORSSafelistedRequestHeader(mList[i].mName,
    470                                                       mList[i].mValue)) {
    471      return false;
    472    }
    473  }
    474 
    475  return true;
    476 }
    477 
    478 bool InternalHeaders::HasRevalidationHeaders() const {
    479  for (uint32_t i = 0; i < mList.Length(); ++i) {
    480    if (IsRevalidationHeader(mList[i].mName)) {
    481      return true;
    482    }
    483  }
    484 
    485  return false;
    486 }
    487 
    488 // static
    489 already_AddRefed<InternalHeaders> InternalHeaders::BasicHeaders(
    490    InternalHeaders* aHeaders) {
    491  RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders);
    492  ErrorResult result;
    493  // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
    494  // must succeed.
    495  basic->Delete("Set-Cookie"_ns, result);
    496  MOZ_ASSERT(!result.Failed());
    497  basic->Delete("Set-Cookie2"_ns, result);
    498  MOZ_ASSERT(!result.Failed());
    499  return basic.forget();
    500 }
    501 
    502 // static
    503 already_AddRefed<InternalHeaders> InternalHeaders::CORSHeaders(
    504    InternalHeaders* aHeaders, RequestCredentials aCredentialsMode) {
    505  RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
    506  ErrorResult result;
    507 
    508  nsAutoCString acExposedNames;
    509  aHeaders->Get("Access-Control-Expose-Headers"_ns, acExposedNames, result);
    510  MOZ_ASSERT(!result.Failed());
    511 
    512  bool allowAllHeaders = false;
    513  AutoTArray<nsCString, 5> exposeNamesArray;
    514  for (const nsACString& token :
    515       nsCCharSeparatedTokenizer(acExposedNames, ',').ToRange()) {
    516    if (token.IsEmpty()) {
    517      continue;
    518    }
    519 
    520    if (!NS_IsValidHTTPToken(token)) {
    521      NS_WARNING(
    522          "Got invalid HTTP token in Access-Control-Expose-Headers. Header "
    523          "value is:");
    524      NS_WARNING(acExposedNames.get());
    525      exposeNamesArray.Clear();
    526      break;
    527    }
    528 
    529    if (token.EqualsLiteral("*") &&
    530        aCredentialsMode != RequestCredentials::Include) {
    531      allowAllHeaders = true;
    532    }
    533 
    534    exposeNamesArray.AppendElement(token);
    535  }
    536 
    537  nsCaseInsensitiveCStringArrayComparator comp;
    538  for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
    539    const Entry& entry = aHeaders->mList[i];
    540    if (allowAllHeaders) {
    541      cors->Append(entry.mName, entry.mValue, result);
    542      MOZ_ASSERT(!result.Failed());
    543    } else if (entry.mName.EqualsIgnoreCase("cache-control") ||
    544               entry.mName.EqualsIgnoreCase("content-language") ||
    545               entry.mName.EqualsIgnoreCase("content-type") ||
    546               entry.mName.EqualsIgnoreCase("content-length") ||
    547               entry.mName.EqualsIgnoreCase("expires") ||
    548               entry.mName.EqualsIgnoreCase("last-modified") ||
    549               entry.mName.EqualsIgnoreCase("pragma") ||
    550               exposeNamesArray.Contains(entry.mName, comp)) {
    551      cors->Append(entry.mName, entry.mValue, result);
    552      MOZ_ASSERT(!result.Failed());
    553    }
    554  }
    555 
    556  return cors.forget();
    557 }
    558 
    559 void InternalHeaders::GetEntries(
    560    nsTArray<InternalHeaders::Entry>& aEntries) const {
    561  MOZ_ASSERT(aEntries.IsEmpty());
    562  aEntries.AppendElements(mList);
    563 }
    564 
    565 void InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const {
    566  MOZ_ASSERT(aNames.IsEmpty());
    567  for (uint32_t i = 0; i < mList.Length(); ++i) {
    568    const Entry& header = mList[i];
    569    if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
    570                                                       header.mValue)) {
    571      aNames.AppendElement(header.mName);
    572    }
    573  }
    574 }
    575 
    576 void InternalHeaders::MaybeSortList() {
    577  class Comparator {
    578   public:
    579    bool Equals(const Entry& aA, const Entry& aB) const {
    580      return aA.mName == aB.mName;
    581    }
    582 
    583    bool LessThan(const Entry& aA, const Entry& aB) const {
    584      return aA.mName < aB.mName;
    585    }
    586  };
    587 
    588  if (!mListDirty) {
    589    return;
    590  }
    591 
    592  mListDirty = false;
    593 
    594  Comparator comparator;
    595 
    596  mSortedList.Clear();
    597  for (const Entry& entry : mList) {
    598    bool found = false;
    599 
    600    // We combine every header but Set-Cookie.
    601    if (!entry.mName.EqualsIgnoreCase("Set-Cookie")) {
    602      for (Entry& sortedEntry : mSortedList) {
    603        if (sortedEntry.mName.EqualsIgnoreCase(entry.mName.get())) {
    604          sortedEntry.mValue += ", ";
    605          sortedEntry.mValue += entry.mValue;
    606          found = true;
    607          break;
    608        }
    609      }
    610    }
    611 
    612    if (!found) {
    613      Entry newEntry = entry;
    614      ToLowerCase(newEntry.mName);
    615      mSortedList.InsertElementSorted(newEntry, comparator);
    616    }
    617  }
    618 }
    619 
    620 void InternalHeaders::SetListDirty() {
    621  mSortedList.Clear();
    622  mListDirty = true;
    623 }
    624 
    625 void InternalHeaders::ReuseExistingNameIfExists(nsCString& aName) const {
    626  for (const Entry& entry : mList) {
    627    if (entry.mName.EqualsIgnoreCase(aName.get())) {
    628      aName = entry.mName;
    629      break;
    630    }
    631  }
    632 }
    633 
    634 }  // namespace mozilla::dom