summaryrefslogtreecommitdiff
path: root/ext/node/zlib
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/zlib')
-rw-r--r--ext/node/zlib/alloc.rs64
-rw-r--r--ext/node/zlib/mod.rs450
-rw-r--r--ext/node/zlib/mode.rs71
-rw-r--r--ext/node/zlib/stream.rs136
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 _,
+ )
+ }
+ }
+}