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/AutoUtils.h"
9 #include "mozilla/Unused.h"
10 #include "mozilla/dom/InternalHeaders.h"
11 #include "mozilla/dom/InternalRequest.h"
12 #include "mozilla/dom/cache/CacheParent.h"
13 #include "mozilla/dom/cache/CacheStreamControlParent.h"
14 #include "mozilla/dom/cache/ReadStream.h"
15 #include "mozilla/dom/cache/SavedTypes.h"
16 #include "mozilla/dom/cache/StreamList.h"
17 #include "mozilla/dom/cache/TypeUtils.h"
18 #include "mozilla/ipc/IPCStreamUtils.h"
19 #include "mozilla/ipc/PBackgroundParent.h"
20 #include "nsCharSeparatedTokenizer.h"
24 using mozilla::Unused
;
25 using mozilla::dom::cache::CacheReadStream
;
26 using mozilla::ipc::PBackgroundParent
;
30 enum CleanupAction
{ Forget
, Delete
};
32 void CleanupChild(CacheReadStream
& aReadStream
, CleanupAction aAction
) {
33 // fds cleaned up by mStreamCleanupList
36 void CleanupChild(Maybe
<CacheReadStream
>& aMaybeReadStream
,
37 CleanupAction aAction
) {
38 if (aMaybeReadStream
.isNothing()) {
42 CleanupChild(aMaybeReadStream
.ref(), aAction
);
47 namespace mozilla::dom::cache
{
49 // --------------------------------------------
51 AutoChildOpArgs::AutoChildOpArgs(TypeUtils
* aTypeUtils
,
52 const CacheOpArgs
& aOpArgs
,
54 : mTypeUtils(aTypeUtils
), mOpArgs(aOpArgs
), mSent(false) {
55 MOZ_DIAGNOSTIC_ASSERT(mTypeUtils
);
56 MOZ_RELEASE_ASSERT(aEntryCount
!= 0);
57 if (mOpArgs
.type() == CacheOpArgs::TCachePutAllArgs
) {
58 CachePutAllArgs
& args
= mOpArgs
.get_CachePutAllArgs();
59 args
.requestResponseList().SetCapacity(aEntryCount
);
61 MOZ_DIAGNOSTIC_ASSERT(aEntryCount
== 1);
65 AutoChildOpArgs::~AutoChildOpArgs() {
66 CleanupAction action
= mSent
? Forget
: Delete
;
68 switch (mOpArgs
.type()) {
69 case CacheOpArgs::TCacheMatchArgs
: {
70 CacheMatchArgs
& args
= mOpArgs
.get_CacheMatchArgs();
71 CleanupChild(args
.request().body(), action
);
74 case CacheOpArgs::TCacheMatchAllArgs
: {
75 CacheMatchAllArgs
& args
= mOpArgs
.get_CacheMatchAllArgs();
76 if (args
.maybeRequest().isNothing()) {
79 CleanupChild(args
.maybeRequest().ref().body(), action
);
82 case CacheOpArgs::TCachePutAllArgs
: {
83 CachePutAllArgs
& args
= mOpArgs
.get_CachePutAllArgs();
84 auto& list
= args
.requestResponseList();
85 for (uint32_t i
= 0; i
< list
.Length(); ++i
) {
86 CleanupChild(list
[i
].request().body(), action
);
87 CleanupChild(list
[i
].response().body(), action
);
91 case CacheOpArgs::TCacheDeleteArgs
: {
92 CacheDeleteArgs
& args
= mOpArgs
.get_CacheDeleteArgs();
93 CleanupChild(args
.request().body(), action
);
96 case CacheOpArgs::TCacheKeysArgs
: {
97 CacheKeysArgs
& args
= mOpArgs
.get_CacheKeysArgs();
98 if (args
.maybeRequest().isNothing()) {
101 CleanupChild(args
.maybeRequest().ref().body(), action
);
104 case CacheOpArgs::TStorageMatchArgs
: {
105 StorageMatchArgs
& args
= mOpArgs
.get_StorageMatchArgs();
106 CleanupChild(args
.request().body(), action
);
110 // Other types do not need cleanup
115 void AutoChildOpArgs::Add(const InternalRequest
& aRequest
,
116 BodyAction aBodyAction
, SchemeAction aSchemeAction
,
118 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
120 switch (mOpArgs
.type()) {
121 case CacheOpArgs::TCacheMatchArgs
: {
122 CacheMatchArgs
& args
= mOpArgs
.get_CacheMatchArgs();
123 mTypeUtils
->ToCacheRequest(args
.request(), aRequest
, aBodyAction
,
127 case CacheOpArgs::TCacheMatchAllArgs
: {
128 CacheMatchAllArgs
& args
= mOpArgs
.get_CacheMatchAllArgs();
129 MOZ_DIAGNOSTIC_ASSERT(args
.maybeRequest().isNothing());
130 args
.maybeRequest().emplace(CacheRequest());
131 mTypeUtils
->ToCacheRequest(args
.maybeRequest().ref(), aRequest
,
132 aBodyAction
, aSchemeAction
, aRv
);
135 case CacheOpArgs::TCacheDeleteArgs
: {
136 CacheDeleteArgs
& args
= mOpArgs
.get_CacheDeleteArgs();
137 mTypeUtils
->ToCacheRequest(args
.request(), aRequest
, aBodyAction
,
141 case CacheOpArgs::TCacheKeysArgs
: {
142 CacheKeysArgs
& args
= mOpArgs
.get_CacheKeysArgs();
143 MOZ_DIAGNOSTIC_ASSERT(args
.maybeRequest().isNothing());
144 args
.maybeRequest().emplace(CacheRequest());
145 mTypeUtils
->ToCacheRequest(args
.maybeRequest().ref(), aRequest
,
146 aBodyAction
, aSchemeAction
, aRv
);
149 case CacheOpArgs::TStorageMatchArgs
: {
150 StorageMatchArgs
& args
= mOpArgs
.get_StorageMatchArgs();
151 mTypeUtils
->ToCacheRequest(args
.request(), aRequest
, aBodyAction
,
156 MOZ_CRASH("Cache args type cannot send a Request!");
162 bool MatchInPutList(const InternalRequest
& aRequest
,
163 const nsTArray
<CacheRequestResponse
>& aPutList
) {
164 // This method implements the SW spec QueryCache algorithm against an
165 // in memory array of Request/Response objects. This essentially the
166 // same algorithm that is implemented in DBSchema.cpp. Unfortunately
167 // we cannot unify them because when operating against the real database
168 // we don't want to load all request/response objects into memory.
170 // Note, we can skip the check for a invalid request method because
171 // Cache should only call into here with a GET or HEAD.
173 nsAutoCString method
;
174 aRequest
.GetMethod(method
);
175 MOZ_ASSERT(method
.LowerCaseEqualsLiteral("get") ||
176 method
.LowerCaseEqualsLiteral("head"));
179 RefPtr
<InternalHeaders
> requestHeaders
= aRequest
.Headers();
181 for (uint32_t i
= 0; i
< aPutList
.Length(); ++i
) {
182 const CacheRequest
& cachedRequest
= aPutList
[i
].request();
183 const CacheResponse
& cachedResponse
= aPutList
[i
].response();
186 aRequest
.GetURL(url
);
188 nsAutoCString
requestUrl(cachedRequest
.urlWithoutQuery());
189 requestUrl
.Append(cachedRequest
.urlQuery());
191 // If the URLs don't match, then just skip to the next entry.
192 if (url
!= requestUrl
) {
196 RefPtr
<InternalHeaders
> cachedRequestHeaders
=
197 TypeUtils::ToInternalHeaders(cachedRequest
.headers());
199 RefPtr
<InternalHeaders
> cachedResponseHeaders
=
200 TypeUtils::ToInternalHeaders(cachedResponse
.headers());
202 nsCString varyHeaders
;
204 cachedResponseHeaders
->Get("vary"_ns
, varyHeaders
, rv
);
205 MOZ_ALWAYS_TRUE(!rv
.Failed());
207 // Assume the vary headers match until we find a conflict
208 bool varyHeadersMatch
= true;
210 for (const nsACString
& header
:
211 nsCCharSeparatedTokenizer(varyHeaders
, NS_HTTP_HEADER_SEP
).ToRange()) {
212 MOZ_DIAGNOSTIC_ASSERT(!header
.EqualsLiteral("*"),
213 "We should have already caught this in "
214 "TypeUtils::ToPCacheResponseWithoutBody()");
216 ErrorResult headerRv
;
218 requestHeaders
->Get(header
, value
, headerRv
);
219 if (NS_WARN_IF(headerRv
.Failed())) {
220 headerRv
.SuppressException();
221 MOZ_DIAGNOSTIC_ASSERT(value
.IsEmpty());
224 nsAutoCString cachedValue
;
225 cachedRequestHeaders
->Get(header
, cachedValue
, headerRv
);
226 if (NS_WARN_IF(headerRv
.Failed())) {
227 headerRv
.SuppressException();
228 MOZ_DIAGNOSTIC_ASSERT(cachedValue
.IsEmpty());
231 if (value
!= cachedValue
) {
232 varyHeadersMatch
= false;
237 // URL was equal and all vary headers match!
238 if (varyHeadersMatch
) {
248 void AutoChildOpArgs::Add(JSContext
* aCx
, const InternalRequest
& aRequest
,
249 BodyAction aBodyAction
, SchemeAction aSchemeAction
,
250 Response
& aResponse
, ErrorResult
& aRv
) {
251 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
253 switch (mOpArgs
.type()) {
254 case CacheOpArgs::TCachePutAllArgs
: {
255 CachePutAllArgs
& args
= mOpArgs
.get_CachePutAllArgs();
257 // Throw an error if a request/response pair would mask another
258 // request/response pair in the same PutAll operation. This is
259 // step 2.3.2.3 from the "Batch Cache Operations" spec algorithm.
260 if (MatchInPutList(aRequest
, args
.requestResponseList())) {
261 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
265 MOZ_RELEASE_ASSERT(args
.requestResponseList().Length() <
266 args
.requestResponseList().Capacity());
268 // The FileDescriptorSetChild asserts in its destructor that all fds have
269 // been removed. The copy constructor, however, simply duplicates the
270 // fds without removing any. This means each temporary and copy must be
271 // explicitly cleaned up.
273 // Avoid a lot of this hassle by making sure we only create one here. On
274 // error we remove it.
275 CacheRequestResponse
& pair
= *args
.requestResponseList().AppendElement();
276 pair
.request().body() = Nothing();
277 pair
.response().body() = Nothing();
279 mTypeUtils
->ToCacheRequest(pair
.request(), aRequest
, aBodyAction
,
282 mTypeUtils
->ToCacheResponse(aCx
, pair
.response(), aResponse
, aRv
);
286 CleanupChild(pair
.request().body(), Delete
);
287 args
.requestResponseList().RemoveLastElement();
293 MOZ_CRASH("Cache args type cannot send a Request/Response pair!");
297 const CacheOpArgs
& AutoChildOpArgs::SendAsOpArgs() {
298 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
303 // --------------------------------------------
305 AutoParentOpResult::AutoParentOpResult(
306 mozilla::ipc::PBackgroundParent
* aManager
, const CacheOpResult
& aOpResult
,
307 uint32_t aEntryCount
)
308 : mManager(aManager
),
309 mOpResult(aOpResult
),
310 mStreamControl(nullptr),
312 MOZ_DIAGNOSTIC_ASSERT(mManager
);
313 MOZ_RELEASE_ASSERT(aEntryCount
!= 0);
314 if (mOpResult
.type() == CacheOpResult::TCacheMatchAllResult
) {
315 CacheMatchAllResult
& result
= mOpResult
.get_CacheMatchAllResult();
316 result
.responseList().SetCapacity(aEntryCount
);
317 } else if (mOpResult
.type() == CacheOpResult::TCacheKeysResult
) {
318 CacheKeysResult
& result
= mOpResult
.get_CacheKeysResult();
319 result
.requestList().SetCapacity(aEntryCount
);
321 MOZ_DIAGNOSTIC_ASSERT(aEntryCount
== 1);
325 AutoParentOpResult::~AutoParentOpResult() {
326 CleanupAction action
= mSent
? Forget
: Delete
;
328 switch (mOpResult
.type()) {
329 case CacheOpResult::TStorageOpenResult
: {
330 StorageOpenResult
& result
= mOpResult
.get_StorageOpenResult();
331 if (action
== Forget
|| !result
.actor()) {
336 OkIf(PCacheParent::Send__delete__(result
.actor().AsParent())));
340 // other types do not need additional clean up
344 if (action
== Delete
&& mStreamControl
) {
345 mStreamControl
->AssertWillDelete();
347 OkIf(PCacheStreamControlParent::Send__delete__(mStreamControl
)));
351 void AutoParentOpResult::Add(CacheId aOpenedCacheId
,
352 SafeRefPtr
<Manager
> aManager
) {
353 MOZ_DIAGNOSTIC_ASSERT(mOpResult
.type() == CacheOpResult::TStorageOpenResult
);
354 MOZ_DIAGNOSTIC_ASSERT(!mOpResult
.get_StorageOpenResult().actor());
355 mOpResult
.get_StorageOpenResult().actor() = mManager
->SendPCacheConstructor(
356 new CacheParent(std::move(aManager
), aOpenedCacheId
));
359 void AutoParentOpResult::Add(const SavedResponse
& aSavedResponse
,
360 StreamList
& aStreamList
) {
361 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
363 switch (mOpResult
.type()) {
364 case CacheOpResult::TCacheMatchResult
: {
365 CacheMatchResult
& result
= mOpResult
.get_CacheMatchResult();
366 MOZ_DIAGNOSTIC_ASSERT(result
.maybeResponse().isNothing());
367 result
.maybeResponse().emplace(aSavedResponse
.mValue
);
368 SerializeResponseBody(aSavedResponse
, aStreamList
,
369 &result
.maybeResponse().ref());
372 case CacheOpResult::TCacheMatchAllResult
: {
373 CacheMatchAllResult
& result
= mOpResult
.get_CacheMatchAllResult();
374 MOZ_RELEASE_ASSERT(result
.responseList().Length() <
375 result
.responseList().Capacity());
376 result
.responseList().AppendElement(aSavedResponse
.mValue
);
377 SerializeResponseBody(aSavedResponse
, aStreamList
,
378 &result
.responseList().LastElement());
381 case CacheOpResult::TStorageMatchResult
: {
382 StorageMatchResult
& result
= mOpResult
.get_StorageMatchResult();
383 MOZ_DIAGNOSTIC_ASSERT(result
.maybeResponse().isNothing());
384 result
.maybeResponse().emplace(aSavedResponse
.mValue
);
385 SerializeResponseBody(aSavedResponse
, aStreamList
,
386 &result
.maybeResponse().ref());
390 MOZ_CRASH("Cache result type cannot handle returning a Response!");
394 void AutoParentOpResult::Add(const SavedRequest
& aSavedRequest
,
395 StreamList
& aStreamList
) {
396 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
398 switch (mOpResult
.type()) {
399 case CacheOpResult::TCacheKeysResult
: {
400 CacheKeysResult
& result
= mOpResult
.get_CacheKeysResult();
401 MOZ_RELEASE_ASSERT(result
.requestList().Length() <
402 result
.requestList().Capacity());
403 result
.requestList().AppendElement(aSavedRequest
.mValue
);
404 CacheRequest
& request
= result
.requestList().LastElement();
406 if (!aSavedRequest
.mHasBodyId
) {
407 request
.body() = Nothing();
411 request
.body().emplace(CacheReadStream());
412 SerializeReadStream(aSavedRequest
.mBodyId
, aStreamList
,
413 &request
.body().ref());
417 MOZ_CRASH("Cache result type cannot handle returning a Request!");
421 const CacheOpResult
& AutoParentOpResult::SendAsOpResult() {
422 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
427 void AutoParentOpResult::SerializeResponseBody(
428 const SavedResponse
& aSavedResponse
, StreamList
& aStreamList
,
429 CacheResponse
* aResponseOut
) {
430 MOZ_DIAGNOSTIC_ASSERT(aResponseOut
);
432 if (!aSavedResponse
.mHasBodyId
) {
433 aResponseOut
->body() = Nothing();
437 aResponseOut
->body().emplace(CacheReadStream());
438 SerializeReadStream(aSavedResponse
.mBodyId
, aStreamList
,
439 &aResponseOut
->body().ref());
442 void AutoParentOpResult::SerializeReadStream(const nsID
& aId
,
443 StreamList
& aStreamList
,
444 CacheReadStream
* aReadStreamOut
) {
445 MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut
);
446 MOZ_DIAGNOSTIC_ASSERT(!mSent
);
448 nsCOMPtr
<nsIInputStream
> stream
= aStreamList
.Extract(aId
);
450 if (!mStreamControl
) {
451 mStreamControl
= static_cast<CacheStreamControlParent
*>(
452 mManager
->SendPCacheStreamControlConstructor(
453 new CacheStreamControlParent()));
455 // If this failed, then the child process is gone. Warn and allow actor
456 // cleanup to proceed as normal.
457 if (!mStreamControl
) {
458 NS_WARNING("Cache failed to create stream control actor.");
463 aStreamList
.SetStreamControl(mStreamControl
);
465 RefPtr
<ReadStream
> readStream
=
466 ReadStream::Create(mStreamControl
, aId
, stream
);
468 readStream
->Serialize(aReadStreamOut
, rv
);
469 MOZ_DIAGNOSTIC_ASSERT(!rv
.Failed());
472 } // namespace mozilla::dom::cache