diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/http/Cargo.toml | 2 | ||||
-rw-r--r-- | ext/http/response_body.rs | 143 | ||||
-rw-r--r-- | ext/node/ops/zlib/brotli.rs | 303 |
3 files changed, 178 insertions, 270 deletions
diff --git a/ext/http/Cargo.toml b/ext/http/Cargo.toml index e5fed0750..4b5755d89 100644 --- a/ext/http/Cargo.toml +++ b/ext/http/Cargo.toml @@ -24,7 +24,7 @@ harness = false async-compression = { version = "0.4", features = ["tokio", "brotli", "gzip"] } async-trait.workspace = true base64.workspace = true -brotli = "3.3.4" +brotli.workspace = true bytes.workspace = true cache_control.workspace = true deno_core.workspace = true diff --git a/ext/http/response_body.rs b/ext/http/response_body.rs index 3c25265d7..42080e8c9 100644 --- a/ext/http/response_body.rs +++ b/ext/http/response_body.rs @@ -3,8 +3,10 @@ use std::io::Write; use std::pin::Pin; use std::rc::Rc; +use brotli::enc::encode::BrotliEncoderOperation; use brotli::enc::encode::BrotliEncoderParameter; -use brotli::ffi::compressor::BrotliEncoderState; +use brotli::enc::encode::BrotliEncoderStateStruct; +use brotli::writer::StandardAlloc; use bytes::Bytes; use bytes::BytesMut; use deno_core::error::AnyError; @@ -448,58 +450,24 @@ enum BrotliState { EndOfStream, } -struct BrotliEncoderStateWrapper { - stm: *mut BrotliEncoderState, -} - #[pin_project] pub struct BrotliResponseStream { state: BrotliState, - stm: BrotliEncoderStateWrapper, - current_cursor: usize, - output_written_so_far: usize, + stm: BrotliEncoderStateStruct<StandardAlloc>, #[pin] underlying: ResponseStream, } -impl Drop for BrotliEncoderStateWrapper { - fn drop(&mut self) { - // SAFETY: since we are dropping, we can be sure that this instance will not - // be used again. - unsafe { - brotli::ffi::compressor::BrotliEncoderDestroyInstance(self.stm); - } - } -} - impl BrotliResponseStream { pub fn new(underlying: ResponseStream) -> Self { - // SAFETY: creating an FFI instance should be OK with these args. - let stm = unsafe { - let stm = brotli::ffi::compressor::BrotliEncoderCreateInstance( - None, - None, - std::ptr::null_mut(), - ); - // Quality level 6 is based on google's nginx default value for on-the-fly compression - // https://github.com/google/ngx_brotli#brotli_comp_level - // lgwin 22 is equivalent to brotli window size of (2**22)-16 bytes (~4MB) - brotli::ffi::compressor::BrotliEncoderSetParameter( - stm, - BrotliEncoderParameter::BROTLI_PARAM_QUALITY, - 6, - ); - brotli::ffi::compressor::BrotliEncoderSetParameter( - stm, - BrotliEncoderParameter::BROTLI_PARAM_LGWIN, - 22, - ); - BrotliEncoderStateWrapper { stm } - }; + let mut stm = BrotliEncoderStateStruct::new(StandardAlloc::default()); + // Quality level 6 is based on google's nginx default value for on-the-fly compression + // https://github.com/google/ngx_brotli#brotli_comp_level + // lgwin 22 is equivalent to brotli window size of (2**22)-16 bytes (~4MB) + stm.set_parameter(BrotliEncoderParameter::BROTLI_PARAM_QUALITY, 6); + stm.set_parameter(BrotliEncoderParameter::BROTLI_PARAM_LGWIN, 22); Self { stm, - output_written_so_far: 0, - current_cursor: 0, state: BrotliState::Streaming, underlying, } @@ -546,71 +514,46 @@ impl PollFrame for BrotliResponseStream { let res = match frame { ResponseStreamResult::NonEmptyBuf(buf) => { - let mut output_written = 0; - let mut total_output_written = 0; - let mut input_size = buf.len(); - let input_buffer = buf.as_ref(); - let mut len = max_compressed_size(input_size); - let mut output_buffer = vec![0u8; len]; - let mut ob_ptr = output_buffer.as_mut_ptr(); - - // SAFETY: these are okay arguments to these FFI calls. - unsafe { - brotli::ffi::compressor::BrotliEncoderCompressStream( - this.stm.stm, - brotli::ffi::compressor::BrotliEncoderOperation::BROTLI_OPERATION_PROCESS, - &mut input_size, - &input_buffer.as_ptr() as *const *const u8 as *mut *const u8, - &mut len, - &mut ob_ptr, - &mut output_written, - ); - total_output_written += output_written; - output_written = 0; - - brotli::ffi::compressor::BrotliEncoderCompressStream( - this.stm.stm, - brotli::ffi::compressor::BrotliEncoderOperation::BROTLI_OPERATION_FLUSH, - &mut input_size, - &input_buffer.as_ptr() as *const *const u8 as *mut *const u8, - &mut len, - &mut ob_ptr, - &mut output_written, - ); - total_output_written += output_written; - }; - - output_buffer - .truncate(total_output_written - this.output_written_so_far); - this.output_written_so_far = total_output_written; + let mut output_buffer = vec![0; max_compressed_size(buf.len())]; + let mut output_offset = 0; + + this.stm.compress_stream( + BrotliEncoderOperation::BROTLI_OPERATION_FLUSH, + &mut buf.len(), + &buf, + &mut 0, + &mut output_buffer.len(), + &mut output_buffer, + &mut output_offset, + &mut None, + &mut |_, _, _, _| (), + ); + + output_buffer.truncate(output_offset); ResponseStreamResult::NonEmptyBuf(BufView::from(output_buffer)) } ResponseStreamResult::EndOfStream => { - let mut len = 1024usize; - let mut output_buffer = vec![0u8; len]; - let mut input_size = 0; - let mut output_written = 0; - let ob_ptr = output_buffer.as_mut_ptr(); - - // SAFETY: these are okay arguments to these FFI calls. - unsafe { - brotli::ffi::compressor::BrotliEncoderCompressStream( - this.stm.stm, - brotli::ffi::compressor::BrotliEncoderOperation::BROTLI_OPERATION_FINISH, - &mut input_size, - std::ptr::null_mut(), - &mut len, - &ob_ptr as *const *mut u8 as *mut *mut u8, - &mut output_written, - ); - }; - - if output_written == 0 { + let mut output_buffer = vec![0; 1024]; + let mut output_offset = 0; + + this.stm.compress_stream( + BrotliEncoderOperation::BROTLI_OPERATION_FINISH, + &mut 0, + &[], + &mut 0, + &mut output_buffer.len(), + &mut output_buffer, + &mut output_offset, + &mut None, + &mut |_, _, _, _| (), + ); + + if output_offset == 0 { this.state = BrotliState::EndOfStream; ResponseStreamResult::EndOfStream } else { this.state = BrotliState::Flushing; - output_buffer.truncate(output_written - this.output_written_so_far); + output_buffer.truncate(output_offset); ResponseStreamResult::NonEmptyBuf(BufView::from(output_buffer)) } } diff --git a/ext/node/ops/zlib/brotli.rs b/ext/node/ops/zlib/brotli.rs index dc3a40441..3e3905fc3 100644 --- a/ext/node/ops/zlib/brotli.rs +++ b/ext/node/ops/zlib/brotli.rs @@ -1,9 +1,13 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use brotli::enc::backward_references::BrotliEncoderMode; +use brotli::enc::encode::BrotliEncoderCompress; +use brotli::enc::encode::BrotliEncoderOperation; use brotli::enc::encode::BrotliEncoderParameter; -use brotli::ffi::compressor::*; -use brotli::ffi::decompressor::ffi::interface::BrotliDecoderResult; -use brotli::ffi::decompressor::ffi::BrotliDecoderState; -use brotli::ffi::decompressor::*; +use brotli::enc::encode::BrotliEncoderStateStruct; +use brotli::writer::StandardAlloc; +use brotli::BrotliDecompressStream; +use brotli::BrotliResult; +use brotli::BrotliState; use brotli::Decompressor; use deno_core::error::type_error; use deno_core::error::AnyError; @@ -12,14 +16,20 @@ use deno_core::JsBuffer; use deno_core::OpState; use deno_core::Resource; use deno_core::ToJsBuffer; +use std::cell::RefCell; use std::io::Read; fn encoder_mode(mode: u32) -> Result<BrotliEncoderMode, AnyError> { - if mode > 6 { - return Err(type_error("Invalid encoder mode")); - } - // SAFETY: mode is a valid discriminant for BrotliEncoderMode - unsafe { Ok(std::mem::transmute::<u32, BrotliEncoderMode>(mode)) } + Ok(match mode { + 0 => BrotliEncoderMode::BROTLI_MODE_GENERIC, + 1 => BrotliEncoderMode::BROTLI_MODE_TEXT, + 2 => BrotliEncoderMode::BROTLI_MODE_FONT, + 3 => BrotliEncoderMode::BROTLI_FORCE_LSB_PRIOR, + 4 => BrotliEncoderMode::BROTLI_FORCE_MSB_PRIOR, + 5 => BrotliEncoderMode::BROTLI_FORCE_UTF8_PRIOR, + 6 => BrotliEncoderMode::BROTLI_FORCE_SIGNED_PRIOR, + _ => return Err(type_error("Invalid encoder mode")), + }) } #[op2(fast)] @@ -31,24 +41,22 @@ pub fn op_brotli_compress( #[smi] lgwin: i32, #[smi] mode: u32, ) -> Result<usize, AnyError> { - let in_buffer = buffer.as_ptr(); - let in_size = buffer.len(); - let out_buffer = out.as_mut_ptr(); + let mode = encoder_mode(mode)?; let mut out_size = out.len(); - // SAFETY: in_size and in_buffer, out_size and out_buffer are valid for this call. - if unsafe { - BrotliEncoderCompress( - quality, - lgwin, - encoder_mode(mode)?, - in_size, - in_buffer, - &mut out_size as *mut usize, - out_buffer, - ) - } != 1 - { + let result = BrotliEncoderCompress( + StandardAlloc::default(), + &mut StandardAlloc::default(), + quality, + lgwin, + mode, + buffer.len(), + buffer, + &mut out_size, + out, + &mut |_, _, _, _| (), + ); + if result != 1 { return Err(type_error("Failed to compress")); } @@ -80,28 +88,25 @@ pub async fn op_brotli_compress_async( #[smi] lgwin: i32, #[smi] mode: u32, ) -> Result<ToJsBuffer, AnyError> { + let mode = encoder_mode(mode)?; tokio::task::spawn_blocking(move || { - let in_buffer = input.as_ptr(); - let in_size = input.len(); - - let mut out = vec![0u8; max_compressed_size(in_size)]; - let out_buffer = out.as_mut_ptr(); + let input = &*input; + let mut out = vec![0u8; max_compressed_size(input.len())]; let mut out_size = out.len(); - // SAFETY: in_size and in_buffer, out_size and out_buffer - // are valid for this call. - if unsafe { - BrotliEncoderCompress( - quality, - lgwin, - encoder_mode(mode)?, - in_size, - in_buffer, - &mut out_size as *mut usize, - out_buffer, - ) - } != 1 - { + let result = BrotliEncoderCompress( + StandardAlloc::default(), + &mut StandardAlloc::default(), + quality, + lgwin, + mode, + input.len(), + input, + &mut out_size, + &mut out, + &mut |_, _, _, _| (), + ); + if result != 1 { return Err(type_error("Failed to compress")); } @@ -112,38 +117,26 @@ pub async fn op_brotli_compress_async( } struct BrotliCompressCtx { - inst: *mut BrotliEncoderState, + inst: RefCell<BrotliEncoderStateStruct<StandardAlloc>>, } impl Resource for BrotliCompressCtx {} -impl Drop for BrotliCompressCtx { - fn drop(&mut self) { - // SAFETY: `self.inst` is the current brotli encoder instance. - // It is not used after the following call. - unsafe { BrotliEncoderDestroyInstance(self.inst) }; - } -} - #[op2] #[smi] pub fn op_create_brotli_compress( state: &mut OpState, #[serde] params: Vec<(u8, i32)>, ) -> u32 { - let inst = - // SAFETY: Creates a brotli encoder instance for default allocators. - unsafe { BrotliEncoderCreateInstance(None, None, std::ptr::null_mut()) }; + let mut inst = BrotliEncoderStateStruct::new(StandardAlloc::default()); for (key, value) in params { - // SAFETY: `key` can range from 0-255. - // Any valid u32 can be used for the `value`. - unsafe { - BrotliEncoderSetParameter(inst, encoder_param(key), value as u32); - } + inst.set_parameter(encoder_param(key), value as u32); } - state.resource_table.add(BrotliCompressCtx { inst }) + state.resource_table.add(BrotliCompressCtx { + inst: RefCell::new(inst), + }) } fn encoder_param(param: u8) -> BrotliEncoderParameter { @@ -160,30 +153,25 @@ pub fn op_brotli_compress_stream( #[buffer] output: &mut [u8], ) -> Result<usize, AnyError> { let ctx = state.resource_table.get::<BrotliCompressCtx>(rid)?; - - // SAFETY: TODO(littledivy) - unsafe { - let mut available_in = input.len(); - let mut next_in = input.as_ptr(); - let mut available_out = output.len(); - let mut next_out = output.as_mut_ptr(); - - if BrotliEncoderCompressStream( - ctx.inst, - BrotliEncoderOperation::BROTLI_OPERATION_PROCESS, - &mut available_in, - &mut next_in, - &mut available_out, - &mut next_out, - std::ptr::null_mut(), - ) != 1 - { - return Err(type_error("Failed to compress")); - } - - // On progress, next_out is advanced and available_out is reduced. - Ok(output.len() - available_out) + let mut inst = ctx.inst.borrow_mut(); + let mut output_offset = 0; + + let result = inst.compress_stream( + BrotliEncoderOperation::BROTLI_OPERATION_PROCESS, + &mut input.len(), + input, + &mut 0, + &mut output.len(), + output, + &mut output_offset, + &mut None, + &mut |_, _, _, _| (), + ); + if !result { + return Err(type_error("Failed to compress")); } + + Ok(output_offset) } #[op2(fast)] @@ -194,29 +182,25 @@ pub fn op_brotli_compress_stream_end( #[buffer] output: &mut [u8], ) -> Result<usize, AnyError> { let ctx = state.resource_table.get::<BrotliCompressCtx>(rid)?; - - // SAFETY: TODO(littledivy) - unsafe { - let mut available_out = output.len(); - let mut next_out = output.as_mut_ptr(); - let mut total_out = 0; - - if BrotliEncoderCompressStream( - ctx.inst, - BrotliEncoderOperation::BROTLI_OPERATION_FINISH, - &mut 0, - std::ptr::null_mut(), - &mut available_out, - &mut next_out, - &mut total_out, - ) != 1 - { - return Err(type_error("Failed to compress")); - } - - // On finish, next_out is advanced and available_out is reduced. - Ok(output.len() - available_out) + let mut inst = ctx.inst.borrow_mut(); + let mut output_offset = 0; + + let result = inst.compress_stream( + BrotliEncoderOperation::BROTLI_OPERATION_FINISH, + &mut 0, + &[], + &mut 0, + &mut output.len(), + output, + &mut output_offset, + &mut None, + &mut |_, _, _, _| (), + ); + if !result { + return Err(type_error("Failed to compress")); } + + Ok(output_offset) } fn brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> { @@ -243,25 +227,22 @@ pub async fn op_brotli_decompress_async( } struct BrotliDecompressCtx { - inst: *mut BrotliDecoderState, + inst: RefCell<BrotliState<StandardAlloc, StandardAlloc, StandardAlloc>>, } impl Resource for BrotliDecompressCtx {} -impl Drop for BrotliDecompressCtx { - fn drop(&mut self) { - // SAFETY: TODO(littledivy) - unsafe { CBrotliDecoderDestroyInstance(self.inst) }; - } -} - #[op2(fast)] #[smi] pub fn op_create_brotli_decompress(state: &mut OpState) -> u32 { - let inst = - // SAFETY: TODO(littledivy) - unsafe { CBrotliDecoderCreateInstance(None, None, std::ptr::null_mut()) }; - state.resource_table.add(BrotliDecompressCtx { inst }) + let inst = BrotliState::new( + StandardAlloc::default(), + StandardAlloc::default(), + StandardAlloc::default(), + ); + state.resource_table.add(BrotliDecompressCtx { + inst: RefCell::new(inst), + }) } #[op2(fast)] @@ -273,32 +254,24 @@ pub fn op_brotli_decompress_stream( #[buffer] output: &mut [u8], ) -> Result<usize, AnyError> { let ctx = state.resource_table.get::<BrotliDecompressCtx>(rid)?; - - // SAFETY: TODO(littledivy) - unsafe { - let mut available_in = input.len(); - let mut next_in = input.as_ptr(); - let mut available_out = output.len(); - let mut next_out = output.as_mut_ptr(); - - if matches!( - CBrotliDecoderDecompressStream( - ctx.inst, - &mut available_in, - &mut next_in, - &mut available_out, - &mut next_out, - std::ptr::null_mut(), - ), - BrotliDecoderResult::BROTLI_DECODER_RESULT_ERROR - ) { - let ec = CBrotliDecoderGetErrorCode(ctx.inst) as i32; - return Err(type_error(format!("Failed to decompress, error {ec}"))); - } - - // On progress, next_out is advanced and available_out is reduced. - Ok(output.len() - available_out) + let mut inst = ctx.inst.borrow_mut(); + let mut output_offset = 0; + + let result = BrotliDecompressStream( + &mut input.len(), + &mut 0, + input, + &mut output.len(), + &mut output_offset, + output, + &mut 0, + &mut inst, + ); + if matches!(result, BrotliResult::ResultFailure) { + return Err(type_error("Failed to decompress")); } + + Ok(output_offset) } #[op2(fast)] @@ -309,30 +282,22 @@ pub fn op_brotli_decompress_stream_end( #[buffer] output: &mut [u8], ) -> Result<usize, AnyError> { let ctx = state.resource_table.get::<BrotliDecompressCtx>(rid)?; - - // SAFETY: TODO(littledivy) - unsafe { - let mut available_out = output.len(); - let mut next_out = output.as_mut_ptr(); - let mut available_in = 0; - let mut next_in = []; - let mut total_out = 0; - - if matches!( - CBrotliDecoderDecompressStream( - ctx.inst, - &mut available_in, - next_in.as_mut_ptr(), - &mut available_out, - &mut next_out, - &mut total_out, - ), - BrotliDecoderResult::BROTLI_DECODER_RESULT_ERROR - ) { - return Err(type_error("Failed to decompress")); - } - - // On finish, next_out is advanced and available_out is reduced. - Ok(output.len() - available_out) + let mut inst = ctx.inst.borrow_mut(); + let mut output_offset = 0; + + let result = BrotliDecompressStream( + &mut 0, + &mut 0, + &[], + &mut output.len(), + &mut output_offset, + output, + &mut 0, + &mut inst, + ); + if matches!(result, BrotliResult::ResultFailure) { + return Err(type_error("Failed to decompress")); } + + Ok(output_offset) } |