tor-browser

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

FormatZlib.cpp (11884B)


      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 "FormatZlib.h"
      8 
      9 #include "BaseAlgorithms.h"
     10 #include "mozilla/dom/CompressionStreamBinding.h"
     11 #include "mozilla/dom/TransformStreamDefaultController.h"
     12 
     13 namespace mozilla::dom::compression {
     14 
     15 NS_IMPL_CYCLE_COLLECTION_INHERITED(ZLibCompressionStreamAlgorithms,
     16                                   TransformerAlgorithmsBase)
     17 NS_IMPL_ADDREF_INHERITED(ZLibCompressionStreamAlgorithms,
     18                         TransformerAlgorithmsBase)
     19 NS_IMPL_RELEASE_INHERITED(ZLibCompressionStreamAlgorithms,
     20                          TransformerAlgorithmsBase)
     21 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZLibCompressionStreamAlgorithms)
     22 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
     23 
     24 NS_IMPL_CYCLE_COLLECTION_INHERITED(ZLibDecompressionStreamAlgorithms,
     25                                   TransformerAlgorithmsBase)
     26 NS_IMPL_ADDREF_INHERITED(ZLibDecompressionStreamAlgorithms,
     27                         TransformerAlgorithmsBase)
     28 NS_IMPL_RELEASE_INHERITED(ZLibDecompressionStreamAlgorithms,
     29                          TransformerAlgorithmsBase)
     30 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ZLibDecompressionStreamAlgorithms)
     31 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
     32 
     33 inline uint8_t intoZLibFlush(Flush aFlush) {
     34  switch (aFlush) {
     35    case Flush::No: {
     36      return Z_NO_FLUSH;
     37    }
     38    case Flush::Yes: {
     39      return Z_FINISH;
     40    }
     41    default: {
     42      MOZ_ASSERT_UNREACHABLE("Unknown flush mode");
     43      return Z_NO_FLUSH;
     44    }
     45  }
     46 }
     47 
     48 // From the docs in
     49 // https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
     50 inline int8_t ZLibWindowBits(CompressionFormat format) {
     51  switch (format) {
     52    case CompressionFormat::Deflate:
     53      // The windowBits parameter is the base two logarithm of the window size
     54      // (the size of the history buffer). It should be in the range 8..15 for
     55      // this version of the library. Larger values of this parameter result
     56      // in better compression at the expense of memory usage.
     57      return 15;
     58    case CompressionFormat::Deflate_raw:
     59      // windowBits can also be –8..–15 for raw deflate. In this case,
     60      // -windowBits determines the window size.
     61      return -15;
     62    case CompressionFormat::Gzip:
     63      // windowBits can also be greater than 15 for optional gzip encoding.
     64      // Add 16 to windowBits to write a simple gzip header and trailer around
     65      // the compressed data instead of a zlib wrapper.
     66      return 31;
     67    default:
     68      MOZ_ASSERT_UNREACHABLE("Unknown compression format");
     69      return 0;
     70  }
     71 }
     72 
     73 Result<already_AddRefed<ZLibCompressionStreamAlgorithms>, nsresult>
     74 ZLibCompressionStreamAlgorithms::Create(CompressionFormat format) {
     75  RefPtr<ZLibCompressionStreamAlgorithms> alg =
     76      new ZLibCompressionStreamAlgorithms();
     77  MOZ_TRY(alg->Init(format));
     78  return alg.forget();
     79 }
     80 
     81 [[nodiscard]] nsresult ZLibCompressionStreamAlgorithms::Init(
     82    CompressionFormat format) {
     83  int8_t err = deflateInit2(&mZStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
     84                            ZLibWindowBits(format), 8 /* default memLevel */,
     85                            Z_DEFAULT_STRATEGY);
     86  if (err == Z_MEM_ERROR) {
     87    return NS_ERROR_OUT_OF_MEMORY;
     88  }
     89  MOZ_ASSERT(err == Z_OK);
     90  return NS_OK;
     91 }
     92 
     93 // Shared by:
     94 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
     95 // https://wicg.github.io/compression/#compress-flush-and-enqueue
     96 void ZLibCompressionStreamAlgorithms::Compress(
     97    JSContext* aCx, Span<const uint8_t> aInput,
     98    JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
     99    ErrorResult& aRv) {
    100  mZStream.avail_in = aInput.Length();
    101  mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
    102 
    103  do {
    104    static uint16_t kBufferSize = 16384;
    105    UniquePtr<uint8_t[], JS::FreePolicy> buffer(
    106        static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
    107    if (!buffer) {
    108      aRv.ThrowTypeError("Out of memory");
    109      return;
    110    }
    111 
    112    mZStream.avail_out = kBufferSize;
    113    mZStream.next_out = buffer.get();
    114 
    115    int8_t err = deflate(&mZStream, intoZLibFlush(aFlush));
    116 
    117    // From the manual: deflate() returns ...
    118    switch (err) {
    119      case Z_OK:
    120      case Z_STREAM_END:
    121      case Z_BUF_ERROR:
    122        // * Z_OK if some progress has been made
    123        // * Z_STREAM_END if all input has been consumed and all output has
    124        // been produced (only when flush is set to Z_FINISH)
    125        // * Z_BUF_ERROR if no progress is possible (for example avail_in or
    126        // avail_out was zero). Note that Z_BUF_ERROR is not fatal, and
    127        // deflate() can be called again with more input and more output space
    128        // to continue compressing.
    129        //
    130        // (But of course no input should be given after Z_FINISH)
    131        break;
    132      case Z_STREAM_ERROR:
    133      default:
    134        // * Z_STREAM_ERROR if the stream state was inconsistent
    135        // (which is fatal)
    136        MOZ_ASSERT_UNREACHABLE("Unexpected compression error code");
    137        aRv.ThrowTypeError("Unexpected compression error");
    138        return;
    139    }
    140 
    141    // Stream should end only when flushed, see above
    142    // The reverse is not true as zlib may have big data to be flushed that is
    143    // larger than the buffer size
    144    MOZ_ASSERT_IF(err == Z_STREAM_END, aFlush == Flush::Yes);
    145 
    146    // At this point we either exhausted the input or the output buffer
    147    MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out);
    148 
    149    size_t written = kBufferSize - mZStream.avail_out;
    150    if (!written) {
    151      break;
    152    }
    153 
    154    // Step 3: If buffer is empty, return.
    155    // (We'll implicitly return when the array is empty.)
    156 
    157    // Step 4: Split buffer into one or more non-empty pieces and convert them
    158    // into Uint8Arrays.
    159    // (The buffer is 'split' by having a fixed sized buffer above.)
    160 
    161    JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
    162                                        aCx, written, std::move(buffer)));
    163    if (!view || !aOutput.append(view)) {
    164      JS_ClearPendingException(aCx);
    165      aRv.ThrowTypeError("Out of memory");
    166      return;
    167    }
    168  } while (mZStream.avail_out == 0);
    169  // From the manual:
    170  // If deflate returns with avail_out == 0, this function must be called
    171  // again with the same value of the flush parameter and more output space
    172  // (updated avail_out)
    173 }
    174 
    175 ZLibCompressionStreamAlgorithms::~ZLibCompressionStreamAlgorithms() {
    176  deflateEnd(&mZStream);
    177 };
    178 
    179 Result<already_AddRefed<ZLibDecompressionStreamAlgorithms>, nsresult>
    180 ZLibDecompressionStreamAlgorithms::Create(CompressionFormat format) {
    181  RefPtr<ZLibDecompressionStreamAlgorithms> alg =
    182      new ZLibDecompressionStreamAlgorithms();
    183  MOZ_TRY(alg->Init(format));
    184  return alg.forget();
    185 }
    186 
    187 [[nodiscard]] nsresult ZLibDecompressionStreamAlgorithms::Init(
    188    CompressionFormat format) {
    189  int8_t err = inflateInit2(&mZStream, ZLibWindowBits(format));
    190  if (err == Z_MEM_ERROR) {
    191    return NS_ERROR_OUT_OF_MEMORY;
    192  }
    193  MOZ_ASSERT(err == Z_OK);
    194  return NS_OK;
    195 }
    196 
    197 // Shared by:
    198 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
    199 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
    200 // All data errors throw TypeError by step 2: If this results in an error,
    201 // then throw a TypeError.
    202 bool ZLibDecompressionStreamAlgorithms::Decompress(
    203    JSContext* aCx, Span<const uint8_t> aInput,
    204    JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush,
    205    ErrorResult& aRv) {
    206  mZStream.avail_in = aInput.Length();
    207  mZStream.next_in = const_cast<uint8_t*>(aInput.Elements());
    208 
    209  do {
    210    UniquePtr<uint8_t[], JS::FreePolicy> buffer(
    211        static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize)));
    212    if (!buffer) {
    213      aRv.ThrowTypeError("Out of memory");
    214      return false;
    215    }
    216 
    217    mZStream.avail_out = kBufferSize;
    218    mZStream.next_out = buffer.get();
    219 
    220    int8_t err = inflate(&mZStream, intoZLibFlush(aFlush));
    221 
    222    // From the manual: inflate() returns ...
    223    switch (err) {
    224      case Z_DATA_ERROR:
    225        // Z_DATA_ERROR if the input data was corrupted (input stream not
    226        // conforming to the zlib format or incorrect check value, in which
    227        // case strm->msg points to a string with a more specific error)
    228        aRv.ThrowTypeError("The input data is corrupted: "_ns +
    229                           nsDependentCString(mZStream.msg));
    230        return false;
    231      case Z_MEM_ERROR:
    232        // Z_MEM_ERROR if there was not enough memory
    233        aRv.ThrowTypeError("Out of memory");
    234        return false;
    235      case Z_NEED_DICT:
    236        // Z_NEED_DICT if a preset dictionary is needed at this point
    237        //
    238        // From the `deflate` section of
    239        // https://wicg.github.io/compression/#supported-formats:
    240        // * The FDICT flag is not supported by these APIs, and will error the
    241        // stream if set.
    242        // And FDICT means preset dictionary per
    243        // https://datatracker.ietf.org/doc/html/rfc1950#page-5.
    244        aRv.ThrowTypeError(
    245            "The stream needs a preset dictionary but such setup is "
    246            "unsupported");
    247        return false;
    248      case Z_STREAM_END:
    249        // Z_STREAM_END if the end of the compressed data has been reached and
    250        // all uncompressed output has been produced
    251        //
    252        // https://wicg.github.io/compression/#supported-formats has error
    253        // conditions for each compression format when additional input comes
    254        // after stream end.
    255        // Note that additional calls for inflate() immediately emits
    256        // Z_STREAM_END after this point.
    257        mObservedStreamEnd = true;
    258        break;
    259      case Z_OK:
    260      case Z_BUF_ERROR:
    261        // * Z_OK if some progress has been made
    262        // * Z_BUF_ERROR if no progress was possible or if there was not
    263        // enough room in the output buffer when Z_FINISH is used. Note that
    264        // Z_BUF_ERROR is not fatal, and inflate() can be called again with
    265        // more input and more output space to continue decompressing.
    266        //
    267        // (But of course no input should be given after Z_FINISH)
    268        break;
    269      case Z_STREAM_ERROR:
    270      default:
    271        // * Z_STREAM_ERROR if the stream state was inconsistent
    272        // (which is fatal)
    273        MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
    274        aRv.ThrowTypeError("Unexpected decompression error");
    275        return false;
    276    }
    277 
    278    // At this point we either exhausted the input or the output buffer, or
    279    // met the stream end.
    280    MOZ_ASSERT(!mZStream.avail_in || !mZStream.avail_out || mObservedStreamEnd);
    281 
    282    size_t written = kBufferSize - mZStream.avail_out;
    283    if (!written) {
    284      break;
    285    }
    286 
    287    // Step 3: If buffer is empty, return.
    288    // (We'll implicitly return when the array is empty.)
    289 
    290    // Step 4: Split buffer into one or more non-empty pieces and convert them
    291    // into Uint8Arrays.
    292    // (The buffer is 'split' by having a fixed sized buffer above.)
    293 
    294    JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
    295                                        aCx, written, std::move(buffer)));
    296    if (!view || !aOutput.append(view)) {
    297      JS_ClearPendingException(aCx);
    298      aRv.ThrowTypeError("Out of memory");
    299      return false;
    300    }
    301  } while (mZStream.avail_out == 0 && !mObservedStreamEnd);
    302  // From the manual:
    303  // * It must update next_out and avail_out when avail_out has dropped to
    304  // zero.
    305  // * inflate() should normally be called until it returns Z_STREAM_END or an
    306  // error.
    307 
    308  return mZStream.avail_in == 0;
    309 }
    310 
    311 ZLibDecompressionStreamAlgorithms::~ZLibDecompressionStreamAlgorithms() {
    312  inflateEnd(&mZStream);
    313 }
    314 
    315 }  // namespace mozilla::dom::compression