script/dom/
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 dom_struct::dom_struct;
10use flate2::write::{DeflateDecoder, GzDecoder, ZlibDecoder};
11use js::jsapi::JSObject;
12use js::jsval::UndefinedValue;
13use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
14use js::typedarray::Uint8Array;
15use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
16
17use crate::dom::bindings::buffer_source::create_buffer_source;
18use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
19use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
20use crate::dom::bindings::conversions::SafeToJSValConvertible;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
23use crate::dom::bindings::root::{Dom, DomRoot};
24use crate::dom::compressionstream::convert_chunk_to_vec;
25use crate::dom::transformstreamdefaultcontroller::TransformerType;
26use crate::dom::types::{
27    GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
28};
29use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
30
31/// A wrapper to blend ZlibDecoder<Vec<u8>>, DeflateDecoder<Vec<u8>> and GzDecoder<Vec<u8>>
32/// together as a single type.
33enum Decompressor {
34    Deflate(ZlibDecoder<Vec<u8>>),
35    DeflateRaw(DeflateDecoder<Vec<u8>>),
36    Gzip(GzDecoder<Vec<u8>>),
37}
38
39/// Expose methods of the inner decoder.
40impl Decompressor {
41    fn new(format: CompressionFormat) -> Decompressor {
42        match format {
43            CompressionFormat::Deflate => Decompressor::Deflate(ZlibDecoder::new(Vec::new())),
44            CompressionFormat::Deflate_raw => {
45                Decompressor::DeflateRaw(DeflateDecoder::new(Vec::new()))
46            },
47            CompressionFormat::Gzip => Decompressor::Gzip(GzDecoder::new(Vec::new())),
48        }
49    }
50
51    fn get_ref(&self) -> &Vec<u8> {
52        match self {
53            Decompressor::Deflate(zlib_decoder) => zlib_decoder.get_ref(),
54            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.get_ref(),
55            Decompressor::Gzip(gz_decoder) => gz_decoder.get_ref(),
56        }
57    }
58
59    fn get_mut(&mut self) -> &mut Vec<u8> {
60        match self {
61            Decompressor::Deflate(zlib_decoder) => zlib_decoder.get_mut(),
62            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.get_mut(),
63            Decompressor::Gzip(gz_decoder) => gz_decoder.get_mut(),
64        }
65    }
66
67    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
68        match self {
69            Decompressor::Deflate(zlib_decoder) => zlib_decoder.write(buf),
70            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.write(buf),
71            Decompressor::Gzip(gz_decoder) => gz_decoder.write(buf),
72        }
73    }
74
75    fn flush(&mut self) -> io::Result<()> {
76        match self {
77            Decompressor::Deflate(zlib_decoder) => zlib_decoder.flush(),
78            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.flush(),
79            Decompressor::Gzip(gz_decoder) => gz_decoder.flush(),
80        }
81    }
82
83    fn try_finish(&mut self) -> io::Result<()> {
84        match self {
85            Decompressor::Deflate(zlib_decoder) => zlib_decoder.try_finish(),
86            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.try_finish(),
87            Decompressor::Gzip(gz_decoder) => gz_decoder.try_finish(),
88        }
89    }
90}
91
92impl MallocSizeOf for Decompressor {
93    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
94        match self {
95            Decompressor::Deflate(zlib_decoder) => zlib_decoder.size_of(ops),
96            Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.size_of(ops),
97            Decompressor::Gzip(gz_decoder) => gz_decoder.size_of(ops),
98        }
99    }
100}
101
102/// <https://compression.spec.whatwg.org/#decompressionstream>
103#[dom_struct]
104pub(crate) struct DecompressionStream {
105    reflector_: Reflector,
106
107    /// <https://streams.spec.whatwg.org/#generictransformstream>
108    transform: Dom<TransformStream>,
109
110    /// <https://compression.spec.whatwg.org/#decompressionstream-format>
111    format: CompressionFormat,
112
113    // <https://compression.spec.whatwg.org/#decompressionstream-context>
114    #[no_trace]
115    context: RefCell<Decompressor>,
116}
117
118impl DecompressionStream {
119    fn new_inherited(
120        transform: &TransformStream,
121        format: CompressionFormat,
122    ) -> DecompressionStream {
123        DecompressionStream {
124            reflector_: Reflector::new(),
125            transform: Dom::from_ref(transform),
126            format,
127            context: RefCell::new(Decompressor::new(format)),
128        }
129    }
130
131    fn new_with_proto(
132        global: &GlobalScope,
133        proto: Option<SafeHandleObject>,
134        transform: &TransformStream,
135        format: CompressionFormat,
136        can_gc: CanGc,
137    ) -> DomRoot<DecompressionStream> {
138        reflect_dom_object_with_proto(
139            Box::new(DecompressionStream::new_inherited(transform, format)),
140            global,
141            proto,
142            can_gc,
143        )
144    }
145}
146
147impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
148    /// <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream>
149    fn Constructor(
150        global: &GlobalScope,
151        proto: Option<SafeHandleObject>,
152        can_gc: CanGc,
153        format: CompressionFormat,
154    ) -> Fallible<DomRoot<DecompressionStream>> {
155        // Step 1. If format is unsupported in DecompressionStream, then throw a TypeError.
156        // NOTE: All of "deflate", "deflate_raw" and "gzip" are supported.
157
158        // Step 2. Set this’s format to format.
159        // Step 5. Set this’s transform to a new TransformStream.
160        let transform = TransformStream::new_with_proto(global, None, can_gc);
161        let decompression_stream =
162            DecompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
163
164        // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the
165        // decompress and enqueue a chunk algorithm with this and chunk.
166        // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the
167        // decompress flush and enqueue algorithm with this.
168        let transformer_type = TransformerType::Decompressor(decompression_stream.clone());
169
170        // Step 6. Set up this’s transform with transformAlgorithm set to transformAlgorithm and
171        // flushAlgorithm set to flushAlgorithm.
172        let cx = GlobalScope::get_cx();
173        transform.set_up(cx, global, transformer_type, can_gc)?;
174
175        Ok(decompression_stream)
176    }
177
178    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
179    fn Readable(&self) -> DomRoot<ReadableStream> {
180        // The readable getter steps are to return this’s transform.[[readable]].
181        self.transform.get_readable()
182    }
183
184    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
185    fn Writable(&self) -> DomRoot<WritableStream> {
186        // The writable getter steps are to return this’s transform.[[writable]].
187        self.transform.get_writable()
188    }
189}
190
191/// <https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk>
192pub(crate) fn decompress_and_enqueue_a_chunk(
193    cx: SafeJSContext,
194    global: &GlobalScope,
195    ds: &DecompressionStream,
196    chunk: SafeHandleValue,
197    controller: &TransformStreamDefaultController,
198    can_gc: CanGc,
199) -> Fallible<()> {
200    // Step 1. If chunk is not a BufferSource type, then throw a TypeError.
201    let chunk = convert_chunk_to_vec(cx, chunk, can_gc)?;
202
203    // Step 2. Let buffer be the result of decompressing chunk with ds’s format and context. If
204    // this results in an error, then throw a TypeError.
205    // NOTE: In our implementation, the enum type of context already indicates the format.
206    let mut decompressor = ds.context.borrow_mut();
207    let mut offset = 0;
208    let mut written = 1;
209    while offset < chunk.len() && written > 0 {
210        written = decompressor
211            .write(&chunk[offset..])
212            .map_err(|_| Error::Type("DecompressionStream: write() failed".to_string()))?;
213        offset += written;
214    }
215    decompressor
216        .flush()
217        .map_err(|_| Error::Type("DecompressionStream: flush() failed".to_string()))?;
218    let buffer = decompressor.get_ref();
219
220    // Step 3. If buffer is empty, return.
221    if buffer.is_empty() {
222        return Ok(());
223    }
224
225    // Step 4. Let arrays be the result of splitting buffer into one or more non-empty pieces and
226    // converting them into Uint8Arrays.
227    // Step 5. For each Uint8Array array of arrays, enqueue array in ds’s transform.
228    // NOTE: We process the result in a single Uint8Array.
229    rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
230    let array: Uint8Array = create_buffer_source(cx, buffer, js_object.handle_mut(), can_gc)
231        .map_err(|_| Error::Type("Cannot convert byte sequence to Uint8Array".to_owned()))?;
232    rooted!(in(*cx) let mut rval = UndefinedValue());
233    array.safe_to_jsval(cx, rval.handle_mut(), can_gc);
234    controller.enqueue(cx, global, rval.handle(), can_gc)?;
235
236    // NOTE: We don't need to keep result that has been copied to Uint8Array. Clear the inner
237    // buffer of decompressor to save memory.
238    decompressor.get_mut().clear();
239
240    // Step 6. If the end of the compressed input has been reached, and ds’s context has not fully
241    // consumed chunk, then throw a TypeError.
242    if offset < chunk.len() {
243        return Err(Error::Type(
244            "The end of the compressed input has been reached".to_string(),
245        ));
246    }
247
248    Ok(())
249}
250
251/// <https://compression.spec.whatwg.org/#decompress-flush-and-enqueue>
252pub(crate) fn decompress_flush_and_enqueue(
253    cx: SafeJSContext,
254    global: &GlobalScope,
255    ds: &DecompressionStream,
256    controller: &TransformStreamDefaultController,
257    can_gc: CanGc,
258) -> Fallible<()> {
259    // Step 1. Let buffer be the result of decompressing an empty input with ds’s format and
260    // context, with the finish flag.
261    // NOTE: In our implementation, the enum type of context already indicates the format.
262    let mut decompressor = ds.context.borrow_mut();
263    let offset = decompressor.get_ref().len();
264    let is_ended = decompressor
265        .write(&[0])
266        .map_err(|_| Error::Type("DecompressionStream: write() failed".to_string()))? ==
267        0;
268    decompressor
269        .try_finish()
270        .map_err(|_| Error::Type("DecompressionStream: try_finish() failed".to_string()))?;
271    let buffer = &decompressor.get_ref()[offset..];
272
273    // Step 2. If buffer is empty, return.
274    if !buffer.is_empty() {
275        // Step 2.1. Let arrays be the result of splitting buffer into one or more non-empty pieces
276        // and converting them into Uint8Arrays.
277        // Step 2.2. For each Uint8Array array of arrays, enqueue array in ds’s transform.
278        // NOTE: We process the result in a single Uint8Array.
279        rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
280        let array: Uint8Array = create_buffer_source(cx, buffer, js_object.handle_mut(), can_gc)
281            .map_err(|_| Error::Type("Cannot convert byte sequence to Uint8Array".to_owned()))?;
282        rooted!(in(*cx) let mut rval = UndefinedValue());
283        array.safe_to_jsval(cx, rval.handle_mut(), can_gc);
284        controller.enqueue(cx, global, rval.handle(), can_gc)?;
285    }
286
287    // NOTE: We don't need to keep result that has been copied to Uint8Array. Clear the inner
288    // buffer of decompressor to save memory.
289    decompressor.get_mut().clear();
290
291    // Step 3. If the end of the compressed input has not been reached, then throw a TypeError.
292    //
293    // NOTE: If the end of the compressed input has not been reached, flate2::write::DeflateDecoder
294    // and flate2::write::GzDecoder can detect it and throw an error on `try_finish` in Step 1.
295    // However, flate2::write::ZlibDecoder does not. We need to test it by ourselves.
296    //
297    // To test it, we write one more byte to the decoder. If it accepts the extra byte, this
298    // indicates the end has not been reached. Otherwise, the end has been reached. This test has
299    // to been done before calling `try_finish`, so we execute it in Step 1, and store the result
300    // in `is_ended`.
301    if !is_ended {
302        return Err(Error::Type(
303            "The end of the compressed input has not been reached".to_string(),
304        ));
305    }
306
307    Ok(())
308}