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