TypeUtils.cpp (16197B)
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 <algorithm> 10 11 #include "mozilla/StaticPrefs_extensions.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/InputStreamUtils.h" 24 #include "mozilla/ipc/PBackgroundChild.h" 25 #include "nsCOMPtr.h" 26 #include "nsCharSeparatedTokenizer.h" 27 #include "nsHttp.h" 28 #include "nsIIPCSerializableInputStream.h" 29 #include "nsPromiseFlatString.h" 30 #include "nsQueryObject.h" 31 #include "nsStreamUtils.h" 32 #include "nsString.h" 33 #include "nsURLParsers.h" 34 35 namespace mozilla::dom::cache { 36 37 using mozilla::ipc::BackgroundChild; 38 using mozilla::ipc::FileDescriptor; 39 using mozilla::ipc::PBackgroundChild; 40 41 namespace { 42 43 static bool HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) { 44 nsCString varyHeaders; 45 ErrorResult rv; 46 aHeaders->Get("vary"_ns, varyHeaders, rv); 47 MOZ_ALWAYS_TRUE(!rv.Failed()); 48 49 for (const nsACString& header : 50 nsCCharSeparatedTokenizer(varyHeaders, NS_HTTP_HEADER_SEP).ToRange()) { 51 if (header.EqualsLiteral("*")) { 52 return true; 53 } 54 } 55 return false; 56 } 57 58 nsTArray<HeadersEntry> ToHeadersEntryList(InternalHeaders* aHeaders) { 59 MOZ_DIAGNOSTIC_ASSERT(aHeaders); 60 61 AutoTArray<InternalHeaders::Entry, 16> entryList; 62 aHeaders->GetEntries(entryList); 63 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); 69 }); 70 71 return result; 72 } 73 74 } // namespace 75 76 SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest( 77 JSContext* aCx, const RequestOrUTF8String& aIn, BodyAction aBodyAction, 78 ErrorResult& aRv) { 79 if (aIn.IsRequest()) { 80 Request& request = aIn.GetAsRequest(); 81 82 // Check and set bodyUsed flag immediately because its on Request 83 // instead of InternalRequest. 84 CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv); 85 if (aRv.Failed()) { 86 return nullptr; 87 } 88 89 return request.GetInternalRequest(); 90 } 91 92 return ToInternalRequest(aIn.GetAsUTF8String(), aRv); 93 } 94 95 SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest( 96 JSContext* aCx, const OwningRequestOrUTF8String& aIn, 97 BodyAction aBodyAction, ErrorResult& aRv) { 98 if (aIn.IsRequest()) { 99 Request& request = aIn.GetAsRequest(); 100 101 // Check and set bodyUsed flag immediately because its on Request 102 // instead of InternalRequest. 103 CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv); 104 if (aRv.Failed()) { 105 return nullptr; 106 } 107 108 return request.GetInternalRequest(); 109 } 110 111 return ToInternalRequest(aIn.GetAsUTF8String(), aRv); 112 } 113 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()); 119 bool schemeValid; 120 ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv); 121 if (aRv.Failed()) { 122 return; 123 } 124 if (!schemeValid) { 125 if (aSchemeAction == TypeErrorOnInvalidScheme) { 126 aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Request", url); 127 return; 128 } 129 } 130 aOut.urlFragment() = aIn.GetFragment(); 131 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(); 143 144 aOut.integrity() = aIn.GetIntegrity(); 145 aOut.loadingEmbedderPolicy() = aIn.GetEmbedderPolicy(); 146 const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo = 147 aIn.GetPrincipalInfo(); 148 if (principalInfo) { 149 aOut.principalInfo() = Some(*(principalInfo.get())); 150 } 151 152 if (aBodyAction == IgnoreBody) { 153 aOut.body() = Nothing(); 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(), aRv); 162 if (NS_WARN_IF(aRv.Failed())) { 163 return; 164 } 165 } 166 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 (aIn.Type() != ResponseType::Opaque && HasVaryStar(headers)) { 188 aRv.ThrowTypeError("Invalid Response object with a 'Vary: *' header."); 189 return; 190 } 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()); 196 } else { 197 aOut.principalInfo() = Nothing(); 198 } 199 200 aOut.paddingInfo() = aIn.GetPaddingInfo(); 201 aOut.paddingSize() = aIn.GetPaddingSize(); 202 } 203 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>(); 208 return; 209 } 210 211 SafeRefPtr<InternalResponse> ir = aIn.GetInternalResponse(); 212 ToCacheResponseWithoutBody(aOut, *ir, aRv); 213 if (NS_WARN_IF(aRv.Failed())) { 214 return; 215 } 216 217 nsCOMPtr<nsIInputStream> stream; 218 ir->GetUnfilteredBody(getter_AddRefs(stream)); 219 if (stream) { 220 aIn.SetBodyUsed(aCx, aRv); 221 if (NS_WARN_IF(aRv.Failed())) { 222 return; 223 } 224 } 225 226 SerializeCacheStream(stream, &aOut.body(), aRv); 227 if (NS_WARN_IF(aRv.Failed())) { 228 return; 229 } 230 } 231 232 // static 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; 238 } 239 240 // static 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(); 247 } else { 248 aOut.cacheName() = u""_ns; 249 } 250 } 251 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... 255 RefPtr<Response> r = 256 new Response(GetGlobalObject(), 257 InternalResponse::NetworkError(NS_ERROR_FAILURE), nullptr); 258 return r.forget(); 259 } 260 261 SafeRefPtr<InternalResponse> ir = 262 MakeSafeRefPtr<InternalResponse>(aIn.status(), aIn.statusText()); 263 ir->SetURLList(aIn.urlList()); 264 265 RefPtr<InternalHeaders> internalHeaders = 266 ToInternalHeaders(aIn.headers(), aIn.headersGuard()); 267 ErrorResult result; 268 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()); 275 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)); 281 } 282 283 nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body()); 284 ir->SetBody(stream, InternalResponse::UNKNOWN_BODY_SIZE); 285 286 switch (aIn.type()) { 287 case ResponseType::Basic: 288 ir = ir->BasicResponse(); 289 break; 290 case ResponseType::Cors: 291 ir = ir->CORSResponse(); 292 break; 293 case ResponseType::Default: 294 break; 295 case ResponseType::Opaque: 296 ir = ir->OpaqueResponse(); 297 break; 298 case ResponseType::Opaqueredirect: 299 ir = ir->OpaqueRedirectResponse(); 300 break; 301 default: 302 MOZ_CRASH("Unexpected ResponseType!"); 303 } 304 MOZ_DIAGNOSTIC_ASSERT(ir); 305 306 ir->SetPaddingSize(aIn.paddingSize()); 307 308 RefPtr<Response> ref = 309 new Response(GetGlobalObject(), std::move(ir), nullptr); 310 return ref.forget(); 311 } 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()); 327 328 RefPtr<InternalHeaders> internalHeaders = 329 ToInternalHeaders(aIn.headers(), aIn.headersGuard()); 330 ErrorResult result; 331 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()); 336 337 internalRequest->Headers()->SetGuard(aIn.headersGuard(), result); 338 MOZ_DIAGNOSTIC_ASSERT(!result.Failed()); 339 340 nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body()); 341 342 internalRequest->SetBody(stream, -1); 343 344 return internalRequest; 345 } 346 347 SafeRefPtr<Request> TypeUtils::ToRequest(const CacheRequest& aIn) { 348 return MakeSafeRefPtr<Request>(GetGlobalObject(), ToInternalRequest(aIn), 349 nullptr); 350 } 351 352 // static 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()); 361 }); 362 363 RefPtr<InternalHeaders> ref = 364 new InternalHeaders(std::move(entryList), aGuard); 365 return ref.forget(); 366 } 367 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(); 376 377 // off the main thread URL parsing using nsStdURLParser. 378 nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser(); 379 380 uint32_t pathPos; 381 int32_t pathLen; 382 uint32_t schemePos; 383 int32_t schemeLen; 384 aRv = urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen, 385 nullptr, nullptr, // ignore authority 386 &pathPos, &pathLen); 387 if (NS_WARN_IF(aRv.Failed())) { 388 return; 389 } 390 391 if (aSchemeValidOut) { 392 nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen)); 393 *aSchemeValidOut = 394 scheme.LowerCaseEqualsLiteral("http") || 395 scheme.LowerCaseEqualsLiteral("https") || 396 (StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup() && 397 scheme.LowerCaseEqualsLiteral("moz-extension")); 398 } 399 400 uint32_t queryPos; 401 int32_t queryLen; 402 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())) { 407 return; 408 } 409 410 if (!aUrlWithoutQueryOut) { 411 return; 412 } 413 414 MOZ_DIAGNOSTIC_ASSERT(aUrlQueryOut); 415 416 if (queryLen < 0) { 417 *aUrlWithoutQueryOut = aUrl; 418 aUrlQueryOut->Truncate(); 419 return; 420 } 421 422 // ParsePath gives us query position relative to the start of the path 423 queryPos += pathPos; 424 425 *aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1); 426 *aUrlQueryOut = Substring(aUrl, queryPos - 1, queryLen + 1); 427 } 428 429 void TypeUtils::CheckAndSetBodyUsed(JSContext* aCx, Request& aRequest, 430 BodyAction aBodyAction, ErrorResult& aRv) { 431 if (aBodyAction == IgnoreBody) { 432 return; 433 } 434 435 if (aRequest.BodyUsed()) { 436 aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); 437 return; 438 } 439 440 nsCOMPtr<nsIInputStream> stream; 441 aRequest.GetBody(getter_AddRefs(stream)); 442 if (stream) { 443 aRequest.SetBodyUsed(aCx, aRv); 444 if (NS_WARN_IF(aRv.Failed())) { 445 return; 446 } 447 } 448 } 449 450 SafeRefPtr<InternalRequest> TypeUtils::ToInternalRequest(const nsACString& aIn, 451 ErrorResult& aRv) { 452 RequestOrUTF8String requestOrString; 453 requestOrString.SetAsUTF8String().ShareOrDependUpon(aIn); 454 455 // Re-create a GlobalObject stack object so we can use webidl Constructors. 456 AutoJSAPI jsapi; 457 if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) { 458 aRv.Throw(NS_ERROR_UNEXPECTED); 459 return nullptr; 460 } 461 JSContext* cx = jsapi.cx(); 462 GlobalObject global(cx, GetGlobalObject()->GetGlobalJSObject()); 463 MOZ_DIAGNOSTIC_ASSERT(!global.Failed()); 464 465 RootedDictionary<RequestInit> requestInit(cx); 466 SafeRefPtr<Request> request = 467 Request::Constructor(global, requestOrString, requestInit, aRv); 468 if (NS_WARN_IF(aRv.Failed())) { 469 return nullptr; 470 } 471 472 return request->GetInternalRequest(); 473 } 474 475 void TypeUtils::SerializeCacheStream(nsIInputStream* aStream, 476 Maybe<CacheReadStream>* aStreamOut, 477 ErrorResult& aRv) { 478 *aStreamOut = Nothing(); 479 if (!aStream) { 480 return; 481 } 482 483 RefPtr<ReadStream> controlled = do_QueryObject(aStream); 484 if (controlled) { 485 controlled->Serialize(aStreamOut, aRv); 486 return; 487 } 488 489 aStreamOut->emplace(CacheReadStream()); 490 CacheReadStream& cacheStream = aStreamOut->ref(); 491 492 cacheStream.control() = nullptr; 493 494 MOZ_ALWAYS_TRUE(mozilla::ipc::SerializeIPCStream(do_AddRef(aStream), 495 cacheStream.stream(), 496 /* aAllowLazy */ false)); 497 } 498 499 } // namespace mozilla::dom::cache