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/. */
7 #include "mozilla/dom/DecompressionStream.h"
9 #include "js/TypeDecls.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/dom/DecompressionStreamBinding.h"
12 #include "mozilla/dom/ReadableStream.h"
13 #include "mozilla/dom/WritableStream.h"
14 #include "mozilla/dom/TextDecoderStream.h"
15 #include "mozilla/dom/TransformStream.h"
16 #include "mozilla/dom/TransformerCallbackHelpers.h"
18 #include "ZLibHelper.h"
20 // See the zlib manual in https://www.zlib.net/manual.html or in
21 // https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
23 namespace mozilla::dom
{
25 class DecompressionStreamAlgorithms
: public TransformerAlgorithmsWrapper
{
27 NS_DECL_ISUPPORTS_INHERITED
28 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms
,
29 TransformerAlgorithmsBase
)
31 explicit DecompressionStreamAlgorithms(CompressionFormat format
) {
32 int8_t err
= inflateInit2(&mZStream
, ZLibWindowBits(format
));
33 if (err
== Z_MEM_ERROR
) {
34 MOZ_CRASH("Out of memory");
36 MOZ_ASSERT(err
== Z_OK
);
40 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
41 // Let transformAlgorithm be an algorithm which takes a chunk argument and
42 // runs the compress and enqueue a chunk algorithm with this and chunk.
44 void TransformCallbackImpl(JS::Handle
<JS::Value
> aChunk
,
45 TransformStreamDefaultController
& aController
,
46 ErrorResult
& aRv
) override
{
48 if (!jsapi
.Init(aController
.GetParentObject())) {
49 aRv
.ThrowUnknownError("Internal error");
52 JSContext
* cx
= jsapi
.cx();
54 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
56 // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
57 // (ExtractSpanFromBufferSource does it)
58 Span
<const uint8_t> input
= ExtractSpanFromBufferSource(cx
, aChunk
, aRv
);
63 // Step 2: Let buffer be the result of decompressing chunk with ds's format
64 // and context. If this results in an error, then throw a TypeError.
65 // Step 3 - 5: (Done in CompressAndEnqueue)
66 DecompressAndEnqueue(cx
, input
, ZLibFlush::No
, aController
, aRv
);
70 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
71 // Let flushAlgorithm be an algorithm which takes no argument and runs the
72 // compress flush and enqueue algorithm with this.
73 MOZ_CAN_RUN_SCRIPT
void FlushCallbackImpl(
74 TransformStreamDefaultController
& aController
,
75 ErrorResult
& aRv
) override
{
77 if (!jsapi
.Init(aController
.GetParentObject())) {
78 aRv
.ThrowUnknownError("Internal error");
81 JSContext
* cx
= jsapi
.cx();
83 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
85 // Step 1: Let buffer be the result of decompressing an empty input with
86 // ds's format and context, with the finish flag.
87 // Step 2 - 4: (Done in CompressAndEnqueue)
88 DecompressAndEnqueue(cx
, Span
<const uint8_t>(), ZLibFlush::Yes
, aController
,
94 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
95 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
96 // All data errors throw TypeError by step 2: If this results in an error,
97 // then throw a TypeError.
98 MOZ_CAN_RUN_SCRIPT
void DecompressAndEnqueue(
99 JSContext
* aCx
, Span
<const uint8_t> aInput
, ZLibFlush aFlush
,
100 TransformStreamDefaultController
& aController
, ErrorResult
& aRv
) {
101 MOZ_ASSERT_IF(aFlush
== ZLibFlush::Yes
, !aInput
.Length());
103 mZStream
.avail_in
= aInput
.Length();
104 mZStream
.next_in
= const_cast<uint8_t*>(aInput
.Elements());
106 JS::RootedVector
<JSObject
*> array(aCx
);
109 static uint16_t kBufferSize
= 16384;
110 UniquePtr
<uint8_t> buffer(
111 static_cast<uint8_t*>(JS_malloc(aCx
, kBufferSize
)));
113 aRv
.ThrowTypeError("Out of memory");
117 mZStream
.avail_out
= kBufferSize
;
118 mZStream
.next_out
= buffer
.get();
120 int8_t err
= inflate(&mZStream
, aFlush
);
122 // From the manual: inflate() returns ...
125 // Z_DATA_ERROR if the input data was corrupted (input stream not
126 // conforming to the zlib format or incorrect check value, in which
127 // case strm->msg points to a string with a more specific error)
128 aRv
.ThrowTypeError("The input data is corrupted: "_ns
+
129 nsDependentCString(mZStream
.msg
));
132 // Z_MEM_ERROR if there was not enough memory
133 aRv
.ThrowTypeError("Out of memory");
136 // Z_NEED_DICT if a preset dictionary is needed at this point
138 // From the `deflate` section of
139 // https://wicg.github.io/compression/#supported-formats:
140 // * The FDICT flag is not supported by these APIs, and will error the
142 // And FDICT means preset dictionary per
143 // https://datatracker.ietf.org/doc/html/rfc1950#page-5.
145 "The stream needs a preset dictionary but such setup is "
149 // Z_STREAM_END if the end of the compressed data has been reached and
150 // all uncompressed output has been produced
152 // https://wicg.github.io/compression/#supported-formats has error
153 // conditions for each compression format when additional input comes
155 // Note that additional calls for inflate() immediately emits
156 // Z_STREAM_END after this point.
157 if (mZStream
.avail_in
> 0) {
158 aRv
.ThrowTypeError("Unexpected input after the end of stream");
161 mObservedStreamEnd
= true;
165 // * Z_OK if some progress has been made
166 // * Z_BUF_ERROR if no progress was possible or if there was not
167 // enough room in the output buffer when Z_FINISH is used. Note that
168 // Z_BUF_ERROR is not fatal, and inflate() can be called again with
169 // more input and more output space to continue decompressing.
171 // (But of course no input should be given after Z_FINISH)
175 // * Z_STREAM_ERROR if the stream state was inconsistent
177 MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
178 aRv
.ThrowTypeError("Unexpected decompression error");
182 // At this point we either exhausted the input or the output buffer
183 MOZ_ASSERT(!mZStream
.avail_in
|| !mZStream
.avail_out
);
185 size_t written
= kBufferSize
- mZStream
.avail_out
;
190 // Step 3: If buffer is empty, return.
191 // (We'll implicitly return when the array is empty.)
193 // Step 4: Split buffer into one or more non-empty pieces and convert them
195 // (The buffer is 'split' by having a fixed sized buffer above.)
197 JS::Rooted
<JSObject
*> view(
198 aCx
, nsJSUtils::MoveBufferAsUint8Array(aCx
, written
, buffer
));
199 if (!view
|| !array
.append(view
)) {
200 JS_ClearPendingException(aCx
);
201 aRv
.ThrowTypeError("Out of memory");
204 } while (mZStream
.avail_out
== 0 && !mObservedStreamEnd
);
206 // * It must update next_out and avail_out when avail_out has dropped to
208 // * inflate() should normally be called until it returns Z_STREAM_END or an
211 if (aFlush
== ZLibFlush::Yes
&& !mObservedStreamEnd
) {
213 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
214 // If the end of the compressed input has not been reached, then throw a
216 aRv
.ThrowTypeError("The input is ended without reaching the stream end");
220 // Step 5: For each Uint8Array array, enqueue array in cs's transform.
221 for (const auto& view
: array
) {
222 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*view
));
223 aController
.Enqueue(aCx
, value
, aRv
);
230 ~DecompressionStreamAlgorithms() override
{ inflateEnd(&mZStream
); };
232 z_stream mZStream
= {};
233 bool mObservedStreamEnd
= false;
236 NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms
,
237 TransformerAlgorithmsBase
)
238 NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms
,
239 TransformerAlgorithmsBase
)
240 NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms
,
241 TransformerAlgorithmsBase
)
242 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms
)
243 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase
)
245 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream
, mGlobal
, mStream
)
246 NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream
)
247 NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream
)
248 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream
)
249 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
250 NS_INTERFACE_MAP_ENTRY(nsISupports
)
253 DecompressionStream::DecompressionStream(nsISupports
* aGlobal
,
254 TransformStream
& aStream
)
255 : mGlobal(aGlobal
), mStream(&aStream
) {}
257 DecompressionStream::~DecompressionStream() = default;
259 JSObject
* DecompressionStream::WrapObject(JSContext
* aCx
,
260 JS::Handle
<JSObject
*> aGivenProto
) {
261 return DecompressionStream_Binding::Wrap(aCx
, this, aGivenProto
);
264 // https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream
265 already_AddRefed
<DecompressionStream
> DecompressionStream::Constructor(
266 const GlobalObject
& aGlobal
, CompressionFormat aFormat
, ErrorResult
& aRv
) {
267 // Step 1: If format is unsupported in DecompressionStream, then throw a
269 // XXX: Skipped as we are using enum for this
271 // Step 2 - 4: (Done in DecompressionStreamAlgorithms)
273 // Step 5: Set this's transform to a new TransformStream.
275 // Step 6: Set up this's transform with transformAlgorithm set to
276 // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
277 auto algorithms
= MakeRefPtr
<DecompressionStreamAlgorithms
>(aFormat
);
279 RefPtr
<TransformStream
> stream
=
280 TransformStream::CreateGeneric(aGlobal
, *algorithms
, aRv
);
284 return do_AddRef(new DecompressionStream(aGlobal
.GetAsSupports(), *stream
));
287 already_AddRefed
<ReadableStream
> DecompressionStream::Readable() const {
288 return do_AddRef(mStream
->Readable());
291 already_AddRefed
<WritableStream
> DecompressionStream::Writable() const {
292 return do_AddRef(mStream
->Writable());
295 } // namespace mozilla::dom