tor-browser

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

StreamLoader.cpp (10449B)


      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/css/StreamLoader.h"
      8 
      9 #include "mozilla/Encoding.h"
     10 #include "mozilla/StaticPrefs_network.h"
     11 #include "mozilla/TaskQueue.h"
     12 #include "mozilla/dom/CacheExpirationTime.h"
     13 #include "mozilla/net/UrlClassifierFeatureFactory.h"
     14 #include "nsContentUtils.h"
     15 #include "nsIAsyncVerifyRedirectCallback.h"
     16 #include "nsIChannel.h"
     17 #include "nsIInputStream.h"
     18 #include "nsIStreamTransportService.h"
     19 #include "nsIThreadRetargetableRequest.h"
     20 #include "nsNetCID.h"
     21 #include "nsNetUtil.h"
     22 #include "nsProxyRelease.h"
     23 #include "nsServiceManagerUtils.h"
     24 
     25 namespace mozilla::css {
     26 
     27 StreamLoader::StreamLoader(SheetLoadData& aSheetLoadData)
     28    : mSheetLoadData(&aSheetLoadData),
     29      mStatus(NS_OK),
     30      mMainThreadSheetLoadData(new nsMainThreadPtrHolder<SheetLoadData>(
     31          "StreamLoader::SheetLoadData", mSheetLoadData, false)) {}
     32 
     33 StreamLoader::~StreamLoader() {
     34 #ifdef NIGHTLY_BUILD
     35  MOZ_RELEASE_ASSERT(mOnStopProcessingDone || mChannelOpenFailed);
     36 #endif
     37 }
     38 
     39 NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener,
     40                  nsIThreadRetargetableStreamListener, nsIChannelEventSink,
     41                  nsIInterfaceRequestor)
     42 
     43 /* nsIRequestObserver implementation */
     44 NS_IMETHODIMP
     45 StreamLoader::OnStartRequest(nsIRequest* aRequest) {
     46  MOZ_ASSERT(aRequest);
     47  mRequest = aRequest;
     48  mSheetLoadData->OnStartRequest(aRequest);
     49 
     50  // It's kinda bad to let Web content send a number that results
     51  // in a potentially large allocation directly, but efficiency of
     52  // compression bombs is so great that it doesn't make much sense
     53  // to require a site to send one before going ahead and allocating.
     54  if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest)) {
     55    int64_t length;
     56    nsresult rv = channel->GetContentLength(&length);
     57    if (NS_SUCCEEDED(rv) && length > 0) {
     58      CheckedInt<nsACString::size_type> checkedLength(length);
     59      if (!checkedLength.isValid()) {
     60        return (mStatus = NS_ERROR_OUT_OF_MEMORY);
     61      }
     62      if (!mBytes.SetCapacity(checkedLength.value(), fallible)) {
     63        return (mStatus = NS_ERROR_OUT_OF_MEMORY);
     64      }
     65    }
     66  }
     67  if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
     68    nsCOMPtr<nsIEventTarget> sts =
     69        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
     70    RefPtr queue =
     71        TaskQueue::Create(sts.forget(), "css::StreamLoader Delivery Queue");
     72    rr->RetargetDeliveryTo(queue);
     73  }
     74 
     75  return NS_OK;
     76 }
     77 
     78 NS_IMETHODIMP
     79 StreamLoader::CheckListenerChain() { return NS_OK; }
     80 
     81 NS_IMETHODIMP
     82 StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
     83  MOZ_ASSERT_IF(!StaticPrefs::network_send_OnDataFinished_cssLoader(),
     84                !mOnStopProcessingDone);
     85  mRequest = nullptr;
     86 
     87  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
     88 
     89  // StreamLoader::OnStopRequest can get triggered twice for a request.
     90  // Once from the path
     91  // nsIThreadRetargetableStreamListener::OnDataFinished->StreamLoader::OnDataFinished
     92  // (non-main thread)  and
     93  // once from nsIRequestObserver::OnStopRequest path (main thread). It is
     94  // guaranteed that we will always get
     95  // nsIThreadRetargetableStreamListener::OnDataFinished trigger first and this
     96  // is always followed by nsIRequestObserver::OnStopRequest
     97 
     98  // If we are executing OnStopRequest OMT, we need to block resolution of parse
     99  // promise and unblock again if we are executing this in main thread.
    100  // Resolution of parse promise fires onLoadEvent and this should not happen
    101  // before main thread OnStopRequest is dispatched.
    102  if (NS_IsMainThread()) {
    103    channel->SetNotificationCallbacks(nullptr);
    104 
    105    mSheetLoadData->mNetworkMetadata =
    106        new SubResourceNetworkMetadataHolder(aRequest);
    107 
    108    mSheetLoadData->mSheet->UnblockParsePromise();
    109  } else {
    110    if (mSheetLoadData->mRecordErrors) {
    111      // We can't report errors off main thread right now.
    112      return NS_OK;
    113    }
    114  }
    115 
    116  auto HandleErrorInMainThread = [&] {
    117    MOZ_ASSERT(mStatus != NS_OK_PARSE_SHEET);
    118    MOZ_ASSERT(NS_IsMainThread());
    119    if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
    120            mStatus)) {
    121      // Handle sheet not loading error because source was a tracking URL (or
    122      // fingerprinting, cryptomining, etc). We make a note of this sheet node
    123      // by including it in a dedicated array of blocked tracking nodes under
    124      // its parent document.
    125      //
    126      // Multiple sheet load instances might be tied to this request, we
    127      // annotate each one linked to a valid owning element (node).
    128      //
    129      // TODO(emilio): Maybe this should be done in Loader::NotifyObservers?
    130      // Feels pretty random here.
    131      for (SheetLoadData* data = mSheetLoadData; data; data = data->mNext) {
    132        if (nsINode* node = data->mSheet->GetOwnerNode()) {
    133          node->OwnerDoc()->AddBlockedNodeByClassifier(node);
    134        }
    135      }
    136    }
    137    mSheetLoadData->mLoader->SheetComplete(*mSheetLoadData, mStatus);
    138  };
    139 
    140  if (mOnStopProcessingDone) {
    141    MOZ_ASSERT(NS_IsMainThread());
    142    if (mStatus != NS_OK_PARSE_SHEET) {
    143      HandleErrorInMainThread();
    144    }
    145    return NS_OK;
    146  }
    147 
    148  mOnStopProcessingDone = true;
    149 
    150  // Decoded data
    151  nsCString utf8String;
    152  {
    153    nsresult status = NS_FAILED(mStatus) ? mStatus : aStatus;
    154    mStatus = mSheetLoadData->VerifySheetReadyToParse(status, mBOMBytes, mBytes,
    155                                                      channel);
    156    if (mStatus != NS_OK_PARSE_SHEET) {
    157      if (NS_IsMainThread()) {
    158        HandleErrorInMainThread();
    159      }
    160      return mStatus;
    161    }
    162 
    163    // At this point all the conditions that requires us to run on main
    164    // are checked in VerifySheetReadyToParse
    165 
    166    // BOM detection generally happens during the write callback, but that
    167    // won't have happened if fewer than three bytes were received.
    168    if (mEncodingFromBOM.isNothing()) {
    169      HandleBOM();
    170      MOZ_ASSERT(mEncodingFromBOM.isSome());
    171    }
    172    // Hold the nsStringBuffer for the bytes from the stack to ensure release
    173    // after its scope ends
    174    nsCString bytes = std::move(mBytes);
    175    // The BOM handling has happened, but we still may not have an encoding if
    176    // there was no BOM. Ensure we have one.
    177    const Encoding* encoding = mEncodingFromBOM.value();
    178    if (!encoding) {
    179      // No BOM
    180      encoding = mSheetLoadData->DetermineNonBOMEncoding(bytes, channel);
    181    }
    182    mSheetLoadData->mEncoding = encoding;
    183 
    184    size_t validated = 0;
    185    if (encoding == UTF_8_ENCODING) {
    186      validated = Encoding::UTF8ValidUpTo(bytes);
    187    }
    188 
    189    if (validated == bytes.Length()) {
    190      // Either this is UTF-8 and all valid, or it's not UTF-8 but is an empty
    191      // string. This assumes that an empty string in any encoding decodes to
    192      // empty string, which seems like a plausible assumption.
    193      utf8String = std::move(bytes);
    194    } else {
    195      // FIXME: Seems early returning here is wrong, what completes the sheet?
    196      MOZ_TRY(encoding->DecodeWithoutBOMHandling(bytes, utf8String, validated));
    197    }
    198  }  // run destructor for `bytes`
    199 
    200  // For reasons I don't understand, factoring the below lines into
    201  // a method on SheetLoadData resulted in a linker error. Hence,
    202  // accessing fields of mSheetLoadData from here.
    203  mSheetLoadData->mLoader->ParseSheet(utf8String, mMainThreadSheetLoadData,
    204                                      Loader::AllowAsyncParse::Yes);
    205 
    206  return NS_OK;
    207 }
    208 
    209 /* nsIStreamListener implementation */
    210 NS_IMETHODIMP
    211 StreamLoader::OnDataAvailable(nsIRequest*, nsIInputStream* aInputStream,
    212                              uint64_t, uint32_t aCount) {
    213  if (NS_FAILED(mStatus)) {
    214    return mStatus;
    215  }
    216  uint32_t dummy;
    217  return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
    218 }
    219 
    220 void StreamLoader::HandleBOM() {
    221  MOZ_ASSERT(mEncodingFromBOM.isNothing());
    222  MOZ_ASSERT(mBytes.IsEmpty());
    223 
    224  auto [encoding, bomLength] = Encoding::ForBOM(mBOMBytes);
    225  mEncodingFromBOM.emplace(encoding);  // Null means no BOM.
    226 
    227  // BOMs are three bytes at most, but may be fewer. Copy over anything
    228  // that wasn't part of the BOM to mBytes. Note that we need to track
    229  // any BOM bytes as well for SRI handling.
    230  mBytes.Append(Substring(mBOMBytes, bomLength));
    231  mBOMBytes.Truncate(bomLength);
    232 }
    233 
    234 NS_IMETHODIMP
    235 StreamLoader::OnDataFinished(nsresult aResult) {
    236  nsCOMPtr<nsIRequest> request = mRequest.forget();
    237  if (StaticPrefs::network_send_OnDataFinished_cssLoader()) {
    238    return OnStopRequest(request, aResult);
    239  }
    240 
    241  return NS_OK;
    242 }
    243 
    244 NS_IMETHODIMP
    245 StreamLoader::GetInterface(const nsIID& aIID, void** aResult) {
    246  if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
    247    return QueryInterface(aIID, aResult);
    248  }
    249 
    250  return NS_NOINTERFACE;
    251 }
    252 
    253 nsresult StreamLoader::AsyncOnChannelRedirect(
    254    nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags,
    255    nsIAsyncVerifyRedirectCallback* aCallback) {
    256  mSheetLoadData->SetMinimumExpirationTime(
    257      nsContentUtils::GetSubresourceCacheExpirationTime(aOld,
    258                                                        mSheetLoadData->mURI));
    259 
    260  aCallback->OnRedirectVerifyCallback(NS_OK);
    261 
    262  return NS_OK;
    263 }
    264 
    265 nsresult StreamLoader::WriteSegmentFun(nsIInputStream*, void* aClosure,
    266                                       const char* aSegment, uint32_t,
    267                                       uint32_t aCount, uint32_t* aWriteCount) {
    268  *aWriteCount = 0;
    269  StreamLoader* self = static_cast<StreamLoader*>(aClosure);
    270  if (NS_FAILED(self->mStatus)) {
    271    return self->mStatus;
    272  }
    273 
    274  // If we haven't done BOM detection yet, divert bytes into the special buffer.
    275  if (self->mEncodingFromBOM.isNothing()) {
    276    size_t bytesToCopy = std::min<size_t>(3 - self->mBOMBytes.Length(), aCount);
    277    self->mBOMBytes.Append(aSegment, bytesToCopy);
    278    aSegment += bytesToCopy;
    279    *aWriteCount += bytesToCopy;
    280    aCount -= bytesToCopy;
    281 
    282    if (self->mBOMBytes.Length() == 3) {
    283      self->HandleBOM();
    284    } else {
    285      return NS_OK;
    286    }
    287  }
    288 
    289  if (!self->mBytes.Append(aSegment, aCount, fallible)) {
    290    self->mBytes.Truncate();
    291    return (self->mStatus = NS_ERROR_OUT_OF_MEMORY);
    292  }
    293 
    294  *aWriteCount += aCount;
    295  return NS_OK;
    296 }
    297 
    298 }  // namespace mozilla::css