1use std::cell::RefCell;
6use std::io::{self, Write};
7use std::ptr;
8
9#[cfg(feature = "brotli-compression-stream")]
10use brotli::DecompressorWriter as BrotliDecoder;
11use dom_struct::dom_struct;
12use flate2::write::{DeflateDecoder, GzDecoder, ZlibDecoder};
13use js::jsapi::JSObject;
14use js::jsval::UndefinedValue;
15use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
16use js::typedarray::Uint8;
17use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
18use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
19
20use crate::dom::bindings::buffer_source::create_buffer_source;
21use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
22use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
23use crate::dom::bindings::conversions::ToJSValConvertible;
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::root::{Dom, DomRoot};
26#[cfg(feature = "brotli-compression-stream")]
27use crate::dom::stream::compressionstream::BROTLI_BUFFER_SIZE;
28use crate::dom::stream::compressionstream::convert_chunk_to_vec;
29use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
30use crate::dom::types::{
31 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
32};
33
34#[dom_struct]
36pub(crate) struct DecompressionStream {
37 reflector_: Reflector,
38
39 transform: Dom<TransformStream>,
41
42 format: CompressionFormat,
44
45 #[no_trace]
47 context: RefCell<DecompressionContext>,
48}
49
50impl DecompressionStream {
51 fn new_inherited(
52 transform: &TransformStream,
53 format: CompressionFormat,
54 ) -> Fallible<DecompressionStream> {
55 Ok(DecompressionStream {
56 reflector_: Reflector::new(),
57 transform: Dom::from_ref(transform),
58 format,
59 context: RefCell::new(DecompressionContext::new(format)?),
60 })
61 }
62
63 #[cfg_attr(crown, expect(crown::unrooted_must_root))] fn new_with_proto(
65 cx: &mut js::context::JSContext,
66 global: &GlobalScope,
67 proto: Option<SafeHandleObject>,
68 transform: &TransformStream,
69 format: CompressionFormat,
70 ) -> Fallible<DomRoot<DecompressionStream>> {
71 Ok(reflect_dom_object_with_proto(
72 cx,
73 Box::new(DecompressionStream::new_inherited(transform, format)?),
74 global,
75 proto,
76 ))
77 }
78}
79
80impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
81 fn Constructor(
83 cx: &mut js::context::JSContext,
84 global: &GlobalScope,
85 proto: Option<SafeHandleObject>,
86 format: CompressionFormat,
87 ) -> Fallible<DomRoot<DecompressionStream>> {
88 let transform = TransformStream::new_with_proto(cx, global, None);
94 let decompression_stream =
95 DecompressionStream::new_with_proto(cx, global, proto, &transform, format)?;
96
97 transform.set_up(
105 cx,
106 global,
107 TransformerType::Decompressor(decompression_stream.as_traced()),
108 )?;
109
110 Ok(decompression_stream)
111 }
112
113 fn Readable(&self) -> DomRoot<ReadableStream> {
115 self.transform.get_readable()
117 }
118
119 fn Writable(&self) -> DomRoot<WritableStream> {
121 self.transform.get_writable()
123 }
124}
125
126pub(crate) fn decompress_and_enqueue_a_chunk(
128 cx: &mut js::context::JSContext,
129 global: &GlobalScope,
130 ds: &DecompressionStream,
131 chunk: SafeHandleValue,
132 controller: &TransformStreamDefaultController,
133) -> Fallible<()> {
134 let chunk = convert_chunk_to_vec(cx, chunk)?;
136
137 let buffer = {
141 let mut decompression_context = ds.context.borrow_mut();
142 let buffer = decompression_context
143 .decompress(&chunk)
144 .map_err(|_| Error::Type(c"Failed to decompress a chunk of compressed input".into()))?;
145
146 if buffer.is_empty() {
148 return Ok(());
149 }
150 buffer
151 };
152 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
157 let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
158 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
159 rooted!(&in(cx) let mut rval = UndefinedValue());
160 array.safe_to_jsval(cx, rval.handle_mut());
161 controller.enqueue(cx, global, rval.handle())?;
162
163 if ds.context.borrow().is_ended {
166 return Err(Error::Type(
167 c"The end of the compressed input has been reached".to_owned(),
168 ));
169 }
170
171 Ok(())
172}
173
174pub(crate) fn decompress_flush_and_enqueue(
176 cx: &mut js::context::JSContext,
177 global: &GlobalScope,
178 ds: &DecompressionStream,
179 controller: &TransformStreamDefaultController,
180) -> Fallible<()> {
181 let buffer = {
185 let mut decompression_context = ds.context.borrow_mut();
186 decompression_context
187 .finalize()
188 .map_err(|_| Error::Type(c"Failed to finalize the decompression stream".into()))?
189 };
190 if !buffer.is_empty() {
192 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
197 let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
198 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
199 rooted!(&in(cx) let mut rval = UndefinedValue());
200 array.safe_to_jsval(cx, rval.handle_mut());
201 controller.enqueue(cx, global, rval.handle())?;
202 }
203
204 if !ds.context.borrow().is_ended {
215 return Err(Error::Type(
216 c"The end of the compressed input has not been reached".to_owned(),
217 ));
218 }
219
220 Ok(())
221}
222
223enum Decoder {
225 #[cfg(feature = "brotli-compression-stream")]
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 #[cfg_attr(feature = "brotli-compression-stream", expect(unsafe_code))]
234 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
235 match self {
236 #[cfg(feature = "brotli-compression-stream")]
237 Decoder::Brotli(decoder) => unsafe { ops.malloc_size_of(&**decoder) },
238 Decoder::Deflate(decoder) => decoder.size_of(ops),
239 Decoder::DeflateRaw(decoder) => decoder.size_of(ops),
240 Decoder::Gzip(decoder) => decoder.size_of(ops),
241 }
242 }
243}
244
245#[derive(MallocSizeOf)]
248struct DecompressionContext {
249 decoder: Decoder,
250 is_ended: bool,
251}
252
253impl DecompressionContext {
254 fn new(format: CompressionFormat) -> Fallible<DecompressionContext> {
255 let decoder = match format {
256 #[cfg(feature = "brotli-compression-stream")]
257 CompressionFormat::Brotli => {
258 Decoder::Brotli(Box::new(BrotliDecoder::new(Vec::new(), BROTLI_BUFFER_SIZE)))
259 },
260 #[cfg(not(feature = "brotli-compression-stream"))]
261 CompressionFormat::Brotli => {
262 return Err(Error::NotSupported(Some("Brotli not supported".into())));
263 },
264 CompressionFormat::Deflate => Decoder::Deflate(ZlibDecoder::new(Vec::new())),
265 CompressionFormat::Deflate_raw => Decoder::DeflateRaw(DeflateDecoder::new(Vec::new())),
266 CompressionFormat::Gzip => Decoder::Gzip(GzDecoder::new(Vec::new())),
267 };
268 Ok(DecompressionContext {
269 decoder,
270 is_ended: false,
271 })
272 }
273
274 fn decompress(&mut self, mut chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
275 let mut result = Vec::new();
276
277 match &mut self.decoder {
278 #[cfg(feature = "brotli-compression-stream")]
279 Decoder::Brotli(decoder) => {
280 while !chunk.is_empty() {
281 let written = decoder.write(chunk)?;
282 if written == 0 {
283 self.is_ended = true;
284 break;
285 }
286 chunk = &chunk[written..];
287 }
288 decoder.flush()?;
289 result.append(decoder.get_mut());
290 },
291 Decoder::Deflate(decoder) => {
292 while !chunk.is_empty() {
293 let written = decoder.write(chunk)?;
294 if written == 0 {
295 self.is_ended = true;
296 break;
297 }
298 chunk = &chunk[written..];
299 }
300 decoder.flush()?;
301 result.append(decoder.get_mut());
302 },
303 Decoder::DeflateRaw(decoder) => {
304 while !chunk.is_empty() {
305 let written = decoder.write(chunk)?;
306 if written == 0 {
307 self.is_ended = true;
308 break;
309 }
310 chunk = &chunk[written..];
311 }
312 decoder.flush()?;
313 result.append(decoder.get_mut());
314 },
315 Decoder::Gzip(decoder) => {
316 while !chunk.is_empty() {
317 let written = decoder.write(chunk)?;
318 if written == 0 {
319 self.is_ended = true;
320 break;
321 }
322 chunk = &chunk[written..];
323 }
324 decoder.flush()?;
325 result.append(decoder.get_mut());
326 },
327 }
328
329 Ok(result)
330 }
331
332 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
333 let mut result = Vec::new();
334
335 match &mut self.decoder {
336 #[cfg(feature = "brotli-compression-stream")]
337 Decoder::Brotli(decoder) => {
338 if decoder.close().is_ok() {
339 self.is_ended = true;
340 };
341 result.append(decoder.get_mut());
342 },
343 Decoder::Deflate(decoder) => {
344 decoder.flush()?;
355 result.append(decoder.get_mut());
356 if decoder.write(&[0])? == 0 {
357 self.is_ended = true;
358 }
359 decoder.try_finish()?;
360 },
361 Decoder::DeflateRaw(decoder) => {
362 if decoder.try_finish().is_ok() {
363 self.is_ended = true;
364 };
365 result.append(decoder.get_mut());
366 },
367 Decoder::Gzip(decoder) => {
368 if decoder.try_finish().is_ok() {
369 self.is_ended = true;
370 };
371 result.append(decoder.get_mut());
372 },
373 }
374
375 Ok(result)
376 }
377}