tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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