/* -*- 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/cache/CacheOpParent.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/Unused.h" #include "mozilla/dom/cache/AutoUtils.h" #include "mozilla/dom/cache/ManagerId.h" #include "mozilla/dom/cache/ReadStream.h" #include "mozilla/dom/cache/SavedTypes.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/ipc/IPCStreamUtils.h" namespace mozilla::dom::cache { using mozilla::ipc::PBackgroundParent; CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager, CacheId aCacheId, const CacheOpArgs& aOpArgs) : mIpcManager(aIpcManager), mCacheId(aCacheId), mNamespace(INVALID_NAMESPACE), mOpArgs(aOpArgs) { MOZ_DIAGNOSTIC_ASSERT(mIpcManager); } CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager, Namespace aNamespace, const CacheOpArgs& aOpArgs) : mIpcManager(aIpcManager), mCacheId(INVALID_CACHE_ID), mNamespace(aNamespace), mOpArgs(aOpArgs) { MOZ_DIAGNOSTIC_ASSERT(mIpcManager); } CacheOpParent::~CacheOpParent() { NS_ASSERT_OWNINGTHREAD(CacheOpParent); } void CacheOpParent::Execute(const SafeRefPtr& aManagerId) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); MOZ_DIAGNOSTIC_ASSERT(!mManager); MOZ_DIAGNOSTIC_ASSERT(!mVerifier); auto managerOrErr = cache::Manager::AcquireCreateIfNonExistent(aManagerId); if (NS_WARN_IF(managerOrErr.isErr())) { ErrorResult result(managerOrErr.unwrapErr()); Unused << Send__delete__(this, std::move(result), void_t()); return; } Execute(managerOrErr.unwrap()); } void CacheOpParent::Execute(SafeRefPtr aManager) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); MOZ_DIAGNOSTIC_ASSERT(!mManager); MOZ_DIAGNOSTIC_ASSERT(!mVerifier); mManager = std::move(aManager); // Handle put op if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) { MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID); const CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); const nsTArray& list = args.requestResponseList(); AutoTArray, 256> requestStreamList; AutoTArray, 256> responseStreamList; for (uint32_t i = 0; i < list.Length(); ++i) { requestStreamList.AppendElement( DeserializeCacheStream(list[i].request().body())); responseStreamList.AppendElement( DeserializeCacheStream(list[i].response().body())); } mManager->ExecutePutAll(this, mCacheId, args.requestResponseList(), requestStreamList, responseStreamList); return; } // Handle all other cache ops if (mCacheId != INVALID_CACHE_ID) { MOZ_DIAGNOSTIC_ASSERT(mNamespace == INVALID_NAMESPACE); mManager->ExecuteCacheOp(this, mCacheId, mOpArgs); return; } // Handle all storage ops MOZ_DIAGNOSTIC_ASSERT(mNamespace != INVALID_NAMESPACE); mManager->ExecuteStorageOp(this, mNamespace, mOpArgs); } void CacheOpParent::WaitForVerification(PrincipalVerifier* aVerifier) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); MOZ_DIAGNOSTIC_ASSERT(!mManager); MOZ_DIAGNOSTIC_ASSERT(!mVerifier); mVerifier = aVerifier; mVerifier->AddListener(*this); } void CacheOpParent::ActorDestroy(ActorDestroyReason aReason) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); if (mVerifier) { mVerifier->RemoveListener(*this); mVerifier = nullptr; } if (mManager) { mManager->RemoveListener(this); mManager = nullptr; } mIpcManager = nullptr; } void CacheOpParent::OnPrincipalVerified( nsresult aRv, const SafeRefPtr& aManagerId) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); mVerifier->RemoveListener(*this); mVerifier = nullptr; if (NS_WARN_IF(NS_FAILED(aRv))) { ErrorResult result(aRv); Unused << Send__delete__(this, std::move(result), void_t()); return; } Execute(aManagerId); } void CacheOpParent::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, CacheId aOpenedCacheId, const Maybe& aStreamInfo) { NS_ASSERT_OWNINGTHREAD(CacheOpParent); MOZ_DIAGNOSTIC_ASSERT(mIpcManager); MOZ_DIAGNOSTIC_ASSERT(mManager); // Never send an op-specific result if we have an error. Instead, send // void_t() to ensure that we don't leak actors on the child side. if (NS_WARN_IF(aRv.Failed())) { Unused << Send__delete__(this, std::move(aRv), void_t()); return; } if (aStreamInfo.isSome()) { ProcessCrossOriginResourcePolicyHeader(aRv, aStreamInfo->mSavedResponseList); if (NS_WARN_IF(aRv.Failed())) { Unused << Send__delete__(this, std::move(aRv), void_t()); return; } } uint32_t entryCount = std::max(1lu, aStreamInfo ? static_cast(std::max( aStreamInfo->mSavedResponseList.Length(), aStreamInfo->mSavedRequestList.Length())) : 0lu); // The result must contain the appropriate type at this point. It may // or may not contain the additional result data yet. For types that // do not need special processing, it should already be set. If the // result requires actor-specific operations, then we do that below. // If the type and data types don't match, then we will trigger an // assertion in AutoParentOpResult::Add(). AutoParentOpResult result(mIpcManager, aResult, entryCount); if (aOpenedCacheId != INVALID_CACHE_ID) { result.Add(aOpenedCacheId, mManager.clonePtr()); } if (aStreamInfo) { const auto& streamInfo = *aStreamInfo; for (const auto& savedResponse : streamInfo.mSavedResponseList) { result.Add(savedResponse, streamInfo.mStreamList); } for (const auto& savedRequest : streamInfo.mSavedRequestList) { result.Add(savedRequest, streamInfo.mStreamList); } } Unused << Send__delete__(this, std::move(aRv), result.SendAsOpResult()); } already_AddRefed CacheOpParent::DeserializeCacheStream( const Maybe& aMaybeStream) { if (aMaybeStream.isNothing()) { return nullptr; } nsCOMPtr stream; const CacheReadStream& readStream = aMaybeStream.ref(); // Option 1: One of our own ReadStreams was passed back to us with a stream // control actor. stream = ReadStream::Create(readStream); if (stream) { return stream.forget(); } // Option 2: A stream was serialized using normal methods or passed // as a DataPipe. Use the standard method for extracting the // resulting stream. return DeserializeIPCStream(readStream.stream()); } void CacheOpParent::ProcessCrossOriginResourcePolicyHeader( ErrorResult& aRv, const nsTArray& aResponses) { if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { return; } // Only checking for match/matchAll. nsILoadInfo::CrossOriginEmbedderPolicy loadingCOEP = nsILoadInfo::EMBEDDER_POLICY_NULL; Maybe principalInfo; switch (mOpArgs.type()) { case CacheOpArgs::TCacheMatchArgs: { const auto& request = mOpArgs.get_CacheMatchArgs().request(); loadingCOEP = request.loadingEmbedderPolicy(); principalInfo = request.principalInfo(); break; } case CacheOpArgs::TCacheMatchAllArgs: { if (mOpArgs.get_CacheMatchAllArgs().maybeRequest().isSome()) { const auto& request = mOpArgs.get_CacheMatchAllArgs().maybeRequest().ref(); loadingCOEP = request.loadingEmbedderPolicy(); principalInfo = request.principalInfo(); } break; } default: { return; } } // skip checking if the request has no principal for same-origin/same-site // checking. if (principalInfo.isNothing() || principalInfo.ref().type() != mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) { return; } const mozilla::ipc::ContentPrincipalInfo& contentPrincipalInfo = principalInfo.ref().get_ContentPrincipalInfo(); for (auto it = aResponses.cbegin(); it != aResponses.cend(); ++it) { if (it->mValue.type() != ResponseType::Opaque && it->mValue.type() != ResponseType::Opaqueredirect) { continue; } const auto& headers = it->mValue.headers(); const RequestCredentials credentials = it->mValue.credentials(); const auto corpHeaderIt = std::find_if(headers.cbegin(), headers.cend(), [](const auto& header) { return header.name().EqualsLiteral("Cross-Origin-Resource-Policy"); }); // According to https://github.com/w3c/ServiceWorker/issues/1490, the cache // response is expected with CORP header, otherwise, throw the type error. // Note that this is different with the CORP checking for fetch metioned in // https://wicg.github.io/cross-origin-embedder-policy/#corp-check. // For fetch, if the response has no CORP header, "same-origin" checking // will be performed. if (corpHeaderIt == headers.cend() && loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) { aRv.ThrowTypeError("Response is expected with CORP header."); return; } // Skip the case if the response has no principal for same-origin/same-site // checking. if (it->mValue.principalInfo().isNothing() || it->mValue.principalInfo().ref().type() != mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) { continue; } const mozilla::ipc::ContentPrincipalInfo& responseContentPrincipalInfo = it->mValue.principalInfo().ref().get_ContentPrincipalInfo(); nsCString corp = corpHeaderIt == headers.cend() ? EmptyCString() : corpHeaderIt->value(); if (corp.IsEmpty()) { if (loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) { // This means the request of this request doesn't have // credentials, so it's safe for us to return. if (credentials == RequestCredentials::Omit) { return; } corp = "same-origin"; } } if (corp.EqualsLiteral("same-origin")) { if (responseContentPrincipalInfo != contentPrincipalInfo) { aRv.ThrowTypeError("Response is expected from same origin."); return; } } else if (corp.EqualsLiteral("same-site")) { if (!responseContentPrincipalInfo.baseDomain().Equals( contentPrincipalInfo.baseDomain())) { aRv.ThrowTypeError("Response is expected from same site."); return; } } } } } // namespace mozilla::dom::cache