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;
17#[cfg(feature = "brotli-compression-stream")]
18use malloc_size_of::MallocShallowSizeOf;
19use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
20use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
21
22use crate::dom::bindings::buffer_source::{create_buffer_source, get_buffer_source_slice};
23use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
24use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
25use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
26use crate::dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible};
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::root::{Dom, DomRoot};
29#[cfg(feature = "brotli-compression-stream")]
30use crate::dom::stream::compressionstream::BROTLI_BUFFER_SIZE;
31use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
32use crate::dom::types::{
33 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
34};
35
36#[dom_struct]
38pub(crate) struct DecompressionStream {
39 reflector_: Reflector,
40
41 transform: Dom<TransformStream>,
43
44 format: CompressionFormat,
46
47 #[no_trace]
49 context: RefCell<DecompressionContext>,
50}
51
52impl DecompressionStream {
53 fn new_inherited(
54 transform: &TransformStream,
55 format: CompressionFormat,
56 ) -> Fallible<DecompressionStream> {
57 Ok(DecompressionStream {
58 reflector_: Reflector::new(),
59 transform: Dom::from_ref(transform),
60 format,
61 context: RefCell::new(DecompressionContext::new(format)?),
62 })
63 }
64
65 #[cfg_attr(crown, expect(crown::unrooted_must_root))] fn new_with_proto(
67 cx: &mut js::context::JSContext,
68 global: &GlobalScope,
69 proto: Option<SafeHandleObject>,
70 transform: &TransformStream,
71 format: CompressionFormat,
72 ) -> Fallible<DomRoot<DecompressionStream>> {
73 Ok(reflect_dom_object_with_proto(
74 cx,
75 Box::new(DecompressionStream::new_inherited(transform, format)?),
76 global,
77 proto,
78 ))
79 }
80}
81
82impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
83 fn Constructor(
85 cx: &mut js::context::JSContext,
86 global: &GlobalScope,
87 proto: Option<SafeHandleObject>,
88 format: CompressionFormat,
89 ) -> Fallible<DomRoot<DecompressionStream>> {
90 let transform = TransformStream::new_with_proto(cx, global, None);
96 let decompression_stream =
97 DecompressionStream::new_with_proto(cx, global, proto, &transform, format)?;
98
99 transform.set_up(
107 cx,
108 global,
109 TransformerType::Decompressor(decompression_stream.as_traced()),
110 )?;
111
112 Ok(decompression_stream)
113 }
114
115 fn Readable(&self) -> DomRoot<ReadableStream> {
117 self.transform.get_readable()
119 }
120
121 fn Writable(&self) -> DomRoot<WritableStream> {
123 self.transform.get_writable()
125 }
126}
127
128pub(crate) fn decompress_and_enqueue_a_chunk(
130 cx: &mut js::context::JSContext,
131 global: &GlobalScope,
132 ds: &DecompressionStream,
133 chunk: SafeHandleValue,
134 controller: &TransformStreamDefaultController,
135) -> Fallible<()> {
136 let conversion_result =
138 ArrayBufferViewOrArrayBuffer::from_jsval(cx, chunk, ()).map_err(|_| {
139 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
140 })?;
141 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
142 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
143 })?;
144
145 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
149 let array = {
150 let mut decompression_context = ds.context.borrow_mut();
151 let buffer = decompression_context
152 .decompress(get_buffer_source_slice(buffer_source, cx.no_gc()))
153 .map_err(|_| Error::Type(c"Failed to decompress a chunk of compressed input".into()))?;
154
155 if buffer.is_empty() {
157 return Ok(());
158 }
159 create_buffer_source::<Uint8>(cx, buffer, js_object.handle_mut())
164 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?
165 };
166 rooted!(&in(cx) let mut rval = UndefinedValue());
168 array.to_jsval(cx, rval.handle_mut());
169 controller.enqueue(cx, global, rval.handle())?;
170
171 if ds.context.borrow().is_ended {
174 return Err(Error::Type(
175 c"The end of the compressed input has been reached".to_owned(),
176 ));
177 }
178
179 Ok(())
180}
181
182pub(crate) fn decompress_flush_and_enqueue(
184 cx: &mut js::context::JSContext,
185 global: &GlobalScope,
186 ds: &DecompressionStream,
187 controller: &TransformStreamDefaultController,
188) -> Fallible<()> {
189 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
193 let array = {
194 let mut decompression_context = ds.context.borrow_mut();
195 let buffer = decompression_context
196 .finalize()
197 .map_err(|_| Error::Type(c"Failed to finalize the decompression stream".into()))?;
198 if buffer.is_empty() {
200 None
201 } else {
202 Some(
206 create_buffer_source::<Uint8>(cx, buffer, js_object.handle_mut()).map_err(
207 |_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()),
208 )?,
209 )
210 }
211 };
212 if let Some(array) = array {
213 rooted!(&in(cx) let mut rval = UndefinedValue());
215 array.to_jsval(cx, rval.handle_mut());
216 controller.enqueue(cx, global, rval.handle())?;
217 }
218
219 if !ds.context.borrow().is_ended {
230 return Err(Error::Type(
231 c"The end of the compressed input has not been reached".to_owned(),
232 ));
233 }
234
235 Ok(())
236}
237
238enum Decoder {
240 #[cfg(feature = "brotli-compression-stream")]
241 Brotli(Box<BrotliDecoder<Vec<u8>>>),
242 Deflate(ZlibDecoder<Vec<u8>>),
243 DeflateRaw(DeflateDecoder<Vec<u8>>),
244 Gzip(GzDecoder<Vec<u8>>),
245}
246
247impl MallocSizeOf for Decoder {
248 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
249 match self {
250 #[cfg(feature = "brotli-compression-stream")]
251 Decoder::Brotli(decoder) => decoder.shallow_size_of(ops),
252 Decoder::Deflate(decoder) => decoder.size_of(ops),
253 Decoder::DeflateRaw(decoder) => decoder.size_of(ops),
254 Decoder::Gzip(decoder) => decoder.size_of(ops),
255 }
256 }
257}
258
259#[derive(MallocSizeOf)]
262struct DecompressionContext {
263 decoder: Decoder,
264 is_ended: bool,
265}
266
267impl DecompressionContext {
268 fn new(format: CompressionFormat) -> Fallible<DecompressionContext> {
269 let decoder = match format {
270 #[cfg(feature = "brotli-compression-stream")]
271 CompressionFormat::Brotli => {
272 Decoder::Brotli(Box::new(BrotliDecoder::new(Vec::new(), BROTLI_BUFFER_SIZE)))
273 },
274 #[cfg(not(feature = "brotli-compression-stream"))]
275 CompressionFormat::Brotli => {
276 return Err(Error::NotSupported(Some("Brotli not supported".into())));
277 },
278 CompressionFormat::Deflate => Decoder::Deflate(ZlibDecoder::new(Vec::new())),
279 CompressionFormat::Deflate_raw => Decoder::DeflateRaw(DeflateDecoder::new(Vec::new())),
280 CompressionFormat::Gzip => Decoder::Gzip(GzDecoder::new(Vec::new())),
281 };
282 Ok(DecompressionContext {
283 decoder,
284 is_ended: false,
285 })
286 }
287
288 fn decompress(&mut self, mut chunk: &[u8]) -> Result<&[u8], io::Error> {
290 match &mut self.decoder {
291 #[cfg(feature = "brotli-compression-stream")]
292 Decoder::Brotli(decoder) => {
293 decoder.get_mut().clear();
294 while !chunk.is_empty() {
295 let written = decoder.write(chunk)?;
296 if written == 0 {
297 self.is_ended = true;
298 break;
299 }
300 chunk = &chunk[written..];
301 }
302 decoder.flush()?;
303 Ok(decoder.get_ref().as_slice())
304 },
305 Decoder::Deflate(decoder) => {
306 decoder.get_mut().clear();
307 while !chunk.is_empty() {
308 let written = decoder.write(chunk)?;
309 if written == 0 {
310 self.is_ended = true;
311 break;
312 }
313 chunk = &chunk[written..];
314 }
315 decoder.flush()?;
316 Ok(decoder.get_ref().as_slice())
317 },
318 Decoder::DeflateRaw(decoder) => {
319 decoder.get_mut().clear();
320 while !chunk.is_empty() {
321 let written = decoder.write(chunk)?;
322 if written == 0 {
323 self.is_ended = true;
324 break;
325 }
326 chunk = &chunk[written..];
327 }
328 decoder.flush()?;
329 Ok(decoder.get_ref().as_slice())
330 },
331 Decoder::Gzip(decoder) => {
332 decoder.get_mut().clear();
333 while !chunk.is_empty() {
334 let written = decoder.write(chunk)?;
335 if written == 0 {
336 self.is_ended = true;
337 break;
338 }
339 chunk = &chunk[written..];
340 }
341 decoder.flush()?;
342 Ok(decoder.get_ref().as_slice())
343 },
344 }
345 }
346
347 fn finalize(&mut self) -> Result<&[u8], io::Error> {
350 match &mut self.decoder {
351 #[cfg(feature = "brotli-compression-stream")]
352 Decoder::Brotli(decoder) => {
353 decoder.get_mut().clear();
354 if decoder.close().is_ok() {
355 self.is_ended = true;
356 };
357 Ok(decoder.get_ref().as_slice())
358 },
359 Decoder::Deflate(decoder) => {
360 decoder.get_mut().clear();
361 decoder.flush()?;
374 let output_len = decoder.get_mut().len();
375 if decoder.write(&[0])? == 0 {
376 self.is_ended = true;
377 }
378 decoder.try_finish()?;
379 Ok(&decoder.get_ref()[..output_len])
380 },
381 Decoder::DeflateRaw(decoder) => {
382 decoder.get_mut().clear();
383 if decoder.try_finish().is_ok() {
384 self.is_ended = true;
385 };
386 Ok(decoder.get_ref().as_slice())
387 },
388 Decoder::Gzip(decoder) => {
389 decoder.get_mut().clear();
390 if decoder.try_finish().is_ok() {
391 self.is_ended = true;
392 };
393 Ok(decoder.get_ref().as_slice())
394 },
395 }
396 }
397}