tor-browser

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

CookieStoreManager.cpp (9784B)


      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 "CookieStoreManager.h"
      8 
      9 #include "CookieStoreChild.h"
     10 #include "mozilla/dom/Promise.h"
     11 #include "mozilla/dom/WorkerCommon.h"
     12 #include "mozilla/dom/WorkerPrivate.h"
     13 #include "mozilla/ipc/BackgroundChild.h"
     14 #include "mozilla/ipc/PBackgroundChild.h"
     15 #include "nsGlobalWindowInner.h"
     16 #include "nsIGlobalObject.h"
     17 #include "nsIPrincipal.h"
     18 
     19 using mozilla::ipc::PrincipalInfo;
     20 
     21 namespace mozilla::dom {
     22 
     23 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CookieStoreManager)
     24 
     25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CookieStoreManager)
     26  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObject)
     27  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     28  tmp->Shutdown();
     29 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     30 
     31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CookieStoreManager)
     32  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObject)
     33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     34 
     35 CookieStoreManager::CookieStoreManager(
     36    nsIGlobalObject* aGlobalObject,
     37    const nsACString& aServiceWorkerRegistrationScopeURL)
     38    : mGlobalObject(aGlobalObject),
     39      mScopeURL(aServiceWorkerRegistrationScopeURL) {
     40  MOZ_ASSERT(aGlobalObject);
     41 }
     42 
     43 namespace {
     44 
     45 nsIPrincipal* RetrievePrincipal(nsIGlobalObject* aGlobalObject) {
     46  if (NS_IsMainThread()) {
     47    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobalObject);
     48    if (NS_WARN_IF(!window)) {
     49      return nullptr;
     50    }
     51 
     52    return nsGlobalWindowInner::Cast(window)->GetClientPrincipal();
     53  }
     54 
     55  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     56  MOZ_ASSERT(worker);
     57  worker->AssertIsOnWorkerThread();
     58 
     59  return worker->GetPrincipal();
     60 }
     61 
     62 }  // namespace
     63 
     64 CookieStoreManager::~CookieStoreManager() { Shutdown(); }
     65 
     66 JSObject* CookieStoreManager::WrapObject(JSContext* aCx,
     67                                         JS::Handle<JSObject*> aGivenProto) {
     68  return CookieStoreManager_Binding::Wrap(aCx, this, aGivenProto);
     69 }
     70 
     71 already_AddRefed<Promise> CookieStoreManager::Subscribe(
     72    const CopyableTArray<CookieStoreGetOptions>& aSubscriptions,
     73    ErrorResult& aRv) {
     74  return SubscribeOrUnsubscribe(Action::eSubscribe, aSubscriptions, aRv);
     75 }
     76 
     77 already_AddRefed<Promise> CookieStoreManager::GetSubscriptions(
     78    ErrorResult& aRv) {
     79  nsCOMPtr<nsIPrincipal> principal = RetrievePrincipal(mGlobalObject);
     80  if (NS_WARN_IF(!principal)) {
     81    aRv.ThrowInvalidStateError("Invalid context");
     82    return nullptr;
     83  }
     84 
     85  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
     86  if (NS_WARN_IF(!promise)) {
     87    return nullptr;
     88  }
     89 
     90  // ServiceWorkers only have one principal: it's either partitioned or
     91  // unpartitioned. If the "context" is partitioned, the window or the
     92  // WorkerPrivate will return a partitioned principal.
     93 
     94  // Let's dispatch a runnable to implement the "Run the following steps in
     95  // parallel".
     96  NS_DispatchToCurrentThread(NS_NewRunnableFunction(
     97      __func__, [self = RefPtr(this), principal = RefPtr(principal.get()),
     98                 promise = RefPtr(promise)]() {
     99        if (!self->MaybeCreateActor()) {
    100          promise->MaybeRejectWithNotAllowedError("Permission denied");
    101          return;
    102        }
    103 
    104        PrincipalInfo principalInfo;
    105        nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
    106        if (NS_WARN_IF(NS_FAILED(rv))) {
    107          promise->MaybeResolve(nsTArray<CookieStoreGetOptions>());
    108          return;
    109        }
    110 
    111        RefPtr<CookieStoreChild::GetSubscriptionsRequestPromise> ipcPromise =
    112            self->mActor->SendGetSubscriptionsRequest(principalInfo,
    113                                                      self->mScopeURL);
    114        if (NS_WARN_IF(!ipcPromise)) {
    115          promise->MaybeResolve(nsTArray<CookieStoreGetOptions>());
    116          return;
    117        }
    118 
    119        ipcPromise->Then(
    120            NS_GetCurrentThread(), __func__,
    121            [promise = RefPtr(promise)](
    122                const CookieStoreChild::GetSubscriptionsRequestPromise::
    123                    ResolveOrRejectValue& aResult) {
    124              if (aResult.IsReject()) {
    125                promise->MaybeResolve(nsTArray<CookieStoreGetOptions>());
    126                return;
    127              }
    128 
    129              nsTArray<CookieStoreGetOptions> results;
    130              for (const auto& subscription : aResult.ResolveValue()) {
    131                CookieStoreGetOptions* result = results.AppendElement();
    132 
    133                if (subscription.name().isSome()) {
    134                  result->mName.Construct();
    135                  result->mName.Value() = subscription.name().value();
    136                }
    137 
    138                result->mUrl.Construct();
    139                result->mUrl.Value() = subscription.url();
    140              }
    141 
    142              promise->MaybeResolve(results);
    143            });
    144      }));
    145 
    146  return promise.forget();
    147 }
    148 
    149 already_AddRefed<Promise> CookieStoreManager::Unsubscribe(
    150    const CopyableTArray<CookieStoreGetOptions>& aSubscriptions,
    151    ErrorResult& aRv) {
    152  return SubscribeOrUnsubscribe(Action::eUnsubscribe, aSubscriptions, aRv);
    153 }
    154 
    155 already_AddRefed<Promise> CookieStoreManager::SubscribeOrUnsubscribe(
    156    Action aAction, const CopyableTArray<CookieStoreGetOptions>& aSubscriptions,
    157    ErrorResult& aRv) {
    158  nsCOMPtr<nsIPrincipal> principal = RetrievePrincipal(mGlobalObject);
    159  if (NS_WARN_IF(!principal)) {
    160    aRv.ThrowInvalidStateError("Invalid context");
    161    return nullptr;
    162  }
    163 
    164  RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv);
    165  if (NS_WARN_IF(!promise)) {
    166    return nullptr;
    167  }
    168 
    169  // ServiceWorkers only have one principal: it's either partitioned or
    170  // unpartitioned. If the "context" is partitioned, the window or the
    171  // WorkerPrivate will return a partitioned principal.
    172 
    173  NS_DispatchToCurrentThread(NS_NewRunnableFunction(
    174      __func__,
    175      [self = RefPtr(this), promise = RefPtr(promise),
    176       principal = RefPtr(principal.get()), aSubscriptions, aAction]() {
    177        nsCOMPtr<nsIURI> baseURI;
    178        nsresult rv = NS_NewURI(getter_AddRefs(baseURI), self->mScopeURL,
    179                                nullptr, nullptr);
    180        if (NS_WARN_IF(NS_FAILED(rv))) {
    181          promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(self->mScopeURL);
    182          return;
    183        }
    184 
    185        if (NS_WARN_IF(!baseURI)) {
    186          promise->MaybeRejectWithSecurityError(
    187              "Couldn't acquire the base URI of this context");
    188          return;
    189        }
    190 
    191        nsTArray<CookieSubscription> subscriptions;
    192 
    193        for (const CookieStoreGetOptions& subscription : aSubscriptions) {
    194          nsString subscriptionURL;
    195          if (subscription.mUrl.WasPassed()) {
    196            subscriptionURL.Assign(subscription.mUrl.Value());
    197          }
    198 
    199          nsCOMPtr<nsIURI> uri;
    200          rv =
    201              NS_NewURI(getter_AddRefs(uri), subscriptionURL, nullptr, baseURI);
    202          if (NS_WARN_IF(NS_FAILED(rv))) {
    203            promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
    204                NS_ConvertUTF16toUTF8(subscriptionURL));
    205            return;
    206          }
    207 
    208          nsAutoCString subscriptionURI;
    209          rv = uri->GetSpec(subscriptionURI);
    210          if (NS_WARN_IF(NS_FAILED(rv))) {
    211            promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
    212                NS_ConvertUTF16toUTF8(subscriptionURL));
    213            return;
    214          }
    215 
    216          if (!StringBeginsWith(subscriptionURI, self->mScopeURL)) {
    217            promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
    218                NS_ConvertUTF16toUTF8(subscriptionURL));
    219            return;
    220          }
    221 
    222          subscriptions.AppendElement(CookieSubscription{
    223              subscription.mName.WasPassed() ? Some(subscription.mName.Value())
    224                                             : Nothing(),
    225              NS_ConvertUTF8toUTF16(subscriptionURI)});
    226        }
    227 
    228        if (!self->MaybeCreateActor()) {
    229          promise->MaybeRejectWithNotAllowedError("Permission denied");
    230          return;
    231        }
    232 
    233        PrincipalInfo principalInfo;
    234        rv = PrincipalToPrincipalInfo(principal, &principalInfo);
    235        if (NS_WARN_IF(NS_FAILED(rv))) {
    236          promise->MaybeResolveWithUndefined();
    237          return;
    238        }
    239 
    240        RefPtr<CookieStoreChild::SubscribeOrUnsubscribeRequestPromise>
    241            ipcPromise = self->mActor->SendSubscribeOrUnsubscribeRequest(
    242                principalInfo, self->mScopeURL, subscriptions,
    243                (aAction == Action::eSubscribe));
    244        if (NS_WARN_IF(!ipcPromise)) {
    245          promise->MaybeResolveWithUndefined();
    246          return;
    247        }
    248 
    249        ipcPromise->Then(
    250            NS_GetCurrentThread(), __func__,
    251            [promise = RefPtr(promise)](
    252                const CookieStoreChild::SubscribeOrUnsubscribeRequestPromise::
    253                    ResolveOrRejectValue& aResult) {
    254              // TODO We don't really want to expose internal errors.
    255              promise->MaybeResolveWithUndefined();
    256            });
    257      }));
    258 
    259  return promise.forget();
    260 }
    261 
    262 bool CookieStoreManager::MaybeCreateActor() {
    263  if (mActor) {
    264    return mActor->CanSend();
    265  }
    266 
    267  mozilla::ipc::PBackgroundChild* actorChild =
    268      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
    269  if (NS_WARN_IF(!actorChild)) {
    270    // The process is probably shutting down. Let's return a 'generic' error.
    271    return false;
    272  }
    273 
    274  PCookieStoreChild* actor = actorChild->SendPCookieStoreConstructor();
    275  if (!actor) {
    276    return false;
    277  }
    278 
    279  mActor = static_cast<CookieStoreChild*>(actor);
    280 
    281  return true;
    282 }
    283 
    284 void CookieStoreManager::Shutdown() {
    285  if (mActor) {
    286    mActor->Close();
    287    mActor = nullptr;
    288  }
    289 }
    290 
    291 }  // namespace mozilla::dom