tor-browser

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

ScriptLoadHandler.cpp (16668B)


      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 "ScriptLoadHandler.h"
      8 
      9 #include <stdlib.h>
     10 
     11 #include <utility>
     12 
     13 #include "ScriptCompression.h"
     14 #include "ScriptLoader.h"
     15 #include "ScriptTrace.h"
     16 #include "js/Transcoding.h"
     17 #include "js/loader/ScriptLoadRequest.h"
     18 #include "mozilla/Assertions.h"
     19 #include "mozilla/CheckedInt.h"
     20 #include "mozilla/DebugOnly.h"
     21 #include "mozilla/Encoding.h"
     22 #include "mozilla/Logging.h"
     23 #include "mozilla/PerfStats.h"
     24 #include "mozilla/ScopeExit.h"
     25 #include "mozilla/SharedSubResourceCache.h"
     26 #include "mozilla/StaticPrefs_dom.h"
     27 #include "mozilla/Utf8.h"
     28 #include "mozilla/Vector.h"
     29 #include "mozilla/dom/CacheExpirationTime.h"
     30 #include "mozilla/dom/Document.h"
     31 #include "mozilla/dom/SRICheck.h"
     32 #include "mozilla/dom/ScriptDecoding.h"
     33 #include "nsCOMPtr.h"
     34 #include "nsContentUtils.h"
     35 #include "nsDebug.h"
     36 #include "nsIAsyncVerifyRedirectCallback.h"
     37 #include "nsICacheInfoChannel.h"
     38 #include "nsIChannel.h"
     39 #include "nsIHttpChannel.h"
     40 #include "nsIRequest.h"
     41 #include "nsIScriptElement.h"
     42 #include "nsIURI.h"
     43 #include "nsJSUtils.h"
     44 #include "nsMimeTypes.h"
     45 #include "nsString.h"
     46 #include "nsTArray.h"
     47 #include "zlib.h"
     48 
     49 namespace mozilla::dom {
     50 
     51 #undef LOG
     52 #define LOG(args) \
     53  MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
     54 
     55 #define LOG_ENABLED() \
     56  MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
     57 
     58 ScriptDecoder::ScriptDecoder(const Encoding* aEncoding,
     59                             ScriptDecoder::BOMHandling handleBOM) {
     60  if (handleBOM == BOMHandling::Ignore) {
     61    mDecoder = aEncoding->NewDecoderWithoutBOMHandling();
     62  } else {
     63    mDecoder = aEncoding->NewDecoderWithBOMRemoval();
     64  }
     65  MOZ_ASSERT(mDecoder);
     66 }
     67 
     68 template <typename Unit>
     69 nsresult ScriptDecoder::DecodeRawDataHelper(
     70    JS::loader::ScriptLoadRequest* aRequest, const uint8_t* aData,
     71    uint32_t aDataLength, bool aEndOfStream) {
     72  CheckedInt<size_t> needed =
     73      ScriptDecoding<Unit>::MaxBufferLength(mDecoder, aDataLength);
     74  if (!needed.isValid()) {
     75    return NS_ERROR_OUT_OF_MEMORY;
     76  }
     77 
     78  // Reference to the script source buffer which we will update.
     79  JS::loader::ScriptLoadRequest::ScriptTextBuffer<Unit>& scriptText =
     80      aRequest->ScriptText<Unit>();
     81 
     82  uint32_t haveRead = scriptText.length();
     83 
     84  CheckedInt<uint32_t> capacity = haveRead;
     85  capacity += needed.value();
     86 
     87  if (!capacity.isValid() || !scriptText.resize(capacity.value())) {
     88    return NS_ERROR_OUT_OF_MEMORY;
     89  }
     90 
     91  size_t written = ScriptDecoding<Unit>::DecodeInto(
     92      mDecoder, Span(aData, aDataLength),
     93      Span(scriptText.begin() + haveRead, needed.value()), aEndOfStream);
     94  MOZ_ASSERT(written <= needed.value());
     95 
     96  haveRead += written;
     97  MOZ_ASSERT(haveRead <= capacity.value(),
     98             "mDecoder produced more data than expected");
     99  MOZ_ALWAYS_TRUE(scriptText.resize(haveRead));
    100  aRequest->SetReceivedScriptTextLength(scriptText.length());
    101 
    102  return NS_OK;
    103 }
    104 
    105 nsresult ScriptDecoder::DecodeRawData(JS::loader::ScriptLoadRequest* aRequest,
    106                                      const uint8_t* aData,
    107                                      uint32_t aDataLength, bool aEndOfStream) {
    108  if (aRequest->IsUTF16Text()) {
    109    return DecodeRawDataHelper<char16_t>(aRequest, aData, aDataLength,
    110                                         aEndOfStream);
    111  }
    112 
    113  return DecodeRawDataHelper<Utf8Unit>(aRequest, aData, aDataLength,
    114                                       aEndOfStream);
    115 }
    116 
    117 ScriptLoadHandler::ScriptLoadHandler(
    118    ScriptLoader* aScriptLoader, JS::loader::ScriptLoadRequest* aRequest,
    119    UniquePtr<SRICheckDataVerifier>&& aSRIDataVerifier)
    120    : mScriptLoader(aScriptLoader),
    121      mRequest(aRequest),
    122      mSRIDataVerifier(std::move(aSRIDataVerifier)),
    123      mSRIStatus(NS_OK) {
    124  MOZ_ASSERT(aRequest->IsUnknownDataType());
    125  MOZ_ASSERT(aRequest->IsFetching());
    126 }
    127 
    128 ScriptLoadHandler::~ScriptLoadHandler() = default;
    129 
    130 NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver,
    131                  nsIChannelEventSink, nsIInterfaceRequestor)
    132 
    133 NS_IMETHODIMP
    134 ScriptLoadHandler::OnStartRequest(nsIRequest* aRequest) {
    135  mRequest->SetMinimumExpirationTime(
    136      nsContentUtils::GetSubresourceCacheExpirationTime(aRequest,
    137                                                        mRequest->URI()));
    138 
    139  return NS_OK;
    140 }
    141 
    142 NS_IMETHODIMP
    143 ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
    144                                     nsISupports* aContext,
    145                                     uint32_t aDataLength, const uint8_t* aData,
    146                                     uint32_t* aConsumedLength) {
    147  nsCOMPtr<nsIRequest> channelRequest;
    148  aLoader->GetRequest(getter_AddRefs(channelRequest));
    149 
    150  auto firstTime = !mPreloadStartNotified;
    151  if (!mPreloadStartNotified) {
    152    mPreloadStartNotified = true;
    153    mRequest->GetScriptLoadContext()->NotifyStart(channelRequest);
    154  }
    155 
    156  if (mRequest->IsCanceled()) {
    157    // If request cancelled, ignore any incoming data.
    158    *aConsumedLength = aDataLength;
    159    return NS_OK;
    160  }
    161 
    162  nsresult rv = NS_OK;
    163  if (mRequest->IsUnknownDataType()) {
    164    rv = EnsureKnownDataType(aLoader);
    165    NS_ENSURE_SUCCESS(rv, rv);
    166  }
    167 
    168  if (mRequest->IsSerializedStencil() && firstTime) {
    169    PerfStats::RecordMeasurementStart(PerfStats::Metric::JSBC_IO_Read);
    170  }
    171 
    172  if (mRequest->IsTextSource()) {
    173    if (!EnsureDecoder(aLoader, aData, aDataLength,
    174                       /* aEndOfStream = */ false)) {
    175      return NS_OK;
    176    }
    177 
    178    // Below we will/shall consume entire data chunk.
    179    *aConsumedLength = aDataLength;
    180 
    181    // Decoder has already been initialized. -- trying to decode all loaded
    182    // bytes.
    183    rv = mDecoder->DecodeRawData(mRequest, aData, aDataLength,
    184                                 /* aEndOfStream = */ false);
    185    NS_ENSURE_SUCCESS(rv, rv);
    186 
    187    // If SRI is required for this load, appending new bytes to the hash.
    188    if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
    189      mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
    190    }
    191  } else {
    192    MOZ_ASSERT(mRequest->IsSerializedStencil());
    193    if (!mRequest->SRIAndSerializedStencil().append(aData, aDataLength)) {
    194      return NS_ERROR_OUT_OF_MEMORY;
    195    }
    196 
    197    *aConsumedLength = aDataLength;
    198    uint32_t sriLength = 0;
    199    rv = MaybeDecodeSRI(&sriLength);
    200    if (NS_FAILED(rv)) {
    201      return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
    202    }
    203    if (sriLength) {
    204      mRequest->SetSRILength(sriLength);
    205    }
    206  }
    207 
    208  return rv;
    209 }
    210 
    211 bool ScriptLoadHandler::TrySetDecoder(nsIIncrementalStreamLoader* aLoader,
    212                                      const uint8_t* aData,
    213                                      uint32_t aDataLength, bool aEndOfStream) {
    214  MOZ_ASSERT(mDecoder == nullptr,
    215             "can't have a decoder already if we're trying to set one");
    216 
    217  // JavaScript modules are always UTF-8.
    218  if (mRequest->IsModuleRequest()) {
    219    mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING,
    220                                         ScriptDecoder::BOMHandling::Remove);
    221    return true;
    222  }
    223 
    224  // Determine if BOM check should be done.  This occurs either
    225  // if end-of-stream has been reached, or at least 3 bytes have
    226  // been read from input.
    227  if (!aEndOfStream && (aDataLength < 3)) {
    228    return false;
    229  }
    230 
    231  // Do BOM detection.
    232  const Encoding* encoding;
    233  std::tie(encoding, std::ignore) = Encoding::ForBOM(Span(aData, aDataLength));
    234  if (encoding) {
    235    mDecoder =
    236        MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Remove);
    237    return true;
    238  }
    239 
    240  // BOM detection failed, check content stream for charset.
    241  nsCOMPtr<nsIRequest> req;
    242  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
    243  NS_ASSERTION(req, "StreamLoader's request went away prematurely");
    244  NS_ENSURE_SUCCESS(rv, false);
    245 
    246  nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
    247 
    248  if (channel) {
    249    nsAutoCString label;
    250    if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
    251        (encoding = Encoding::ForLabel(label))) {
    252      mDecoder = MakeUnique<ScriptDecoder>(encoding,
    253                                           ScriptDecoder::BOMHandling::Ignore);
    254      return true;
    255    }
    256  }
    257 
    258  // Check the hint charset from the script element or preload
    259  // request.
    260  nsAutoString hintCharset;
    261  if (!mRequest->GetScriptLoadContext()->IsPreload()) {
    262    mRequest->GetScriptLoadContext()->GetHintCharset(hintCharset);
    263  } else {
    264    nsTArray<ScriptLoader::PreloadInfo>::index_type i =
    265        mScriptLoader->mPreloads.IndexOf(
    266            mRequest, 0, ScriptLoader::PreloadRequestComparator());
    267 
    268    NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
    269                 "Incorrect preload bookkeeping");
    270    hintCharset = mScriptLoader->mPreloads[i].mCharset;
    271  }
    272 
    273  if ((encoding = Encoding::ForLabel(hintCharset))) {
    274    mDecoder =
    275        MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore);
    276    return true;
    277  }
    278 
    279  // Get the charset from the charset of the document.
    280  if (mScriptLoader->mDocument) {
    281    encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
    282    mDecoder =
    283        MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore);
    284    return true;
    285  }
    286 
    287  // Curiously, there are various callers that don't pass aDocument. The
    288  // fallback in the old code was ISO-8859-1, which behaved like
    289  // windows-1252.
    290  mDecoder = MakeUnique<ScriptDecoder>(WINDOWS_1252_ENCODING,
    291                                       ScriptDecoder::BOMHandling::Ignore);
    292  return true;
    293 }
    294 
    295 nsresult ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength) {
    296  *sriLength = 0;
    297 
    298  if (!mSRIDataVerifier || mSRIDataVerifier->IsInvalid() ||
    299      mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) {
    300    return NS_OK;
    301  }
    302 
    303  // Skip until the content is large enough to be decoded.
    304  JS::TranscodeBuffer& receivedData = mRequest->SRIAndSerializedStencil();
    305  if (receivedData.length() <= mSRIDataVerifier->DataSummaryLength()) {
    306    return NS_OK;
    307  }
    308 
    309  mSRIStatus = mSRIDataVerifier->ImportDataSummary(receivedData.length(),
    310                                                   receivedData.begin());
    311 
    312  if (NS_FAILED(mSRIStatus)) {
    313    // We are unable to decode the hash contained in the alternate data which
    314    // contains the serialized Stencil, or it does not use the same algorithm.
    315    LOG(
    316        ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
    317         "request"));
    318    return mSRIStatus;
    319  }
    320 
    321  *sriLength = mSRIDataVerifier->DataSummaryLength();
    322  MOZ_ASSERT(*sriLength > 0);
    323  return NS_OK;
    324 }
    325 
    326 nsresult ScriptLoadHandler::EnsureKnownDataType(
    327    nsIIncrementalStreamLoader* aLoader) {
    328  MOZ_ASSERT(mRequest->IsUnknownDataType());
    329  MOZ_ASSERT(mRequest->IsFetching());
    330 
    331  nsCOMPtr<nsIRequest> req;
    332  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
    333  MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
    334  NS_ENSURE_SUCCESS(rv, rv);
    335 
    336  if (mRequest->mFetchSourceOnly) {
    337    mRequest->SetTextSource(mRequest->mLoadContext.get());
    338    TRACE_FOR_TEST(mRequest, "load:source");
    339    return NS_OK;
    340  }
    341 
    342  nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
    343  if (cic) {
    344    nsAutoCString altDataType;
    345    cic->GetAlternativeDataType(altDataType);
    346    if (altDataType.Equals(ScriptLoader::BytecodeMimeTypeFor(mRequest))) {
    347      mRequest->SetSerializedStencil();
    348      TRACE_FOR_TEST(mRequest, "load:diskcache");
    349      return NS_OK;
    350    }
    351    MOZ_ASSERT(altDataType.IsEmpty());
    352  }
    353 
    354  mRequest->SetTextSource(mRequest->mLoadContext.get());
    355  TRACE_FOR_TEST(mRequest, "load:source");
    356 
    357  MOZ_ASSERT(!mRequest->IsUnknownDataType());
    358  MOZ_ASSERT(mRequest->IsFetching());
    359  return NS_OK;
    360 }
    361 
    362 NS_IMETHODIMP
    363 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
    364                                    nsISupports* aContext, nsresult aStatus,
    365                                    uint32_t aDataLength,
    366                                    const uint8_t* aData) {
    367  nsresult rv = NS_OK;
    368  if (LOG_ENABLED()) {
    369    nsAutoCString url;
    370    mRequest->URI()->GetAsciiSpec(url);
    371    LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest.get(),
    372         url.get()));
    373  }
    374 
    375  nsCOMPtr<nsIRequest> channelRequest;
    376  aLoader->GetRequest(getter_AddRefs(channelRequest));
    377 
    378  mRequest->mNetworkMetadata =
    379      new SubResourceNetworkMetadataHolder(channelRequest);
    380 
    381  {
    382    nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelRequest);
    383    channel->SetNotificationCallbacks(nullptr);
    384  }
    385 
    386  auto firstMessage = !mPreloadStartNotified;
    387  if (!mPreloadStartNotified) {
    388    mPreloadStartNotified = true;
    389    mRequest->GetScriptLoadContext()->NotifyStart(channelRequest);
    390  }
    391 
    392  auto notifyStop = MakeScopeExit([&] {
    393    mRequest->GetScriptLoadContext()->NotifyStop(channelRequest, rv);
    394  });
    395 
    396  if (!mRequest->IsCanceled()) {
    397    if (mRequest->IsUnknownDataType()) {
    398      rv = EnsureKnownDataType(aLoader);
    399      NS_ENSURE_SUCCESS(rv, rv);
    400    }
    401 
    402    if (mRequest->IsSerializedStencil() && !firstMessage) {
    403      // if firstMessage, then entire stream is in aData, and PerfStats would
    404      // measure 0 time
    405      PerfStats::RecordMeasurementEnd(PerfStats::Metric::JSBC_IO_Read);
    406    }
    407 
    408    if (mRequest->IsTextSource()) {
    409      DebugOnly<bool> encoderSet =
    410          EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
    411      MOZ_ASSERT(encoderSet);
    412      rv = mDecoder->DecodeRawData(mRequest, aData, aDataLength,
    413                                   /* aEndOfStream = */ true);
    414      NS_ENSURE_SUCCESS(rv, rv);
    415 
    416      LOG(("ScriptLoadRequest (%p): Source length in code units = %u",
    417           mRequest.get(), unsigned(mRequest->ScriptTextLength())));
    418 
    419      // If SRI is required for this load, appending new bytes to the hash.
    420      if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
    421        mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
    422      }
    423    } else {
    424      MOZ_ASSERT(mRequest->IsSerializedStencil());
    425      JS::TranscodeBuffer& buf = mRequest->SRIAndSerializedStencil();
    426      if (!buf.append(aData, aDataLength)) {
    427        return NS_ERROR_OUT_OF_MEMORY;
    428      }
    429 
    430      LOG(("ScriptLoadRequest (%p): SRIAndSerializedStencil length = %u",
    431           mRequest.get(), unsigned(buf.length())));
    432 
    433      // If we abort while decoding the SRI, we fallback on explicitly
    434      // requesting the source. Thus, we should not continue in
    435      // ScriptLoader::OnStreamComplete, which removes the request from the
    436      // waiting lists.
    437      //
    438      // We calculate the SRI length below.
    439      uint32_t unused;
    440      rv = MaybeDecodeSRI(&unused);
    441      if (NS_FAILED(rv)) {
    442        return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
    443      }
    444 
    445      // The serialized stencil always starts with the SRI hash, thus even if
    446      // there is no SRI data verifier instance, we still want to skip the hash.
    447      uint32_t sriLength;
    448      rv = SRICheckDataVerifier::DataSummaryLength(buf.length(), buf.begin(),
    449                                                   &sriLength);
    450      if (NS_FAILED(rv)) {
    451        return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
    452      }
    453 
    454      mRequest->SetSRILength(sriLength);
    455 
    456      Vector<uint8_t> compressed;
    457      // mRequest has the compressed data, but will be filled with the
    458      // uncompressed data
    459      compressed.swap(buf);
    460      if (!JS::loader::ScriptBytecodeDecompress(
    461              compressed, mRequest->GetSRILength(), buf)) {
    462        return NS_ERROR_UNEXPECTED;
    463      }
    464    }
    465  }
    466 
    467  // Everything went well, keep the CacheInfoChannel alive such that we can
    468  // later save the serialized stencil on the cache entry.
    469  // we have to mediate and use mRequest.
    470  rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
    471                                       mSRIDataVerifier.get());
    472 
    473  return rv;
    474 }
    475 
    476 NS_IMETHODIMP
    477 ScriptLoadHandler::GetInterface(const nsIID& aIID, void** aResult) {
    478  if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
    479    return QueryInterface(aIID, aResult);
    480  }
    481 
    482  return NS_NOINTERFACE;
    483 }
    484 
    485 nsresult ScriptLoadHandler::AsyncOnChannelRedirect(
    486    nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags,
    487    nsIAsyncVerifyRedirectCallback* aCallback) {
    488  mRequest->SetMinimumExpirationTime(
    489      nsContentUtils::GetSubresourceCacheExpirationTime(aOld, mRequest->URI()));
    490 
    491  aCallback->OnRedirectVerifyCallback(NS_OK);
    492 
    493  return NS_OK;
    494 }
    495 
    496 #undef LOG_ENABLED
    497 #undef LOG
    498 
    499 }  // namespace mozilla::dom