1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00
denoland-deno/ext/node/ops/zlib/brotli.rs
Curran McConnell a9cc4631ca
fix(ext/node/ops/zlib/brotli): Allow decompressing more than 4096 bytes (#20301)
Fixes https://github.com/denoland/deno/issues/19816

In that issue, I suggest switching over the other brotli functionality
to the Rust API provided by the `brotli` crate. Here, I only do that
with the `brotli_decompress` function to fix the bug with buffers longer
than 4096 bytes.
2023-09-08 09:11:33 +05:30

325 lines
7.9 KiB
Rust

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
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::Decompressor;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::JsBuffer;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ToJsBuffer;
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)) }
}
#[op]
pub fn op_brotli_compress(
buffer: &[u8],
out: &mut [u8],
quality: i32,
lgwin: i32,
mode: u32,
) -> Result<usize, AnyError> {
let in_buffer = buffer.as_ptr();
let in_size = buffer.len();
let out_buffer = out.as_mut_ptr();
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
{
return Err(type_error("Failed to compress"));
}
Ok(out_size)
}
fn max_compressed_size(input_size: usize) -> usize {
if input_size == 0 {
return 2;
}
// [window bits / empty metadata] + N * [uncompressed] + [last empty]
let num_large_blocks = input_size >> 14;
let overhead = 2 + (4 * num_large_blocks) + 3 + 1;
let result = input_size + overhead;
if result < input_size {
0
} else {
result
}
}
#[op]
pub async fn op_brotli_compress_async(
input: JsBuffer,
quality: i32,
lgwin: i32,
mode: u32,
) -> Result<ToJsBuffer, AnyError> {
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 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
{
return Err(type_error("Failed to compress"));
}
out.truncate(out_size);
Ok(out.into())
})
.await?
}
struct BrotliCompressCtx {
inst: *mut BrotliEncoderState,
}
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) };
}
}
#[op]
pub fn op_create_brotli_compress(
state: &mut OpState,
params: Vec<(u8, i32)>,
) -> u32 {
let inst =
// SAFETY: Creates a brotli encoder instance for default allocators.
unsafe { BrotliEncoderCreateInstance(None, None, std::ptr::null_mut()) };
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);
}
}
state.resource_table.add(BrotliCompressCtx { inst })
}
fn encoder_param(param: u8) -> BrotliEncoderParameter {
// SAFETY: BrotliEncoderParam is valid for 0-255
unsafe { std::mem::transmute(param as u32) }
}
#[op]
pub fn op_brotli_compress_stream(
state: &mut OpState,
rid: u32,
input: &[u8],
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();
let mut total_out = 0;
if BrotliEncoderCompressStream(
ctx.inst,
BrotliEncoderOperation::BROTLI_OPERATION_PROCESS,
&mut available_in,
&mut next_in,
&mut available_out,
&mut next_out,
&mut total_out,
) != 1
{
return Err(type_error("Failed to compress"));
}
// On progress, next_out is advanced and available_out is reduced.
Ok(output.len() - available_out)
}
}
#[op]
pub fn op_brotli_compress_stream_end(
state: &mut OpState,
rid: u32,
output: &mut [u8],
) -> Result<usize, AnyError> {
let ctx = state.resource_table.take::<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)
}
}
fn brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> {
let mut output = Vec::with_capacity(4096);
let mut decompressor = Decompressor::new(buffer, buffer.len());
decompressor.read_to_end(&mut output)?;
Ok(output.into())
}
#[op]
pub fn op_brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> {
brotli_decompress(buffer)
}
#[op]
pub async fn op_brotli_decompress_async(
buffer: JsBuffer,
) -> Result<ToJsBuffer, AnyError> {
tokio::task::spawn_blocking(move || brotli_decompress(&buffer)).await?
}
struct BrotliDecompressCtx {
inst: *mut BrotliDecoderState,
}
impl Resource for BrotliDecompressCtx {}
impl Drop for BrotliDecompressCtx {
fn drop(&mut self) {
// SAFETY: TODO(littledivy)
unsafe { CBrotliDecoderDestroyInstance(self.inst) };
}
}
#[op]
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 })
}
#[op]
pub fn op_brotli_decompress_stream(
state: &mut OpState,
rid: u32,
input: &[u8],
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();
let mut total_out = 0;
if matches!(
CBrotliDecoderDecompressStream(
ctx.inst,
&mut available_in,
&mut next_in,
&mut available_out,
&mut next_out,
&mut total_out,
),
BrotliDecoderResult::BROTLI_DECODER_RESULT_ERROR
) {
return Err(type_error("Failed to decompress"));
}
// On progress, next_out is advanced and available_out is reduced.
Ok(output.len() - available_out)
}
}
#[op]
pub fn op_brotli_decompress_stream_end(
state: &mut OpState,
rid: u32,
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 total_out = 0;
if matches!(
CBrotliDecoderDecompressStream(
ctx.inst,
&mut 0,
std::ptr::null_mut(),
&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)
}
}