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