Quelle  PushManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/PushManager.h"

#include "mozilla/Base64.h"
#include "mozilla/Preferences.h"
#include "mozilla/Components.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/PermissionStatusBinding.h"
#include "mozilla/dom/PushManagerBinding.h"
#include "mozilla/dom/PushSubscription.h"
#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ServiceWorker.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"

#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"

#include "nsIGlobalObject.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIPushService.h"

#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"

namespace mozilla::dom {

namespace {

nsresult GetPermissionState(nsIPrincipal* aPrincipal, PermissionState& aState) {
  nsCOMPtr<nsIPermissionManager> permManager =
      mozilla::components::PermissionManager::Service();

  if (!permManager) {
    return NS_ERROR_FAILURE;
  }
  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
  nsresult rv = permManager->TestExactPermissionFromPrincipal(
      aPrincipal, "desktop-notification"_ns, &permission);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (permission == nsIPermissionManager::ALLOW_ACTION ||
      Preferences::GetBool("dom.push.testing.ignorePermission"false)) {
    aState = PermissionState::Granted;
  } else if (permission == nsIPermissionManager::DENY_ACTION) {
    aState = PermissionState::Denied;
  } else {
    aState = PermissionState::Prompt;
  }

  return NS_OK;
}

nsresult GetSubscriptionParams(nsIPushSubscription* aSubscription,
                               nsAString& aEndpoint,
                               nsTArray<uint8_t>& aRawP256dhKey,
                               nsTArray<uint8_t>& aAuthSecret,
                               nsTArray<uint8_t>& aAppServerKey) {
  if (!aSubscription) {
    return NS_OK;
  }

  nsresult rv = aSubscription->GetEndpoint(aEndpoint);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aSubscription->GetKey(u"p256dh"_ns, aRawP256dhKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  rv = aSubscription->GetKey(u"auth"_ns, aAuthSecret);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  rv = aSubscription->GetKey(u"appServer"_ns, aAppServerKey);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

class GetSubscriptionResultRunnable final : public WorkerThreadRunnable {
 public:
  GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
                                RefPtr<PromiseWorkerProxy>&& aProxy,
                                nsresult aStatus, const nsAString& aEndpoint,
                                const nsAString& aScope,
                                Nullable<EpochTimeStamp>&& aExpirationTime,
                                nsTArray<uint8_t>&& aRawP256dhKey,
                                nsTArray<uint8_t>&& aAuthSecret,
                                nsTArray<uint8_t>&& aAppServerKey)
      : WorkerThreadRunnable("GetSubscriptionResultRunnable"),
        mProxy(std::move(aProxy)),
        mStatus(aStatus),
        mEndpoint(aEndpoint),
        mScope(aScope),
        mExpirationTime(std::move(aExpirationTime)),
        mRawP256dhKey(std::move(aRawP256dhKey)),
        mAuthSecret(std::move(aAuthSecret)),
        mAppServerKey(std::move(aAppServerKey)) {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    RefPtr<Promise> promise = mProxy->GetWorkerPromise();
    // Once Worker had already started shutdown, workerPromise would be nullptr
    if (!promise) {
      return true;
    }
    if (NS_SUCCEEDED(mStatus)) {
      if (mEndpoint.IsEmpty()) {
        promise->MaybeResolve(JS::NullHandleValue);
      } else {
        RefPtr<PushSubscription> sub = new PushSubscription(
            nullptr, mEndpoint, mScope, std::move(mExpirationTime),
            std::move(mRawP256dhKey), std::move(mAuthSecret),
            std::move(mAppServerKey));
        promise->MaybeResolve(sub);
      }
    } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH) {
      promise->MaybeReject(mStatus);
    } else {
      promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
    }

    mProxy->CleanUp();

    return true;
  }

 private:
  ~GetSubscriptionResultRunnable() = default;

  RefPtr<PromiseWorkerProxy> mProxy;
  nsresult mStatus;
  nsString mEndpoint;
  nsString mScope;
  Nullable<EpochTimeStamp> mExpirationTime;
  nsTArray<uint8_t> mRawP256dhKey;
  nsTArray<uint8_t> mAuthSecret;
  nsTArray<uint8_t> mAppServerKey;
};

class GetSubscriptionCallback final : public nsIPushSubscriptionCallback {
 public:
  NS_DECL_ISUPPORTS

