tor-browser

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

PushSubscription.cpp (10801B)


      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/PushSubscription.h"
      8 
      9 #include "mozilla/Base64.h"
     10 #include "mozilla/dom/Promise.h"
     11 #include "mozilla/dom/PromiseWorkerProxy.h"
     12 #include "mozilla/dom/PushSubscriptionOptions.h"
     13 #include "mozilla/dom/TypedArray.h"
     14 #include "mozilla/dom/WorkerCommon.h"
     15 #include "mozilla/dom/WorkerPrivate.h"
     16 #include "mozilla/dom/WorkerRunnable.h"
     17 #include "mozilla/dom/WorkerScope.h"
     18 #include "nsGlobalWindowInner.h"
     19 #include "nsIPushService.h"
     20 #include "nsIScriptObjectPrincipal.h"
     21 #include "nsServiceManagerUtils.h"
     22 
     23 namespace mozilla::dom {
     24 
     25 namespace {
     26 
     27 class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback {
     28 public:
     29  NS_DECL_ISUPPORTS
     30 
     31  explicit UnsubscribeResultCallback(Promise* aPromise) : mPromise(aPromise) {
     32    AssertIsOnMainThread();
     33  }
     34 
     35  NS_IMETHOD
     36  OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
     37    if (NS_SUCCEEDED(aStatus)) {
     38      mPromise->MaybeResolve(aSuccess);
     39    } else {
     40      mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
     41    }
     42 
     43    return NS_OK;
     44  }
     45 
     46 private:
     47  ~UnsubscribeResultCallback() = default;
     48 
     49  RefPtr<Promise> mPromise;
     50 };
     51 
     52 NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
     53 
     54 class UnsubscribeResultRunnable final : public WorkerThreadRunnable {
     55 public:
     56  UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
     57                            RefPtr<PromiseWorkerProxy>&& aProxy,
     58                            nsresult aStatus, bool aSuccess)
     59      : WorkerThreadRunnable("UnsubscribeResultRunnable"),
     60        mProxy(std::move(aProxy)),
     61        mStatus(aStatus),
     62        mSuccess(aSuccess) {
     63    AssertIsOnMainThread();
     64  }
     65 
     66  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
     67    MOZ_ASSERT(aWorkerPrivate);
     68    aWorkerPrivate->AssertIsOnWorkerThread();
     69 
     70    RefPtr<Promise> promise = mProxy->GetWorkerPromise();
     71    // Once Worker had already started shutdown, workerPromise would be nullptr
     72    if (!promise) {
     73      return true;
     74    }
     75    if (NS_SUCCEEDED(mStatus)) {
     76      promise->MaybeResolve(mSuccess);
     77    } else {
     78      promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
     79    }
     80 
     81    mProxy->CleanUp();
     82 
     83    return true;
     84  }
     85 
     86 private:
     87  ~UnsubscribeResultRunnable() = default;
     88 
     89  RefPtr<PromiseWorkerProxy> mProxy;
     90  nsresult mStatus;
     91  bool mSuccess;
     92 };
     93 
     94 class WorkerUnsubscribeResultCallback final
     95    : public nsIUnsubscribeResultCallback {
     96 public:
     97  NS_DECL_ISUPPORTS
     98 
     99  explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy)
    100      : mProxy(aProxy) {
    101    AssertIsOnMainThread();
    102  }
    103 
    104  NS_IMETHOD
    105  OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
    106    AssertIsOnMainThread();
    107    MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
    108 
    109    MutexAutoLock lock(mProxy->Lock());
    110    if (mProxy->CleanedUp()) {
    111      return NS_OK;
    112    }
    113 
    114    WorkerPrivate* worker = mProxy->GetWorkerPrivate();
    115    RefPtr<UnsubscribeResultRunnable> r = new UnsubscribeResultRunnable(
    116        worker, std::move(mProxy), aStatus, aSuccess);
    117    MOZ_ALWAYS_TRUE(r->Dispatch(worker));
    118 
    119    return NS_OK;
    120  }
    121 
    122 private:
    123  ~WorkerUnsubscribeResultCallback() = default;
    124 
    125  RefPtr<PromiseWorkerProxy> mProxy;
    126 };
    127 
    128 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
    129 
    130 class UnsubscribeRunnable final : public Runnable {
    131 public:
    132  UnsubscribeRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope)
    133      : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy), mScope(aScope) {
    134    MOZ_ASSERT(aProxy);
    135    MOZ_ASSERT(!aScope.IsEmpty());
    136  }
    137 
    138  NS_IMETHOD
    139  Run() override {
    140    AssertIsOnMainThread();
    141 
    142    nsCOMPtr<nsIPrincipal> principal;
    143 
    144    {
    145      MutexAutoLock lock(mProxy->Lock());
    146      if (mProxy->CleanedUp()) {
    147        return NS_OK;
    148      }
    149      principal = mProxy->GetWorkerPrivate()->GetPrincipal();
    150    }
    151 
    152    MOZ_ASSERT(principal);
    153 
    154    RefPtr<WorkerUnsubscribeResultCallback> callback =
    155        new WorkerUnsubscribeResultCallback(mProxy);
    156 
    157    nsCOMPtr<nsIPushService> service =
    158        do_GetService("@mozilla.org/push/Service;1");
    159    if (NS_WARN_IF(!service)) {
    160      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
    161      return NS_OK;
    162    }
    163 
    164    if (NS_WARN_IF(
    165            NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
    166      callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
    167      return NS_OK;
    168    }
    169 
    170    return NS_OK;
    171  }
    172 
    173 private:
    174  ~UnsubscribeRunnable() = default;
    175 
    176  RefPtr<PromiseWorkerProxy> mProxy;
    177  nsString mScope;
    178 };
    179 
    180 }  // anonymous namespace
    181 
    182 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
    183                                   const nsAString& aEndpoint,
    184                                   const nsAString& aScope,
    185                                   Nullable<EpochTimeStamp>&& aExpirationTime,
    186                                   nsTArray<uint8_t>&& aRawP256dhKey,
    187                                   nsTArray<uint8_t>&& aAuthSecret,
    188                                   nsTArray<uint8_t>&& aAppServerKey)
    189    : mEndpoint(aEndpoint),
    190      mScope(aScope),
    191      mExpirationTime(std::move(aExpirationTime)),
    192      mRawP256dhKey(std::move(aRawP256dhKey)),
    193      mAuthSecret(std::move(aAuthSecret)) {
    194  if (NS_IsMainThread()) {
    195    mGlobal = aGlobal;
    196  } else {
    197 #ifdef DEBUG
    198    // There's only one global on a worker, so we don't need to pass a global
    199    // object to the constructor.
    200    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
    201    MOZ_ASSERT(worker);
    202    worker->AssertIsOnWorkerThread();
    203 #endif
    204  }
    205  mOptions = new PushSubscriptionOptions(mGlobal, std::move(aAppServerKey));
    206 }
    207 
    208 PushSubscription::~PushSubscription() = default;
    209 
    210 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions)
    211 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
    212 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
    213 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
    214  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
    215  NS_INTERFACE_MAP_ENTRY(nsISupports)
    216 NS_INTERFACE_MAP_END
    217 
    218 JSObject* PushSubscription::WrapObject(JSContext* aCx,
    219                                       JS::Handle<JSObject*> aGivenProto) {
    220  return PushSubscription_Binding::Wrap(aCx, this, aGivenProto);
    221 }
    222 
    223 // static
    224 already_AddRefed<PushSubscription> PushSubscription::Constructor(
    225    GlobalObject& aGlobal, const PushSubscriptionInit& aInitDict,
    226    ErrorResult& aRv) {
    227  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    228 
    229  nsTArray<uint8_t> rawKey;
    230  if (!aInitDict.mP256dhKey.IsNull() &&
    231      !aInitDict.mP256dhKey.Value().AppendDataTo(rawKey)) {
    232    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    233    return nullptr;
    234  }
    235 
    236  nsTArray<uint8_t> authSecret;
    237  if (!aInitDict.mAuthSecret.IsNull() &&
    238      !aInitDict.mAuthSecret.Value().AppendDataTo(authSecret)) {
    239    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    240    return nullptr;
    241  }
    242 
    243  nsTArray<uint8_t> appServerKey;
    244  if (!aInitDict.mAppServerKey.IsNull() &&
    245      !AppendTypedArrayDataTo(aInitDict.mAppServerKey.Value(), appServerKey)) {
    246    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    247    return nullptr;
    248  }
    249 
    250  Nullable<EpochTimeStamp> expirationTime;
    251  if (aInitDict.mExpirationTime.IsNull()) {
    252    expirationTime.SetNull();
    253  } else {
    254    expirationTime.SetValue(aInitDict.mExpirationTime.Value());
    255  }
    256 
    257  RefPtr<PushSubscription> sub = new PushSubscription(
    258      global, aInitDict.mEndpoint, aInitDict.mScope, std::move(expirationTime),
    259      std::move(rawKey), std::move(authSecret), std::move(appServerKey));
    260 
    261  return sub.forget();
    262 }
    263 
    264 already_AddRefed<Promise> PushSubscription::Unsubscribe(ErrorResult& aRv) {
    265  if (!NS_IsMainThread()) {
    266    RefPtr<Promise> p = UnsubscribeFromWorker(aRv);
    267    return p.forget();
    268  }
    269 
    270  MOZ_ASSERT(mGlobal);
    271 
    272  nsCOMPtr<nsIPushService> service =
    273      do_GetService("@mozilla.org/push/Service;1");
    274  if (NS_WARN_IF(!service)) {
    275    aRv.Throw(NS_ERROR_FAILURE);
    276    return nullptr;
    277  }
    278 
    279  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
    280  if (!window) {
    281    aRv.Throw(NS_ERROR_FAILURE);
    282    return nullptr;
    283  }
    284 
    285  RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
    286  if (NS_WARN_IF(aRv.Failed())) {
    287    return nullptr;
    288  }
    289 
    290  RefPtr<UnsubscribeResultCallback> callback = new UnsubscribeResultCallback(p);
    291  (void)NS_WARN_IF(NS_FAILED(service->Unsubscribe(
    292      mScope, nsGlobalWindowInner::Cast(window)->GetClientPrincipal(),
    293      callback)));
    294 
    295  return p.forget();
    296 }
    297 
    298 void PushSubscription::GetKey(JSContext* aCx, PushEncryptionKeyName aType,
    299                              JS::MutableHandle<JSObject*> aKey,
    300                              ErrorResult& aRv) {
    301  if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
    302    aKey.set(ArrayBuffer::Create(aCx, mRawP256dhKey, aRv));
    303  } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
    304    aKey.set(ArrayBuffer::Create(aCx, mAuthSecret, aRv));
    305  } else {
    306    aKey.set(nullptr);
    307  }
    308 }
    309 
    310 void PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) {
    311  aJSON.mEndpoint.Construct();
    312  aJSON.mEndpoint.Value() = mEndpoint;
    313 
    314  aJSON.mKeys.mP256dh.Construct();
    315  nsresult rv = Base64URLEncode(
    316      mRawP256dhKey.Length(), mRawP256dhKey.Elements(),
    317      Base64URLEncodePaddingPolicy::Omit, aJSON.mKeys.mP256dh.Value());
    318  if (NS_WARN_IF(NS_FAILED(rv))) {
    319    aRv.Throw(rv);
    320    return;
    321  }
    322 
    323  aJSON.mKeys.mAuth.Construct();
    324  rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
    325                       Base64URLEncodePaddingPolicy::Omit,
    326                       aJSON.mKeys.mAuth.Value());
    327  if (NS_WARN_IF(NS_FAILED(rv))) {
    328    aRv.Throw(rv);
    329    return;
    330  }
    331  aJSON.mExpirationTime.Construct(mExpirationTime);
    332 }
    333 
    334 already_AddRefed<PushSubscriptionOptions> PushSubscription::Options() {
    335  RefPtr<PushSubscriptionOptions> options = mOptions;
    336  return options.forget();
    337 }
    338 
    339 already_AddRefed<Promise> PushSubscription::UnsubscribeFromWorker(
    340    ErrorResult& aRv) {
    341  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
    342  MOZ_ASSERT(worker);
    343  worker->AssertIsOnWorkerThread();
    344 
    345  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
    346  RefPtr<Promise> p = Promise::Create(global, aRv);
    347  if (NS_WARN_IF(aRv.Failed())) {
    348    return nullptr;
    349  }
    350 
    351  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
    352  if (!proxy) {
    353    p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
    354    return p.forget();
    355  }
    356 
    357  RefPtr<UnsubscribeRunnable> r = new UnsubscribeRunnable(proxy, mScope);
    358  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
    359 
    360  return p.forget();
    361 }
    362 
    363 }  // namespace mozilla::dom