tor-browser

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

FetchUtil.cpp (27768B)


      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 "FetchUtil.h"
      8 
      9 #include "js/BuildId.h"
     10 #include "js/friend/ErrorMessages.h"  // JSMSG_*
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/dom/DOMException.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "mozilla/dom/InternalRequest.h"
     15 #include "mozilla/dom/ReferrerInfo.h"
     16 #include "mozilla/dom/Response.h"
     17 #include "mozilla/dom/WorkerRef.h"
     18 #include "nsCRT.h"
     19 #include "nsError.h"
     20 #include "nsIAsyncInputStream.h"
     21 #include "nsICloneableInputStream.h"
     22 #include "nsIHttpChannel.h"
     23 #include "nsNetUtil.h"
     24 #include "nsStreamUtils.h"
     25 #include "nsString.h"
     26 #include "zlib.h"
     27 
     28 namespace mozilla::dom {
     29 
     30 // static
     31 nsresult FetchUtil::GetValidRequestMethod(const nsACString& aMethod,
     32                                          nsCString& outMethod) {
     33  nsAutoCString upperCaseMethod(aMethod);
     34  ToUpperCase(upperCaseMethod);
     35  if (!NS_IsValidHTTPToken(aMethod)) {
     36    outMethod.SetIsVoid(true);
     37    return NS_ERROR_DOM_SYNTAX_ERR;
     38  }
     39 
     40  if (upperCaseMethod.EqualsLiteral("CONNECT") ||
     41      upperCaseMethod.EqualsLiteral("TRACE") ||
     42      upperCaseMethod.EqualsLiteral("TRACK")) {
     43    outMethod.SetIsVoid(true);
     44    return NS_ERROR_DOM_SECURITY_ERR;
     45  }
     46 
     47  if (upperCaseMethod.EqualsLiteral("DELETE") ||
     48      upperCaseMethod.EqualsLiteral("GET") ||
     49      upperCaseMethod.EqualsLiteral("HEAD") ||
     50      upperCaseMethod.EqualsLiteral("OPTIONS") ||
     51      upperCaseMethod.EqualsLiteral("POST") ||
     52      upperCaseMethod.EqualsLiteral("PUT")) {
     53    outMethod = upperCaseMethod;
     54  } else {
     55    outMethod = aMethod;  // Case unchanged for non-standard methods
     56  }
     57  return NS_OK;
     58 }
     59 
     60 static bool FindCRLF(nsACString::const_iterator& aStart,
     61                     nsACString::const_iterator& aEnd) {
     62  nsACString::const_iterator end(aEnd);
     63  return FindInReadable("\r\n"_ns, aStart, end);
     64 }
     65 
     66 // Reads over a CRLF and positions start after it.
     67 static bool PushOverLine(nsACString::const_iterator& aStart,
     68                         const nsACString::const_iterator& aEnd) {
     69  if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
     70    ++aStart;  // advance to after CRLF
     71    return true;
     72  }
     73 
     74  return false;
     75 }
     76 
     77 // static
     78 bool FetchUtil::IncrementPendingKeepaliveRequestSize(
     79    nsILoadGroup* aLoadGroup, const uint64_t aBodyLength) {
     80  uint64_t pendingKeepaliveRequestSize = 0;
     81  MOZ_ASSERT(aLoadGroup);
     82 
     83  aLoadGroup->GetTotalKeepAliveBytes(&pendingKeepaliveRequestSize);
     84  pendingKeepaliveRequestSize += aBodyLength;
     85 
     86  if (pendingKeepaliveRequestSize > FETCH_KEEPALIVE_MAX_SIZE) {
     87    return false;
     88  }
     89 
     90  aLoadGroup->SetTotalKeepAliveBytes(pendingKeepaliveRequestSize);
     91  return true;
     92 }
     93 
     94 // static
     95 void FetchUtil::DecrementPendingKeepaliveRequestSize(
     96    nsILoadGroup* aLoadGroup, const uint64_t aBodyLength) {
     97  MOZ_ASSERT(aLoadGroup);
     98 
     99  uint64_t pendingKeepaliveRequestSize = 0;
    100  aLoadGroup->GetTotalKeepAliveBytes(&pendingKeepaliveRequestSize);
    101  MOZ_ASSERT(pendingKeepaliveRequestSize >= aBodyLength);
    102  pendingKeepaliveRequestSize -= aBodyLength;
    103  aLoadGroup->SetTotalKeepAliveBytes(pendingKeepaliveRequestSize);
    104 }
    105 
    106 // static
    107 nsCOMPtr<nsILoadGroup> FetchUtil::GetLoadGroupFromGlobal(
    108    nsIGlobalObject* aGlobalObject) {
    109  MOZ_ASSERT(NS_IsMainThread());
    110  nsCOMPtr<nsILoadGroup> loadGroup = nullptr;
    111  auto* innerWindow = aGlobalObject->GetAsInnerWindow();
    112 
    113  if (innerWindow) {
    114    Document* doc = innerWindow->GetExtantDoc();
    115    if (doc) {
    116      loadGroup = doc->GetDocumentLoadGroup();
    117    }
    118  }
    119 
    120  return loadGroup;
    121 }
    122 
    123 // static
    124 bool FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
    125                              nsACString::const_iterator& aEnd,
    126                              nsCString& aHeaderName, nsCString& aHeaderValue,
    127                              bool* aWasEmptyHeader) {
    128  MOZ_ASSERT(aWasEmptyHeader);
    129  // Set it to a valid value here so we don't forget later.
    130  *aWasEmptyHeader = false;
    131 
    132  const char* beginning = aStart.get();
    133  nsACString::const_iterator end(aEnd);
    134  if (!FindCRLF(aStart, end)) {
    135    return false;
    136  }
    137 
    138  if (aStart.get() == beginning) {
    139    *aWasEmptyHeader = true;
    140    return true;
    141  }
    142 
    143  nsAutoCString header(beginning, aStart.get() - beginning);
    144 
    145  nsACString::const_iterator headerStart, iter, headerEnd;
    146  header.BeginReading(headerStart);
    147  header.EndReading(headerEnd);
    148  iter = headerStart;
    149  if (!FindCharInReadable(':', iter, headerEnd)) {
    150    return false;
    151  }
    152 
    153  aHeaderName.Assign(StringHead(header, iter - headerStart));
    154  aHeaderName.CompressWhitespace();
    155  if (!NS_IsValidHTTPToken(aHeaderName)) {
    156    return false;
    157  }
    158 
    159  aHeaderValue.Assign(Substring(++iter, headerEnd));
    160  if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
    161    return false;
    162  }
    163  aHeaderValue.CompressWhitespace();
    164 
    165  return PushOverLine(aStart, aEnd);
    166 }
    167 
    168 // static
    169 nsresult FetchUtil::SetRequestReferrer(nsIPrincipal* aPrincipal, Document* aDoc,
    170                                       nsIHttpChannel* aChannel,
    171                                       InternalRequest& aRequest) {
    172  MOZ_ASSERT(NS_IsMainThread());
    173 
    174  nsresult rv = NS_OK;
    175  nsAutoCString referrer;
    176  aRequest.GetReferrer(referrer);
    177 
    178  ReferrerPolicy policy = aRequest.ReferrerPolicy_();
    179  nsCOMPtr<nsIReferrerInfo> referrerInfo;
    180  if (referrer.IsEmpty()) {
    181    // This is the case request’s referrer is "no-referrer"
    182    referrerInfo = new ReferrerInfo(nullptr, ReferrerPolicy::No_referrer);
    183  } else if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
    184    referrerInfo = ReferrerInfo::CreateForFetch(aPrincipal, aDoc);
    185    // In the first step, we should use referrer info from requetInit
    186    referrerInfo = static_cast<ReferrerInfo*>(referrerInfo.get())
    187                       ->CloneWithNewPolicy(policy);
    188  } else {
    189    // From "Determine request's Referrer" step 3
    190    // "If request's referrer is a URL, let referrerSource be request's
    191    // referrer."
    192    nsCOMPtr<nsIURI> referrerURI;
    193    rv = NS_NewURI(getter_AddRefs(referrerURI), referrer);
    194    NS_ENSURE_SUCCESS(rv, rv);
    195    referrerInfo = new ReferrerInfo(referrerURI, policy);
    196  }
    197 
    198  rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo);
    199  NS_ENSURE_SUCCESS(rv, rv);
    200 
    201  nsAutoCString computedReferrerSpec;
    202  referrerInfo = aChannel->GetReferrerInfo();
    203  if (referrerInfo) {
    204    (void)referrerInfo->GetComputedReferrerSpec(computedReferrerSpec);
    205  }
    206 
    207  // Step 8 https://fetch.spec.whatwg.org/#main-fetch
    208  // If request’s referrer is not "no-referrer", set request’s referrer to
    209  // the result of invoking determine request’s referrer.
    210  aRequest.SetReferrer(computedReferrerSpec);
    211 
    212  return NS_OK;
    213 }
    214 
    215 class StoreOptimizedEncodingRunnable final : public Runnable {
    216  nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
    217  Vector<uint8_t> mBytes;
    218 
    219 public:
    220  StoreOptimizedEncodingRunnable(
    221      nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
    222      Vector<uint8_t>&& aBytes)
    223      : Runnable("StoreOptimizedEncodingRunnable"),
    224        mCache(std::move(aCache)),
    225        mBytes(std::move(aBytes)) {}
    226 
    227  NS_IMETHOD Run() override {
    228    nsresult rv;
    229 
    230    nsCOMPtr<nsIAsyncOutputStream> stream;
    231    rv = mCache->OpenAlternativeOutputStream(FetchUtil::GetWasmAltDataType(),
    232                                             int64_t(mBytes.length()),
    233                                             getter_AddRefs(stream));
    234    if (NS_FAILED(rv)) {
    235      return rv;
    236    }
    237 
    238    auto closeStream = MakeScopeExit([&]() { stream->CloseWithStatus(rv); });
    239 
    240    uint32_t written;
    241    rv = stream->Write((char*)mBytes.begin(), mBytes.length(), &written);
    242    if (NS_FAILED(rv)) {
    243      return rv;
    244    }
    245 
    246    MOZ_RELEASE_ASSERT(mBytes.length() == written);
    247    return NS_OK;
    248  };
    249 };
    250 
    251 class WindowStreamOwner final : public GlobalTeardownObserver {
    252 private:
    253  // Read from any thread but only set/cleared on the main thread. The lifecycle
    254  // of WindowStreamOwner prevents concurrent read/clear.
    255  nsCOMPtr<nsIAsyncInputStream> mStream;
    256 
    257  ~WindowStreamOwner() { MOZ_ASSERT(NS_IsMainThread()); }
    258 
    259 public:
    260  NS_DECL_ISUPPORTS
    261 
    262  WindowStreamOwner(nsIAsyncInputStream* aStream, nsIGlobalObject* aGlobal)
    263      : GlobalTeardownObserver(aGlobal), mStream(aStream) {
    264    MOZ_DIAGNOSTIC_ASSERT(aGlobal);
    265    MOZ_ASSERT(NS_IsMainThread());
    266  }
    267 
    268  // GlobalTeardownObserver:
    269 
    270  void DisconnectFromOwner() override {
    271    MOZ_ASSERT(NS_IsMainThread());
    272 
    273    if (!mStream) {
    274      return;
    275    }
    276 
    277    // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
    278    // then destory itself, but GTO should be strongly grabbing us right as it's
    279    // calling DisconnectFromOwner.
    280 
    281    mStream->Close();
    282    mStream = nullptr;
    283 
    284    GlobalTeardownObserver::DisconnectFromOwner();
    285  }
    286 };
    287 
    288 NS_IMPL_ISUPPORTS0(WindowStreamOwner)
    289 
    290 inline nsISupports* ToSupports(WindowStreamOwner* aObj) {
    291  return static_cast<GlobalTeardownObserver*>(aObj);
    292 }
    293 
    294 class WorkerStreamOwner final {
    295 public:
    296  NS_INLINE_DECL_REFCOUNTING(WorkerStreamOwner)
    297 
    298  explicit WorkerStreamOwner(nsIAsyncInputStream* aStream,
    299                             nsCOMPtr<nsIEventTarget>&& target)
    300      : mStream(aStream), mOwningEventTarget(std::move(target)) {}
    301 
    302  static already_AddRefed<WorkerStreamOwner> Create(
    303      nsIAsyncInputStream* aStream, WorkerPrivate* aWorker,
    304      nsCOMPtr<nsIEventTarget>&& target) {
    305    RefPtr<WorkerStreamOwner> self =
    306        new WorkerStreamOwner(aStream, std::move(target));
    307 
    308    self->mWorkerRef =
    309        StrongWorkerRef::Create(aWorker, "JSStreamConsumer", [self]() {
    310          if (self->mStream) {
    311            // If this Close() calls JSStreamConsumer::OnInputStreamReady and
    312            // drops the last reference to the JSStreamConsumer, 'this' will not
    313            // be destroyed since ~JSStreamConsumer() only enqueues a release
    314            // proxy.
    315            self->mStream->Close();
    316            self->mStream = nullptr;
    317          }
    318        });
    319 
    320    if (!self->mWorkerRef) {
    321      return nullptr;
    322    }
    323 
    324    return self.forget();
    325  }
    326 
    327  static void ProxyRelease(already_AddRefed<WorkerStreamOwner> aDoomed) {
    328    RefPtr<WorkerStreamOwner> doomed = aDoomed;
    329    nsIEventTarget* target = doomed->mOwningEventTarget;
    330    NS_ProxyRelease("WorkerStreamOwner", target, doomed.forget(),
    331                    /* aAlwaysProxy = */ true);
    332  }
    333 
    334 private:
    335  ~WorkerStreamOwner() = default;
    336 
    337  // Read from any thread but only set/cleared on the worker thread. The
    338  // lifecycle of WorkerStreamOwner prevents concurrent read/clear.
    339  nsCOMPtr<nsIAsyncInputStream> mStream;
    340  RefPtr<StrongWorkerRef> mWorkerRef;
    341  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
    342 };
    343 
    344 class JSStreamConsumer final : public nsIInputStreamCallback,
    345                               public JS::OptimizedEncodingListener {
    346  // A LengthPrefixType is stored at the start of the compressed optimized
    347  // encoding, allowing the decompressed buffer to be allocated to exactly
    348  // the right size.
    349  using LengthPrefixType = uint32_t;
    350  static const unsigned PrefixBytes = sizeof(LengthPrefixType);
    351 
    352  RefPtr<WindowStreamOwner> mWindowStreamOwner;
    353  RefPtr<WorkerStreamOwner> mWorkerStreamOwner;
    354  nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
    355  const bool mOptimizedEncoding;
    356  z_stream mZStream;
    357  bool mZStreamInitialized;
    358  Vector<uint8_t> mOptimizedEncodingBytes;
    359  JS::StreamConsumer* mConsumer;
    360  bool mConsumerAborted;
    361 
    362  JSStreamConsumer(already_AddRefed<WindowStreamOwner> aWindowStreamOwner,
    363                   nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
    364                   nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
    365                   bool aOptimizedEncoding)
    366      : mWindowStreamOwner(aWindowStreamOwner),
    367        mCache(std::move(aCache)),
    368        mOptimizedEncoding(aOptimizedEncoding),
    369        mZStreamInitialized(false),
    370        mConsumer(aConsumer),
    371        mConsumerAborted(false) {
    372    MOZ_DIAGNOSTIC_ASSERT(mWindowStreamOwner);
    373    MOZ_DIAGNOSTIC_ASSERT(mConsumer);
    374  }
    375 
    376  JSStreamConsumer(RefPtr<WorkerStreamOwner> aWorkerStreamOwner,
    377                   nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
    378                   nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
    379                   bool aOptimizedEncoding)
    380      : mWorkerStreamOwner(std::move(aWorkerStreamOwner)),
    381        mCache(std::move(aCache)),
    382        mOptimizedEncoding(aOptimizedEncoding),
    383        mZStreamInitialized(false),
    384        mConsumer(aConsumer),
    385        mConsumerAborted(false) {
    386    MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
    387    MOZ_DIAGNOSTIC_ASSERT(mConsumer);
    388  }
    389 
    390  ~JSStreamConsumer() {
    391    if (mZStreamInitialized) {
    392      inflateEnd(&mZStream);
    393    }
    394 
    395    // Both WindowStreamOwner and WorkerStreamOwner need to be destroyed on
    396    // their global's event target thread.
    397 
    398    if (mWindowStreamOwner) {
    399      MOZ_DIAGNOSTIC_ASSERT(!mWorkerStreamOwner);
    400      NS_ReleaseOnMainThread("JSStreamConsumer::mWindowStreamOwner",
    401                             mWindowStreamOwner.forget(),
    402                             /* aAlwaysProxy = */ true);
    403    } else {
    404      MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
    405      WorkerStreamOwner::ProxyRelease(mWorkerStreamOwner.forget());
    406    }
    407 
    408    // Bug 1733674: these annotations currently do nothing, because they are
    409    // member variables and the annotation mechanism only applies to locals. But
    410    // the analysis could be extended so that these could replace the big-hammer
    411    // ~JSStreamConsumer annotation and thus the analysis could check that
    412    // nothing is added that might GC for a different reason.
    413    JS_HAZ_VALUE_IS_GC_SAFE(mWindowStreamOwner);
    414    JS_HAZ_VALUE_IS_GC_SAFE(mWorkerStreamOwner);
    415  }
    416 
    417  static nsresult WriteSegment(nsIInputStream* aStream, void* aClosure,
    418                               const char* aFromSegment, uint32_t aToOffset,
    419                               uint32_t aCount, uint32_t* aWriteCount) {
    420    JSStreamConsumer* self = reinterpret_cast<JSStreamConsumer*>(aClosure);
    421    MOZ_DIAGNOSTIC_ASSERT(!self->mConsumerAborted);
    422 
    423    if (self->mOptimizedEncoding) {
    424      if (!self->mZStreamInitialized) {
    425        // mOptimizedEncodingBytes is used as temporary storage until we have
    426        // the full prefix.
    427        MOZ_ASSERT(self->mOptimizedEncodingBytes.length() < PrefixBytes);
    428        uint32_t remain = PrefixBytes - self->mOptimizedEncodingBytes.length();
    429        uint32_t consume = std::min(remain, aCount);
    430 
    431        if (!self->mOptimizedEncodingBytes.append(aFromSegment, consume)) {
    432          return NS_ERROR_UNEXPECTED;
    433        }
    434 
    435        if (consume == remain) {
    436          // Initialize zlib once all prefix bytes are loaded.
    437          LengthPrefixType length;
    438          memcpy(&length, self->mOptimizedEncodingBytes.begin(), PrefixBytes);
    439 
    440          if (!self->mOptimizedEncodingBytes.resizeUninitialized(length)) {
    441            return NS_ERROR_UNEXPECTED;
    442          }
    443 
    444          memset(&self->mZStream, 0, sizeof(self->mZStream));
    445          self->mZStream.avail_out = length;
    446          self->mZStream.next_out = self->mOptimizedEncodingBytes.begin();
    447 
    448          if (inflateInit(&self->mZStream) != Z_OK) {
    449            return NS_ERROR_UNEXPECTED;
    450          }
    451          self->mZStreamInitialized = true;
    452        }
    453 
    454        *aWriteCount = consume;
    455        return NS_OK;
    456      }
    457 
    458      // Zlib is initialized, overwrite the prefix with the inflated data.
    459 
    460      MOZ_DIAGNOSTIC_ASSERT(aCount > 0);
    461      self->mZStream.avail_in = aCount;
    462      self->mZStream.next_in = (uint8_t*)aFromSegment;
    463 
    464      int ret = inflate(&self->mZStream, Z_NO_FLUSH);
    465 
    466      MOZ_DIAGNOSTIC_ASSERT(ret == Z_OK || ret == Z_STREAM_END,
    467                            "corrupt optimized wasm cache file: data");
    468      MOZ_DIAGNOSTIC_ASSERT(self->mZStream.avail_in == 0,
    469                            "corrupt optimized wasm cache file: input");
    470      MOZ_DIAGNOSTIC_ASSERT_IF(ret == Z_STREAM_END,
    471                               self->mZStream.avail_out == 0);
    472      // Gracefully handle corruption in release.
    473      bool ok =
    474          (ret == Z_OK || ret == Z_STREAM_END) && self->mZStream.avail_in == 0;
    475      if (!ok) {
    476        return NS_ERROR_UNEXPECTED;
    477      }
    478    } else {
    479      // This callback can be called on any thread which is explicitly allowed
    480      // by this particular JS API call.
    481      if (!self->mConsumer->consumeChunk((const uint8_t*)aFromSegment,
    482                                         aCount)) {
    483        self->mConsumerAborted = true;
    484        return NS_ERROR_UNEXPECTED;
    485      }
    486    }
    487 
    488    *aWriteCount = aCount;
    489    return NS_OK;
    490  }
    491 
    492 public:
    493  NS_DECL_THREADSAFE_ISUPPORTS
    494 
    495  static bool Start(nsCOMPtr<nsIInputStream> aStream, nsIGlobalObject* aGlobal,
    496                    WorkerPrivate* aMaybeWorker, JS::StreamConsumer* aConsumer,
    497                    nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
    498                    bool aOptimizedEncoding) {
    499    nsCOMPtr<nsIAsyncInputStream> asyncStream;
    500    nsresult rv = NS_MakeAsyncNonBlockingInputStream(
    501        aStream.forget(), getter_AddRefs(asyncStream));
    502    if (NS_WARN_IF(NS_FAILED(rv))) {
    503      return false;
    504    }
    505 
    506    RefPtr<JSStreamConsumer> consumer;
    507    if (aMaybeWorker) {
    508      RefPtr<WorkerStreamOwner> owner = WorkerStreamOwner::Create(
    509          asyncStream, aMaybeWorker, aGlobal->SerialEventTarget());
    510      if (!owner) {
    511        return false;
    512      }
    513 
    514      consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer,
    515                                      std::move(aCache), aOptimizedEncoding);
    516    } else {
    517      RefPtr<WindowStreamOwner> owner =
    518          new WindowStreamOwner(asyncStream, aGlobal);
    519      if (!owner) {
    520        return false;
    521      }
    522 
    523      consumer = new JSStreamConsumer(owner.forget(), aGlobal, aConsumer,
    524                                      std::move(aCache), aOptimizedEncoding);
    525    }
    526 
    527    // This AsyncWait() creates a ref-cycle between asyncStream and consumer:
    528    //
    529    //   asyncStream -> consumer -> (Window|Worker)StreamOwner -> asyncStream
    530    //
    531    // The cycle is broken when the stream completes or errors out and
    532    // asyncStream drops its reference to consumer.
    533    return NS_SUCCEEDED(asyncStream->AsyncWait(consumer, 0, 0, nullptr));
    534  }
    535 
    536  // nsIInputStreamCallback:
    537 
    538  NS_IMETHOD
    539  OnInputStreamReady(nsIAsyncInputStream* aStream) override {
    540    // Can be called on any stream. The JS API calls made below explicitly
    541    // support being called from any thread.
    542    MOZ_DIAGNOSTIC_ASSERT(!mConsumerAborted);
    543 
    544    nsresult rv;
    545 
    546    uint64_t available = 0;
    547    rv = aStream->Available(&available);
    548    if (NS_SUCCEEDED(rv) && available == 0) {
    549      rv = NS_BASE_STREAM_CLOSED;
    550    }
    551 
    552    if (rv == NS_BASE_STREAM_CLOSED) {
    553      if (mOptimizedEncoding) {
    554        // Gracefully handle corruption of compressed data stream in release.
    555        // From on investigations in bug 1738987, the incomplete data cases
    556        // mostly happen during shutdown. Some corruptions in the cache entry
    557        // can still happen and will be handled in the WriteSegment above.
    558        bool ok = mZStreamInitialized && mZStream.avail_out == 0;
    559        if (!ok) {
    560          mConsumer->streamError(size_t(NS_ERROR_UNEXPECTED));
    561          return NS_OK;
    562        }
    563 
    564        mConsumer->consumeOptimizedEncoding(mOptimizedEncodingBytes.begin(),
    565                                            mOptimizedEncodingBytes.length());
    566      } else {
    567        // If there is cache entry associated with this stream, then listen for
    568        // an optimized encoding so we can store it in the alt data. By JS API
    569        // contract, the compilation process will hold a refcount to 'this'
    570        // until it's done, optionally calling storeOptimizedEncoding().
    571        mConsumer->streamEnd(mCache ? this : nullptr);
    572      }
    573      return NS_OK;
    574    }
    575 
    576    if (NS_FAILED(rv)) {
    577      mConsumer->streamError(size_t(rv));
    578      return NS_OK;
    579    }
    580 
    581    // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
    582    // if consumeChunk() returned false per JS API contract.
    583    uint32_t written = 0;
    584    rv = aStream->ReadSegments(WriteSegment, this, available, &written);
    585    if (mConsumerAborted) {
    586      return NS_OK;
    587    }
    588    if (NS_WARN_IF(NS_FAILED(rv))) {
    589      mConsumer->streamError(size_t(rv));
    590      return NS_OK;
    591    }
    592 
    593    rv = aStream->AsyncWait(this, 0, 0, nullptr);
    594    if (NS_WARN_IF(NS_FAILED(rv))) {
    595      mConsumer->streamError(size_t(rv));
    596      return NS_OK;
    597    }
    598 
    599    return NS_OK;
    600  }
    601 
    602  // JS::OptimizedEncodingListener
    603 
    604  void storeOptimizedEncoding(const uint8_t* aSrcBytes,
    605                              size_t aSrcLength) override {
    606    MOZ_ASSERT(mCache, "we only listen if there's a cache entry");
    607 
    608    z_stream zstream;
    609    memset(&zstream, 0, sizeof(zstream));
    610    zstream.avail_in = aSrcLength;
    611    zstream.next_in = (uint8_t*)aSrcBytes;
    612 
    613    // The wins from increasing compression levels are tiny, while the time
    614    // to compress increases drastically. For example, for a 148mb alt-data
    615    // produced by a 40mb .wasm file, the level 2 takes 2.5s to get a 3.7x size
    616    // reduction while level 9 takes 22.5s to get a 4x size reduction. Read-time
    617    // wins from smaller compressed cache files are not found to be
    618    // significant, thus the fastest compression level is used. (On test
    619    // workloads, level 2 actually was faster *and* smaller than level 1.)
    620    const int COMPRESSION = 2;
    621    if (deflateInit(&zstream, COMPRESSION) != Z_OK) {
    622      return;
    623    }
    624    auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); });
    625 
    626    Vector<uint8_t> dstBytes;
    627    if (!dstBytes.resizeUninitialized(PrefixBytes +
    628                                      deflateBound(&zstream, aSrcLength))) {
    629      return;
    630    }
    631 
    632    MOZ_RELEASE_ASSERT(LengthPrefixType(aSrcLength) == aSrcLength);
    633    LengthPrefixType srcLength = aSrcLength;
    634    memcpy(dstBytes.begin(), &srcLength, PrefixBytes);
    635 
    636    uint8_t* compressBegin = dstBytes.begin() + PrefixBytes;
    637    zstream.next_out = compressBegin;
    638    zstream.avail_out = dstBytes.length() - PrefixBytes;
    639 
    640    int ret = deflate(&zstream, Z_FINISH);
    641    if (ret == Z_MEM_ERROR) {
    642      return;
    643    }
    644    MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
    645 
    646    dstBytes.shrinkTo(zstream.next_out - dstBytes.begin());
    647 
    648    NS_DispatchToMainThread(new StoreOptimizedEncodingRunnable(
    649        std::move(mCache), std::move(dstBytes)));
    650  }
    651 };
    652 
    653 NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
    654 
    655 // static
    656 constinit nsCString FetchUtil::WasmAltDataType;
    657 
    658 // static
    659 void FetchUtil::InitWasmAltDataType() {
    660  MOZ_ASSERT(WasmAltDataType.IsEmpty());
    661 
    662  RunOnShutdown([]() {
    663    // Avoid StringBuffer leak tests failures.
    664    WasmAltDataType.Truncate();
    665  });
    666 
    667  WasmAltDataType.Append(nsLiteralCString("wasm-"));
    668 
    669  JS::BuildIdCharVector buildId;
    670  if (!JS::GetOptimizedEncodingBuildId(&buildId)) {
    671    MOZ_CRASH("build id oom");
    672  }
    673 
    674  WasmAltDataType.Append(buildId.begin(), buildId.length());
    675 }
    676 
    677 static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
    678  JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
    679  return false;
    680 }
    681 
    682 // static
    683 bool FetchUtil::StreamResponseToJS(JSContext* aCx, JS::Handle<JSObject*> aObj,
    684                                   JS::MimeType aMimeType,
    685                                   JS::StreamConsumer* aConsumer,
    686                                   WorkerPrivate* aMaybeWorker) {
    687  MOZ_ASSERT(!WasmAltDataType.IsEmpty());
    688  MOZ_ASSERT(!aMaybeWorker == NS_IsMainThread());
    689 
    690  RefPtr<Response> response;
    691  nsresult rv = UNWRAP_OBJECT(Response, aObj, response);
    692  if (NS_FAILED(rv)) {
    693    return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_VALUE);
    694  }
    695 
    696  const char* requiredMimeType = nullptr;
    697  switch (aMimeType) {
    698    case JS::MimeType::Wasm:
    699      requiredMimeType = WASM_CONTENT_TYPE;
    700      break;
    701  }
    702 
    703  // For WASM, the Content-Type must be exactly "application/wasm" with no
    704  // parameters. Check the raw header value before parsing normalizes it.
    705  ErrorResult result;
    706  nsAutoCString mimeType;
    707  response->GetInternalHeaders()->Get("Content-Type"_ns, mimeType, result);
    708  MOZ_ALWAYS_TRUE(!result.Failed());
    709  ToLowerCase(mimeType);
    710 
    711  if (!mimeType.EqualsASCII(requiredMimeType)) {
    712    JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
    713                              JSMSG_WASM_BAD_RESPONSE_MIME_TYPE, mimeType.get(),
    714                              requiredMimeType);
    715    return false;
    716  }
    717 
    718  if (response->Type() != ResponseType::Basic &&
    719      response->Type() != ResponseType::Cors &&
    720      response->Type() != ResponseType::Default) {
    721    return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN);
    722  }
    723 
    724  if (!response->Ok()) {
    725    return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_STATUS);
    726  }
    727 
    728  if (response->BodyUsed()) {
    729    return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
    730  }
    731 
    732  switch (aMimeType) {
    733    case JS::MimeType::Wasm:
    734      nsAutoCString url;
    735      response->GetUrl(url);
    736 
    737      IgnoredErrorResult result;
    738      nsCString sourceMapUrl;
    739      response->GetInternalHeaders()->Get("SourceMap"_ns, sourceMapUrl, result);
    740      if (NS_WARN_IF(result.Failed())) {
    741        return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
    742      }
    743      aConsumer->noteResponseURLs(
    744          url.get(), sourceMapUrl.IsVoid() ? nullptr : sourceMapUrl.get());
    745      break;
    746  }
    747 
    748  SafeRefPtr<InternalResponse> ir = response->GetInternalResponse();
    749  if (NS_WARN_IF(!ir)) {
    750    return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
    751  }
    752 
    753  nsCOMPtr<nsIInputStream> stream;
    754 
    755  nsMainThreadPtrHandle<nsICacheInfoChannel> cache;
    756  bool optimizedEncoding = false;
    757  if (ir->HasCacheInfoChannel()) {
    758    cache = ir->TakeCacheInfoChannel();
    759 
    760    nsAutoCString altDataType;
    761    if (NS_SUCCEEDED(cache->GetAlternativeDataType(altDataType)) &&
    762        WasmAltDataType.Equals(altDataType)) {
    763      optimizedEncoding = true;
    764      rv = cache->GetAlternativeDataInputStream(getter_AddRefs(stream));
    765      if (NS_WARN_IF(NS_FAILED(rv))) {
    766        return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
    767      }
    768      if (ir->HasBeenCloned()) {
    769        // If `Response` is cloned, clone alternative data stream instance.
    770        // The cache entry does not clone automatically, and multiple
    771        // JSStreamConsumer instances will collide during read if not cloned.
    772        nsCOMPtr<nsICloneableInputStream> original = do_QueryInterface(stream);
    773        if (NS_WARN_IF(!original)) {
    774          return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
    775        }
    776        rv = original->Clone(getter_AddRefs(stream));
    777        if (NS_WARN_IF(NS_FAILED(rv))) {
    778          return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
    779        }
    780      }
    781    }
    782  }
    783 
    784  if (!optimizedEncoding) {
    785    ir->GetUnfilteredBody(getter_AddRefs(stream));
    786    if (!stream) {
    787      aConsumer->streamEnd();
    788      return true;
    789    }
    790  }
    791 
    792  MOZ_ASSERT(stream);
    793 
    794  IgnoredErrorResult error;
    795  response->SetBodyUsed(aCx, error);
    796  if (NS_WARN_IF(error.Failed())) {
    797    return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
    798  }
    799 
    800  nsIGlobalObject* global = xpc::NativeGlobal(js::UncheckedUnwrap(aObj));
    801 
    802  if (!JSStreamConsumer::Start(stream, global, aMaybeWorker, aConsumer,
    803                               std::move(cache), optimizedEncoding)) {
    804    return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
    805  }
    806 
    807  return true;
    808 }
    809 
    810 // static
    811 void FetchUtil::ReportJSStreamError(JSContext* aCx, size_t aErrorCode) {
    812  // For now, convert *all* errors into AbortError.
    813 
    814  RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
    815 
    816  JS::Rooted<JS::Value> value(aCx);
    817  if (!GetOrCreateDOMReflector(aCx, e, &value)) {
    818    return;
    819  }
    820 
    821  JS_SetPendingException(aCx, value);
    822 }
    823 
    824 }  // namespace mozilla::dom