  explicit GetSubscriptionCallback(PromiseWorkerProxy* aProxy,
                                   const nsAString& aScope)
      : mProxy(aProxy), mScope(aScope) {}

  NS_IMETHOD
  OnPushSubscription(nsresult aStatus,
                     nsIPushSubscription* aSubscription) override {
    AssertIsOnMainThread();
    MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?");

    MutexAutoLock lock(mProxy->Lock());
    if (mProxy->CleanedUp()) {
      return NS_OK;
    }

    nsAutoString endpoint;
    nsTArray<uint8_t> rawP256dhKey, authSecret, appServerKey;
    if (NS_SUCCEEDED(aStatus)) {
      aStatus = GetSubscriptionParams(aSubscription, endpoint, rawP256dhKey,
                                      authSecret, appServerKey);
    }

    WorkerPrivate* worker = mProxy->GetWorkerPrivate();
    RefPtr<GetSubscriptionResultRunnable> r = new GetSubscriptionResultRunnable(
        worker, std::move(mProxy), aStatus, endpoint, mScope,
        std::move(mExpirationTime), std::move(rawP256dhKey),
        std::move(authSecret), std::move(appServerKey));
    if (!r->Dispatch(worker)) {
      return NS_ERROR_UNEXPECTED;
    }

    return NS_OK;
  }

  // Convenience method for use in this file.
  void OnPushSubscriptionError(nsresult aStatus) {
    Unused << NS_WARN_IF(NS_FAILED(OnPushSubscription(aStatus, nullptr)));
  }

 protected:
  ~GetSubscriptionCallback() = default;

 private:
  RefPtr<PromiseWorkerProxy> mProxy;
  nsString mScope;
  Nullable<EpochTimeStamp> mExpirationTime;
};

NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushSubscriptionCallback)

class GetSubscriptionRunnable final : public Runnable {
 public:
  GetSubscriptionRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope,
                          PushManager::SubscriptionAction aAction,
                          nsTArray<uint8_t>&& aAppServerKey)
      : Runnable("dom::GetSubscriptionRunnable"),
        mProxy(aProxy),
        mScope(aScope),
        mAction(aAction),
        mAppServerKey(std::move(aAppServerKey)) {}

  NS_IMETHOD
  Run() override {
    AssertIsOnMainThread();

    nsCOMPtr<nsIPrincipal> principal;

    {
      // Bug 1228723: If permission is revoked or an error occurs, the
      // subscription callback will be called synchronously. This causes
      // `GetSubscriptionCallback::OnPushSubscription` to deadlock when
      // it tries to acquire the lock.
      MutexAutoLock lock(mProxy->Lock());
      if (mProxy->CleanedUp()) {
        return NS_OK;
      }
      principal = mProxy->GetWorkerPrivate()->GetPrincipal();
    }

    MOZ_ASSERT(principal);

    RefPtr<GetSubscriptionCallback> callback =
        new GetSubscriptionCallback(mProxy, mScope);

    PermissionState state;
    nsresult rv = GetPermissionState(principal, state);
    if (NS_FAILED(rv)) {
      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
      return NS_OK;
    }

    if (state != PermissionState::Granted) {
      if (mAction == PushManager::GetSubscriptionAction) {
        callback->OnPushSubscriptionError(NS_OK);
        return NS_OK;
      }
      callback->OnPushSubscriptionError(NS_ERROR_DOM_PUSH_DENIED_ERR);
      return NS_OK;
    }

    nsCOMPtr<nsIPushService> service =
        do_GetService("@mozilla.org/push/Service;1");
    if (NS_WARN_IF(!service)) {
      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
      return NS_OK;
    }

    if (mAction == PushManager::SubscribeAction) {
      if (mAppServerKey.IsEmpty()) {
        rv = service->Subscribe(mScope, principal, callback);
      } else {
        rv = service->SubscribeWithKey(mScope, principal, mAppServerKey,
                                       callback);
      }
    } else {
      MOZ_ASSERT(mAction == PushManager::GetSubscriptionAction);
      rv = service->GetSubscription(mScope, principal, callback);
    }

    if (NS_WARN_IF(NS_FAILED(rv))) {
      callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
      return NS_OK;
    }

    return NS_OK;
  }

 private:
  ~GetSubscriptionRunnable() = default;

