/* 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 "BoundStorageKey.h" #include "BoundStorageKeyCache.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/cache/AutoUtils.h" #include "mozilla/dom/cache/Cache.h" #include "mozilla/dom/cache/CacheStorageChild.h" #include "mozilla/dom/cache/CacheWorkerRef.h" #include "mozilla/dom/quota/PrincipalUtils.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" namespace mozilla::dom::cache { using mozilla::ipc::BackgroundChild; using mozilla::ipc::PrincipalInfo; NS_IMPL_ISUPPORTS(BoundStorageKey, nsISupports) template struct BoundStorageKeyCacheStorage::Entry final { RefPtr mPromise; CacheOpArgs mArgs; }; nsresult BoundStorageKey::Init(Namespace aNamespace, const PrincipalInfo& aPrincipalInfo, nsISerialEventTarget* aTarget) { MOZ_DIAGNOSTIC_ASSERT(aTarget); // setup child and parent actors and retarget to aTarget auto* actor = new BoundStorageKeyChild(this); MOZ_ASSERT(actor); { ::mozilla::ipc::Endpoint childEP; ::mozilla::ipc::Endpoint parentEP; auto rv = PBoundStorageKey::CreateEndpoints(&parentEP, &childEP); (void)NS_WARN_IF(NS_FAILED(rv)); PBackgroundChild* bgActor = BackgroundChild::GetOrCreateForCurrentThread(); if (NS_WARN_IF(!bgActor)) { NS_WARNING("BoundStorageKey failed to obtain bgActor"); return NS_ERROR_UNEXPECTED; } bgActor->SendCreateBoundStorageKeyParent(std::move(parentEP), aNamespace, aPrincipalInfo); if (NS_WARN_IF(!(childEP.Bind(actor, aTarget)))) { NS_WARNING("BoundStorageKeyChildActor failed to bind to target."); return NS_ERROR_UNEXPECTED; } } mActor = actor; return NS_OK; } void BoundStorageKey::OnActorDestroy(BoundStorageKeyChild* aActor) { MOZ_DIAGNOSTIC_ASSERT(mActor); MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); MOZ_DIAGNOSTIC_ASSERT(!NS_FAILED(mStatus)); // Note that we will never get an actor again in case another request is // made before this object is destructed. mActor->ClearListener(); mActor = nullptr; mStatus = NS_ERROR_UNEXPECTED; } BoundStorageKey::~BoundStorageKey() { if (mActor) { mActor->StartDestroyFromListener(); } MOZ_DIAGNOSTIC_ASSERT(!mActor); } // static already_AddRefed BoundStorageKeyCacheStorage::Create(Namespace aNamespace, nsIGlobalObject* aGlobal, WorkerPrivate* aWorkerPrivate, nsISerialEventTarget* aActorTarget, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); if (aWorkerPrivate->GetOriginAttributes().IsPrivateBrowsing() && !StaticPrefs::dom_cache_privateBrowsing_enabled()) { NS_WARNING("BoundStorageKey not supported during private browsing."); aRv = NS_ERROR_DOM_SECURITY_ERR; return nullptr; } const PrincipalInfo& principalInfo = aWorkerPrivate->GetEffectiveStoragePrincipalInfo(); QM_TRY(OkIf(quota::IsPrincipalInfoValid(principalInfo)), nullptr, [&aRv](const auto) { aRv = NS_ERROR_FAILURE; }); // We have a number of cases where we want to skip the https scheme // validation: // // 1) Any worker when dom.caches.testing.enabled pref is true. // 2) Any worker when dom.serviceWorkers.testing.enabled pref is true. This // is mainly because most sites using SWs will expect Cache to work if // SWs are enabled. // 3) If the window that created this worker has the devtools SW testing // option enabled. Same reasoning as (2). // 4) If the worker itself is a ServiceWorker, then we always skip the // origin checks. The ServiceWorker has its own trusted origin checks // that are better than ours. In addition, we don't have information // about the window any more, so we can't do our own checks. bool testingEnabled = StaticPrefs::dom_caches_testing_enabled() || StaticPrefs::dom_serviceWorkers_testing_enabled() || aWorkerPrivate->ServiceWorkersTestingInWindow() || aWorkerPrivate->IsServiceWorker(); if (!IsTrusted(principalInfo, testingEnabled)) { NS_WARNING("BoundStorageKey not supported on untrusted origins."); aRv = NS_ERROR_UNEXPECTED; return nullptr; } RefPtr ref = new BoundStorageKeyCacheStorage(aNamespace, aGlobal, principalInfo); auto rv = ref->Init(aWorkerPrivate, aNamespace, principalInfo, aActorTarget); if (NS_WARN_IF(NS_FAILED(rv))) { aRv = rv; return nullptr; } MOZ_ASSERT(ref->mActor); return ref.forget(); } nsresult BoundStorageKeyCacheStorage::Init(WorkerPrivate* aWorkerPrivate, Namespace aNamespace, const PrincipalInfo& aPrincipalInfo, nsISerialEventTarget* aTarget) { auto rc = BoundStorageKey::Init(aNamespace, aPrincipalInfo, aTarget); if (NS_FAILED(rc)) { return rc; } mCacheStorageChild = CreateCacheStorageChild(aWorkerPrivate); MOZ_ASSERT(mCacheStorageChild); return mCacheStorageChild ? NS_OK : NS_ERROR_FAILURE; } BoundStorageKeyCacheStorage::BoundStorageKeyCacheStorage( Namespace aNamespace, nsIGlobalObject* aGlobal, const mozilla::ipc::PrincipalInfo& aPrincipalInfo) : mGlobal(aGlobal), mPrincipalInfo(MakeUnique(aPrincipalInfo)), mNamespace(aNamespace) {} void BoundStorageKeyCacheStorage::OnActorDestroy(CacheStorageChild* aActor) { AssertOwningThread(); MOZ_DIAGNOSTIC_ASSERT(mActor); MOZ_DIAGNOSTIC_ASSERT(mCacheStorageChild.get() == aActor); mCacheStorageChild->ClearListener(); mCacheStorageChild = nullptr; } BoundStorageKeyCacheStorage::~BoundStorageKeyCacheStorage() { AssertOwningThread(); if (mCacheStorageChild) { mCacheStorageChild->StartDestroyFromListener(); } } already_AddRefed BoundStorageKeyCacheStorage::CreateCacheStorageChild( WorkerPrivate* aWorkerPrivate) { SafeRefPtr workerRef; if (aWorkerPrivate) { aWorkerPrivate->AssertIsOnWorkerThread(); workerRef = CacheWorkerRef::Create(aWorkerPrivate, CacheWorkerRef::eIPCWorkerRef); if (NS_WARN_IF(!workerRef)) { return nullptr; } } RefPtr newActor = new CacheStorageChild(this, std::move(workerRef), mActor.get()); auto* constructedActor = mActor->SendPCacheStorageConstructor( newActor, mNamespace, *mPrincipalInfo); if (NS_WARN_IF(!constructedActor)) { mStatus = NS_ERROR_UNEXPECTED; return nullptr; } MOZ_DIAGNOSTIC_ASSERT(newActor == constructedActor); return newActor.forget(); } template void BoundStorageKeyCacheStorage::RunRequest(EntryType&& aEntry) { MOZ_ASSERT(mActor); MOZ_ASSERT(mCacheStorageChild); AutoChildOpArgs args(this, aEntry.mArgs, 1); RefPtr p{aEntry.mPromise.forget()}; mCacheStorageChild->ExecuteOp(mGlobal, p, this, args.SendAsOpArgs()); } auto BoundStorageKeyCacheStorage::Open(const nsAString& aKey, ErrorResult& aRv) -> already_AddRefed { AssertOwningThread(); if (NS_WARN_IF(NS_FAILED(mStatus))) { aRv = mStatus; return nullptr; } RefPtr promise{ new OpenResultPromise::Private(__func__)}; RunRequest(Entry{ promise, StorageOpenArgs{nsString(aKey)}}); return promise.forget(); } auto BoundStorageKeyCacheStorage::Has(const nsAString& aKey, ErrorResult& aRv) -> already_AddRefed { AssertOwningThread(); if (NS_WARN_IF(NS_FAILED(mStatus))) { aRv = mStatus; return nullptr; } RefPtr promise{ new HasResultPromise::Private(__func__)}; RunRequest(Entry{promise, StorageHasArgs(nsString(aKey))}); return promise.forget(); } auto BoundStorageKeyCacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv) -> already_AddRefed { AssertOwningThread(); if (NS_WARN_IF(NS_FAILED(mStatus))) { aRv = mStatus; return nullptr; } RefPtr promise{ new DeleteResultPromise::Private(__func__)}; RunRequest(Entry{ promise, StorageDeleteArgs{nsString(aKey)}}); return promise.forget(); } auto BoundStorageKeyCacheStorage::Keys(ErrorResult& aRv) -> already_AddRefed { AssertOwningThread(); if (NS_WARN_IF(NS_FAILED(mStatus))) { aRv = mStatus; return nullptr; } RefPtr promise{ new KeysResultPromise::Private(__func__)}; RunRequest(Entry{promise, StorageKeysArgs()}); return promise.forget(); } } // namespace mozilla::dom::cache