diff options
Diffstat (limited to 'ext/node/zlib')
-rw-r--r-- | ext/node/zlib/alloc.rs | 64 | ||||
-rw-r--r-- | ext/node/zlib/mod.rs | 450 | ||||
-rw-r--r-- | ext/node/zlib/mode.rs | 71 | ||||
-rw-r--r-- | ext/node/zlib/stream.rs | 136 |
4 files changed, 721 insertions, 0 deletions
diff --git a/ext/node/zlib/alloc.rs b/ext/node/zlib/alloc.rs new file mode 100644 index 000000000..c7fecadd8 --- /dev/null +++ b/ext/node/zlib/alloc.rs @@ -0,0 +1,64 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Workaround for https://github.com/rust-lang/libz-sys/issues/55 +// See https://github.com/rust-lang/flate2-rs/blob/31fb07820345691352aaa64f367c1e482ad9cfdc/src/ffi/c.rs#L60 +use std::alloc::Layout; +use std::alloc::{self}; +use std::os::raw::c_void; +use std::ptr; + +const ALIGN: usize = std::mem::align_of::<usize>(); + +fn align_up(size: usize, align: usize) -> usize { + (size + align - 1) & !(align - 1) +} + +pub extern "C" fn zalloc( + _ptr: *mut c_void, + items: u32, + item_size: u32, +) -> *mut c_void { + // We need to multiply `items` and `item_size` to get the actual desired + // allocation size. Since `zfree` doesn't receive a size argument we + // also need to allocate space for a `usize` as a header so we can store + // how large the allocation is to deallocate later. + let size = match (items as usize) + .checked_mul(item_size as usize) + .map(|size| align_up(size, ALIGN)) + .and_then(|i| i.checked_add(std::mem::size_of::<usize>())) + { + Some(i) => i, + None => return ptr::null_mut(), + }; + + // Make sure the `size` isn't too big to fail `Layout`'s restrictions + let layout = match Layout::from_size_align(size, ALIGN) { + Ok(layout) => layout, + Err(_) => return ptr::null_mut(), + }; + + // SAFETY: `layout` has non-zero size, guaranteed to be a sentinel address + // or a null pointer. + unsafe { + // Allocate the data, and if successful store the size we allocated + // at the beginning and then return an offset pointer. + let ptr = alloc::alloc(layout) as *mut usize; + if ptr.is_null() { + return ptr as *mut c_void; + } + *ptr = size; + ptr.add(1) as *mut c_void + } +} + +pub extern "C" fn zfree(_ptr: *mut c_void, address: *mut c_void) { + // SAFETY: Move our address being free'd back one pointer, read the size we + // stored in `zalloc`, and then free it using the standard Rust + // allocator. + unsafe { + let ptr = (address as *mut usize).offset(-1); + let size = *ptr; + let layout = Layout::from_size_align_unchecked(size, ALIGN); + alloc::dealloc(ptr as *mut u8, layout) + } +} diff --git a/ext/node/zlib/mod.rs b/ext/node/zlib/mod.rs new file mode 100644 index 000000000..c103b3009 --- /dev/null +++ b/ext/node/zlib/mod.rs @@ -0,0 +1,450 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::op; +use deno_core::OpState; +use libz_sys::*; +use std::borrow::Cow; +use std::cell::RefCell; +use std::future::Future; +use std::rc::Rc; + +mod alloc; +mod mode; +mod stream; + +use mode::Flush; +use mode::Mode; + +use self::stream::StreamWrapper; + +#[inline] +fn check(condition: bool, msg: &str) -> Result<(), AnyError> { + if condition { + Ok(()) + } else { + Err(type_error(msg.to_string())) + } +} + +#[inline] +fn zlib(state: &mut OpState, handle: u32) -> Result<Rc<Zlib>, AnyError> { + state + .resource_table + .get::<Zlib>(handle) + .map_err(|_| bad_resource_id()) +} + +#[derive(Default)] +struct ZlibInner { + dictionary: Option<Vec<u8>>, + err: i32, + flush: Flush, + init_done: bool, + level: i32, + mem_level: i32, + mode: Mode, + strategy: i32, + window_bits: i32, + write_in_progress: bool, + pending_close: bool, + gzib_id_bytes_read: u32, + strm: StreamWrapper, +} + +const GZIP_HEADER_ID1: u8 = 0x1f; +const GZIP_HEADER_ID2: u8 = 0x8b; + +impl ZlibInner { + #[allow(clippy::too_many_arguments)] + fn start_write( + &mut self, + input: &[u8], + in_off: u32, + in_len: u32, + out: &mut [u8], + out_off: u32, + out_len: u32, + flush: Flush, + ) -> Result<(), AnyError> { + check(self.init_done, "write before init")?; + check(!self.write_in_progress, "write already in progress")?; + check(!self.pending_close, "close already in progress")?; + + self.write_in_progress = true; + + let next_in = input + .get(in_off as usize..in_off as usize + in_len as usize) + .ok_or_else(|| type_error("invalid input range"))? + .as_ptr() as *mut _; + let next_out = out + .get_mut(out_off as usize..out_off as usize + out_len as usize) + .ok_or_else(|| type_error("invalid output range"))? + .as_mut_ptr(); + + self.strm.avail_in = in_len; + self.strm.next_in = next_in; + self.strm.avail_out = out_len; + self.strm.next_out = next_out; + + self.flush = flush; + Ok(()) + } + + fn do_write(&mut self, flush: Flush) -> Result<(), AnyError> { + self.flush = flush; + match self.mode { + Mode::Deflate | Mode::Gzip | Mode::DeflateRaw => { + self.err = self.strm.deflate(flush); + } + // Auto-detect mode. + Mode::Unzip if self.strm.avail_in > 0 => 'blck: { + let mut next_expected_header_byte = Some(0); + // SAFETY: `self.strm.next_in` is valid pointer to the input buffer. + // `self.strm.avail_in` is the length of the input buffer that is only set by + // `start_write`. + let strm = unsafe { + std::slice::from_raw_parts( + self.strm.next_in, + self.strm.avail_in as usize, + ) + }; + + if self.gzib_id_bytes_read == 0 { + if strm[0] == GZIP_HEADER_ID1 { + self.gzib_id_bytes_read = 1; + next_expected_header_byte = Some(1); + + // Not enough. + if self.strm.avail_in == 1 { + break 'blck; + } + } else { + self.mode = Mode::Inflate; + next_expected_header_byte = None; + } + } + + if self.gzib_id_bytes_read == 1 { + let byte = match next_expected_header_byte { + Some(i) => strm[i], + None => break 'blck, + }; + if byte == GZIP_HEADER_ID2 { + self.gzib_id_bytes_read = 2; + self.mode = Mode::Gunzip; + } else { + self.mode = Mode::Inflate; + } + } else if next_expected_header_byte.is_some() { + return Err(type_error( + "invalid number of gzip magic number bytes read", + )); + } + } + _ => {} + } + + match self.mode { + Mode::Inflate + | Mode::Gunzip + | Mode::InflateRaw + // We're still reading the header. + | Mode::Unzip => { + self.err = self.strm.inflate(self.flush); + // TODO(@littledivy): Use if let chain when it is stable. + // https://github.com/rust-lang/rust/issues/53667 + // + // Data was encoded with dictionary + if let (Z_NEED_DICT, Some(dictionary)) = (self.err, &self.dictionary) { + self.err = self.strm.inflate_set_dictionary(dictionary); + + if self.err == Z_OK { + self.err = self.strm.inflate(flush); + } else if self.err == Z_DATA_ERROR { + self.err = Z_NEED_DICT; + } + } + + while self.strm.avail_in > 0 + && self.mode == Mode::Gunzip + && self.err == Z_STREAM_END + // SAFETY: `strm` is a valid pointer to zlib strm. + // `strm.next_in` is initialized to the input buffer. + && unsafe { *self.strm.next_in } != 0x00 + { + self.err = self.strm.reset(self.mode); + self.err = self.strm.inflate(flush); + } + } + _ => {} + } + + let done = self.strm.avail_out != 0 && self.flush == Flush::Finish; + // We're are not done yet, but output buffer is full + if self.err == Z_BUF_ERROR && !done { + // Set to Z_OK to avoid reporting the error in JS. + self.err = Z_OK; + } + + self.write_in_progress = false; + Ok(()) + } + + fn init_stream(&mut self) -> Result<(), AnyError> { + match self.mode { + Mode::Gzip | Mode::Gunzip => self.window_bits += 16, + Mode::Unzip => self.window_bits += 32, + Mode::DeflateRaw | Mode::InflateRaw => self.window_bits *= -1, + _ => {} + } + + self.err = match self.mode { + Mode::Deflate | Mode::Gzip | Mode::DeflateRaw => self.strm.deflate_init( + self.level, + self.window_bits, + self.mem_level, + self.strategy, + ), + Mode::Inflate | Mode::Gunzip | Mode::InflateRaw | Mode::Unzip => { + self.strm.inflate_init(self.window_bits) + } + Mode::None => return Err(type_error("Unknown mode")), + }; + + self.write_in_progress = false; + self.init_done = true; + + Ok(()) + } + + fn close(&mut self) -> Result<bool, AnyError> { + if self.write_in_progress { + self.pending_close = true; + return Ok(false); + } + + self.pending_close = false; + check(self.init_done, "close before init")?; + + self.strm.end(self.mode); + self.mode = Mode::None; + Ok(true) + } + + fn reset_stream(&mut self) -> Result<(), AnyError> { + self.err = self.strm.reset(self.mode); + + Ok(()) + } +} + +struct Zlib { + inner: RefCell<ZlibInner>, +} + +impl deno_core::Resource for Zlib { + fn name(&self) -> Cow<str> { + "zlib".into() + } +} + +#[op] +pub fn op_zlib_new(state: &mut OpState, mode: i32) -> Result<u32, AnyError> { + let mode = Mode::try_from(mode)?; + + let inner = ZlibInner { + mode, + ..Default::default() + }; + + Ok(state.resource_table.add(Zlib { + inner: RefCell::new(inner), + })) +} + +#[op] +pub fn op_zlib_close(state: &mut OpState, handle: u32) -> Result<(), AnyError> { + let resource = zlib(state, handle)?; + let mut zlib = resource.inner.borrow_mut(); + + // If there is a pending write, defer the close until the write is done. + zlib.close()?; + + Ok(()) +} + +#[op] +pub fn op_zlib_write_async( + state: Rc<RefCell<OpState>>, + handle: u32, + flush: i32, + input: &[u8], + in_off: u32, + in_len: u32, + out: &mut [u8], + out_off: u32, + out_len: u32, +) -> Result< + impl Future<Output = Result<(i32, u32, u32), AnyError>> + 'static, + AnyError, +> { + let mut state_mut = state.borrow_mut(); + let resource = zlib(&mut state_mut, handle)?; + + let mut strm = resource.inner.borrow_mut(); + let flush = Flush::try_from(flush)?; + strm.start_write(input, in_off, in_len, out, out_off, out_len, flush)?; + + let state = state.clone(); + Ok(async move { + let mut state_mut = state.borrow_mut(); + let resource = zlib(&mut state_mut, handle)?; + let mut zlib = resource.inner.borrow_mut(); + + zlib.do_write(flush)?; + Ok((zlib.err, zlib.strm.avail_out, zlib.strm.avail_in)) + }) +} + +#[op] +pub fn op_zlib_write( + state: &mut OpState, + handle: u32, + flush: i32, + input: &[u8], + in_off: u32, + in_len: u32, + out: &mut [u8], + out_off: u32, + out_len: u32, + result: &mut [u32], +) -> Result<i32, AnyError> { + let resource = zlib(state, handle)?; + + let mut zlib = resource.inner.borrow_mut(); + let flush = Flush::try_from(flush)?; + zlib.start_write(input, in_off, in_len, out, out_off, out_len, flush)?; + zlib.do_write(flush)?; + + result[0] = zlib.strm.avail_out; + result[1] = zlib.strm.avail_in; + + Ok(zlib.err) +} + +#[op] +pub fn op_zlib_init( + state: &mut OpState, + handle: u32, + level: i32, + window_bits: i32, + mem_level: i32, + strategy: i32, + dictionary: Option<&[u8]>, +) -> Result<i32, AnyError> { + let resource = zlib(state, handle)?; + let mut zlib = resource.inner.borrow_mut(); + + check((8..=15).contains(&window_bits), "invalid windowBits")?; + check((-1..=9).contains(&level), "invalid level")?; + + check((1..=9).contains(&mem_level), "invalid memLevel")?; + + check( + strategy == Z_DEFAULT_STRATEGY + || strategy == Z_FILTERED + || strategy == Z_HUFFMAN_ONLY + || strategy == Z_RLE + || strategy == Z_FIXED, + "invalid strategy", + )?; + + zlib.level = level; + zlib.window_bits = window_bits; + zlib.mem_level = mem_level; + zlib.strategy = strategy; + + zlib.flush = Flush::None; + zlib.err = Z_OK; + + zlib.init_stream()?; + + zlib.dictionary = dictionary.map(|buf| buf.to_vec()); + + Ok(zlib.err) +} + +#[op] +pub fn op_zlib_reset( + state: &mut OpState, + handle: u32, +) -> Result<i32, AnyError> { + let resource = zlib(state, handle)?; + + let mut zlib = resource.inner.borrow_mut(); + zlib.reset_stream()?; + + Ok(zlib.err) +} + +#[op] +pub fn op_zlib_close_if_pending( + state: &mut OpState, + handle: u32, +) -> Result<(), AnyError> { + let resource = zlib(state, handle)?; + let pending_close = { + let mut zlib = resource.inner.borrow_mut(); + zlib.write_in_progress = false; + zlib.pending_close + }; + if pending_close { + drop(resource); + state.resource_table.close(handle)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zlib_start_write() { + // buffer, length, should pass + type WriteVector = (&'static [u8], u32, u32, bool); + const WRITE_VECTORS: [WriteVector; 8] = [ + (b"Hello", 5, 0, true), + (b"H", 1, 0, true), + (b"", 0, 0, true), + // Overrun the buffer + (b"H", 5, 0, false), + (b"ello", 5, 0, false), + (b"Hello", 5, 1, false), + (b"H", 1, 1, false), + (b"", 0, 1, false), + ]; + + for (input, len, offset, expected) in WRITE_VECTORS.iter() { + let mut stream = ZlibInner { + mode: Mode::Inflate, + ..Default::default() + }; + + stream.init_stream().unwrap(); + assert_eq!(stream.err, Z_OK); + assert_eq!( + stream + .start_write(input, *offset, *len, &mut [], 0, 0, Flush::None) + .is_ok(), + *expected + ); + assert_eq!(stream.err, Z_OK); + stream.close().unwrap(); + } + } +} diff --git a/ext/node/zlib/mode.rs b/ext/node/zlib/mode.rs new file mode 100644 index 000000000..ef89805ba --- /dev/null +++ b/ext/node/zlib/mode.rs @@ -0,0 +1,71 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use libz_sys as sys; + +#[derive(Debug)] +pub enum Error { + BadArgument, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::BadArgument => write!(f, "bad argument"), + } + } +} + +impl std::error::Error for Error {} + +macro_rules! repr_i32 { + ($(#[$meta:meta])* $vis:vis enum $name:ident { + $($(#[$vmeta:meta])* $vname:ident $(= $val:expr)?,)* + }) => { + $(#[$meta])* + $vis enum $name { + $($(#[$vmeta])* $vname $(= $val)?,)* + } + + impl core::convert::TryFrom<i32> for $name { + type Error = Error; + + fn try_from(v: i32) -> Result<Self, Self::Error> { + match v { + $(x if x == $name::$vname as i32 => Ok($name::$vname),)* + _ => Err(Error::BadArgument), + } + } + } + } + } + +repr_i32! { + #[repr(i32)] + #[derive(Clone, Copy, Debug, PartialEq, Default)] + pub enum Mode { + #[default] + None, + Deflate, + Inflate, + Gzip, + Gunzip, + DeflateRaw, + InflateRaw, + Unzip, + } +} + +repr_i32! { + #[repr(i32)] + #[derive(Clone, Copy, Debug, PartialEq, Default)] + pub enum Flush { + #[default] + None = sys::Z_NO_FLUSH, + Partial = sys::Z_PARTIAL_FLUSH, + Sync = sys::Z_SYNC_FLUSH, + Full = sys::Z_FULL_FLUSH, + Finish = sys::Z_FINISH, + Block = sys::Z_BLOCK, + Trees = sys::Z_TREES, + } +} diff --git a/ext/node/zlib/stream.rs b/ext/node/zlib/stream.rs new file mode 100644 index 000000000..90cd58ed3 --- /dev/null +++ b/ext/node/zlib/stream.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use super::mode::Flush; +use super::mode::Mode; +use libz_sys as sys; +use std::ffi::c_int; +use std::ops::Deref; +use std::ops::DerefMut; + +pub struct StreamWrapper { + pub strm: sys::z_stream, +} + +impl Default for StreamWrapper { + fn default() -> Self { + Self { + strm: sys::z_stream { + next_in: std::ptr::null_mut(), + avail_in: 0, + total_in: 0, + next_out: std::ptr::null_mut(), + avail_out: 0, + total_out: 0, + msg: std::ptr::null_mut(), + state: std::ptr::null_mut(), + zalloc: super::alloc::zalloc, + zfree: super::alloc::zfree, + opaque: 0 as sys::voidpf, + data_type: 0, + adler: 0, + reserved: 0, + }, + } + } +} + +impl Deref for StreamWrapper { + type Target = sys::z_stream; + + fn deref(&self) -> &Self::Target { + &self.strm + } +} + +impl DerefMut for StreamWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.strm + } +} + +impl StreamWrapper { + pub fn reset(&mut self, mode: Mode) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { + match mode { + Mode::Deflate | Mode::Gzip | Mode::DeflateRaw => { + sys::deflateReset(&mut self.strm) + } + Mode::Inflate | Mode::Gunzip | Mode::InflateRaw | Mode::Unzip => { + sys::inflateReset(&mut self.strm) + } + Mode::None => unreachable!(), + } + } + } + + pub fn end(&mut self, mode: Mode) { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { + match mode { + Mode::Deflate | Mode::Gzip | Mode::DeflateRaw => { + sys::deflateEnd(&mut self.strm); + } + Mode::Inflate | Mode::Gunzip | Mode::InflateRaw | Mode::Unzip => { + sys::inflateEnd(&mut self.strm); + } + Mode::None => {} + } + } + } + + pub fn deflate_init( + &mut self, + level: c_int, + window_bits: c_int, + mem_level: c_int, + strategy: c_int, + ) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { + sys::deflateInit2_( + &mut self.strm, + level, + sys::Z_DEFLATED, + window_bits, + mem_level, + strategy, + sys::zlibVersion(), + std::mem::size_of::<sys::z_stream>() as i32, + ) + } + } + + pub fn inflate_init(&mut self, window_bits: c_int) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { + sys::inflateInit2_( + &mut self.strm, + window_bits, + sys::zlibVersion(), + std::mem::size_of::<sys::z_stream>() as i32, + ) + } + } + + pub fn deflate(&mut self, flush: Flush) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { sys::deflate(&mut self.strm, flush as _) } + } + + pub fn inflate(&mut self, flush: Flush) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { sys::inflate(&mut self.strm, flush as _) } + } + + pub fn inflate_set_dictionary(&mut self, dictionary: &[u8]) -> c_int { + // SAFETY: `self.strm` is an initialized `z_stream`. + unsafe { + sys::inflateSetDictionary( + &mut self.strm, + dictionary.as_ptr() as *const _, + dictionary.len() as _, + ) + } + } +} |