use std::cell::RefCell;
use std::io::{self, Write};
use std::ptr;
use brotli::DecompressorWriter as BrotliDecoder;
use dom_struct::dom_struct;
use flate2::write::{DeflateDecoder, GzDecoder, ZlibDecoder};
use js::jsapi::JSObject;
use js::jsval::UndefinedValue;
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
use js::typedarray::Uint8;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat;
use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods;
use crate::dom::bindings::conversions::SafeToJSValConvertible;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::stream::compressionstream::convert_chunk_to_vec;
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::types::{
GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
};
use crate::script_runtime::CanGc;
enum Decompressor {
Deflate(ZlibDecoder<Vec<u8>>),
DeflateRaw(DeflateDecoder<Vec<u8>>),
Gzip(GzDecoder<Vec<u8>>),
Brotli(Box<BrotliDecoder<Vec<u8>>>),
}
impl Decompressor {
fn new(format: CompressionFormat) -> Decompressor {
match format {
CompressionFormat::Deflate => Decompressor::Deflate(ZlibDecoder::new(Vec::new())),
CompressionFormat::Deflate_raw => {
Decompressor::DeflateRaw(DeflateDecoder::new(Vec::new()))
},
CompressionFormat::Gzip => Decompressor::Gzip(GzDecoder::new(Vec::new())),
CompressionFormat::Brotli => {
Decompressor::Brotli(Box::new(BrotliDecoder::new(Vec::new(), 4096)))
},
}
}
fn get_ref(&self) -> &Vec<u8> {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.get_ref(),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.get_ref(),
Decompressor::Gzip(gz_decoder) => gz_decoder.get_ref(),
Decompressor::Brotli(brotli_decoder) => brotli_decoder.get_ref(),
}
}
fn get_mut(&mut self) -> &mut Vec<u8> {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.get_mut(),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.get_mut(),
Decompressor::Gzip(gz_decoder) => gz_decoder.get_mut(),
Decompressor::Brotli(brotli_decoder) => brotli_decoder.get_mut(),
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.write(buf),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.write(buf),
Decompressor::Gzip(gz_decoder) => gz_decoder.write(buf),
Decompressor::Brotli(brotli_decoder) => brotli_decoder.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.flush(),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.flush(),
Decompressor::Gzip(gz_decoder) => gz_decoder.flush(),
Decompressor::Brotli(brotli_decoder) => brotli_decoder.flush(),
}
}
fn try_finish(&mut self) -> io::Result<()> {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.try_finish(),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.try_finish(),
Decompressor::Gzip(gz_decoder) => gz_decoder.try_finish(),
Decompressor::Brotli(brotli_decoder) => brotli_decoder.flush(),
}
}
}
impl MallocSizeOf for Decompressor {
#[expect(unsafe_code)]
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
match self {
Decompressor::Deflate(zlib_decoder) => zlib_decoder.size_of(ops),
Decompressor::DeflateRaw(deflate_decoder) => deflate_decoder.size_of(ops),
Decompressor::Gzip(gz_decoder) => gz_decoder.size_of(ops),
Decompressor::Brotli(brotli_decoder) => unsafe {
ops.malloc_size_of(&**brotli_decoder)
},
}
}
}
#[dom_struct]
pub(crate) struct DecompressionStream {
reflector_: Reflector,
transform: Dom<TransformStream>,
format: CompressionFormat,
#[no_trace]
context: RefCell<Decompressor>,
}
impl DecompressionStream {
fn new_inherited(
transform: &TransformStream,
format: CompressionFormat,
) -> DecompressionStream {
DecompressionStream {
reflector_: Reflector::new(),
transform: Dom::from_ref(transform),
format,
context: RefCell::new(Decompressor::new(format)),
}
}
fn new_with_proto(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
transform: &TransformStream,
format: CompressionFormat,
can_gc: CanGc,
) -> DomRoot<DecompressionStream> {
reflect_dom_object_with_proto(
Box::new(DecompressionStream::new_inherited(transform, format)),
global,
proto,
can_gc,
)
}
}
impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream {
fn Constructor(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
format: CompressionFormat,
) -> Fallible<DomRoot<DecompressionStream>> {
let transform = TransformStream::new_with_proto(global, None, can_gc);
let decompression_stream =
DecompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
let transformer_type = TransformerType::Decompressor(decompression_stream.clone());
let cx = GlobalScope::get_cx();
transform.set_up(cx, global, transformer_type, can_gc)?;
Ok(decompression_stream)
}
fn Readable(&self) -> DomRoot<ReadableStream> {
self.transform.get_readable()
}
fn Writable(&self) -> DomRoot<WritableStream> {
self.transform.get_writable()
}
}
pub(crate) fn decompress_and_enqueue_a_chunk(
cx: &mut js::context::JSContext,
global: &GlobalScope,
ds: &DecompressionStream,
chunk: SafeHandleValue,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
let chunk = convert_chunk_to_vec(cx.into(), chunk, CanGc::from_cx(cx))?;
let mut decompressor = ds.context.borrow_mut();
let mut offset = 0;
let mut written = 1;
while offset < chunk.len() && written > 0 {
written = decompressor
.write(&chunk[offset..])
.map_err(|_| Error::Type(c"DecompressionStream: write() failed".to_owned()))?;
offset += written;
}
decompressor
.flush()
.map_err(|_| Error::Type(c"DecompressionStream: flush() failed".to_owned()))?;
let buffer = decompressor.get_ref();
if buffer.is_empty() {
return Ok(());
}
rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
let array = create_buffer_source::<Uint8>(
cx.into(),
buffer,
js_object.handle_mut(),
CanGc::from_cx(cx),
)
.map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
rooted!(&in(cx) let mut rval = UndefinedValue());
array.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
controller.enqueue(cx, global, rval.handle())?;
decompressor.get_mut().clear();
if offset < chunk.len() {
return Err(Error::Type(
c"The end of the compressed input has been reached".to_owned(),
));
}
Ok(())
}
pub(crate) fn decompress_flush_and_enqueue(
cx: &mut js::context::JSContext,
global: &GlobalScope,
ds: &DecompressionStream,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
let mut decompressor = ds.context.borrow_mut();
let offset = decompressor.get_ref().len();
let is_ended = decompressor
.write(&[0])
.map_err(|_| Error::Type(c"DecompressionStream: write() failed".to_owned()))? ==
0;
decompressor
.try_finish()
.map_err(|_| Error::Type(c"DecompressionStream: try_finish() failed".to_owned()))?;
let buffer = &decompressor.get_ref()[offset..];
if !buffer.is_empty() {
rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
let array = create_buffer_source::<Uint8>(
cx.into(),
buffer,
js_object.handle_mut(),
CanGc::from_cx(cx),
)
.map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
rooted!(&in(cx) let mut rval = UndefinedValue());
array.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
controller.enqueue(cx, global, rval.handle())?;
}
decompressor.get_mut().clear();
if !is_ended {
return Err(Error::Type(
c"The end of the compressed input has not been reached".to_owned(),
));
}
Ok(())
}