tor-browser

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

FormatBrotli.cpp (7541B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 "FormatBrotli.h"
      8 
      9 #include <memory>
     10 
     11 #include "BaseAlgorithms.h"
     12 #include "brotli/decode.h"
     13 #include "brotli/encode.h"
     14 #include "mozilla/dom/TransformStreamDefaultController.h"
     15 
     16 namespace mozilla::dom::compression {
     17 
     18 NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliCompressionStreamAlgorithms,
     19                                   TransformerAlgorithmsBase)
     20 NS_IMPL_ADDREF_INHERITED(BrotliCompressionStreamAlgorithms,
     21                         TransformerAlgorithmsBase)
     22 NS_IMPL_RELEASE_INHERITED(BrotliCompressionStreamAlgorithms,
     23                          TransformerAlgorithmsBase)
     24 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliCompressionStreamAlgorithms)
     25 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
     26 
     27 NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliDecompressionStreamAlgorithms,
     28                                   TransformerAlgorithmsBase)
     29 NS_IMPL_ADDREF_INHERITED(BrotliDecompressionStreamAlgorithms,
     30                         TransformerAlgorithmsBase)
     31 NS_IMPL_RELEASE_INHERITED(BrotliDecompressionStreamAlgorithms,
     32                          TransformerAlgorithmsBase)
     33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliDecompressionStreamAlgorithms)
     34 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
     35 
     36 inline BrotliEncoderOperation intoBrotliOp(Flush aFlush) {
     37  switch (aFlush) {
     38    case Flush::No: {
     39      return BROTLI_OPERATION_PROCESS;
     40    }
     41    case Flush::Yes: {
     42      return BROTLI_OPERATION_FINISH;
     43    }
     44    default: {
     45      MOZ_ASSERT_UNREACHABLE("Unknown flush mode");
     46      return BROTLI_OPERATION_PROCESS;
     47    }
     48  }
     49 }
     50 
     51 Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult>
     52 BrotliCompressionStreamAlgorithms::Create() {
     53  RefPtr<BrotliCompressionStreamAlgorithms> alg =
     54      new BrotliCompressionStreamAlgorithms();
     55  MOZ_TRY(alg->Init());
     56  return alg.forget();
     57 }
     58 
     59 [[nodiscard]] nsresult BrotliCompressionStreamAlgorithms::Init() {
     60  mState = std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter>(
     61      BrotliEncoderCreateInstance(nullptr, nullptr, nullptr));
     62  if (!mState) {
     63    return NS_ERROR_OUT_OF_MEMORY;
     64  }
     65  return NS_OK;
     66 }
     67 
     68 // Shared by:
     69 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
     70 // https://wicg.github.io/compression/#compress-flush-and-enqueue
     71 // All data errors throw TypeError by step 2: If this results in an error,
     72 // then throw a TypeError.
     73 void BrotliCompressionStreamAlgorithms::Compress(
     74    JSContext* aCx, Span<const uint8_t> aInput,
     75    JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
     76    ErrorResult& aRv) {
     77  size_t inputLength = aInput.Length();
     78  const uint8_t* inputBuffer = aInput.Elements();
     79 
     80  do {
     81    std::unique_ptr<uint8_t[], JS::FreePolicy> buffer(
     82        static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
     83    if (!buffer) {
     84      aRv.ThrowTypeError("Out of memory");
     85      return;
     86    }
     87 
     88    size_t outputLength = kBufferSize;
     89    uint8_t* outputBuffer = buffer.get();
     90    bool succeeded = BrotliEncoderCompressStream(
     91        mState.get(), intoBrotliOp(aFlush), &inputLength, &inputBuffer,
     92        &outputLength, &outputBuffer, nullptr);
     93    if (!succeeded) {
     94      aRv.ThrowTypeError("Unexpected compression error");
     95      return;
     96    }
     97 
     98    // Step 3: If buffer is empty, return.
     99    // (We'll implicitly return when the array is empty.)
    100 
    101    // Step 4: Split buffer into one or more non-empty pieces and convert them
    102    // into Uint8Arrays.
    103    // (The buffer is 'split' by having a fixed sized buffer above.)
    104 
    105    size_t written = kBufferSize - outputLength;
    106    if (written > 0) {
    107      JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
    108                                          aCx, written, std::move(buffer)));
    109      if (!view || !aOutput.append(view)) {
    110        JS_ClearPendingException(aCx);
    111        aRv.ThrowTypeError("Out of memory");
    112        return;
    113      }
    114    }
    115  } while (BrotliEncoderHasMoreOutput(mState.get()));
    116 }
    117 
    118 void BrotliCompressionStreamAlgorithms::BrotliDeleter::operator()(
    119    BrotliEncoderStateStruct* aState) {
    120  BrotliEncoderDestroyInstance(aState);
    121 }
    122 
    123 Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult>
    124 BrotliDecompressionStreamAlgorithms::Create() {
    125  RefPtr<BrotliDecompressionStreamAlgorithms> alg =
    126      new BrotliDecompressionStreamAlgorithms();
    127  MOZ_TRY(alg->Init());
    128  return alg.forget();
    129 }
    130 
    131 [[nodiscard]] nsresult BrotliDecompressionStreamAlgorithms::Init() {
    132  mState = std::unique_ptr<BrotliDecoderStateStruct, BrotliDeleter>(
    133      BrotliDecoderCreateInstance(nullptr, nullptr, nullptr));
    134  if (!mState) {
    135    return NS_ERROR_OUT_OF_MEMORY;
    136  }
    137  return NS_OK;
    138 }
    139 
    140 // Shared by:
    141 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
    142 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
    143 // All data errors throw TypeError by step 2: If this results in an error,
    144 // then throw a TypeError.
    145 bool BrotliDecompressionStreamAlgorithms::Decompress(
    146    JSContext* aCx, Span<const uint8_t> aInput,
    147    JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
    148    ErrorResult& aRv) {
    149  size_t inputLength = aInput.Length();
    150  const uint8_t* inputBuffer = aInput.Elements();
    151 
    152  do {
    153    std::unique_ptr<uint8_t[], JS::FreePolicy> buffer(
    154        static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
    155    if (!buffer) {
    156      aRv.ThrowTypeError("Out of memory");
    157      return false;
    158    }
    159 
    160    size_t outputLength = kBufferSize;
    161    uint8_t* outputBuffer = buffer.get();
    162    BrotliDecoderResult rv =
    163        BrotliDecoderDecompressStream(mState.get(), &inputLength, &inputBuffer,
    164                                      &outputLength, &outputBuffer, nullptr);
    165    switch (rv) {
    166      case BROTLI_DECODER_RESULT_ERROR:
    167        aRv.ThrowTypeError("Brotli decompression error: "_ns +
    168                           nsDependentCString(BrotliDecoderErrorString(
    169                               BrotliDecoderGetErrorCode(mState.get()))));
    170        return false;
    171      case BROTLI_DECODER_RESULT_SUCCESS:
    172        mObservedStreamEnd = true;
    173        break;
    174      case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
    175      case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
    176        break;
    177      default:
    178        MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
    179        aRv.ThrowTypeError("Unexpected decompression error");
    180        return false;
    181    }
    182 
    183    // Step 3: If buffer is empty, return.
    184    // (We'll implicitly return when the array is empty.)
    185 
    186    // Step 4: Split buffer into one or more non-empty pieces and convert them
    187    // into Uint8Arrays.
    188    // (The buffer is 'split' by having a fixed sized buffer above.)
    189 
    190    size_t written = kBufferSize - outputLength;
    191    if (written > 0) {
    192      JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
    193                                          aCx, written, std::move(buffer)));
    194      if (!view || !aOutput.append(view)) {
    195        JS_ClearPendingException(aCx);
    196        aRv.ThrowTypeError("Out of memory");
    197        return false;
    198      }
    199    }
    200  } while (BrotliDecoderHasMoreOutput(mState.get()));
    201 
    202  return inputLength == 0;
    203 }
    204 
    205 void BrotliDecompressionStreamAlgorithms::BrotliDeleter::operator()(
    206    BrotliDecoderStateStruct* aState) {
    207  BrotliDecoderDestroyInstance(aState);
    208 }
    209 
    210 }  // namespace mozilla::dom::compression