use crate::host::{with_host, JsObj};
use flate2::read::{DeflateDecoder, GzDecoder, ZlibDecoder};
use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
use flate2::Compression;
use fusevm::Value;
use std::io::{Read, Write};
use super::buffer;
pub const MODULE_METHODS: &[&str] = &[
"gzipSync",
"gunzipSync",
"deflateSync",
"inflateSync",
"deflateRawSync",
"inflateRawSync",
"unzipSync",
"brotliCompressSync",
"brotliDecompressSync",
"zstdCompressSync",
"zstdDecompressSync",
"gzip",
"gunzip",
"deflate",
"inflate",
"deflateRaw",
"inflateRaw",
"unzip",
"brotliCompress",
"brotliDecompress",
"zstdCompress",
"zstdDecompress",
"crc32",
"createDeflate",
"createInflate",
"createGzip",
"createGunzip",
"createDeflateRaw",
"createInflateRaw",
"createUnzip",
"createBrotliCompress",
"createBrotliDecompress",
"createZstdCompress",
"createZstdDecompress",
];
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
if method.starts_with("create") {
return Some(Err(crate::host::type_error(&format!(
"zlib.{method} is not supported in node-js (no streaming backend)"
))));
}
if method == "crc32" {
let data = input_bytes(args);
let init = {
let n = super::arg_num(args, 1);
if n.is_nan() {
0
} else {
n as i64 as u32
}
};
return Some(Ok(Value::Float(crc32(&data, init) as f64)));
}
if is_async(method) {
return Some(run_async(method, args));
}
let base = method.strip_suffix("Sync")?;
let out = oneshot(base, &input_bytes(args));
Some(out.map(|bytes| buffer::from_bytes(&bytes)))
}
fn is_async(method: &str) -> bool {
matches!(
method,
"gzip"
| "gunzip"
| "deflate"
| "inflate"
| "deflateRaw"
| "inflateRaw"
| "unzip"
| "brotliCompress"
| "brotliDecompress"
| "zstdCompress"
| "zstdDecompress"
)
}
fn run_async(op: &str, args: &[Value]) -> Result<Value, String> {
let Some(cb) = args.last().cloned() else {
return Ok(Value::Undef);
};
let input = input_bytes(args);
let (err, buf) = match oneshot(op, &input) {
Ok(bytes) => (with_host(|h| h.null()), buffer::from_bytes(&bytes)),
Err(e) => (with_host(|h| h.new_str(e)), Value::Undef),
};
with_host(|h| h.queue_micro(cb, vec![err, buf]));
Ok(Value::Undef)
}
fn oneshot(op: &str, input: &[u8]) -> Result<Vec<u8>, String> {
match op {
"gzip" => gzip(input),
"gunzip" => gunzip(input),
"deflate" => deflate(input),
"inflate" => inflate(input),
"deflateRaw" => deflate_raw(input),
"inflateRaw" => inflate_raw(input),
"unzip" => unzip(input),
"brotliCompress" => brotli_compress(input),
"brotliDecompress" => brotli_decompress(input),
"zstdCompress" => zstd_compress(input),
"zstdDecompress" => zstd_decompress(input),
_ => Err(format!("Error: unknown zlib op '{op}'")),
}
}
fn input_bytes(args: &[Value]) -> Vec<u8> {
let v = args.first().cloned().unwrap_or(Value::Undef);
with_host(|h| match h.get(&v) {
Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
_ => h.str_of(&v).into_bytes(),
},
_ => h.str_of(&v).into_bytes(),
})
}
fn io_err(e: std::io::Error) -> String {
format!("Error: {e}")
}
fn gzip(input: &[u8]) -> Result<Vec<u8>, String> {
let mut enc = GzEncoder::new(Vec::new(), Compression::default());
enc.write_all(input).map_err(io_err)?;
enc.finish().map_err(io_err)
}
fn gunzip(input: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
GzDecoder::new(input)
.read_to_end(&mut out)
.map_err(io_err)?;
Ok(out)
}
fn deflate(input: &[u8]) -> Result<Vec<u8>, String> {
let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
enc.write_all(input).map_err(io_err)?;
enc.finish().map_err(io_err)
}
fn inflate(input: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
ZlibDecoder::new(input)
.read_to_end(&mut out)
.map_err(io_err)?;
Ok(out)
}
fn deflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
let mut enc = DeflateEncoder::new(Vec::new(), Compression::default());
enc.write_all(input).map_err(io_err)?;
enc.finish().map_err(io_err)
}
fn inflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
DeflateDecoder::new(input)
.read_to_end(&mut out)
.map_err(io_err)?;
Ok(out)
}
fn unzip(input: &[u8]) -> Result<Vec<u8>, String> {
if input.starts_with(&[0x1f, 0x8b]) {
gunzip(input)
} else {
inflate(input)
}
}
fn brotli_compress(input: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
{
let mut enc = brotli::CompressorWriter::new(&mut out, 4096, 11, 22);
enc.write_all(input).map_err(io_err)?;
}
Ok(out)
}
fn brotli_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
brotli::Decompressor::new(input, 4096)
.read_to_end(&mut out)
.map_err(io_err)?;
Ok(out)
}
fn zstd_compress(input: &[u8]) -> Result<Vec<u8>, String> {
zstd::encode_all(input, 3).map_err(io_err)
}
fn zstd_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
zstd::decode_all(input).map_err(io_err)
}
fn crc32(data: &[u8], init: u32) -> u32 {
let mut h = crc32fast::Hasher::new_with_initial(init);
h.update(data);
h.finalize()
}