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/. */
7 #include "mozilla/dom/cache/CacheOpParent.h"
9 #include "mozilla/ErrorResult.h"
10 #include "mozilla/StaticPrefs_browser.h"
11 #include "mozilla/Unused.h"
12 #include "mozilla/dom/cache/AutoUtils.h"
13 #include "mozilla/dom/cache/ManagerId.h"
14 #include "mozilla/dom/cache/ReadStream.h"
15 #include "mozilla/dom/cache/SavedTypes.h"
16 #include "mozilla/ipc/InputStreamUtils.h"
17 #include "mozilla/ipc/IPCStreamUtils.h"
19 namespace mozilla::dom::cache
{
21 using mozilla::ipc::PBackgroundParent
;
23 CacheOpParent::CacheOpParent(PBackgroundParent
* aIpcManager
, CacheId aCacheId
,
24 const CacheOpArgs
& aOpArgs
)
25 : mIpcManager(aIpcManager
),
27 mNamespace(INVALID_NAMESPACE
),
29 MOZ_DIAGNOSTIC_ASSERT(mIpcManager
);
32 CacheOpParent::CacheOpParent(PBackgroundParent
* aIpcManager
,
33 Namespace aNamespace
, const CacheOpArgs
& aOpArgs
)
34 : mIpcManager(aIpcManager
),
35 mCacheId(INVALID_CACHE_ID
),
36 mNamespace(aNamespace
),
38 MOZ_DIAGNOSTIC_ASSERT(mIpcManager
);
41 CacheOpParent::~CacheOpParent() { NS_ASSERT_OWNINGTHREAD(CacheOpParent
); }
43 void CacheOpParent::Execute(const SafeRefPtr
<ManagerId
>& aManagerId
) {
44 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
45 MOZ_DIAGNOSTIC_ASSERT(!mManager
);
46 MOZ_DIAGNOSTIC_ASSERT(!mVerifier
);
48 auto managerOrErr
= cache::Manager::AcquireCreateIfNonExistent(aManagerId
);
49 if (NS_WARN_IF(managerOrErr
.isErr())) {
50 (void)Send__delete__(this, CopyableErrorResult(managerOrErr
.unwrapErr()),
55 Execute(managerOrErr
.unwrap());
58 void CacheOpParent::Execute(SafeRefPtr
<cache::Manager
> aManager
) {
59 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
60 MOZ_DIAGNOSTIC_ASSERT(!mManager
);
61 MOZ_DIAGNOSTIC_ASSERT(!mVerifier
);
63 mManager
= std::move(aManager
);
66 if (mOpArgs
.type() == CacheOpArgs::TCachePutAllArgs
) {
67 MOZ_DIAGNOSTIC_ASSERT(mCacheId
!= INVALID_CACHE_ID
);
69 const CachePutAllArgs
& args
= mOpArgs
.get_CachePutAllArgs();
70 const nsTArray
<CacheRequestResponse
>& list
= args
.requestResponseList();
72 AutoTArray
<nsCOMPtr
<nsIInputStream
>, 256> requestStreamList
;
73 AutoTArray
<nsCOMPtr
<nsIInputStream
>, 256> responseStreamList
;
75 for (uint32_t i
= 0; i
< list
.Length(); ++i
) {
76 requestStreamList
.AppendElement(
77 DeserializeCacheStream(list
[i
].request().body()));
78 responseStreamList
.AppendElement(
79 DeserializeCacheStream(list
[i
].response().body()));
82 mManager
->ExecutePutAll(this, mCacheId
, args
.requestResponseList(),
83 requestStreamList
, responseStreamList
);
87 // Handle all other cache ops
88 if (mCacheId
!= INVALID_CACHE_ID
) {
89 MOZ_DIAGNOSTIC_ASSERT(mNamespace
== INVALID_NAMESPACE
);
90 mManager
->ExecuteCacheOp(this, mCacheId
, mOpArgs
);
94 // Handle all storage ops
95 MOZ_DIAGNOSTIC_ASSERT(mNamespace
!= INVALID_NAMESPACE
);
96 mManager
->ExecuteStorageOp(this, mNamespace
, mOpArgs
);
99 void CacheOpParent::WaitForVerification(PrincipalVerifier
* aVerifier
) {
100 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
101 MOZ_DIAGNOSTIC_ASSERT(!mManager
);
102 MOZ_DIAGNOSTIC_ASSERT(!mVerifier
);
104 mVerifier
= aVerifier
;
105 mVerifier
->AddListener(*this);
108 void CacheOpParent::ActorDestroy(ActorDestroyReason aReason
) {
109 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
112 mVerifier
->RemoveListener(*this);
117 mManager
->RemoveListener(this);
121 mIpcManager
= nullptr;
124 void CacheOpParent::OnPrincipalVerified(
125 nsresult aRv
, const SafeRefPtr
<ManagerId
>& aManagerId
) {
126 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
128 mVerifier
->RemoveListener(*this);
131 if (NS_WARN_IF(NS_FAILED(aRv
))) {
132 (void)Send__delete__(this, CopyableErrorResult(aRv
), void_t());
139 void CacheOpParent::OnOpComplete(ErrorResult
&& aRv
,
140 const CacheOpResult
& aResult
,
141 CacheId aOpenedCacheId
,
142 const Maybe
<StreamInfo
>& aStreamInfo
) {
143 NS_ASSERT_OWNINGTHREAD(CacheOpParent
);
144 MOZ_DIAGNOSTIC_ASSERT(mIpcManager
);
145 MOZ_DIAGNOSTIC_ASSERT(mManager
);
147 // Never send an op-specific result if we have an error. Instead, send
148 // void_t() to ensure that we don't leak actors on the child side.
149 if (NS_WARN_IF(aRv
.Failed())) {
150 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv
)), void_t());
154 if (aStreamInfo
.isSome()) {
155 ProcessCrossOriginResourcePolicyHeader(aRv
,
156 aStreamInfo
->mSavedResponseList
);
157 if (NS_WARN_IF(aRv
.Failed())) {
158 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv
)), void_t());
163 uint32_t entryCount
=
164 std::max(1lu, aStreamInfo
? static_cast<unsigned long>(std::max(
165 aStreamInfo
->mSavedResponseList
.Length(),
166 aStreamInfo
->mSavedRequestList
.Length()))
169 // The result must contain the appropriate type at this point. It may
170 // or may not contain the additional result data yet. For types that
171 // do not need special processing, it should already be set. If the
172 // result requires actor-specific operations, then we do that below.
173 // If the type and data types don't match, then we will trigger an
174 // assertion in AutoParentOpResult::Add().
175 AutoParentOpResult
result(mIpcManager
, aResult
, entryCount
);
177 if (aOpenedCacheId
!= INVALID_CACHE_ID
) {
178 result
.Add(aOpenedCacheId
, mManager
.clonePtr());
182 const auto& streamInfo
= *aStreamInfo
;
184 for (const auto& savedResponse
: streamInfo
.mSavedResponseList
) {
185 result
.Add(savedResponse
, streamInfo
.mStreamList
);
188 for (const auto& savedRequest
: streamInfo
.mSavedRequestList
) {
189 result
.Add(savedRequest
, streamInfo
.mStreamList
);
193 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv
)),
194 result
.SendAsOpResult());
197 already_AddRefed
<nsIInputStream
> CacheOpParent::DeserializeCacheStream(
198 const Maybe
<CacheReadStream
>& aMaybeStream
) {
199 if (aMaybeStream
.isNothing()) {
203 nsCOMPtr
<nsIInputStream
> stream
;
204 const CacheReadStream
& readStream
= aMaybeStream
.ref();
206 // Option 1: One of our own ReadStreams was passed back to us with a stream
208 stream
= ReadStream::Create(readStream
);
210 return stream
.forget();
213 // Option 2: A stream was serialized using normal methods or passed
214 // as a DataPipe. Use the standard method for extracting the
216 return DeserializeIPCStream(readStream
.stream());
219 void CacheOpParent::ProcessCrossOriginResourcePolicyHeader(
220 ErrorResult
& aRv
, const nsTArray
<SavedResponse
>& aResponses
) {
221 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
224 // Only checking for match/matchAll.
225 nsILoadInfo::CrossOriginEmbedderPolicy loadingCOEP
=
226 nsILoadInfo::EMBEDDER_POLICY_NULL
;
227 Maybe
<mozilla::ipc::PrincipalInfo
> principalInfo
;
228 switch (mOpArgs
.type()) {
229 case CacheOpArgs::TCacheMatchArgs
: {
230 const auto& request
= mOpArgs
.get_CacheMatchArgs().request();
231 loadingCOEP
= request
.loadingEmbedderPolicy();
232 principalInfo
= request
.principalInfo();
235 case CacheOpArgs::TCacheMatchAllArgs
: {
236 if (mOpArgs
.get_CacheMatchAllArgs().maybeRequest().isSome()) {
237 const auto& request
=
238 mOpArgs
.get_CacheMatchAllArgs().maybeRequest().ref();
239 loadingCOEP
= request
.loadingEmbedderPolicy();
240 principalInfo
= request
.principalInfo();
249 // skip checking if the request has no principal for same-origin/same-site
251 if (principalInfo
.isNothing() ||
252 principalInfo
.ref().type() !=
253 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo
) {
256 const mozilla::ipc::ContentPrincipalInfo
& contentPrincipalInfo
=
257 principalInfo
.ref().get_ContentPrincipalInfo();
259 for (auto it
= aResponses
.cbegin(); it
!= aResponses
.cend(); ++it
) {
260 if (it
->mValue
.type() != ResponseType::Opaque
&&
261 it
->mValue
.type() != ResponseType::Opaqueredirect
) {
265 const auto& headers
= it
->mValue
.headers();
266 const RequestCredentials credentials
= it
->mValue
.credentials();
267 const auto corpHeaderIt
=
268 std::find_if(headers
.cbegin(), headers
.cend(), [](const auto& header
) {
269 return header
.name().EqualsLiteral("Cross-Origin-Resource-Policy");
272 // According to https://github.com/w3c/ServiceWorker/issues/1490, the cache
273 // response is expected with CORP header, otherwise, throw the type error.
274 // Note that this is different with the CORP checking for fetch metioned in
275 // https://wicg.github.io/cross-origin-embedder-policy/#corp-check.
276 // For fetch, if the response has no CORP header, "same-origin" checking
277 // will be performed.
278 if (corpHeaderIt
== headers
.cend() &&
279 loadingCOEP
== nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP
) {
280 aRv
.ThrowTypeError("Response is expected with CORP header.");
284 // Skip the case if the response has no principal for same-origin/same-site
286 if (it
->mValue
.principalInfo().isNothing() ||
287 it
->mValue
.principalInfo().ref().type() !=
288 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo
) {
292 const mozilla::ipc::ContentPrincipalInfo
& responseContentPrincipalInfo
=
293 it
->mValue
.principalInfo().ref().get_ContentPrincipalInfo();
296 corpHeaderIt
== headers
.cend() ? EmptyCString() : corpHeaderIt
->value();
298 if (corp
.IsEmpty()) {
299 if (loadingCOEP
== nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS
) {
300 // This means the request of this request doesn't have
301 // credentials, so it's safe for us to return.
302 if (credentials
== RequestCredentials::Omit
) {
305 corp
= "same-origin";
309 if (corp
.EqualsLiteral("same-origin")) {
310 if (responseContentPrincipalInfo
!= contentPrincipalInfo
) {
311 aRv
.ThrowTypeError("Response is expected from same origin.");
314 } else if (corp
.EqualsLiteral("same-site")) {
315 if (!responseContentPrincipalInfo
.baseDomain().Equals(
316 contentPrincipalInfo
.baseDomain())) {
317 aRv
.ThrowTypeError("Response is expected from same site.");
324 } // namespace mozilla::dom::cache