tor-browser

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

CacheOpParent.cpp (10869B)


      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/cache/CacheOpParent.h"
      8 
      9 #include "mozilla/ErrorResult.h"
     10 #include "mozilla/StaticPrefs_browser.h"
     11 #include "mozilla/dom/cache/AutoUtils.h"
     12 #include "mozilla/dom/cache/ManagerId.h"
     13 #include "mozilla/dom/cache/ReadStream.h"
     14 #include "mozilla/dom/cache/SavedTypes.h"
     15 #include "mozilla/ipc/IPCStreamUtils.h"
     16 #include "mozilla/ipc/InputStreamUtils.h"
     17 
     18 namespace mozilla::dom::cache {
     19 
     20 using mozilla::ipc::PBackgroundParent;
     21 
     22 CacheOpParent::CacheOpParent(const WeakRefParentType& aIpcManager,
     23                             const CacheOpArgs& aOpArgs, CacheId aCacheId,
     24                             Namespace aNamespace)
     25    : mIpcManager(aIpcManager),
     26      mCacheId(aCacheId),
     27      mNamespace(aNamespace),
     28      mOpArgs(aOpArgs) {
     29  MOZ_DIAGNOSTIC_ASSERT(mIpcManager.isSome());
     30 }
     31 
     32 CacheOpParent::~CacheOpParent() { NS_ASSERT_OWNINGTHREAD(CacheOpParent); }
     33 
     34 void CacheOpParent::Execute(const SafeRefPtr<ManagerId>& aManagerId) {
     35  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
     36  MOZ_DIAGNOSTIC_ASSERT(!mManager);
     37  MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
     38 
     39  auto managerOrErr = cache::Manager::AcquireCreateIfNonExistent(aManagerId);
     40  if (NS_WARN_IF(managerOrErr.isErr())) {
     41    (void)Send__delete__(this, CopyableErrorResult(managerOrErr.unwrapErr()),
     42                         void_t());
     43    return;
     44  }
     45 
     46  Execute(managerOrErr.unwrap());
     47 }
     48 
     49 void CacheOpParent::Execute(SafeRefPtr<cache::Manager> aManager) {
     50  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
     51  MOZ_DIAGNOSTIC_ASSERT(!mManager);
     52  MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
     53 
     54  mManager = std::move(aManager);
     55 
     56  // Handle put op
     57  if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) {
     58    MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
     59 
     60    const CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
     61    const nsTArray<CacheRequestResponse>& list = args.requestResponseList();
     62 
     63    AutoTArray<nsCOMPtr<nsIInputStream>, 256> requestStreamList;
     64    AutoTArray<nsCOMPtr<nsIInputStream>, 256> responseStreamList;
     65 
     66    for (uint32_t i = 0; i < list.Length(); ++i) {
     67      requestStreamList.AppendElement(
     68          DeserializeCacheStream(list[i].request().body()));
     69      responseStreamList.AppendElement(
     70          DeserializeCacheStream(list[i].response().body()));
     71    }
     72 
     73    mManager->ExecutePutAll(this, mCacheId, args.requestResponseList(),
     74                            requestStreamList, responseStreamList);
     75    return;
     76  }
     77 
     78  // Handle all other cache ops
     79  if (mCacheId != INVALID_CACHE_ID) {
     80    MOZ_DIAGNOSTIC_ASSERT(mNamespace == INVALID_NAMESPACE);
     81    mManager->ExecuteCacheOp(this, mCacheId, mOpArgs);
     82    return;
     83  }
     84 
     85  // Handle all storage ops
     86  MOZ_DIAGNOSTIC_ASSERT(mNamespace != INVALID_NAMESPACE);
     87  mManager->ExecuteStorageOp(this, mNamespace, mOpArgs);
     88 }
     89 
     90 void CacheOpParent::WaitForVerification(PrincipalVerifier* aVerifier) {
     91  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
     92  MOZ_DIAGNOSTIC_ASSERT(!mManager);
     93  MOZ_DIAGNOSTIC_ASSERT(!mVerifier);
     94 
     95  mVerifier = aVerifier;
     96  mVerifier->AddListener(*this);
     97 }
     98 
     99 void CacheOpParent::ActorDestroy(ActorDestroyReason aReason) {
    100  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
    101 
    102  if (mVerifier) {
    103    mVerifier->RemoveListener(*this);
    104    mVerifier = nullptr;
    105  }
    106 
    107  if (mManager) {
    108    mManager->RemoveListener(this);
    109    mManager = nullptr;
    110  }
    111 
    112  mIpcManager.destroy();
    113 }
    114 
    115 void CacheOpParent::OnPrincipalVerified(
    116    nsresult aRv, const SafeRefPtr<ManagerId>& aManagerId) {
    117  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
    118 
    119  mVerifier->RemoveListener(*this);
    120  mVerifier = nullptr;
    121 
    122  if (NS_WARN_IF(NS_FAILED(aRv))) {
    123    (void)Send__delete__(this, CopyableErrorResult(aRv), void_t());
    124    return;
    125  }
    126 
    127  Execute(aManagerId);
    128 }
    129 
    130 void CacheOpParent::OnOpComplete(ErrorResult&& aRv,
    131                                 const CacheOpResult& aResult,
    132                                 CacheId aOpenedCacheId,
    133                                 const Maybe<StreamInfo>& aStreamInfo) {
    134  NS_ASSERT_OWNINGTHREAD(CacheOpParent);
    135  MOZ_DIAGNOSTIC_ASSERT(mIpcManager.isSome());
    136  MOZ_DIAGNOSTIC_ASSERT(mManager);
    137 
    138  // Never send an op-specific result if we have an error.  Instead, send
    139  // void_t() to ensure that we don't leak actors on the child side.
    140  if (NS_WARN_IF(aRv.Failed())) {
    141    (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)), void_t());
    142    return;
    143  }
    144 
    145  if (aStreamInfo.isSome()) {
    146    ProcessCrossOriginResourcePolicyHeader(aRv,
    147                                           aStreamInfo->mSavedResponseList);
    148    if (NS_WARN_IF(aRv.Failed())) {
    149      (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)), void_t());
    150      return;
    151    }
    152  }
    153 
    154  uint32_t entryCount =
    155      std::max(1lu, aStreamInfo ? static_cast<unsigned long>(std::max(
    156                                      aStreamInfo->mSavedResponseList.Length(),
    157                                      aStreamInfo->mSavedRequestList.Length()))
    158                                : 0lu);
    159 
    160  // The result must contain the appropriate type at this point.  It may
    161  // or may not contain the additional result data yet.  For types that
    162  // do not need special processing, it should already be set.  If the
    163  // result requires actor-specific operations, then we do that below.
    164  // If the type and data types don't match, then we will trigger an
    165  // assertion in AutoParentOpResult::Add().
    166  AutoParentOpResult result(mIpcManager.ref(), aResult, entryCount);
    167 
    168  if (aOpenedCacheId != INVALID_CACHE_ID) {
    169    result.Add(aOpenedCacheId, mManager.clonePtr());
    170  }
    171 
    172  if (aStreamInfo) {
    173    const auto& streamInfo = *aStreamInfo;
    174 
    175    for (const auto& savedResponse : streamInfo.mSavedResponseList) {
    176      result.Add(savedResponse, streamInfo.mStreamList);
    177    }
    178 
    179    for (const auto& savedRequest : streamInfo.mSavedRequestList) {
    180      result.Add(savedRequest, streamInfo.mStreamList);
    181    }
    182  }
    183 
    184  (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)),
    185                       result.SendAsOpResult());
    186 }
    187 
    188 already_AddRefed<nsIInputStream> CacheOpParent::DeserializeCacheStream(
    189    const Maybe<CacheReadStream>& aMaybeStream) {
    190  if (aMaybeStream.isNothing()) {
    191    return nullptr;
    192  }
    193 
    194  nsCOMPtr<nsIInputStream> stream;
    195  const CacheReadStream& readStream = aMaybeStream.ref();
    196 
    197  // Option 1: One of our own ReadStreams was passed back to us with a stream
    198  //           control actor.
    199  stream = ReadStream::Create(readStream);
    200  if (stream) {
    201    return stream.forget();
    202  }
    203 
    204  // Option 2: A stream was serialized using normal methods or passed
    205  //           as a DataPipe.  Use the standard method for extracting the
    206  //           resulting stream.
    207  return DeserializeIPCStream(readStream.stream());
    208 }
    209 
    210 void CacheOpParent::ProcessCrossOriginResourcePolicyHeader(
    211    ErrorResult& aRv, const nsTArray<SavedResponse>& aResponses) {
    212  if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
    213    return;
    214  }
    215  // Only checking for match/matchAll.
    216  nsILoadInfo::CrossOriginEmbedderPolicy loadingCOEP =
    217      nsILoadInfo::EMBEDDER_POLICY_NULL;
    218  Maybe<mozilla::ipc::PrincipalInfo> principalInfo;
    219  switch (mOpArgs.type()) {
    220    case CacheOpArgs::TCacheMatchArgs: {
    221      const auto& request = mOpArgs.get_CacheMatchArgs().request();
    222      loadingCOEP = request.loadingEmbedderPolicy();
    223      principalInfo = request.principalInfo();
    224      break;
    225    }
    226    case CacheOpArgs::TCacheMatchAllArgs: {
    227      if (mOpArgs.get_CacheMatchAllArgs().maybeRequest().isSome()) {
    228        const auto& request =
    229            mOpArgs.get_CacheMatchAllArgs().maybeRequest().ref();
    230        loadingCOEP = request.loadingEmbedderPolicy();
    231        principalInfo = request.principalInfo();
    232      }
    233      break;
    234    }
    235    default: {
    236      return;
    237    }
    238  }
    239 
    240  // skip checking if the request has no principal for same-origin/same-site
    241  // checking.
    242  if (principalInfo.isNothing() ||
    243      principalInfo.ref().type() !=
    244          mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
    245    return;
    246  }
    247  const mozilla::ipc::ContentPrincipalInfo& contentPrincipalInfo =
    248      principalInfo.ref().get_ContentPrincipalInfo();
    249 
    250  for (auto it = aResponses.cbegin(); it != aResponses.cend(); ++it) {
    251    if (it->mValue.type() != ResponseType::Opaque &&
    252        it->mValue.type() != ResponseType::Opaqueredirect) {
    253      continue;
    254    }
    255 
    256    const auto& headers = it->mValue.headers();
    257    const RequestCredentials credentials = it->mValue.credentials();
    258    const auto corpHeaderIt =
    259        std::find_if(headers.cbegin(), headers.cend(), [](const auto& header) {
    260          return header.name().EqualsLiteral("Cross-Origin-Resource-Policy");
    261        });
    262 
    263    // According to https://github.com/w3c/ServiceWorker/issues/1490, the cache
    264    // response is expected with CORP header, otherwise, throw the type error.
    265    // Note that this is different with the CORP checking for fetch metioned in
    266    // https://wicg.github.io/cross-origin-embedder-policy/#corp-check.
    267    // For fetch, if the response has no CORP header, "same-origin" checking
    268    // will be performed.
    269    if (corpHeaderIt == headers.cend() &&
    270        loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
    271      aRv.ThrowTypeError("Response is expected with CORP header.");
    272      return;
    273    }
    274 
    275    // Skip the case if the response has no principal for same-origin/same-site
    276    // checking.
    277    if (it->mValue.principalInfo().isNothing() ||
    278        it->mValue.principalInfo().ref().type() !=
    279            mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
    280      continue;
    281    }
    282 
    283    const mozilla::ipc::ContentPrincipalInfo& responseContentPrincipalInfo =
    284        it->mValue.principalInfo().ref().get_ContentPrincipalInfo();
    285 
    286    nsCString corp =
    287        corpHeaderIt == headers.cend() ? EmptyCString() : corpHeaderIt->value();
    288 
    289    if (corp.IsEmpty()) {
    290      if (loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
    291        // This means the request of this request doesn't have
    292        // credentials, so it's safe for us to return.
    293        if (credentials == RequestCredentials::Omit) {
    294          return;
    295        }
    296        corp = "same-origin";
    297      }
    298    }
    299 
    300    if (corp.EqualsLiteral("same-origin")) {
    301      if (responseContentPrincipalInfo != contentPrincipalInfo) {
    302        aRv.ThrowTypeError("Response is expected from same origin.");
    303        return;
    304      }
    305    } else if (corp.EqualsLiteral("same-site")) {
    306      if (!responseContentPrincipalInfo.baseDomain().Equals(
    307              contentPrincipalInfo.baseDomain())) {
    308        aRv.ThrowTypeError("Response is expected from same site.");
    309        return;
    310      }
    311    }
    312  }
    313 }
    314 
    315 }  // namespace mozilla::dom::cache