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/. */
6
7 #include "mozilla/dom/cache/TypeUtils.h"
8
9 #include "mozilla/Unused.h"
10 #include "mozilla/dom/CacheBinding.h"
11 #include "mozilla/dom/InternalRequest.h"
12 #include "mozilla/dom/Request.h"
13 #include "mozilla/dom/Response.h"
14 #include "mozilla/dom/cache/CacheTypes.h"
15 #include "mozilla/dom/cache/ReadStream.h"
16 #include "mozilla/ipc/BackgroundChild.h"
17 #include "mozilla/ipc/IPCStreamUtils.h"
18 #include "mozilla/ipc/PBackgroundChild.h"
19 #include "mozilla/ipc/PFileDescriptorSetChild.h"
20 #include "mozilla/ipc/InputStreamUtils.h"
21 #include "nsCOMPtr.h"
22 #include "nsIAsyncInputStream.h"
23 #include "nsIAsyncOutputStream.h"
24 #include "nsIIPCSerializableInputStream.h"
25 #include "nsQueryObject.h"
26 #include "nsPromiseFlatString.h"
27 #include "nsStreamUtils.h"
28 #include "nsString.h"
29 #include "nsURLParsers.h"
30 #include "nsCRT.h"
31 #include "nsHttp.h"
32
33 namespace mozilla {
34 namespace dom {
35 namespace cache {
36
37 using mozilla::ipc::AutoIPCStream;
38 using mozilla::ipc::BackgroundChild;
39 using mozilla::ipc::FileDescriptor;
40 using mozilla::ipc::PBackgroundChild;
41 using mozilla::ipc::PFileDescriptorSetChild;
42
43 namespace {
44
HasVaryStar(mozilla::dom::InternalHeaders * aHeaders)45 static bool HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) {
46 nsCString varyHeaders;
47 ErrorResult rv;
48 aHeaders->Get(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
49 MOZ_ALWAYS_TRUE(!rv.Failed());
50
51 char* rawBuffer = varyHeaders.BeginWriting();
52 char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer);
53 for (; token;
54 token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
55 nsDependentCString header(token);
56 if (header.EqualsLiteral("*")) {
57 return true;
58 }
59 }
60 return false;
61 }
62
ToHeadersEntryList(nsTArray<HeadersEntry> & aOut,InternalHeaders * aHeaders)63 void ToHeadersEntryList(nsTArray<HeadersEntry>& aOut,
64 InternalHeaders* aHeaders) {
65 MOZ_DIAGNOSTIC_ASSERT(aHeaders);
66
67 AutoTArray<InternalHeaders::Entry, 16> entryList;
68 aHeaders->GetEntries(entryList);
69
70 for (uint32_t i = 0; i < entryList.Length(); ++i) {
71 InternalHeaders::Entry& entry = entryList[i];
72 aOut.AppendElement(HeadersEntry(entry.mName, entry.mValue));
73 }
74 }
75
76 } // namespace
77
ToInternalRequest(JSContext * aCx,const RequestOrUSVString & aIn,BodyAction aBodyAction,ErrorResult & aRv)78 already_AddRefed<InternalRequest> TypeUtils::ToInternalRequest(
79 JSContext* aCx, const RequestOrUSVString& aIn, BodyAction aBodyAction,
80 ErrorResult& aRv) {
81 if (aIn.IsRequest()) {
82 Request& request = aIn.GetAsRequest();
83
84 // Check and set bodyUsed flag immediately because its on Request
85 // instead of InternalRequest.
86 CheckAndSetBodyUsed(aCx, &request, aBodyAction, aRv);
87 if (aRv.Failed()) {
88 return nullptr;
89 }
90
91 return request.GetInternalRequest();
92 }
93
94 return ToInternalRequest(aIn.GetAsUSVString(), aRv);
95 }
96
ToInternalRequest(JSContext * aCx,const OwningRequestOrUSVString & aIn,BodyAction aBodyAction,ErrorResult & aRv)97 already_AddRefed<InternalRequest> TypeUtils::ToInternalRequest(
98 JSContext* aCx, const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
99 ErrorResult& aRv) {
100 if (aIn.IsRequest()) {
101 RefPtr<Request> request = aIn.GetAsRequest().get();
102
103 // Check and set bodyUsed flag immediately because its on Request
104 // instead of InternalRequest.
105 CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv);
106 if (aRv.Failed()) {
107 return nullptr;
108 }
109
110 return request->GetInternalRequest();
111 }
112
113 return ToInternalRequest(aIn.GetAsUSVString(), aRv);
114 }
115
ToCacheRequest(CacheRequest & aOut,InternalRequest * aIn,BodyAction aBodyAction,SchemeAction aSchemeAction,nsTArray<UniquePtr<AutoIPCStream>> & aStreamCleanupList,ErrorResult & aRv)116 void TypeUtils::ToCacheRequest(
117 CacheRequest& aOut, InternalRequest* aIn, BodyAction aBodyAction,
118 SchemeAction aSchemeAction,
119 nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList, ErrorResult& aRv) {
120 MOZ_DIAGNOSTIC_ASSERT(aIn);
121 aIn->GetMethod(aOut.method());
122 nsCString url(aIn->GetURLWithoutFragment());
123 bool schemeValid;
124 ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv);
125 if (aRv.Failed()) {
126 return;
127 }
128 if (!schemeValid) {
129 if (aSchemeAction == TypeErrorOnInvalidScheme) {
130 NS_ConvertUTF8toUTF16 urlUTF16(url);
131 aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
132 urlUTF16);
133 return;
134 }
135 }
136 aOut.urlFragment() = aIn->GetFragment();
137
138 aIn->GetReferrer(aOut.referrer());
139 aOut.referrerPolicy() = aIn->ReferrerPolicy_();
140 RefPtr<InternalHeaders> headers = aIn->Headers();
141 MOZ_DIAGNOSTIC_ASSERT(headers);
142 ToHeadersEntryList(aOut.headers(), headers);
143 aOut.headersGuard() = headers->Guard();
144 aOut.mode() = aIn->Mode();
145 aOut.credentials() = aIn->GetCredentialsMode();
146 aOut.contentPolicyType() = aIn->ContentPolicyType();
147 aOut.requestCache() = aIn->GetCacheMode();
148 aOut.requestRedirect() = aIn->GetRedirectMode();
149
150 aOut.integrity() = aIn->GetIntegrity();
151
152 if (aBodyAction == IgnoreBody) {
153 aOut.body() = void_t();
154 return;
155 }
156
157 // BodyUsed flag is checked and set previously in ToInternalRequest()
158
159 nsCOMPtr<nsIInputStream> stream;
160 aIn->GetBody(getter_AddRefs(stream));
161 SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
162 if (NS_WARN_IF(aRv.Failed())) {
163 return;
164 }
165 }
166
ToCacheResponseWithoutBody(CacheResponse & aOut,InternalResponse & aIn,ErrorResult & aRv)167 void TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
168 InternalResponse& aIn,
169 ErrorResult& aRv) {
170 aOut.type() = aIn.Type();
171
172 aIn.GetUnfilteredURLList(aOut.urlList());
173 AutoTArray<nsCString, 4> urlList;
174 aIn.GetURLList(urlList);
175
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);
181 }
182
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<MSG_RESPONSE_HAS_VARY_STAR>();
189 return;
190 }
191 ToHeadersEntryList(aOut.headers(), headers);
192 aOut.headersGuard() = headers->Guard();
193 aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo();
194 if (aIn.GetPrincipalInfo()) {
195 aOut.principalInfo() = *aIn.GetPrincipalInfo();
196 } else {
197 aOut.principalInfo() = void_t();
198 }
199
200 aOut.paddingInfo() = aIn.GetPaddingInfo();
201 aOut.paddingSize() = aIn.GetPaddingSize();
202 }
203
ToCacheResponse(JSContext * aCx,CacheResponse & aOut,Response & aIn,nsTArray<UniquePtr<AutoIPCStream>> & aStreamCleanupList,ErrorResult & aRv)204 void TypeUtils::ToCacheResponse(
205 JSContext* aCx, CacheResponse& aOut, Response& aIn,
206 nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList, ErrorResult& aRv) {
207 if (aIn.BodyUsed()) {
208 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
209 return;
210 }
211
212 RefPtr<InternalResponse> ir = aIn.GetInternalResponse();
213 ToCacheResponseWithoutBody(aOut, *ir, aRv);
214 if (NS_WARN_IF(aRv.Failed())) {
215 return;
216 }
217
218 nsCOMPtr<nsIInputStream> stream;
219 ir->GetUnfilteredBody(getter_AddRefs(stream));
220 if (stream) {
221 aIn.SetBodyUsed(aCx, aRv);
222 if (NS_WARN_IF(aRv.Failed())) {
223 return;
224 }
225 }
226
227 SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
228 if (NS_WARN_IF(aRv.Failed())) {
229 return;
230 }
231 }
232
233 // static
ToCacheQueryParams(CacheQueryParams & aOut,const CacheQueryOptions & aIn)234 void TypeUtils::ToCacheQueryParams(CacheQueryParams& aOut,
235 const CacheQueryOptions& aIn) {
236 aOut.ignoreSearch() = aIn.mIgnoreSearch;
237 aOut.ignoreMethod() = aIn.mIgnoreMethod;
238 aOut.ignoreVary() = aIn.mIgnoreVary;
239 aOut.cacheNameSet() = aIn.mCacheName.WasPassed();
240 if (aOut.cacheNameSet()) {
241 aOut.cacheName() = aIn.mCacheName.Value();
242 } else {
243 aOut.cacheName() = NS_LITERAL_STRING("");
244 }
245 }
246
ToResponse(const CacheResponse & aIn)247 already_AddRefed<Response> TypeUtils::ToResponse(const CacheResponse& aIn) {
248 if (aIn.type() == ResponseType::Error) {
249 // We don't bother tracking the internal error code for cached responses...
250 RefPtr<InternalResponse> error =
251 InternalResponse::NetworkError(NS_ERROR_FAILURE);
252 RefPtr<Response> r = new Response(GetGlobalObject(), error, nullptr);
253 return r.forget();
254 }
255
256 RefPtr<InternalResponse> ir =
257 new InternalResponse(aIn.status(), aIn.statusText());
258 ir->SetURLList(aIn.urlList());
259
260 RefPtr<InternalHeaders> internalHeaders =
261 ToInternalHeaders(aIn.headers(), aIn.headersGuard());
262 ErrorResult result;
263
264 // Be careful to fill the headers before setting the guard in order to
265 // correctly re-create the original headers.
266 ir->Headers()->Fill(*internalHeaders, result);
267 MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
268 ir->Headers()->SetGuard(aIn.headersGuard(), result);
269 MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
270
271 ir->InitChannelInfo(aIn.channelInfo());
272 if (aIn.principalInfo().type() ==
273 mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) {
274 UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(
275 aIn.principalInfo().get_PrincipalInfo()));
276 ir->SetPrincipalInfo(Move(info));
277 }
278
279 nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
280 ir->SetBody(stream, InternalResponse::UNKNOWN_BODY_SIZE);
281
282 switch (aIn.type()) {
283 case ResponseType::Basic:
284 ir = ir->BasicResponse();
285 break;
286 case ResponseType::Cors:
287 ir = ir->CORSResponse();
288 break;
289 case ResponseType::Default:
290 break;
291 case ResponseType::Opaque:
292 ir = ir->OpaqueResponse();
293 break;
294 case ResponseType::Opaqueredirect:
295 ir = ir->OpaqueRedirectResponse();
296 break;
297 default:
298 MOZ_CRASH("Unexpected ResponseType!");
299 }
300 MOZ_DIAGNOSTIC_ASSERT(ir);
301
302 ir->SetPaddingSize(aIn.paddingSize());
303
304 RefPtr<Response> ref = new Response(GetGlobalObject(), ir, nullptr);
305 return ref.forget();
306 }
ToInternalRequest(const CacheRequest & aIn)307 already_AddRefed<InternalRequest> TypeUtils::ToInternalRequest(
308 const CacheRequest& aIn) {
309 nsAutoCString url(aIn.urlWithoutQuery());
310 url.Append(aIn.urlQuery());
311 RefPtr<InternalRequest> internalRequest =
312 new InternalRequest(url, aIn.urlFragment());
313 internalRequest->SetMethod(aIn.method());
314 internalRequest->SetReferrer(aIn.referrer());
315 internalRequest->SetReferrerPolicy(aIn.referrerPolicy());
316 internalRequest->SetMode(aIn.mode());
317 internalRequest->SetCredentialsMode(aIn.credentials());
318 internalRequest->SetContentPolicyType(aIn.contentPolicyType());
319 internalRequest->SetCacheMode(aIn.requestCache());
320 internalRequest->SetRedirectMode(aIn.requestRedirect());
321 internalRequest->SetIntegrity(aIn.integrity());
322
323 RefPtr<InternalHeaders> internalHeaders =
324 ToInternalHeaders(aIn.headers(), aIn.headersGuard());
325 ErrorResult result;
326
327 // Be careful to fill the headers before setting the guard in order to
328 // correctly re-create the original headers.
329 internalRequest->Headers()->Fill(*internalHeaders, result);
330 MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
331
332 internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
333 MOZ_DIAGNOSTIC_ASSERT(!result.Failed());
334
335 nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
336
337 internalRequest->SetBody(stream, -1);
338
339 return internalRequest.forget();
340 }
341
ToRequest(const CacheRequest & aIn)342 already_AddRefed<Request> TypeUtils::ToRequest(const CacheRequest& aIn) {
343 RefPtr<InternalRequest> internalRequest = ToInternalRequest(aIn);
344 RefPtr<Request> request =
345 new Request(GetGlobalObject(), internalRequest, nullptr);
346 return request.forget();
347 }
348
349 // static
ToInternalHeaders(const nsTArray<HeadersEntry> & aHeadersEntryList,HeadersGuardEnum aGuard)350 already_AddRefed<InternalHeaders> TypeUtils::ToInternalHeaders(
351 const nsTArray<HeadersEntry>& aHeadersEntryList, HeadersGuardEnum aGuard) {
352 nsTArray<InternalHeaders::Entry> entryList(aHeadersEntryList.Length());
353
354 for (uint32_t i = 0; i < aHeadersEntryList.Length(); ++i) {
355 const HeadersEntry& headersEntry = aHeadersEntryList[i];
356 entryList.AppendElement(
357 InternalHeaders::Entry(headersEntry.name(), headersEntry.value()));
358 }
359
360 RefPtr<InternalHeaders> ref = new InternalHeaders(Move(entryList), aGuard);
361 return ref.forget();
362 }
363
364 // Utility function to remove the fragment from a URL, check its scheme, and
365 // optionally provide a URL without the query. We're not using nsIURL or URL to
366 // do this because they require going to the main thread. static
ProcessURL(nsACString & aUrl,bool * aSchemeValidOut,nsACString * aUrlWithoutQueryOut,nsACString * aUrlQueryOut,ErrorResult & aRv)367 void TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
368 nsACString* aUrlWithoutQueryOut,
369 nsACString* aUrlQueryOut, ErrorResult& aRv) {
370 const nsCString& flatURL = PromiseFlatCString(aUrl);
371 const char* url = flatURL.get();
372
373 // off the main thread URL parsing using nsStdURLParser.
374 nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
375
376 uint32_t pathPos;
377 int32_t pathLen;
378 uint32_t schemePos;
379 int32_t schemeLen;
380 aRv = urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen,
381 nullptr, nullptr, // ignore authority
382 &pathPos, &pathLen);
383 if (NS_WARN_IF(aRv.Failed())) {
384 return;
385 }
386
387 if (aSchemeValidOut) {
388 nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
389 *aSchemeValidOut = scheme.LowerCaseEqualsLiteral("http") ||
390 scheme.LowerCaseEqualsLiteral("https");
391 }
392
393 uint32_t queryPos;
394 int32_t queryLen;
395
396 aRv = urlParser->ParsePath(url + pathPos, flatURL.Length() - pathPos, nullptr,
397 nullptr, // ignore filepath
398 &queryPos, &queryLen, nullptr, nullptr);
399 if (NS_WARN_IF(aRv.Failed())) {
400 return;
401 }
402
403 if (!aUrlWithoutQueryOut) {
404 return;
405 }
406
407 MOZ_DIAGNOSTIC_ASSERT(aUrlQueryOut);
408
409 if (queryLen < 0) {
410 *aUrlWithoutQueryOut = aUrl;
411 *aUrlQueryOut = EmptyCString();
412 return;
413 }
414
415 // ParsePath gives us query position relative to the start of the path
416 queryPos += pathPos;
417
418 *aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1);
419 *aUrlQueryOut = Substring(aUrl, queryPos - 1, queryLen + 1);
420 }
421
CheckAndSetBodyUsed(JSContext * aCx,Request * aRequest,BodyAction aBodyAction,ErrorResult & aRv)422 void TypeUtils::CheckAndSetBodyUsed(JSContext* aCx, Request* aRequest,
423 BodyAction aBodyAction, ErrorResult& aRv) {
424 MOZ_DIAGNOSTIC_ASSERT(aRequest);
425
426 if (aBodyAction == IgnoreBody) {
427 return;
428 }
429
430 if (aRequest->BodyUsed()) {
431 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
432 return;
433 }
434
435 nsCOMPtr<nsIInputStream> stream;
436 aRequest->GetBody(getter_AddRefs(stream));
437 if (stream) {
438 aRequest->SetBodyUsed(aCx, aRv);
439 if (NS_WARN_IF(aRv.Failed())) {
440 return;
441 }
442 }
443 }
444
ToInternalRequest(const nsAString & aIn,ErrorResult & aRv)445 already_AddRefed<InternalRequest> TypeUtils::ToInternalRequest(
446 const nsAString& aIn, ErrorResult& aRv) {
447 RequestOrUSVString requestOrString;
448 requestOrString.SetAsUSVString().Rebind(aIn.Data(), aIn.Length());
449
450 // Re-create a GlobalObject stack object so we can use webidl Constructors.
451 AutoJSAPI jsapi;
452 if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
453 aRv.Throw(NS_ERROR_UNEXPECTED);
454 return nullptr;
455 }
456 JSContext* cx = jsapi.cx();
457 GlobalObject global(cx, GetGlobalObject()->GetGlobalJSObject());
458 MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
459
460 RefPtr<Request> request =
461 Request::Constructor(global, requestOrString, RequestInit(), aRv);
462 if (NS_WARN_IF(aRv.Failed())) {
463 return nullptr;
464 }
465
466 return request->GetInternalRequest();
467 }
468
SerializeCacheStream(nsIInputStream * aStream,CacheReadStreamOrVoid * aStreamOut,nsTArray<UniquePtr<AutoIPCStream>> & aStreamCleanupList,ErrorResult & aRv)469 void TypeUtils::SerializeCacheStream(
470 nsIInputStream* aStream, CacheReadStreamOrVoid* aStreamOut,
471 nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList, ErrorResult& aRv) {
472 *aStreamOut = void_t();
473 if (!aStream) {
474 return;
475 }
476
477 RefPtr<ReadStream> controlled = do_QueryObject(aStream);
478 if (controlled) {
479 controlled->Serialize(aStreamOut, aStreamCleanupList, aRv);
480 return;
481 }
482
483 *aStreamOut = CacheReadStream();
484 CacheReadStream& cacheStream = aStreamOut->get_CacheReadStream();
485
486 cacheStream.controlChild() = nullptr;
487 cacheStream.controlParent() = nullptr;
488
489 UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream(cacheStream.stream()));
490 autoStream->Serialize(aStream, GetIPCManager());
491
492 aStreamCleanupList.AppendElement(Move(autoStream));
493 }
494
495 } // namespace cache
496 } // namespace dom
497 } // namespace mozilla
498