1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat: port node:zlib to rust (#18291)

This commit is contained in:
Divy Srivastava 2023-03-27 21:33:07 +05:30 committed by GitHub
parent 9ebce6e725
commit b2fd0742bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 897 additions and 7471 deletions

13
Cargo.lock generated
View file

@ -1150,6 +1150,7 @@ dependencies = [
"hex",
"idna 0.3.0",
"indexmap",
"libz-sys",
"md-5",
"md4",
"once_cell",
@ -2582,6 +2583,18 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"

View file

@ -22,6 +22,7 @@ ecb.workspace = true
hex.workspace = true
idna = "0.3.0"
indexmap.workspace = true
libz-sys = { version = "1.1.8", features = ["static"] }
md-5 = "0.10.5"
md4 = "0.10.2"
once_cell.workspace = true

View file

@ -20,6 +20,7 @@ mod polyfill;
mod resolution;
mod v8;
mod winerror;
mod zlib;
pub use package_json::PackageJson;
pub use path::PathClean;
@ -119,6 +120,13 @@ deno_core::extension!(deno_node,
idna::op_node_idna_domain_to_unicode,
idna::op_node_idna_punycode_decode,
idna::op_node_idna_punycode_encode,
zlib::op_zlib_new,
zlib::op_zlib_close,
zlib::op_zlib_close_if_pending,
zlib::op_zlib_write,
zlib::op_zlib_write_async,
zlib::op_zlib_init,
zlib::op_zlib_reset,
op_node_build_os,
ops::op_require_init_paths,
@ -195,7 +203,6 @@ deno_core::extension!(deno_node,
"_http_common.ts",
"_http_outgoing.ts",
"_next_tick.ts",
"_pako.mjs",
"_process/exiting.ts",
"_process/process.ts",
"_process/streams.mjs",

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,37 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// Copyright (c) 2014-2015 Devon Govett <devongovett@gmail.com>
// Forked from https://github.com/browserify/browserify-zlib
// deno-lint-ignore-file
import assert from "ext:deno_node/assert.ts";
import { constants, zlib_deflate, zlib_inflate, Zstream } from "ext:deno_node/_pako.mjs";
import { nextTick } from "ext:deno_node/_next_tick.ts";
export const Z_NO_FLUSH = constants.Z_NO_FLUSH;
export const Z_PARTIAL_FLUSH = constants.Z_PARTIAL_FLUSH;
export const Z_SYNC_FLUSH = constants.Z_SYNC_FLUSH;
export const Z_FULL_FLUSH = constants.Z_FULL_FLUSH;
export const Z_FINISH = constants.Z_FINISH;
export const Z_BLOCK = constants.Z_BLOCK;
export const Z_TREES = constants.Z_TREES;
export const Z_OK = constants.Z_OK;
export const Z_STREAM_END = constants.Z_STREAM_END;
export const Z_NEED_DICT = constants.Z_NEED_DICT;
export const Z_ERRNO = constants.Z_ERRNO;
export const Z_STREAM_ERROR = constants.Z_STREAM_ERROR;
export const Z_DATA_ERROR = constants.Z_DATA_ERROR;
export const Z_MEM_ERROR = constants.Z_MEM_ERROR;
export const Z_BUF_ERROR = constants.Z_BUF_ERROR;
export const Z_VERSION_ERROR = constants.Z_VERSION_ERROR;
export const Z_NO_COMPRESSION = constants.Z_NO_COMPRESSION;
export const Z_BEST_SPEED = constants.Z_BEST_SPEED;
export const Z_BEST_COMPRESSION = constants.Z_BEST_COMPRESSION;
export const Z_DEFAULT_COMPRESSION = constants.Z_DEFAULT_COMPRESSION;
export const Z_FILTERED = constants.Z_FILTERED;
export const Z_HUFFMAN_ONLY = constants.Z_HUFFMAN_ONLYZ_FILTERED;
export const Z_RLE = constants.Z_RLE;
export const Z_FIXED = constants.Z_FIXED;
export const Z_DEFAULT_STRATEGY = constants.Z_DEFAULT_STRATEGY;
export const Z_BINARY = constants.Z_BINARY;
export const Z_TEXT = constants.Z_TEXT;
export const Z_UNKNOWN = constants.Z_UNKNOWN;
export const Z_DEFLATED = constants.Z_DEFLATED;
// https://github.com/nodeca/pako/blob/master/lib/zlib/constants.js
export const Z_NO_FLUSH = 0;
export const Z_PARTIAL_FLUSH = 1;
export const Z_SYNC_FLUSH = 2;
export const Z_FULL_FLUSH = 3;
export const Z_FINISH = 4;
export const Z_BLOCK = 5;
export const Z_TREES = 6;
export const Z_OK = 0;
export const Z_STREAM_END = 1;
export const Z_NEED_DICT = 2;
export const Z_ERRNO = -1;
export const Z_STREAM_ERROR = -2;
export const Z_DATA_ERROR = -3;
export const Z_MEM_ERROR = -4;
export const Z_BUF_ERROR = -5;
export const Z_VERSION_ERROR = -6;
export const Z_NO_COMPRESSION = 0;
export const Z_BEST_SPEED = 1;
export const Z_BEST_COMPRESSION = 9;
export const Z_DEFAULT_COMPRESSION = -1;
export const Z_FILTERED = 1;
export const Z_HUFFMAN_ONLY = 2;
export const Z_RLE = 3;
export const Z_FIXED = 4;
export const Z_DEFAULT_STRATEGY = 0;
export const Z_BINARY = 0;
export const Z_TEXT = 1;
export const Z_UNKNOWN = 2;
export const Z_DEFLATED = 8;
// zlib modes
export const NONE = 0;
@ -48,79 +43,23 @@ export const DEFLATERAW = 5;
export const INFLATERAW = 6;
export const UNZIP = 7;
var GZIP_HEADER_ID1 = 0x1f;
var GZIP_HEADER_ID2 = 0x8b;
const { core } = globalThis.__bootstrap;
const { ops } = core;
/**
* Emulate Node's zlib C++ layer for use by the JS layer in index.js
*/
function Zlib(mode) {
if (typeof mode !== "number" || mode < DEFLATE || mode > UNZIP) {
throw new TypeError("Bad argument");
const writeResult = new Uint32Array(2);
class Zlib {
#handle;
constructor(mode) {
this.#handle = ops.op_zlib_new(mode);
}
this.dictionary = null;
this.err = 0;
this.flush = 0;
this.init_done = false;
this.level = 0;
this.memLevel = 0;
this.mode = mode;
this.strategy = 0;
this.windowBits = 0;
this.write_in_progress = false;
this.pending_close = false;
this.gzip_id_bytes_read = 0;
}
Zlib.prototype.close = function () {
if (this.write_in_progress) {
this.pending_close = true;
return;
close() {
ops.op_zlib_close(this.#handle);
}
this.pending_close = false;
assert(this.init_done, "close before init");
assert(this.mode <= UNZIP);
if (this.mode === DEFLATE || this.mode === GZIP || this.mode === DEFLATERAW) {
zlib_deflate.deflateEnd(this.strm);
} else if (
this.mode === INFLATE || this.mode === GUNZIP || this.mode === INFLATERAW ||
this.mode === UNZIP
) {
zlib_inflate.inflateEnd(this.strm);
}
this.mode = NONE;
this.dictionary = null;
};
Zlib.prototype.write = function (
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
) {
return this._write(true, flush, input, in_off, in_len, out, out_off, out_len);
};
Zlib.prototype.writeSync = function (
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
) {
return this._write(
false,
writeSync(
flush,
input,
in_off,
@ -128,384 +67,111 @@ Zlib.prototype.writeSync = function (
out,
out_off,
out_len,
);
};
Zlib.prototype._write = function (
async,
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
) {
assert.equal(arguments.length, 8);
assert(this.init_done, "write before init");
assert(this.mode !== NONE, "already finalized");
assert.equal(false, this.write_in_progress, "write already in progress");
assert.equal(false, this.pending_close, "close is pending");
this.write_in_progress = true;
assert.equal(false, flush === undefined, "must provide flush value");
this.write_in_progress = true;
if (
flush !== Z_NO_FLUSH && flush !== Z_PARTIAL_FLUSH &&
flush !== Z_SYNC_FLUSH && flush !== Z_FULL_FLUSH && flush !== Z_FINISH &&
flush !== Z_BLOCK
) {
throw new Error("Invalid flush value");
}
const err = ops.op_zlib_write(
this.#handle,
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
writeResult,
);
if (input == null) {
input = Buffer.alloc(0);
in_len = 0;
in_off = 0;
}
this.strm.avail_in = in_len;
this.strm.input = input;
this.strm.next_in = in_off;
this.strm.avail_out = out_len;
this.strm.output = out;
this.strm.next_out = out_off;
this.flush = flush;
if (!async) {
// sync version
this._process();
if (this._checkError()) {
return this._afterSync();
if (this.#checkError(err)) {
return [writeResult[1], writeResult[0]];
}
return;
}
// async version
var self = this;
nextTick(function () {
self._process();
self._after();
});
return this;
};
Zlib.prototype._afterSync = function () {
var avail_out = this.strm.avail_out;
var avail_in = this.strm.avail_in;
this.write_in_progress = false;
return [avail_in, avail_out];
};
Zlib.prototype._process = function () {
var next_expected_header_byte = null;
// If the avail_out is left at 0, then it means that it ran out
// of room. If there was avail_out left over, then it means
// that all of the input was consumed.
switch (this.mode) {
case DEFLATE:
case GZIP:
case DEFLATERAW:
this.err = zlib_deflate.deflate(this.strm, this.flush);
break;
case UNZIP:
if (this.strm.avail_in > 0) {
next_expected_header_byte = this.strm.next_in;
}
switch (this.gzip_id_bytes_read) {
case 0:
if (next_expected_header_byte === null) {
break;
}
if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID1) {
this.gzip_id_bytes_read = 1;
next_expected_header_byte++;
if (this.strm.avail_in === 1) {
// The only available byte was already read.
break;
}
} else {
this.mode = INFLATE;
break;
}
// fallthrough
case 1:
if (next_expected_header_byte === null) {
break;
}
if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID2) {
this.gzip_id_bytes_read = 2;
this.mode = GUNZIP;
} else {
// There is no actual difference between INFLATE and INFLATERAW
// (after initialization).
this.mode = INFLATE;
}
break;
default:
throw new Error("invalid number of gzip magic number bytes read");
}
// fallthrough
case INFLATE:
case GUNZIP:
case INFLATERAW:
this.err = zlib_inflate.inflate(this.strm, this.flush);
// If data was encoded with dictionary
if (this.err === Z_NEED_DICT && this.dictionary) {
// Load it
this.err = zlib_inflate.inflateSetDictionary(
this.strm,
this.dictionary,
);
if (this.err === Z_OK) {
// And try to decode again
this.err = zlib_inflate.inflate(this.strm, this.flush);
} else if (this.err === Z_DATA_ERROR) {
// Both inflateSetDictionary() and inflate() return Z_DATA_ERROR.
// Make it possible for After() to tell a bad dictionary from bad
// input.
this.err = Z_NEED_DICT;
}
}
while (
this.strm.avail_in > 0 && this.mode === GUNZIP &&
this.err === Z_STREAM_END && this.strm.next_in[0] !== 0x00
) {
// Bytes remain in input buffer. Perhaps this is another compressed
// member in the same archive, or just trailing garbage.
// Trailing zero bytes are okay, though, since they are frequently
// used for padding.
this.reset();
this.err = zlib_inflate.inflate(this.strm, this.flush);
}
break;
default:
throw new Error("Unknown mode " + this.mode);
}
};
Zlib.prototype._checkError = function () {
// Acceptable error states depend on the type of zlib stream.
switch (this.err) {
case Z_OK:
case Z_BUF_ERROR:
if (this.strm.avail_out !== 0 && this.flush === Z_FINISH) {
this._error("unexpected end of file");
#checkError(err) {
// Acceptable error states depend on the type of zlib stream.
switch (err) {
case Z_BUF_ERROR:
this.#error("unexpected end of file", err);
return false;
case Z_OK:
case Z_STREAM_END:
// normal statuses, not fatal
break;
case Z_NEED_DICT:
this.#error("Bad dictionary", err);
return false;
default:
// something else.
this.#error("Zlib error", err);
return false;
}
return true;
}
write(
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
) {
core.opAsync(
"op_zlib_write_async",
this.#handle,
flush,
input,
in_off,
in_len,
out,
out_off,
out_len,
).then(([err, availOut, availIn]) => {
if (this.#checkError(err)) {
this.callback(availIn, availOut);
}
break;
case Z_STREAM_END:
// normal statuses, not fatal
break;
case Z_NEED_DICT:
if (this.dictionary == null) {
this._error("Missing dictionary");
} else {
this._error("Bad dictionary");
}
return false;
default:
// something else.
this._error("Zlib error");
return false;
});
return this;
}
return true;
};
init(
windowBits,
level,
memLevel,
strategy,
dictionary,
) {
const err = ops.op_zlib_init(
this.#handle,
level,
windowBits,
memLevel,
strategy,
dictionary,
);
Zlib.prototype._after = function () {
if (!this._checkError()) {
return;
if (err != Z_OK) {
this.#error("Failed to initialize zlib", err);
}
}
var avail_out = this.strm.avail_out;
var avail_in = this.strm.avail_in;
this.write_in_progress = false;
// call the write() cb
this.callback(avail_in, avail_out);
if (this.pending_close) {
this.close();
}
};
Zlib.prototype._error = function (message) {
if (this.strm.msg) {
message = this.strm.msg;
}
this.onerror(message, this.err);
// no hope of rescue.
this.write_in_progress = false;
if (this.pending_close) {
this.close();
}
};
Zlib.prototype.init = function (
windowBits,
level,
memLevel,
strategy,
dictionary,
) {
assert(
arguments.length === 4 || arguments.length === 5,
"init(windowBits, level, memLevel, strategy, [dictionary])",
);
assert(windowBits >= 8 && windowBits <= 15, "invalid windowBits");
assert(level >= -1 && level <= 9, "invalid compression level");
assert(memLevel >= 1 && memLevel <= 9, "invalid memlevel");
assert(
strategy === Z_FILTERED || strategy === Z_HUFFMAN_ONLY ||
strategy === Z_RLE || strategy === Z_FIXED ||
strategy === Z_DEFAULT_STRATEGY,
"invalid strategy",
);
this._init(level, windowBits, memLevel, strategy, dictionary);
this._setDictionary();
};
Zlib.prototype.params = function () {
throw new Error("deflateParams Not supported");
};
Zlib.prototype.reset = function () {
this._reset();
this._setDictionary();
};
Zlib.prototype._init = function (
level,
windowBits,
memLevel,
strategy,
dictionary,
) {
this.level = level;
this.windowBits = windowBits;
this.memLevel = memLevel;
this.strategy = strategy;
this.flush = Z_NO_FLUSH;
this.err = Z_OK;
if (this.mode === GZIP || this.mode === GUNZIP) {
this.windowBits += 16;
params() {
throw new Error("deflateParams Not supported");
}
if (this.mode === UNZIP) {
this.windowBits += 32;
reset() {
const err = ops.op_zlib_reset(this.#handle);
if (err != Z_OK) {
this.#error("Failed to reset stream", err);
}
}
if (this.mode === DEFLATERAW || this.mode === INFLATERAW) {
this.windowBits = -1 * this.windowBits;
#error(message, err) {
this.onerror(message, err);
ops.op_zlib_close_if_pending(this.#handle);
}
this.strm = new Zstream();
switch (this.mode) {
case DEFLATE:
case GZIP:
case DEFLATERAW:
this.err = zlib_deflate.deflateInit2(
this.strm,
this.level,
Z_DEFLATED,
this.windowBits,
this.memLevel,
this.strategy,
);
break;
case INFLATE:
case GUNZIP:
case INFLATERAW:
case UNZIP:
this.err = zlib_inflate.inflateInit2(this.strm, this.windowBits);
break;
default:
throw new Error("Unknown mode " + this.mode);
}
if (this.err !== Z_OK) {
this._error("Init error");
}
this.dictionary = dictionary;
this.write_in_progress = false;
this.init_done = true;
};
Zlib.prototype._setDictionary = function () {
if (this.dictionary == null) {
return;
}
this.err = Z_OK;
switch (this.mode) {
case DEFLATE:
case DEFLATERAW:
this.err = zlib_deflate.deflateSetDictionary(this.strm, this.dictionary);
break;
default:
break;
}
if (this.err !== Z_OK) {
this._error("Failed to set dictionary");
}
};
Zlib.prototype._reset = function () {
this.err = Z_OK;
switch (this.mode) {
case DEFLATE:
case DEFLATERAW:
case GZIP:
this.err = zlib_deflate.deflateReset(this.strm);
break;
case INFLATE:
case INFLATERAW:
case GUNZIP:
this.err = zlib_inflate.inflateReset(this.strm);
break;
default:
break;
}
if (this.err !== Z_OK) {
this._error("Failed to reset stream");
}
};
}
export { Zlib };

64
ext/node/zlib/alloc.rs Normal file
View file

@ -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)
}
}

450
ext/node/zlib/mod.rs Normal file
View file

@ -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();
}
}
}

71
ext/node/zlib/mode.rs Normal file
View file

@ -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,
}
}

136
ext/node/zlib/stream.rs Normal file
View file

@ -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 _,
)
}
}
}

View file

@ -329,6 +329,7 @@ pub(crate) fn generate(
let fast_fn = q!(
Vars { core, pre_transforms, op_name_fast: &fast_fn_ident, op_name: &ident, fast_fn_inputs, generics, call_generics: &caller_generics, where_clause, idents, transforms, output_transforms, output: &output },
{
#[allow(clippy::too_many_arguments)]
fn op_name_fast generics (_: core::v8::Local<core::v8::Object>, fast_fn_inputs) -> output where_clause {
use core::v8;
use core::_ops;

View file

@ -253,7 +253,7 @@ fn codegen_v8_async(
let rust_i0 = special_args.len();
let args_head = special_args.into_iter().collect::<TokenStream2>();
let (arg_decls, args_tail, _) = codegen_args(core, f, rust_i0, 1, true);
let (arg_decls, args_tail, _) = codegen_args(core, f, rust_i0, 1, asyncness);
let type_params = exclude_lifetime_params(&f.sig.generics.params);
let (pre_result, mut result_fut) = match asyncness {

View file

@ -101,6 +101,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_void_async_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_void_async_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
__promise_id: i32,

View file

@ -111,6 +111,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_async_result_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_async_result_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
__promise_id: i32,

View file

@ -70,6 +70,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_fallback_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_fallback_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,

View file

@ -78,6 +78,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_cow_str_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_cow_str_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
c: *const deno_core::v8::fast_api::FastApiOneByteString,

View file

@ -96,6 +96,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_f64_buf_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_f64_buf_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
buffer: *const deno_core::v8::fast_api::FastApiTypedArray<f64>,

View file

@ -110,6 +110,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_ffi_ptr_value_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_ffi_ptr_value_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
ptr: *mut ::std::ffi::c_void,

View file

@ -78,6 +78,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_set_exit_code_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_set_exit_code_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
code: i32,

View file

@ -105,6 +105,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for foo_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn foo_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
a: u32,

View file

@ -80,6 +80,7 @@ where
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_foo_fast_fn<'scope, SP>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,

View file

@ -118,6 +118,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for foo_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn foo_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
a: u32,

View file

@ -109,6 +109,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_listen_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn op_listen_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,

View file

@ -127,6 +127,7 @@ where
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_now_fast_fn<'scope, TP>(
_: deno_core::v8::Local<deno_core::v8::Object>,
buf: *const deno_core::v8::fast_api::FastApiTypedArray<u8>,

View file

@ -136,6 +136,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_add_4_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn op_add_4_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
x1: u32,

View file

@ -90,6 +90,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_string_length_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn op_string_length_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
string: *const deno_core::v8::fast_api::FastApiOneByteString,

View file

@ -153,6 +153,7 @@ where
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_ffi_ptr_of_fast_fn<'scope, FP>(
_: deno_core::v8::Local<deno_core::v8::Object>,
buf: *const deno_core::v8::fast_api::FastApiTypedArray<u8>,

View file

@ -90,6 +90,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_is_proxy_fast {
deno_core::v8::fast_api::CType::Bool
}
}
#[allow(clippy::too_many_arguments)]
fn op_is_proxy_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
value: deno_core::v8::Local<v8::Value>,

View file

@ -91,6 +91,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_string_length_fast {
deno_core::v8::fast_api::CType::Uint32
}
}
#[allow(clippy::too_many_arguments)]
fn op_string_length_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
string: *const deno_core::v8::fast_api::FastApiOneByteString,

View file

@ -167,6 +167,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_import_spki_x25519_fas
deno_core::v8::fast_api::CType::Bool
}
}
#[allow(clippy::too_many_arguments)]
fn op_import_spki_x25519_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
key_data: *const deno_core::v8::fast_api::FastApiTypedArray<u8>,

View file

@ -90,6 +90,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_unit_result_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_unit_result_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,

View file

@ -123,6 +123,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_set_nodelay_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_set_nodelay_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
rid: ResourceId,

View file

@ -79,6 +79,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_unit_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_unit_fast_fn<'scope>(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
use deno_core::v8;
use deno_core::_ops;

View file

@ -66,6 +66,7 @@ impl<'scope> deno_core::v8::fast_api::FastFunction for op_wasm_fast {
deno_core::v8::fast_api::CType::Void
}
}
#[allow(clippy::too_many_arguments)]
fn op_wasm_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,