script/dom/stream/
decompressionstream.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::io::{self, Write};
7use std::ptr;
8
9use brotli::DecompressorWriter as BrotliDecoder;
10use dom_struct::dom_struct;
11use flate2::write::{DeflateDecoder, GzDecoder, ZlibDecoder};
12use js::jsapi::JSObject;
13use js::jsval::UndefinedValue;
14use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
15use js::typedarray::Uint8;
16use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
17
18use crate::dom::bindings::buffer_source::create_buffer_source;
19use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
20use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
21use crate::dom::bindings::conversions::SafeToJSValConvertible;
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::stream::compressionstream::{BROTLI_BUFFER_SIZE, convert_chunk_to_vec};
26use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
27use crate::dom::types::{
28    GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
29};
30use crate::script_runtime::CanGc;
31
32/// <https://compression.spec.whatwg.org/#decompressionstream>
33#[dom_struct]
34pub(crate) struct DecompressionStream {
35    reflector_: Reflector,
36
37    /// <https://streams.spec.whatwg.org/#generictransformstream>
38    transform: Dom<TransformStream>,
39
40    /// <https://compression.spec.whatwg.org/#decompressionstream-format>
41    format: CompressionFormat,
42
43    // <https://compression.spec.whatwg.org/#decompressionstream-context>
44    #[no_trace]
45    context: RefCell<DecompressionContext>,
46}
47
48impl DecompressionStream {
49    fn new_inherited(
50        transform: &TransformStream,
51        format: CompressionFormat,
52    ) -> DecompressionStream {
53        DecompressionStream {
54            reflector_: Reflector::new(),
55            transform: Dom::from_ref(transform),
56            format,
57            context: RefCell::new(DecompressionContext::new(format)),
58        }
59    }
60
61    fn new_with_proto(
62        cx: &mut js::context::JSContext,
63        global: &GlobalScope,
64        proto: Option<SafeHandleObject>,
65        transform: &TransformStream,
66        format: CompressionFormat,
67    ) -> DomRoot<DecompressionStream> {
68        reflect_dom_object_with_proto_and_cx(
69            Box::new(DecompressionStream::new_inherited(transform, format)),
70            global,
71            proto,
72            cx,
73        )
74    }
75}
76
77impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
78    /// <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream>
79    fn Constructor(
80        cx: &mut js::context::JSContext,
81        global: &GlobalScope,
82        proto: Option<SafeHandleObject>,
83        format: CompressionFormat,
84    ) -> Fallible<DomRoot<DecompressionStream>> {
85        // Step 1. If format is unsupported in DecompressionStream, then throw a TypeError.
86        // NOTE: All of "brotli", "deflate", "deflate-raw" and "gzip" are supported.
87
88        // Step 2. Set this’s format to format.
89        // Step 5. Set this’s transform to a new TransformStream.
90        let transform = TransformStream::new_with_proto(global, None, CanGc::from_cx(cx));
91        let decompression_stream =
92            DecompressionStream::new_with_proto(cx, global, proto, &transform, format);
93
94        // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the
95        // decompress and enqueue a chunk algorithm with this and chunk.
96        // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the
97        // decompress flush and enqueue algorithm with this.
98        let transformer_type = TransformerType::Decompressor(decompression_stream.clone());
99
100        // Step 6. Set up this’s transform with transformAlgorithm set to transformAlgorithm and
101        // flushAlgorithm set to flushAlgorithm.
102        transform.set_up(cx, global, transformer_type)?;
103
104        Ok(decompression_stream)
105    }
106
107    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
108    fn Readable(&self) -> DomRoot<ReadableStream> {
109        // The readable getter steps are to return this’s transform.[[readable]].
110        self.transform.get_readable()
111    }
112
113    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
114    fn Writable(&self) -> DomRoot<WritableStream> {
115        // The writable getter steps are to return this’s transform.[[writable]].
116        self.transform.get_writable()
117    }
118}
119
120/// <https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk>
121pub(crate) fn decompress_and_enqueue_a_chunk(
122    cx: &mut js::context::JSContext,
123    global: &GlobalScope,
124    ds: &DecompressionStream,
125    chunk: SafeHandleValue,
126    controller: &TransformStreamDefaultController,
127) -> Fallible<()> {
128    // Step 1. If chunk is not a BufferSource type, then throw a TypeError.
129    let chunk = convert_chunk_to_vec(cx.into(), chunk, CanGc::from_cx(cx))?;
130
131    // Step 2. Let buffer be the result of decompressing chunk with ds’s format and context. If
132    // this results in an error, then throw a TypeError.
133    // NOTE: In our implementation, the enum type of context already indicates the format.
134    let mut decompression_context = ds.context.borrow_mut();
135    let buffer = decompression_context
136        .decompress(&chunk)
137        .map_err(|_| Error::Type(c"Failed to decompress a chunk of compressed input".into()))?;
138
139    // Step 3. If buffer is empty, return.
140    if buffer.is_empty() {
141        return Ok(());
142    }
143
144    // Step 4. Let arrays be the result of splitting buffer into one or more non-empty pieces and
145    // converting them into Uint8Arrays.
146    // Step 5. For each Uint8Array array of arrays, enqueue array in ds’s transform.
147    // NOTE: We process the result in a single Uint8Array.
148    rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
149    let array = create_buffer_source::<Uint8>(
150        cx.into(),
151        &buffer,
152        js_object.handle_mut(),
153        CanGc::from_cx(cx),
154    )
155    .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
156    rooted!(&in(cx) let mut rval = UndefinedValue());
157    array.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
158    controller.enqueue(cx, global, rval.handle())?;
159
160    // Step 6. If the end of the compressed input has been reached, and ds’s context has not fully
161    // consumed chunk, then throw a TypeError.
162    if decompression_context.is_ended {
163        return Err(Error::Type(
164            c"The end of the compressed input has been reached".to_owned(),
165        ));
166    }
167
168    Ok(())
169}
170
171/// <https://compression.spec.whatwg.org/#decompress-flush-and-enqueue>
172pub(crate) fn decompress_flush_and_enqueue(
173    cx: &mut js::context::JSContext,
174    global: &GlobalScope,
175    ds: &DecompressionStream,
176    controller: &TransformStreamDefaultController,
177) -> Fallible<()> {
178    // Step 1. Let buffer be the result of decompressing an empty input with ds’s format and
179    // context, with the finish flag.
180    // NOTE: In our implementation, the enum type of context already indicates the format.
181    let mut decompression_context = ds.context.borrow_mut();
182    let buffer = decompression_context
183        .finalize()
184        .map_err(|_| Error::Type(c"Failed to finalize the decompression stream".into()))?;
185
186    // Step 2. If buffer is empty, return.
187    if !buffer.is_empty() {
188        // Step 2.1. Let arrays be the result of splitting buffer into one or more non-empty pieces
189        // and converting them into Uint8Arrays.
190        // Step 2.2. For each Uint8Array array of arrays, enqueue array in ds’s transform.
191        // NOTE: We process the result in a single Uint8Array.
192        rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
193        let array = create_buffer_source::<Uint8>(
194            cx.into(),
195            &buffer,
196            js_object.handle_mut(),
197            CanGc::from_cx(cx),
198        )
199        .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
200        rooted!(&in(cx) let mut rval = UndefinedValue());
201        array.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
202        controller.enqueue(cx, global, rval.handle())?;
203    }
204
205    // Step 3. If the end of the compressed input has not been reached, then throw a TypeError.
206    //
207    // NOTE: If the end of the compressed input has not been reached, flate2::write::DeflateDecoder
208    // and flate2::write::GzDecoder can detect it and throw an error on `try_finish` in Step 1.
209    // However, flate2::write::ZlibDecoder does not. We need to test it by ourselves.
210    //
211    // To test it, we write one more byte to the decoder. If it accepts the extra byte, this
212    // indicates the end has not been reached. Otherwise, the end has been reached. This test has
213    // to been done before calling `try_finish`, so we execute it in Step 1, and store the result
214    // in `is_ended`.
215    if !decompression_context.is_ended {
216        return Err(Error::Type(
217            c"The end of the compressed input has not been reached".to_owned(),
218        ));
219    }
220
221    Ok(())
222}
223
224/// An enum grouping decoders of differenct compression algorithms.
225enum Decoder {
226    Brotli(Box<BrotliDecoder<Vec<u8>>>),
227    Deflate(ZlibDecoder<Vec<u8>>),
228    DeflateRaw(DeflateDecoder<Vec<u8>>),
229    Gzip(GzDecoder<Vec<u8>>),
230}
231
232impl MallocSizeOf for Decoder {
233    #[expect(unsafe_code)]
234    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
235        match self {
236            Decoder::Brotli(decoder) => unsafe { ops.malloc_size_of(&**decoder) },
237            Decoder::Deflate(decoder) => decoder.size_of(ops),
238            Decoder::DeflateRaw(decoder) => decoder.size_of(ops),
239            Decoder::Gzip(decoder) => decoder.size_of(ops),
240        }
241    }
242}
243
244/// <https://compression.spec.whatwg.org/#decompressionstream-context>
245/// Used to encapsulate the logic of decoder.
246#[derive(MallocSizeOf)]
247struct DecompressionContext {
248    decoder: Decoder,
249    is_ended: bool,
250}
251
252impl DecompressionContext {
253    fn new(format: CompressionFormat) -> DecompressionContext {
254        let decoder = match format {
255            CompressionFormat::Brotli => {
256                Decoder::Brotli(Box::new(BrotliDecoder::new(Vec::new(), BROTLI_BUFFER_SIZE)))
257            },
258            CompressionFormat::Deflate => Decoder::Deflate(ZlibDecoder::new(Vec::new())),
259            CompressionFormat::Deflate_raw => Decoder::DeflateRaw(DeflateDecoder::new(Vec::new())),
260            CompressionFormat::Gzip => Decoder::Gzip(GzDecoder::new(Vec::new())),
261        };
262        DecompressionContext {
263            decoder,
264            is_ended: false,
265        }
266    }
267
268    fn decompress(&mut self, mut chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
269        let mut result = Vec::new();
270
271        match &mut self.decoder {
272            Decoder::Brotli(decoder) => {
273                while !chunk.is_empty() {
274                    let written = decoder.write(chunk)?;
275                    if written == 0 {
276                        self.is_ended = true;
277                        break;
278                    }
279                    chunk = &chunk[written..];
280                }
281                decoder.flush()?;
282                result.append(decoder.get_mut());
283            },
284            Decoder::Deflate(decoder) => {
285                while !chunk.is_empty() {
286                    let written = decoder.write(chunk)?;
287                    if written == 0 {
288                        self.is_ended = true;
289                        break;
290                    }
291                    chunk = &chunk[written..];
292                }
293                decoder.flush()?;
294                result.append(decoder.get_mut());
295            },
296            Decoder::DeflateRaw(decoder) => {
297                while !chunk.is_empty() {
298                    let written = decoder.write(chunk)?;
299                    if written == 0 {
300                        self.is_ended = true;
301                        break;
302                    }
303                    chunk = &chunk[written..];
304                }
305                decoder.flush()?;
306                result.append(decoder.get_mut());
307            },
308            Decoder::Gzip(decoder) => {
309                while !chunk.is_empty() {
310                    let written = decoder.write(chunk)?;
311                    if written == 0 {
312                        self.is_ended = true;
313                        break;
314                    }
315                    chunk = &chunk[written..];
316                }
317                decoder.flush()?;
318                result.append(decoder.get_mut());
319            },
320        }
321
322        Ok(result)
323    }
324
325    fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
326        let mut result = Vec::new();
327
328        match &mut self.decoder {
329            Decoder::Brotli(decoder) => {
330                if decoder.close().is_ok() {
331                    self.is_ended = true;
332                };
333                result.append(decoder.get_mut());
334            },
335            Decoder::Deflate(decoder) => {
336                // Compressed data in "Deflate" format does not have trailing bytes. Therefore,
337                // `ZlibEncoder::try_finish` is designed not to throw an error when the end of
338                // compressed input has not been reached, in order to decompress as much of the
339                // input as possible.
340                //
341                // To detect whether the end is reached, the workaround is to write one more byte to
342                // the encoder. Refusing to take the extra byte indicates the end has been reached.
343                //
344                // Note that we need to pull out the data in buffer first to avoid the extra byte
345                // contaminate the output.
346                decoder.flush()?;
347                result.append(decoder.get_mut());
348                if decoder.write(&[0])? == 0 {
349                    self.is_ended = true;
350                }
351                decoder.try_finish()?;
352            },
353            Decoder::DeflateRaw(decoder) => {
354                if decoder.try_finish().is_ok() {
355                    self.is_ended = true;
356                };
357                result.append(decoder.get_mut());
358            },
359            Decoder::Gzip(decoder) => {
360                if decoder.try_finish().is_ok() {
361                    self.is_ended = true;
362                };
363                result.append(decoder.get_mut());
364            },
365        }
366
367        Ok(result)
368    }
369}