NetworkLoadHandler.cpp (16452B)
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 "NetworkLoadHandler.h" 8 9 #include "CacheLoadHandler.h" // CachePromiseHandler 10 #include "js/loader/ModuleLoadRequest.h" 11 #include "js/loader/ScriptLoadRequest.h" 12 #include "mozilla/Encoding.h" 13 #include "mozilla/StaticPrefs_javascript.h" 14 #include "mozilla/dom/BlobURLProtocolHandler.h" 15 #include "mozilla/dom/InternalResponse.h" 16 #include "mozilla/dom/Response.h" 17 #include "mozilla/dom/ScriptLoader.h" 18 #include "mozilla/dom/ServiceWorkerBinding.h" 19 #include "mozilla/dom/ServiceWorkerManager.h" 20 #include "mozilla/dom/WorkerScope.h" 21 #include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader 22 #include "nsContentUtils.h" 23 #include "nsIChannel.h" 24 #include "nsIHttpChannel.h" 25 #include "nsIHttpChannelInternal.h" 26 #include "nsIPrincipal.h" 27 #include "nsIScriptError.h" 28 #include "nsNetUtil.h" 29 30 using mozilla::ipc::PrincipalInfo; 31 32 namespace mozilla { 33 namespace dom { 34 35 namespace workerinternals::loader { 36 37 NS_IMPL_ISUPPORTS(NetworkLoadHandler, nsIStreamLoaderObserver, 38 nsIRequestObserver) 39 40 NetworkLoadHandler::NetworkLoadHandler(WorkerScriptLoader* aLoader, 41 ThreadSafeRequestHandle* aRequestHandle) 42 : mLoader(aLoader), 43 mWorkerRef(aLoader->mWorkerRef), 44 mRequestHandle(aRequestHandle) { 45 MOZ_ASSERT(mLoader); 46 47 // Worker scripts are always decoded as UTF-8 per spec. 48 mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING, 49 ScriptDecoder::BOMHandling::Remove); 50 } 51 52 NS_IMETHODIMP 53 NetworkLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader, 54 nsISupports* aContext, nsresult aStatus, 55 uint32_t aStringLen, 56 const uint8_t* aString) { 57 // If we have cancelled, or we have no mRequest, it means that the loader has 58 // shut down and we can exit early. If the cancel result is still NS_OK 59 if (mRequestHandle->IsEmpty()) { 60 return NS_OK; 61 } 62 nsresult rv = DataReceivedFromNetwork(aLoader, aStatus, aStringLen, aString); 63 return mRequestHandle->OnStreamComplete(rv); 64 } 65 66 nsresult NetworkLoadHandler::DataReceivedFromNetwork(nsIStreamLoader* aLoader, 67 nsresult aStatus, 68 uint32_t aStringLen, 69 const uint8_t* aString) { 70 AssertIsOnMainThread(); 71 MOZ_ASSERT(!mRequestHandle->IsEmpty()); 72 73 if (aStringLen > GetWorkerScriptMaxSizeInBytes()) { 74 Document* parentDoc = mWorkerRef->Private()->GetDocument(); 75 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, 76 parentDoc, nsContentUtils::eDOM_PROPERTIES, 77 "WorkerScriptTooLargeError"); 78 return NS_ERROR_DOM_ABORT_ERR; 79 } 80 81 WorkerLoadContext* loadContext = mRequestHandle->GetContext(); 82 83 if (!loadContext->mChannel) { 84 return NS_BINDING_ABORTED; 85 } 86 87 #ifdef NIGHTLY_BUILD 88 if (StaticPrefs::javascript_options_experimental_wasm_esm_integration()) { 89 if (mRequestHandle->GetRequest()->IsModuleRequest()) { 90 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script 91 // Extract the content-type. If its essence is wasm, we'll attempt to 92 // compile this module as a wasm module. (Steps 13.2, 13.6) 93 nsAutoCString mimeType; 94 if (NS_SUCCEEDED(loadContext->mChannel->GetContentType(mimeType))) { 95 if (nsContentUtils::HasWasmMimeTypeEssence( 96 NS_ConvertUTF8toUTF16(mimeType))) { 97 mRequestHandle->GetRequest() 98 ->AsModuleRequest() 99 ->SetHasWasmMimeTypeEssence(); 100 } 101 } 102 } 103 } 104 #endif 105 106 loadContext->mChannel = nullptr; 107 108 if (NS_FAILED(aStatus)) { 109 return aStatus; 110 } 111 112 if (mRequestHandle->IsCancelled()) { 113 return mRequestHandle->GetCancelResult(); 114 } 115 116 NS_ASSERTION(aString, "This should never be null!"); 117 118 nsCOMPtr<nsIRequest> request; 119 nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); 120 NS_ENSURE_SUCCESS(rv, rv); 121 122 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 123 MOZ_ASSERT(channel); 124 125 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 126 NS_ASSERTION(ssm, "Should never be null!"); 127 128 nsCOMPtr<nsIPrincipal> channelPrincipal; 129 rv = 130 ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); 131 if (NS_WARN_IF(NS_FAILED(rv))) { 132 return rv; 133 } 134 135 nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); 136 if (!principal) { 137 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); 138 MOZ_ASSERT(parentWorker, "Must have a parent!"); 139 principal = parentWorker->GetPrincipal(); 140 } 141 142 #ifdef DEBUG 143 if (loadContext->IsTopLevel()) { 144 nsCOMPtr<nsIPrincipal> loadingPrincipal = 145 mWorkerRef->Private()->GetLoadingPrincipal(); 146 // if we are not in a ServiceWorker, and the principal is not null, then 147 // the loading principal must subsume the worker principal if it is not a 148 // nullPrincipal (sandbox). 149 MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() || 150 principal->GetIsNullPrincipal() || 151 loadingPrincipal->Subsumes(principal)); 152 } 153 #endif 154 155 // We don't mute the main worker script becase we've already done 156 // same-origin checks on them so we should be able to see their errors. 157 // Note that for data: url, where we allow it through the same-origin check 158 // but then give it a different origin. 159 loadContext->mMutedErrorFlag.emplace(!loadContext->IsTopLevel() && 160 !principal->Subsumes(channelPrincipal)); 161 162 // Make sure we're not seeing the result of a 404 or something by checking 163 // the 'requestSucceeded' attribute on the http channel. 164 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); 165 nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue; 166 167 if (httpChannel) { 168 bool requestSucceeded; 169 rv = httpChannel->GetRequestSucceeded(&requestSucceeded); 170 NS_ENSURE_SUCCESS(rv, rv); 171 172 if (!requestSucceeded) { 173 return NS_ERROR_NOT_AVAILABLE; 174 } 175 176 (void)httpChannel->GetResponseHeader("content-security-policy"_ns, 177 tCspHeaderValue); 178 179 (void)httpChannel->GetResponseHeader( 180 "content-security-policy-report-only"_ns, tCspROHeaderValue); 181 182 (void)httpChannel->GetResponseHeader("referrer-policy"_ns, tRPHeaderCValue); 183 184 nsAutoCString sourceMapURL; 185 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) { 186 loadContext->mRequest->SetSourceMapURL( 187 NS_ConvertUTF8toUTF16(sourceMapURL)); 188 } 189 } 190 191 // May be null. 192 Document* parentDoc = mWorkerRef->Private()->GetDocument(); 193 194 // Set the Source type to "text" for decoding. 195 loadContext->mRequest->SetTextSource(loadContext); 196 197 // Use the regular ScriptDecoder Decoder for this grunt work! Should be just 198 // fine because we're running on the main thread. 199 rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen, 200 /* aEndOfStream = */ true); 201 NS_ENSURE_SUCCESS(rv, rv); 202 203 if (!loadContext->mRequest->ScriptTextLength()) { 204 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, 205 parentDoc, nsContentUtils::eDOM_PROPERTIES, 206 "EmptyWorkerSourceWarning"); 207 } 208 209 // For modules, we need to store the base URI on the module request object, 210 // rather than on the worker private (as we do for classic scripts). This is 211 // because module loading is shared across multiple components, with 212 // ScriptLoadRequests being the common structure among them. This specific 213 // use of the base url is used when resolving the module specifier for child 214 // modules. 215 nsCOMPtr<nsIURI> uri; 216 rv = channel->GetOriginalURI(getter_AddRefs(uri)); 217 NS_ENSURE_SUCCESS(rv, rv); 218 219 loadContext->mRequest->SetBaseURLFromChannelAndOriginalURI(channel, uri); 220 221 // Figure out what we actually loaded. 222 nsCOMPtr<nsIURI> finalURI; 223 rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI)); 224 NS_ENSURE_SUCCESS(rv, rv); 225 226 if (principal->IsSameOrigin(finalURI)) { 227 nsCString filename; 228 rv = finalURI->GetSpec(filename); 229 NS_ENSURE_SUCCESS(rv, rv); 230 231 if (!filename.IsEmpty()) { 232 // This will help callers figure out what their script url resolved to 233 // in case of errors, and is used for debugging. 234 // The full URL shouldn't be exposed to the debugger if cross origin. 235 // See Bug 1634872. 236 loadContext->mRequest->mURL = filename; 237 } 238 } 239 240 // Update the principal of the worker and its base URI if we just loaded the 241 // worker's primary script. 242 bool isDynamic = loadContext->mRequest->IsModuleRequest() && 243 loadContext->mRequest->AsModuleRequest()->IsDynamicImport(); 244 if (loadContext->IsTopLevel() && !isDynamic) { 245 // Take care of the base URI first. 246 mWorkerRef->Private()->SetBaseURI(finalURI); 247 248 // Store the channel info if needed. 249 mWorkerRef->Private()->InitChannelInfo(channel); 250 251 // Our final channel principal should match the loading principal 252 // in terms of the origin. This used to be an assert, but it seems 253 // there are some rare cases where this check can fail in practice. 254 // Perhaps some browser script setting nsIChannel.owner, etc. 255 NS_ENSURE_TRUE(mWorkerRef->Private()->FinalChannelPrincipalIsValid(channel), 256 NS_ERROR_FAILURE); 257 258 // However, we must still override the principal since the nsIPrincipal 259 // URL may be different due to same-origin redirects. Unfortunately this 260 // URL must exactly match the final worker script URL in order to 261 // properly set the referrer header on fetch/xhr requests. If bug 1340694 262 // is ever fixed this can be removed. 263 rv = mWorkerRef->Private()->SetPrincipalsAndCSPFromChannel(channel); 264 NS_ENSURE_SUCCESS(rv, rv); 265 266 nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerRef->Private()->GetCsp(); 267 // We did inherit CSP in bug 1223647. If we do not already have a CSP, we 268 // should get it from the HTTP headers on the worker script. 269 if (!csp) { 270 rv = mWorkerRef->Private()->SetCSPFromHeaderValues(tCspHeaderValue, 271 tCspROHeaderValue); 272 NS_ENSURE_SUCCESS(rv, rv); 273 } else { 274 csp->EnsureEventTarget(mWorkerRef->Private()->MainThreadEventTarget()); 275 } 276 277 mWorkerRef->Private()->UpdateReferrerInfoFromHeader(tRPHeaderCValue); 278 279 WorkerPrivate* parent = mWorkerRef->Private()->GetParent(); 280 if (parent) { 281 // XHR Params Allowed 282 mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed()); 283 } 284 285 nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo(); 286 if (chanLoadInfo) { 287 mLoader->SetController(chanLoadInfo->GetController()); 288 } 289 290 // If we are loading a blob URL we must inherit the controller 291 // from the parent. This is a bit odd as the blob URL may have 292 // been created in a different context with a different controller. 293 // For now, though, this is what the spec says. See: 294 // 295 // https://github.com/w3c/ServiceWorker/issues/1261 296 // 297 if (IsBlobURI(mWorkerRef->Private()->GetBaseURI())) { 298 MOZ_DIAGNOSTIC_ASSERT(mLoader->GetController().isNothing()); 299 mLoader->SetController(mWorkerRef->Private()->GetParentController()); 300 } 301 } 302 303 return NS_OK; 304 } 305 306 NS_IMETHODIMP 307 NetworkLoadHandler::OnStartRequest(nsIRequest* aRequest) { 308 nsresult rv = PrepareForRequest(aRequest); 309 310 if (NS_WARN_IF(NS_FAILED(rv))) { 311 aRequest->Cancel(rv); 312 } 313 314 return rv; 315 } 316 317 nsresult NetworkLoadHandler::PrepareForRequest(nsIRequest* aRequest) { 318 AssertIsOnMainThread(); 319 MOZ_ASSERT(!mRequestHandle->IsEmpty()); 320 WorkerLoadContext* loadContext = mRequestHandle->GetContext(); 321 322 // If one load info cancels or hits an error, it can race with the start 323 // callback coming from another load info. 324 if (mRequestHandle->IsCancelled()) { 325 return NS_ERROR_FAILURE; 326 } 327 328 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 329 330 // Checking the MIME type is only required for ServiceWorkers' 331 // importScripts, per step 10 of 332 // https://w3c.github.io/ServiceWorker/#importscripts 333 // 334 // "Extract a MIME type from the response’s header list. If this MIME type 335 // (ignoring parameters) is not a JavaScript MIME type, return a network 336 // error." 337 if (mWorkerRef->Private()->IsServiceWorker()) { 338 nsAutoCString mimeType; 339 channel->GetContentType(mimeType); 340 341 auto mimeTypeUTF16 = NS_ConvertUTF8toUTF16(mimeType); 342 if (!nsContentUtils::IsJavascriptMIMEType(mimeTypeUTF16)) { 343 // JSON is allowed as a non-toplevel. 344 if (!((!loadContext->IsTopLevel() && 345 nsContentUtils::IsJsonMimeType(mimeTypeUTF16)) 346 #ifdef NIGHTLY_BUILD 347 // Allow wasm modules. 348 || (StaticPrefs:: 349 javascript_options_experimental_wasm_esm_integration() && 350 nsContentUtils::HasWasmMimeTypeEssence(mimeTypeUTF16)) 351 #endif 352 )) { 353 const nsCString& scope = mWorkerRef->Private() 354 ->GetServiceWorkerRegistrationDescriptor() 355 .Scope(); 356 357 ServiceWorkerManager::LocalizeAndReportToAllClients( 358 scope, "ServiceWorkerRegisterMimeTypeError2", 359 nsTArray<nsString>{ 360 NS_ConvertUTF8toUTF16(scope), NS_ConvertUTF8toUTF16(mimeType), 361 NS_ConvertUTF8toUTF16(loadContext->mRequest->mURL)}); 362 363 return NS_ERROR_DOM_NETWORK_ERR; 364 } 365 } 366 } 367 368 // We synthesize the result code, but its never exposed to content. 369 SafeRefPtr<mozilla::dom::InternalResponse> ir = 370 MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns); 371 ir->SetBody(loadContext->mCacheReadStream, 372 InternalResponse::UNKNOWN_BODY_SIZE); 373 374 // Drop our reference to the stream now that we've passed it along, so it 375 // doesn't hang around once the cache is done with it and keep data alive. 376 loadContext->mCacheReadStream = nullptr; 377 378 // Set the channel info of the channel on the response so that it's 379 // saved in the cache. 380 ir->InitChannelInfo(channel); 381 382 // Save the principal of the channel since its URI encodes the script URI 383 // rather than the ServiceWorkerRegistrationInfo URI. 384 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 385 NS_ASSERTION(ssm, "Should never be null!"); 386 387 nsCOMPtr<nsIPrincipal> channelPrincipal; 388 MOZ_TRY(ssm->GetChannelResultPrincipal(channel, 389 getter_AddRefs(channelPrincipal))); 390 391 UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo()); 392 MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get())); 393 394 ir->SetPrincipalInfo(std::move(principalInfo)); 395 ir->Headers()->FillResponseHeaders(channel); 396 397 RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response( 398 mRequestHandle->GetCacheCreator()->Global(), std::move(ir), nullptr); 399 400 mozilla::dom::RequestOrUTF8String request; 401 402 MOZ_ASSERT(!loadContext->mFullURL.IsEmpty()); 403 request.SetAsUTF8String().ShareOrDependUpon(loadContext->mFullURL); 404 405 // This JSContext will not end up executing JS code because here there are 406 // no ReadableStreams involved. 407 AutoJSAPI jsapi; 408 jsapi.Init(); 409 410 ErrorResult error; 411 RefPtr<Promise> cachePromise = 412 mRequestHandle->GetCacheCreator()->Cache_()->Put(jsapi.cx(), request, 413 *response, error); 414 error.WouldReportJSException(); 415 if (NS_WARN_IF(error.Failed())) { 416 return error.StealNSResult(); 417 } 418 419 RefPtr<CachePromiseHandler> promiseHandler = 420 new CachePromiseHandler(mLoader, mRequestHandle); 421 cachePromise->AppendNativeHandler(promiseHandler); 422 423 loadContext->mCachePromise.swap(cachePromise); 424 loadContext->mCacheStatus = WorkerLoadContext::WritingToCache; 425 426 return NS_OK; 427 } 428 429 } // namespace workerinternals::loader 430 431 } // namespace dom 432 } // namespace mozilla