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;
23use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
24use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
25use crate::dom::bindings::conversions::ToJSValConvertible;
26use crate::dom::bindings::error::{Error, Fallible};
27use crate::dom::bindings::root::{Dom, DomRoot};
28#[cfg(feature = "brotli-compression-stream")]
29use crate::dom::stream::compressionstream::BROTLI_BUFFER_SIZE;
30use crate::dom::stream::compressionstream::convert_chunk_to_vec;
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 chunk = convert_chunk_to_vec(cx, chunk)?;
138
139 let buffer = {
143 let mut decompression_context = ds.context.borrow_mut();
144 let buffer = decompression_context
145 .decompress(&chunk)
146 .map_err(|_| Error::Type(c"Failed to decompress a chunk of compressed input".into()))?;
147
148 if buffer.is_empty() {
150 return Ok(());
151 }
152 buffer
153 };
154 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
159 let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
160 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
161 rooted!(&in(cx) let mut rval = UndefinedValue());
162 array.to_jsval(cx, rval.handle_mut());
163 controller.enqueue(cx, global, rval.handle())?;
164
165 if ds.context.borrow().is_ended {
168 return Err(Error::Type(
169 c"The end of the compressed input has been reached".to_owned(),
170 ));
171 }
172
173 Ok(())
174}
175
176pub(crate) fn decompress_flush_and_enqueue(
178 cx: &mut js::context::JSContext,
179 global: &GlobalScope,
180 ds: &DecompressionStream,
181 controller: &TransformStreamDefaultController,
182) -> Fallible<()> {
183 let buffer = {
187 let mut decompression_context = ds.context.borrow_mut();
188 decompression_context
189 .finalize()
190 .map_err(|_| Error::Type(c"Failed to finalize the decompression stream".into()))?
191 };
192 if !buffer.is_empty() {
194 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
199 let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut())
200 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
201 rooted!(&in(cx) let mut rval = UndefinedValue());
202 array.to_jsval(cx, rval.handle_mut());
203 controller.enqueue(cx, global, rval.handle())?;
204 }
205
206 if !ds.context.borrow().is_ended {
217 return Err(Error::Type(
218 c"The end of the compressed input has not been reached".to_owned(),
219 ));
220 }
221
222 Ok(())
223}
224
225enum Decoder {
227 #[cfg(feature = "brotli-compression-stream")]
228 Brotli(Box<BrotliDecoder<Vec<u8>>>),
229 Deflate(ZlibDecoder<Vec<u8>>),
230 DeflateRaw(DeflateDecoder<Vec<u8>>),
231 Gzip(GzDecoder<Vec<u8>>),
232}
233
234impl MallocSizeOf for Decoder {
235 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
236 match self {
237 #[cfg(feature = "brotli-compression-stream")]
238 Decoder::Brotli(decoder) => decoder.shallow_size_of(ops),
239 Decoder::Deflate(decoder) => decoder.size_of(ops),
240 Decoder::DeflateRaw(decoder) => decoder.size_of(ops),
241 Decoder::Gzip(decoder) => decoder.size_of(ops),
242 }
243 }
244}
245
246#[derive(MallocSizeOf)]
249struct DecompressionContext {
250 decoder: Decoder,
251 is_ended: bool,
252}
253
254impl DecompressionContext {
255 fn new(format: CompressionFormat) -> Fallible<DecompressionContext> {
256 let decoder = match format {
257 #[cfg(feature = "brotli-compression-stream")]
258 CompressionFormat::Brotli => {
259 Decoder::Brotli(Box::new(BrotliDecoder::new(Vec::new(), BROTLI_BUFFER_SIZE)))
260 },
261 #[cfg(not(feature = "brotli-compression-stream"))]
262 CompressionFormat::Brotli => {
263 return Err(Error::NotSupported(Some("Brotli not supported".into())));
264 },
265 CompressionFormat::Deflate => Decoder::Deflate(ZlibDecoder::new(Vec::new())),
266 CompressionFormat::Deflate_raw => Decoder::DeflateRaw(DeflateDecoder::new(Vec::new())),
267 CompressionFormat::Gzip => Decoder::Gzip(GzDecoder::new(Vec::new())),
268 };
269 Ok(DecompressionContext {
270 decoder,
271 is_ended: false,
272 })
273 }
274
275 fn decompress(&mut self, mut chunk: &[u8]) -> Result<Vec<u8>, io::Error> {
276 let mut result = Vec::new();
277
278 match &mut self.decoder {
279 #[cfg(feature = "brotli-compression-stream")]
280 Decoder::Brotli(decoder) => {
281 while !chunk.is_empty() {
282 let written = decoder.write(chunk)?;
283 if written == 0 {
284 self.is_ended = true;
285 break;
286 }
287 chunk = &chunk[written..];
288 }
289 decoder.flush()?;
290 result.append(decoder.get_mut());
291 },
292 Decoder::Deflate(decoder) => {
293 while !chunk.is_empty() {
294 let written = decoder.write(chunk)?;
295 if written == 0 {
296 self.is_ended = true;
297 break;
298 }
299 chunk = &chunk[written..];
300 }
301 decoder.flush()?;
302 result.append(decoder.get_mut());
303 },
304 Decoder::DeflateRaw(decoder) => {
305 while !chunk.is_empty() {
306 let written = decoder.write(chunk)?;
307 if written == 0 {
308 self.is_ended = true;
309 break;
310 }
311 chunk = &chunk[written..];
312 }
313 decoder.flush()?;
314 result.append(decoder.get_mut());
315 },
316 Decoder::Gzip(decoder) => {
317 while !chunk.is_empty() {
318 let written = decoder.write(chunk)?;
319 if written == 0 {
320 self.is_ended = true;
321 break;
322 }
323 chunk = &chunk[written..];
324 }
325 decoder.flush()?;
326 result.append(decoder.get_mut());
327 },
328 }
329
330 Ok(result)
331 }
332
333 fn finalize(&mut self) -> Result<Vec<u8>, io::Error> {
334 let mut result = Vec::new();
335
336 match &mut self.decoder {
337 #[cfg(feature = "brotli-compression-stream")]
338 Decoder::Brotli(decoder) => {
339 if decoder.close().is_ok() {
340 self.is_ended = true;
341 };
342 result.append(decoder.get_mut());
343 },
344 Decoder::Deflate(decoder) => {
345 decoder.flush()?;
356 result.append(decoder.get_mut());
357 if decoder.write(&[0])? == 0 {
358 self.is_ended = true;
359 }
360 decoder.try_finish()?;
361 },
362 Decoder::DeflateRaw(decoder) => {
363 if decoder.try_finish().is_ok() {
364 self.is_ended = true;
365 };
366 result.append(decoder.get_mut());
367 },
368 Decoder::Gzip(decoder) => {
369 if decoder.try_finish().is_ok() {
370 self.is_ended = true;
371 };
372 result.append(decoder.get_mut());
373 },
374 }
375
376 Ok(result)
377 }
378}