  RefPtr<PromiseWorkerProxy> mProxy;
  nsString mScope;
  PushManager::SubscriptionAction mAction;
  nsTArray<uint8_t> mAppServerKey;
};

class PermissionResultRunnable final : public WorkerThreadRunnable {
 public:
  PermissionResultRunnable(PromiseWorkerProxy* aProxy, nsresult aStatus,
                           PermissionState aState)
      : WorkerThreadRunnable("PermissionResultRunnable"),
        mProxy(aProxy),
        mStatus(aStatus),
        mState(aState) {
    AssertIsOnMainThread();
  }

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
    RefPtr<Promise> promise = mProxy->GetWorkerPromise();
    if (!promise) {
      return true;
    }
    if (NS_SUCCEEDED(mStatus)) {
      promise->MaybeResolve(mState);
    } else {
      promise->MaybeRejectWithUndefined();
    }

    mProxy->CleanUp();

    return true;
  }

 private:
  ~PermissionResultRunnable() = default;

  RefPtr<PromiseWorkerProxy> mProxy;
  nsresult mStatus;
  PermissionState mState;
};

class PermissionStateRunnable final : public Runnable {
 public:
  explicit PermissionStateRunnable(PromiseWorkerProxy* aProxy)
      : Runnable("dom::PermissionStateRunnable"), mProxy(aProxy) {}

  NS_IMETHOD
  Run() override {
    AssertIsOnMainThread();
    MutexAutoLock lock(mProxy->Lock());
    if (mProxy->CleanedUp()) {
      return NS_OK;
    }

    PermissionState state;
    nsresult rv =
        GetPermissionState(mProxy->GetWorkerPrivate()->GetPrincipal(), state);

    RefPtr<PermissionResultRunnable> r =
        new PermissionResultRunnable(mProxy, rv, state);

    // This can fail if the worker thread is already shutting down, but there's
    // nothing we can do in that case.
    Unused << NS_WARN_IF(!r->Dispatch(mProxy->GetWorkerPrivate()));

    return NS_OK;
  }

 private:
  ~PermissionStateRunnable() = default;

  RefPtr<PromiseWorkerProxy> mProxy;
};

}  // anonymous namespace

PushManager::PushManager(nsIGlobalObject* aGlobal, PushManagerImpl* aImpl)
    : mGlobal(aGlobal), mImpl(aImpl) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aImpl);
}

PushManager::PushManager(const nsAString& aScope) : mScope(aScope) {
#ifdef DEBUG
  // There's only one global on a worker, so we don't need to pass a global
  // object to the constructor.
  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(worker);
  worker->AssertIsOnWorkerThread();
#endif
}

PushManager::~PushManager() = default;

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushManager, mGlobal, mImpl)
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushManager)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushManager)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

JSObject* PushManager::WrapObject(JSContext* aCx,
                                  JS::Handle<JSObject*> aGivenProto) {
  return PushManager_Binding::Wrap(aCx, this, aGivenProto);
}

