/* -*- 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 "CookieStoreManager.h" #include "CookieStoreChild.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/PBackgroundChild.h" #include "nsGlobalWindowInner.h" #include "nsIGlobalObject.h" #include "nsIPrincipal.h" using mozilla::ipc::PrincipalInfo; namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CookieStoreManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CookieStoreManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObject) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER tmp->Shutdown(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CookieStoreManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObject) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END CookieStoreManager::CookieStoreManager( nsIGlobalObject* aGlobalObject, const nsACString& aServiceWorkerRegistrationScopeURL) : mGlobalObject(aGlobalObject), mScopeURL(aServiceWorkerRegistrationScopeURL) { MOZ_ASSERT(aGlobalObject); } namespace { nsIPrincipal* RetrievePrincipal(nsIGlobalObject* aGlobalObject) { if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobalObject); if (NS_WARN_IF(!window)) { return nullptr; } return nsGlobalWindowInner::Cast(window)->GetClientPrincipal(); } WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); return worker->GetPrincipal(); } } // namespace CookieStoreManager::~CookieStoreManager() { Shutdown(); } JSObject* CookieStoreManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return CookieStoreManager_Binding::Wrap(aCx, this, aGivenProto); } already_AddRefed CookieStoreManager::Subscribe( const CopyableTArray& aSubscriptions, ErrorResult& aRv) { return SubscribeOrUnsubscribe(Action::eSubscribe, aSubscriptions, aRv); } already_AddRefed CookieStoreManager::GetSubscriptions( ErrorResult& aRv) { nsCOMPtr principal = RetrievePrincipal(mGlobalObject); if (NS_WARN_IF(!principal)) { aRv.ThrowInvalidStateError("Invalid context"); return nullptr; } RefPtr promise = Promise::Create(mGlobalObject, aRv); if (NS_WARN_IF(!promise)) { return nullptr; } // ServiceWorkers only have one principal: it's either partitioned or // unpartitioned. If the "context" is partitioned, the window or the // WorkerPrivate will return a partitioned principal. // Let's dispatch a runnable to implement the "Run the following steps in // parallel". NS_DispatchToCurrentThread(NS_NewRunnableFunction( __func__, [self = RefPtr(this), principal = RefPtr(principal.get()), promise = RefPtr(promise)]() { if (!self->MaybeCreateActor()) { promise->MaybeRejectWithNotAllowedError("Permission denied"); return; } PrincipalInfo principalInfo; nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeResolve(nsTArray()); return; } RefPtr ipcPromise = self->mActor->SendGetSubscriptionsRequest(principalInfo, self->mScopeURL); if (NS_WARN_IF(!ipcPromise)) { promise->MaybeResolve(nsTArray()); return; } ipcPromise->Then( NS_GetCurrentThread(), __func__, [promise = RefPtr(promise)]( const CookieStoreChild::GetSubscriptionsRequestPromise:: ResolveOrRejectValue& aResult) { if (aResult.IsReject()) { promise->MaybeResolve(nsTArray()); return; } nsTArray results; for (const auto& subscription : aResult.ResolveValue()) { CookieStoreGetOptions* result = results.AppendElement(); if (subscription.name().isSome()) { result->mName.Construct(); result->mName.Value() = subscription.name().value(); } result->mUrl.Construct(); result->mUrl.Value() = subscription.url(); } promise->MaybeResolve(results); }); })); return promise.forget(); } already_AddRefed CookieStoreManager::Unsubscribe( const CopyableTArray& aSubscriptions, ErrorResult& aRv) { return SubscribeOrUnsubscribe(Action::eUnsubscribe, aSubscriptions, aRv); } already_AddRefed CookieStoreManager::SubscribeOrUnsubscribe( Action aAction, const CopyableTArray& aSubscriptions, ErrorResult& aRv) { nsCOMPtr principal = RetrievePrincipal(mGlobalObject); if (NS_WARN_IF(!principal)) { aRv.ThrowInvalidStateError("Invalid context"); return nullptr; } RefPtr promise = Promise::Create(mGlobalObject, aRv); if (NS_WARN_IF(!promise)) { return nullptr; } // ServiceWorkers only have one principal: it's either partitioned or // unpartitioned. If the "context" is partitioned, the window or the // WorkerPrivate will return a partitioned principal. NS_DispatchToCurrentThread(NS_NewRunnableFunction( __func__, [self = RefPtr(this), promise = RefPtr(promise), principal = RefPtr(principal.get()), aSubscriptions, aAction]() { nsCOMPtr baseURI; nsresult rv = NS_NewURI(getter_AddRefs(baseURI), self->mScopeURL, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeRejectWithTypeError(self->mScopeURL); return; } if (NS_WARN_IF(!baseURI)) { promise->MaybeRejectWithSecurityError( "Couldn't acquire the base URI of this context"); return; } nsTArray subscriptions; for (const CookieStoreGetOptions& subscription : aSubscriptions) { nsString subscriptionURL; if (subscription.mUrl.WasPassed()) { subscriptionURL.Assign(subscription.mUrl.Value()); } nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), subscriptionURL, nullptr, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeRejectWithTypeError( NS_ConvertUTF16toUTF8(subscriptionURL)); return; } nsAutoCString subscriptionURI; rv = uri->GetSpec(subscriptionURI); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeRejectWithTypeError( NS_ConvertUTF16toUTF8(subscriptionURL)); return; } if (!StringBeginsWith(subscriptionURI, self->mScopeURL)) { promise->MaybeRejectWithTypeError( NS_ConvertUTF16toUTF8(subscriptionURL)); return; } subscriptions.AppendElement(CookieSubscription{ subscription.mName.WasPassed() ? Some(subscription.mName.Value()) : Nothing(), NS_ConvertUTF8toUTF16(subscriptionURI)}); } if (!self->MaybeCreateActor()) { promise->MaybeRejectWithNotAllowedError("Permission denied"); return; } PrincipalInfo principalInfo; rv = PrincipalToPrincipalInfo(principal, &principalInfo); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeResolveWithUndefined(); return; } RefPtr ipcPromise = self->mActor->SendSubscribeOrUnsubscribeRequest( principalInfo, self->mScopeURL, subscriptions, (aAction == Action::eSubscribe)); if (NS_WARN_IF(!ipcPromise)) { promise->MaybeResolveWithUndefined(); return; } ipcPromise->Then( NS_GetCurrentThread(), __func__, [promise = RefPtr(promise)]( const CookieStoreChild::SubscribeOrUnsubscribeRequestPromise:: ResolveOrRejectValue& aResult) { // TODO We don't really want to expose internal errors. promise->MaybeResolveWithUndefined(); }); })); return promise.forget(); } bool CookieStoreManager::MaybeCreateActor() { if (mActor) { return mActor->CanSend(); } mozilla::ipc::PBackgroundChild* actorChild = mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); if (NS_WARN_IF(!actorChild)) { // The process is probably shutting down. Let's return a 'generic' error. return false; } PCookieStoreChild* actor = actorChild->SendPCookieStoreConstructor(); if (!actor) { return false; } mActor = static_cast(actor); return true; } void CookieStoreManager::Shutdown() { if (mActor) { mActor->Close(); mActor = nullptr; } } } // namespace mozilla::dom