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/TypeUtils.h"
10 #include "mozilla/StaticPrefs_extensions.h"
11 #include "mozilla/Unused.h"
12 #include "mozilla/dom/CacheBinding.h"
13 #include "mozilla/dom/CacheStorageBinding.h"
14 #include "mozilla/dom/FetchTypes.h"
15 #include "mozilla/dom/InternalRequest.h"
16 #include "mozilla/dom/Request.h"
17 #include "mozilla/dom/Response.h"
18 #include "mozilla/dom/RootedDictionary.h"
19 #include "mozilla/dom/cache/CacheTypes.h"
20 #include "mozilla/dom/cache/ReadStream.h"
21 #include "mozilla/ipc/BackgroundChild.h"
22 #include "mozilla/ipc/IPCStreamUtils.h"
23 #include "mozilla/ipc/PBackgroundChild.h"
24 #include "mozilla/ipc/InputStreamUtils.h"
25 #include "nsCharSeparatedTokenizer.h"
28 #include "nsIIPCSerializableInputStream.h"
29 #include "nsPromiseFlatString.h"
30 #include "nsQueryObject.h"
31 #include "nsStreamUtils.h"
33 #include "nsURLParsers.h"
35 namespace mozilla::dom::cache
{
37 using mozilla::ipc::BackgroundChild
;
38 using mozilla::ipc::FileDescriptor
;
39 using mozilla::ipc::PBackgroundChild
;
43 static bool HasVaryStar(mozilla::dom::InternalHeaders
* aHeaders
) {
44 nsCString varyHeaders
;
46 aHeaders
->Get("vary"_ns
, varyHeaders
, rv
);
47 MOZ_ALWAYS_TRUE(!rv
.Failed());
49 for (const nsACString
& header
:
50 nsCCharSeparatedTokenizer(varyHeaders
, NS_HTTP_HEADER_SEP
).ToRange()) {
51 if (header
.EqualsLiteral("*")) {
58 nsTArray
<HeadersEntry
> ToHeadersEntryList(InternalHeaders
* aHeaders
) {
59 MOZ_DIAGNOSTIC_ASSERT(aHeaders
);
61 AutoTArray
<InternalHeaders::Entry
, 16> entryList
;
62 aHeaders
->GetEntries(entryList
);
64 nsTArray
<HeadersEntry
> result
;
65 result
.SetCapacity(entryList
.Length());
66 std::transform(entryList
.cbegin(), entryList
.cend(), MakeBackInserter(result
),
67 [](const auto& entry
) {
68 return HeadersEntry(entry
.mName
, entry
.mValue
);
76 SafeRefPtr
<InternalRequest
> TypeUtils::ToInternalRequest(
77 JSContext
* aCx
, const RequestOrUSVString
& aIn
, BodyAction aBodyAction
,
79 if (aIn
.IsRequest()) {
80 Request
& request
= aIn
.GetAsRequest();
82 // Check and set bodyUsed flag immediately because its on Request
83 // instead of InternalRequest.
84 CheckAndSetBodyUsed(aCx
, request
, aBodyAction
, aRv
);
89 return request
.GetInternalRequest();
92 return ToInternalRequest(aIn
.GetAsUSVString(), aRv
);
95 SafeRefPtr
<InternalRequest
> TypeUtils::ToInternalRequest(
96 JSContext
* aCx
, const OwningRequestOrUSVString
& aIn
, BodyAction aBodyAction
,
98 if (aIn
.IsRequest()) {
99 Request
& request
= aIn
.GetAsRequest();
101 // Check and set bodyUsed flag immediately because its on Request
102 // instead of InternalRequest.
103 CheckAndSetBodyUsed(aCx
, request
, aBodyAction
, aRv
);
108 return request
.GetInternalRequest();
111 return ToInternalRequest(aIn
.GetAsUSVString(), aRv
);
114 void TypeUtils::ToCacheRequest(CacheRequest
& aOut
, const InternalRequest
& aIn
,
115 BodyAction aBodyAction
,
116 SchemeAction aSchemeAction
, ErrorResult
& aRv
) {
117 aIn
.GetMethod(aOut
.method());
118 nsCString
url(aIn
.GetURLWithoutFragment());
120 ProcessURL(url
, &schemeValid
, &aOut
.urlWithoutQuery(), &aOut
.urlQuery(), aRv
);
125 if (aSchemeAction
== TypeErrorOnInvalidScheme
) {
126 aRv
.ThrowTypeError
<MSG_INVALID_URL_SCHEME
>("Request", url
);
130 aOut
.urlFragment() = aIn
.GetFragment();
132 aIn
.GetReferrer(aOut
.referrer());
133 aOut
.referrerPolicy() = aIn
.ReferrerPolicy_();
134 RefPtr
<InternalHeaders
> headers
= aIn
.Headers();
135 MOZ_DIAGNOSTIC_ASSERT(headers
);
136 aOut
.headers() = ToHeadersEntryList(headers
);
137 aOut
.headersGuard() = headers
->Guard();
138 aOut
.mode() = aIn
.Mode();
139 aOut
.credentials() = aIn
.GetCredentialsMode();
140 aOut
.contentPolicyType() = aIn
.ContentPolicyType();
141 aOut
.requestCache() = aIn
.GetCacheMode();
142 aOut
.requestRedirect() = aIn
.GetRedirectMode();
144 aOut
.integrity() = aIn
.GetIntegrity();
145 aOut
.loadingEmbedderPolicy() = aIn
.GetEmbedderPolicy();
146 const mozilla::UniquePtr
<mozilla::ipc::PrincipalInfo
>& principalInfo
=
147 aIn
.GetPrincipalInfo();
149 aOut
.principalInfo() = Some(*(principalInfo
.get()));
152 if (aBodyAction
== IgnoreBody
) {
153 aOut
.body() = Nothing();
157 // BodyUsed flag is checked and set previously in ToInternalRequest()
159 nsCOMPtr
<nsIInputStream
> stream
;
160 aIn
.GetBody(getter_AddRefs(stream
));
161 SerializeCacheStream(stream
, &aOut
.body(), aRv
);
162 if (NS_WARN_IF(aRv
.Failed())) {
167 void TypeUtils::ToCacheResponseWithoutBody(CacheResponse
& aOut
,
168 InternalResponse
& aIn
,
170 aOut
.type() = aIn
.Type();
172 aIn
.GetUnfilteredURLList(aOut
.urlList());
173 AutoTArray
<nsCString
, 4> urlList
;
174 aIn
.GetURLList(urlList
);
176 for (uint32_t i
= 0; i
< aOut
.urlList().Length(); i
++) {
177 MOZ_DIAGNOSTIC_ASSERT(!aOut
.urlList()[i
].IsEmpty());
178 // Pass all Response URL schemes through... The spec only requires we take
179 // action on invalid schemes for Request objects.
180 ProcessURL(aOut
.urlList()[i
], nullptr, nullptr, nullptr, aRv
);
183 aOut
.status() = aIn
.GetUnfilteredStatus();
184 aOut
.statusText() = aIn
.GetUnfilteredStatusText();
185 RefPtr
<InternalHeaders
> headers
= aIn
.UnfilteredHeaders();
186 MOZ_DIAGNOSTIC_ASSERT(headers
);
187 if (HasVaryStar(headers
)) {
188 aRv
.ThrowTypeError("Invalid Response object with a 'Vary: *' header.");
191 aOut
.headers() = ToHeadersEntryList(headers
);
192 aOut
.headersGuard() = headers
->Guard();
193 aOut
.securityInfo() = aIn
.GetChannelInfo().SecurityInfo();
194 if (aIn
.GetPrincipalInfo()) {
195 aOut
.principalInfo() = Some(*aIn
.GetPrincipalInfo());
197 aOut
.principalInfo() = Nothing();
200 aOut
.paddingInfo() = aIn
.GetPaddingInfo();
201 aOut
.paddingSize() = aIn
.GetPaddingSize();
204 void TypeUtils::ToCacheResponse(JSContext
* aCx
, CacheResponse
& aOut
,
205 Response
& aIn
, ErrorResult
& aRv
) {
206 if (aIn
.BodyUsed()) {
207 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
211 SafeRefPtr
<InternalResponse
> ir
= aIn
.GetInternalResponse();
212 ToCacheResponseWithoutBody(aOut
, *ir
, aRv
);
213 if (NS_WARN_IF(aRv
.Failed())) {
217 nsCOMPtr
<nsIInputStream
> stream
;
218 ir
->GetUnfilteredBody(getter_AddRefs(stream
));
220 aIn
.SetBodyUsed(aCx
, aRv
);
221 if (NS_WARN_IF(aRv
.Failed())) {
226 SerializeCacheStream(stream
, &aOut
.body(), aRv
);
227 if (NS_WARN_IF(aRv
.Failed())) {
233 void TypeUtils::ToCacheQueryParams(CacheQueryParams
& aOut
,
234 const CacheQueryOptions
& aIn
) {
235 aOut
.ignoreSearch() = aIn
.mIgnoreSearch
;
236 aOut
.ignoreMethod() = aIn
.mIgnoreMethod
;
237 aOut
.ignoreVary() = aIn
.mIgnoreVary
;
241 void TypeUtils::ToCacheQueryParams(CacheQueryParams
& aOut
,
242 const MultiCacheQueryOptions
& aIn
) {
243 ToCacheQueryParams(aOut
, static_cast<const CacheQueryOptions
&>(aIn
));
244 aOut
.cacheNameSet() = aIn
.mCacheName
.WasPassed();
245 if (aOut
.cacheNameSet()) {
246 aOut
.cacheName() = aIn
.mCacheName
.Value();
248 aOut
.cacheName() = u
""_ns
;
252 already_AddRefed
<Response
> TypeUtils::ToResponse(const CacheResponse
& aIn
) {
253 if (aIn
.type() == ResponseType::Error
) {
254 // We don't bother tracking the internal error code for cached responses...
256 new Response(GetGlobalObject(),
257 InternalResponse::NetworkError(NS_ERROR_FAILURE
), nullptr);
261 SafeRefPtr
<InternalResponse
> ir
=
262 MakeSafeRefPtr
<InternalResponse
>(aIn
.status(), aIn
.statusText());
263 ir
->SetURLList(aIn
.urlList());
265 RefPtr
<InternalHeaders
> internalHeaders
=
266 ToInternalHeaders(aIn
.headers(), aIn
.headersGuard());
269 // Be careful to fill the headers before setting the guard in order to
270 // correctly re-create the original headers.
271 ir
->Headers()->Fill(*internalHeaders
, result
);
272 MOZ_DIAGNOSTIC_ASSERT(!result
.Failed());
273 ir
->Headers()->SetGuard(aIn
.headersGuard(), result
);
274 MOZ_DIAGNOSTIC_ASSERT(!result
.Failed());
276 ir
->InitChannelInfo(aIn
.securityInfo());
277 if (aIn
.principalInfo().isSome()) {
278 UniquePtr
<mozilla::ipc::PrincipalInfo
> info(
279 new mozilla::ipc::PrincipalInfo(aIn
.principalInfo().ref()));
280 ir
->SetPrincipalInfo(std::move(info
));
283 nsCOMPtr
<nsIInputStream
> stream
= ReadStream::Create(aIn
.body());
284 ir
->SetBody(stream
, InternalResponse::UNKNOWN_BODY_SIZE
);
286 switch (aIn
.type()) {
287 case ResponseType::Basic
:
288 ir
= ir
->BasicResponse();
290 case ResponseType::Cors
:
291 ir
= ir
->CORSResponse();
293 case ResponseType::Default
:
295 case ResponseType::Opaque
:
296 ir
= ir
->OpaqueResponse();
298 case ResponseType::Opaqueredirect
:
299 ir
= ir
->OpaqueRedirectResponse();
302 MOZ_CRASH("Unexpected ResponseType!");
304 MOZ_DIAGNOSTIC_ASSERT(ir
);
306 ir
->SetPaddingSize(aIn
.paddingSize());
308 RefPtr
<Response
> ref
=
309 new Response(GetGlobalObject(), std::move(ir
), nullptr);
312 SafeRefPtr
<InternalRequest
> TypeUtils::ToInternalRequest(
313 const CacheRequest
& aIn
) {
314 nsAutoCString
url(aIn
.urlWithoutQuery());
315 url
.Append(aIn
.urlQuery());
316 auto internalRequest
=
317 MakeSafeRefPtr
<InternalRequest
>(url
, aIn
.urlFragment());
318 internalRequest
->SetMethod(aIn
.method());
319 internalRequest
->SetReferrer(aIn
.referrer());
320 internalRequest
->SetReferrerPolicy(aIn
.referrerPolicy());
321 internalRequest
->SetMode(aIn
.mode());
322 internalRequest
->SetCredentialsMode(aIn
.credentials());
323 internalRequest
->SetContentPolicyType(aIn
.contentPolicyType());
324 internalRequest
->SetCacheMode(aIn
.requestCache());
325 internalRequest
->SetRedirectMode(aIn
.requestRedirect());
326 internalRequest
->SetIntegrity(aIn
.integrity());
328 RefPtr
<InternalHeaders
> internalHeaders
=
329 ToInternalHeaders(aIn
.headers(), aIn
.headersGuard());
332 // Be careful to fill the headers before setting the guard in order to
333 // correctly re-create the original headers.
334 internalRequest
->Headers()->Fill(*internalHeaders
, result
);
335 MOZ_DIAGNOSTIC_ASSERT(!result
.Failed());
337 internalRequest
->Headers()->SetGuard(aIn
.headersGuard(), result
);
338 MOZ_DIAGNOSTIC_ASSERT(!result
.Failed());
340 nsCOMPtr
<nsIInputStream
> stream
= ReadStream::Create(aIn
.body());
342 internalRequest
->SetBody(stream
, -1);
344 return internalRequest
;
347 SafeRefPtr
<Request
> TypeUtils::ToRequest(const CacheRequest
& aIn
) {
348 return MakeSafeRefPtr
<Request
>(GetGlobalObject(), ToInternalRequest(aIn
),
353 already_AddRefed
<InternalHeaders
> TypeUtils::ToInternalHeaders(
354 const nsTArray
<HeadersEntry
>& aHeadersEntryList
, HeadersGuardEnum aGuard
) {
355 nsTArray
<InternalHeaders::Entry
> entryList
;
356 entryList
.SetCapacity(aHeadersEntryList
.Length());
357 std::transform(aHeadersEntryList
.cbegin(), aHeadersEntryList
.cend(),
358 MakeBackInserter(entryList
), [](const auto& headersEntry
) {
359 return InternalHeaders::Entry(headersEntry
.name(),
360 headersEntry
.value());
363 RefPtr
<InternalHeaders
> ref
=
364 new InternalHeaders(std::move(entryList
), aGuard
);
368 // Utility function to remove the fragment from a URL, check its scheme, and
369 // optionally provide a URL without the query. We're not using nsIURL or URL to
370 // do this because they require going to the main thread. static
371 void TypeUtils::ProcessURL(nsACString
& aUrl
, bool* aSchemeValidOut
,
372 nsACString
* aUrlWithoutQueryOut
,
373 nsACString
* aUrlQueryOut
, ErrorResult
& aRv
) {
374 const nsCString
& flatURL
= PromiseFlatCString(aUrl
);
375 const char* url
= flatURL
.get();
377 // off the main thread URL parsing using nsStdURLParser.
378 nsCOMPtr
<nsIURLParser
> urlParser
= new nsStdURLParser();
384 aRv
= urlParser
->ParseURL(url
, flatURL
.Length(), &schemePos
, &schemeLen
,
385 nullptr, nullptr, // ignore authority
387 if (NS_WARN_IF(aRv
.Failed())) {
391 if (aSchemeValidOut
) {
392 nsAutoCString
scheme(Substring(flatURL
, schemePos
, schemeLen
));
394 scheme
.LowerCaseEqualsLiteral("http") ||
395 scheme
.LowerCaseEqualsLiteral("https") ||
396 (StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup() &&
397 scheme
.LowerCaseEqualsLiteral("moz-extension"));
403 aRv
= urlParser
->ParsePath(url
+ pathPos
, flatURL
.Length() - pathPos
, nullptr,
404 nullptr, // ignore filepath
405 &queryPos
, &queryLen
, nullptr, nullptr);
406 if (NS_WARN_IF(aRv
.Failed())) {
410 if (!aUrlWithoutQueryOut
) {
414 MOZ_DIAGNOSTIC_ASSERT(aUrlQueryOut
);
417 *aUrlWithoutQueryOut
= aUrl
;
418 aUrlQueryOut
->Truncate();
422 // ParsePath gives us query position relative to the start of the path
425 *aUrlWithoutQueryOut
= Substring(aUrl
, 0, queryPos
- 1);
426 *aUrlQueryOut
= Substring(aUrl
, queryPos
- 1, queryLen
+ 1);
429 void TypeUtils::CheckAndSetBodyUsed(JSContext
* aCx
, Request
& aRequest
,
430 BodyAction aBodyAction
, ErrorResult
& aRv
) {
431 if (aBodyAction
== IgnoreBody
) {
435 if (aRequest
.BodyUsed()) {
436 aRv
.ThrowTypeError
<MSG_FETCH_BODY_CONSUMED_ERROR
>();
440 nsCOMPtr
<nsIInputStream
> stream
;
441 aRequest
.GetBody(getter_AddRefs(stream
));
443 aRequest
.SetBodyUsed(aCx
, aRv
);
444 if (NS_WARN_IF(aRv
.Failed())) {
450 SafeRefPtr
<InternalRequest
> TypeUtils::ToInternalRequest(const nsAString
& aIn
,
452 RequestOrUSVString requestOrString
;
453 requestOrString
.SetAsUSVString().ShareOrDependUpon(aIn
);
455 // Re-create a GlobalObject stack object so we can use webidl Constructors.
457 if (NS_WARN_IF(!jsapi
.Init(GetGlobalObject()))) {
458 aRv
.Throw(NS_ERROR_UNEXPECTED
);
461 JSContext
* cx
= jsapi
.cx();
462 GlobalObject
global(cx
, GetGlobalObject()->GetGlobalJSObject());
463 MOZ_DIAGNOSTIC_ASSERT(!global
.Failed());
465 RootedDictionary
<RequestInit
> requestInit(cx
);
466 SafeRefPtr
<Request
> request
=
467 Request::Constructor(global
, requestOrString
, requestInit
, aRv
);
468 if (NS_WARN_IF(aRv
.Failed())) {
472 return request
->GetInternalRequest();
475 void TypeUtils::SerializeCacheStream(nsIInputStream
* aStream
,
476 Maybe
<CacheReadStream
>* aStreamOut
,
478 *aStreamOut
= Nothing();
483 RefPtr
<ReadStream
> controlled
= do_QueryObject(aStream
);
485 controlled
->Serialize(aStreamOut
, aRv
);
489 aStreamOut
->emplace(CacheReadStream());
490 CacheReadStream
& cacheStream
= aStreamOut
->ref();
492 cacheStream
.control() = nullptr;
494 MOZ_ALWAYS_TRUE(mozilla::ipc::SerializeIPCStream(do_AddRef(aStream
),
495 cacheStream
.stream(),
496 /* aAllowLazy */ false));
499 } // namespace mozilla::dom::cache