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"
17 #include "mozilla/dom/UnionTypes.h"
19 #include "ZLibHelper.h"
21 // See the zlib manual in https://www.zlib.net/manual.html or in
22 // https://searchfox.org/mozilla-central/source/modules/zlib/src/zlib.h
24 namespace mozilla::dom
{
26 class DecompressionStreamAlgorithms
: public TransformerAlgorithmsWrapper
{
28 NS_DECL_ISUPPORTS_INHERITED
29 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecompressionStreamAlgorithms
,
30 TransformerAlgorithmsBase
)
32 explicit DecompressionStreamAlgorithms(CompressionFormat format
) {
33 int8_t err
= inflateInit2(&mZStream
, ZLibWindowBits(format
));
34 if (err
== Z_MEM_ERROR
) {
35 MOZ_CRASH("Out of memory");
37 MOZ_ASSERT(err
== Z_OK
);
41 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
42 // Let transformAlgorithm be an algorithm which takes a chunk argument and
43 // runs the compress and enqueue a chunk algorithm with this and chunk.
45 void TransformCallbackImpl(JS::Handle
<JS::Value
> aChunk
,
46 TransformStreamDefaultController
& aController
,
47 ErrorResult
& aRv
) override
{
49 if (!jsapi
.Init(aController
.GetParentObject())) {
50 aRv
.ThrowUnknownError("Internal error");
53 JSContext
* cx
= jsapi
.cx();
55 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
57 // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
58 RootedUnion
<OwningArrayBufferViewOrArrayBuffer
> bufferSource(cx
);
59 if (!bufferSource
.Init(cx
, aChunk
)) {
60 aRv
.MightThrowJSException();
61 aRv
.StealExceptionFromJSContext(cx
);
65 // Step 2: Let buffer be the result of decompressing chunk with ds's format
66 // and context. If this results in an error, then throw a TypeError.
67 // Step 3 - 5: (Done in CompressAndEnqueue)
68 ProcessTypedArraysFixed(
70 [&](const Span
<uint8_t>& aData
) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
71 DecompressAndEnqueue(cx
, aData
, ZLibFlush::No
, aController
, aRv
);
76 // https://wicg.github.io/compression/#dom-compressionstream-compressionstream
77 // Let flushAlgorithm be an algorithm which takes no argument and runs the
78 // compress flush and enqueue algorithm with this.
79 MOZ_CAN_RUN_SCRIPT
void FlushCallbackImpl(
80 TransformStreamDefaultController
& aController
,
81 ErrorResult
& aRv
) override
{
83 if (!jsapi
.Init(aController
.GetParentObject())) {
84 aRv
.ThrowUnknownError("Internal error");
87 JSContext
* cx
= jsapi
.cx();
89 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
91 // Step 1: Let buffer be the result of decompressing an empty input with
92 // ds's format and context, with the finish flag.
93 // Step 2 - 4: (Done in CompressAndEnqueue)
94 DecompressAndEnqueue(cx
, Span
<const uint8_t>(), ZLibFlush::Yes
, aController
,
100 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk
101 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
102 // All data errors throw TypeError by step 2: If this results in an error,
103 // then throw a TypeError.
104 MOZ_CAN_RUN_SCRIPT
void DecompressAndEnqueue(
105 JSContext
* aCx
, Span
<const uint8_t> aInput
, ZLibFlush aFlush
,
106 TransformStreamDefaultController
& aController
, ErrorResult
& aRv
) {
107 MOZ_ASSERT_IF(aFlush
== ZLibFlush::Yes
, !aInput
.Length());
109 mZStream
.avail_in
= aInput
.Length();
110 mZStream
.next_in
= const_cast<uint8_t*>(aInput
.Elements());
112 JS::RootedVector
<JSObject
*> array(aCx
);
115 static uint16_t kBufferSize
= 16384;
116 UniquePtr
<uint8_t[], JS::FreePolicy
> buffer(
117 static_cast<uint8_t*>(JS_malloc(aCx
, kBufferSize
)));
119 aRv
.ThrowTypeError("Out of memory");
123 mZStream
.avail_out
= kBufferSize
;
124 mZStream
.next_out
= buffer
.get();
126 int8_t err
= inflate(&mZStream
, aFlush
);
128 // From the manual: inflate() returns ...
131 // Z_DATA_ERROR if the input data was corrupted (input stream not
132 // conforming to the zlib format or incorrect check value, in which
133 // case strm->msg points to a string with a more specific error)
134 aRv
.ThrowTypeError("The input data is corrupted: "_ns
+
135 nsDependentCString(mZStream
.msg
));
138 // Z_MEM_ERROR if there was not enough memory
139 aRv
.ThrowTypeError("Out of memory");
142 // Z_NEED_DICT if a preset dictionary is needed at this point
144 // From the `deflate` section of
145 // https://wicg.github.io/compression/#supported-formats:
146 // * The FDICT flag is not supported by these APIs, and will error the
148 // And FDICT means preset dictionary per
149 // https://datatracker.ietf.org/doc/html/rfc1950#page-5.
151 "The stream needs a preset dictionary but such setup is "
155 // Z_STREAM_END if the end of the compressed data has been reached and
156 // all uncompressed output has been produced
158 // https://wicg.github.io/compression/#supported-formats has error
159 // conditions for each compression format when additional input comes
161 // Note that additional calls for inflate() immediately emits
162 // Z_STREAM_END after this point.
163 if (mZStream
.avail_in
> 0) {
164 aRv
.ThrowTypeError("Unexpected input after the end of stream");
167 mObservedStreamEnd
= true;
171 // * Z_OK if some progress has been made
172 // * Z_BUF_ERROR if no progress was possible or if there was not
173 // enough room in the output buffer when Z_FINISH is used. Note that
174 // Z_BUF_ERROR is not fatal, and inflate() can be called again with
175 // more input and more output space to continue decompressing.
177 // (But of course no input should be given after Z_FINISH)
181 // * Z_STREAM_ERROR if the stream state was inconsistent
183 MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code");
184 aRv
.ThrowTypeError("Unexpected decompression error");
188 // At this point we either exhausted the input or the output buffer
189 MOZ_ASSERT(!mZStream
.avail_in
|| !mZStream
.avail_out
);
191 size_t written
= kBufferSize
- mZStream
.avail_out
;
196 // Step 3: If buffer is empty, return.
197 // (We'll implicitly return when the array is empty.)
199 // Step 4: Split buffer into one or more non-empty pieces and convert them
201 // (The buffer is 'split' by having a fixed sized buffer above.)
203 JS::Rooted
<JSObject
*> view(aCx
, nsJSUtils::MoveBufferAsUint8Array(
204 aCx
, written
, std::move(buffer
)));
205 if (!view
|| !array
.append(view
)) {
206 JS_ClearPendingException(aCx
);
207 aRv
.ThrowTypeError("Out of memory");
210 } while (mZStream
.avail_out
== 0 && !mObservedStreamEnd
);
212 // * It must update next_out and avail_out when avail_out has dropped to
214 // * inflate() should normally be called until it returns Z_STREAM_END or an
217 if (aFlush
== ZLibFlush::Yes
&& !mObservedStreamEnd
) {
219 // https://wicg.github.io/compression/#decompress-flush-and-enqueue
220 // If the end of the compressed input has not been reached, then throw a
222 aRv
.ThrowTypeError("The input is ended without reaching the stream end");
226 // Step 5: For each Uint8Array array, enqueue array in cs's transform.
227 for (const auto& view
: array
) {
228 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*view
));
229 aController
.Enqueue(aCx
, value
, aRv
);
236 ~DecompressionStreamAlgorithms() override
{ inflateEnd(&mZStream
); };
238 z_stream mZStream
= {};
239 bool mObservedStreamEnd
= false;
242 NS_IMPL_CYCLE_COLLECTION_INHERITED(DecompressionStreamAlgorithms
,
243 TransformerAlgorithmsBase
)
244 NS_IMPL_ADDREF_INHERITED(DecompressionStreamAlgorithms
,
245 TransformerAlgorithmsBase
)
246 NS_IMPL_RELEASE_INHERITED(DecompressionStreamAlgorithms
,
247 TransformerAlgorithmsBase
)
248 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStreamAlgorithms
)
249 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase
)
251 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DecompressionStream
, mGlobal
, mStream
)
252 NS_IMPL_CYCLE_COLLECTING_ADDREF(DecompressionStream
)
253 NS_IMPL_CYCLE_COLLECTING_RELEASE(DecompressionStream
)
254 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DecompressionStream
)
255 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
256 NS_INTERFACE_MAP_ENTRY(nsISupports
)
259 DecompressionStream::DecompressionStream(nsISupports
* aGlobal
,
260 TransformStream
& aStream
)
261 : mGlobal(aGlobal
), mStream(&aStream
) {}
263 DecompressionStream::~DecompressionStream() = default;
265 JSObject
* DecompressionStream::WrapObject(JSContext
* aCx
,
266 JS::Handle
<JSObject
*> aGivenProto
) {
267 return DecompressionStream_Binding::Wrap(aCx
, this, aGivenProto
);
270 // https://wicg.github.io/compression/#dom-decompressionstream-decompressionstream
271 already_AddRefed
<DecompressionStream
> DecompressionStream::Constructor(
272 const GlobalObject
& aGlobal
, CompressionFormat aFormat
, ErrorResult
& aRv
) {
273 // Step 1: If format is unsupported in DecompressionStream, then throw a
275 // XXX: Skipped as we are using enum for this
277 // Step 2 - 4: (Done in DecompressionStreamAlgorithms)
279 // Step 5: Set this's transform to a new TransformStream.
281 // Step 6: Set up this's transform with transformAlgorithm set to
282 // transformAlgorithm and flushAlgorithm set to flushAlgorithm.
283 auto algorithms
= MakeRefPtr
<DecompressionStreamAlgorithms
>(aFormat
);
285 RefPtr
<TransformStream
> stream
=
286 TransformStream::CreateGeneric(aGlobal
, *algorithms
, aRv
);
290 return do_AddRef(new DecompressionStream(aGlobal
.GetAsSupports(), *stream
));
293 already_AddRefed
<ReadableStream
> DecompressionStream::Readable() const {
294 return do_AddRef(mStream
->Readable());
297 already_AddRefed
<WritableStream
> DecompressionStream::Writable() const {
298 return do_AddRef(mStream
->Writable());
301 } // namespace mozilla::dom