// static
already_AddRefed<PushManager> PushManager::Constructor(GlobalObject& aGlobal,
                                                       const nsAString& aScope,
                                                       ErrorResult& aRv) {
  if (!NS_IsMainThread()) {
    RefPtr<PushManager> ret = new PushManager(aScope);
    return ret.forget();
  }

  RefPtr<PushManagerImpl> impl =
      PushManagerImpl::Constructor(aGlobal, aGlobal.Context(), aScope, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  RefPtr<PushManager> ret = new PushManager(global, impl);

  return ret.forget();
}

bool PushManager::IsEnabled(JSContext* aCx, JSObject* aGlobal) {
  return StaticPrefs::dom_push_enabled() && ServiceWorkersEnabled(aCx, aGlobal);
}

// https://w3c.github.io/push-api/#dom-pushmanager-supportedcontentencodings
void PushManager::GetSupportedContentEncodings(
    GlobalObject& aGlobal, JS::MutableHandle<JSObject*> aEncodings,
    ErrorResult& aRv) {
  JSContext* cx = aGlobal.Context();

  nsTArray<nsString> encodings{u"aes128gcm"_ns};
  if (StaticPrefs::dom_push_indicate_aesgcm_support_enabled()) {
    // The spec does not define orders, but Chrome is returning ["aes128gcm",
    // "aesgcm"] and there are a bunch of code like below, which is copypasted
    // from Minishlink/web-push-php-example endorsed by
    // web-push-libs/web-push-php. Which means practically the preferred
    // algorithm should go to the first place.
    //
    // (PushManager.supportedContentEncodings || ['aesgcm'])[0];
    encodings.AppendElement(u"aesgcm"_ns);
  }

  JS::Rooted<JS::Value> encodingsValue(cx);
  if (!ToJSValue(cx, encodings, &encodingsValue)) {
    if (JS_IsThrowingOutOfMemory(cx)) {
      MOZ_CRASH("Out of memory");
    } else {
      aRv.NoteJSContextException(cx);
      return;
    }
  };
  JS::Rooted<JSObject*> object(cx, &encodingsValue.toObject());
  if (!JS_FreezeObject(cx, object)) {
    aRv.NoteJSContextException(cx);
    return;
  }
  aEncodings.set(object);
}

already_AddRefed<Promise> PushManager::Subscribe(
    const PushSubscriptionOptionsInit& aOptions, ErrorResult& aRv) {
  if (mImpl) {
    MOZ_ASSERT(NS_IsMainThread());
    return mImpl->Subscribe(aOptions, aRv);
  }

  return PerformSubscriptionActionFromWorker(SubscribeAction, aOptions, aRv);
}

already_AddRefed<Promise> PushManager::GetSubscription(ErrorResult& aRv) {
  if (mImpl) {
    MOZ_ASSERT(NS_IsMainThread());
    return mImpl->GetSubscription(aRv);
  }

  return PerformSubscriptionActionFromWorker(GetSubscriptionAction, aRv);
}

already_AddRefed<Promise> PushManager::PermissionState(
    const PushSubscriptionOptionsInit& aOptions, ErrorResult& aRv) {
  if (mImpl) {
    MOZ_ASSERT(NS_IsMainThread());
    return mImpl->PermissionState(aOptions, aRv);
  }

  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(worker);
  worker->AssertIsOnWorkerThread();

  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
  RefPtr<Promise> p = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
  if (!proxy) {
    p->MaybeRejectWithUndefined();
    return p.forget();
  }

  RefPtr<PermissionStateRunnable> r = new PermissionStateRunnable(proxy);
  NS_DispatchToMainThread(r);

  return p.forget();
}

already_AddRefed<Promise> PushManager::PerformSubscriptionActionFromWorker(
    SubscriptionAction aAction, ErrorResult& aRv) {
  RootedDictionary<PushSubscriptionOptionsInit> options(RootingCx());
  return PerformSubscriptionActionFromWorker(aAction, options, aRv);
}

already_AddRefed<Promise> PushManager::PerformSubscriptionActionFromWorker(
    SubscriptionAction aAction, const PushSubscriptionOptionsInit& aOptions,
    ErrorResult& aRv) {
  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  MOZ_ASSERT(worker);
  worker->AssertIsOnWorkerThread();

  nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
  RefPtr<Promise> p = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
  if (!proxy) {
    p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
    return p.forget();
  }

  nsTArray<uint8_t> appServerKey;
  if (!aOptions.mApplicationServerKey.IsNull()) {
    nsresult rv = NormalizeAppServerKey(aOptions.mApplicationServerKey.Value(),
                                        appServerKey);
    if (NS_FAILED(rv)) {
      p->MaybeReject(rv);
      return p.forget();
    }
  }

  RefPtr<GetSubscriptionRunnable> r = new GetSubscriptionRunnable(
      proxy, mScope, aAction, std::move(appServerKey));
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));

  return p.forget();
}

nsresult PushManager::NormalizeAppServerKey(
    const OwningArrayBufferViewOrArrayBufferOrString& aSource,
    nsTArray<uint8_t>& aAppServerKey) {
  if (aSource.IsString()) {
    NS_ConvertUTF16toUTF8 base64Key(aSource.GetAsString());
    FallibleTArray<uint8_t> decodedKey;
    nsresult rv = Base64URLDecode(
        base64Key, Base64URLDecodePaddingPolicy::Reject, decodedKey);
    if (NS_FAILED(rv)) {
      return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
    }
    aAppServerKey = decodedKey;
  } else {
    if (!AppendTypedArrayDataTo(aSource, aAppServerKey)) {
      return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
    }
  }
  if (aAppServerKey.IsEmpty()) {
    return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
  }
  return NS_OK;
}

}  // namespace mozilla::dom

100%


¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge