1use crate::host::{with_host, JsObj};
23use flate2::read::{DeflateDecoder, GzDecoder, ZlibDecoder};
24use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
25use flate2::Compression;
26use fusevm::Value;
27use std::io::{Read, Write};
28
29use super::buffer;
30
31pub const MODULE_METHODS: &[&str] = &[
33 "gzipSync",
35 "gunzipSync",
36 "deflateSync",
37 "inflateSync",
38 "deflateRawSync",
39 "inflateRawSync",
40 "unzipSync",
41 "brotliCompressSync",
42 "brotliDecompressSync",
43 "zstdCompressSync",
44 "zstdDecompressSync",
45 "gzip",
47 "gunzip",
48 "deflate",
49 "inflate",
50 "deflateRaw",
51 "inflateRaw",
52 "unzip",
53 "brotliCompress",
54 "brotliDecompress",
55 "zstdCompress",
56 "zstdDecompress",
57 "crc32",
59 "createDeflate",
61 "createInflate",
62 "createGzip",
63 "createGunzip",
64 "createDeflateRaw",
65 "createInflateRaw",
66 "createUnzip",
67 "createBrotliCompress",
68 "createBrotliDecompress",
69 "createZstdCompress",
70 "createZstdDecompress",
71];
72
73pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
74 if method.starts_with("create") {
76 return Some(Err(crate::host::type_error(&format!(
77 "zlib.{method} is not supported in node-js (no streaming backend)"
78 ))));
79 }
80
81 if method == "crc32" {
83 let data = input_bytes(args);
84 let init = {
85 let n = super::arg_num(args, 1);
86 if n.is_nan() {
87 0
88 } else {
89 n as i64 as u32
90 }
91 };
92 return Some(Ok(Value::Float(crc32(&data, init) as f64)));
93 }
94
95 if is_async(method) {
97 return Some(run_async(method, args));
98 }
99
100 let base = method.strip_suffix("Sync")?;
104 let out = oneshot(base, &input_bytes(args));
105 Some(out.map(|bytes| buffer::from_bytes(&bytes)))
106}
107
108fn is_async(method: &str) -> bool {
110 matches!(
111 method,
112 "gzip"
113 | "gunzip"
114 | "deflate"
115 | "inflate"
116 | "deflateRaw"
117 | "inflateRaw"
118 | "unzip"
119 | "brotliCompress"
120 | "brotliDecompress"
121 | "zstdCompress"
122 | "zstdDecompress"
123 )
124}
125
126fn run_async(op: &str, args: &[Value]) -> Result<Value, String> {
128 let Some(cb) = args.last().cloned() else {
129 return Ok(Value::Undef);
130 };
131 let input = input_bytes(args);
132 let (err, buf) = match oneshot(op, &input) {
133 Ok(bytes) => (with_host(|h| h.null()), buffer::from_bytes(&bytes)),
134 Err(e) => (with_host(|h| h.new_str(e)), Value::Undef),
135 };
136 with_host(|h| h.queue_micro(cb, vec![err, buf]));
137 Ok(Value::Undef)
138}
139
140fn oneshot(op: &str, input: &[u8]) -> Result<Vec<u8>, String> {
142 match op {
143 "gzip" => gzip(input),
144 "gunzip" => gunzip(input),
145 "deflate" => deflate(input),
146 "inflate" => inflate(input),
147 "deflateRaw" => deflate_raw(input),
148 "inflateRaw" => inflate_raw(input),
149 "unzip" => unzip(input),
150 "brotliCompress" => brotli_compress(input),
151 "brotliDecompress" => brotli_decompress(input),
152 "zstdCompress" => zstd_compress(input),
153 "zstdDecompress" => zstd_decompress(input),
154 _ => Err(format!("Error: unknown zlib op '{op}'")),
155 }
156}
157
158fn input_bytes(args: &[Value]) -> Vec<u8> {
161 let v = args.first().cloned().unwrap_or(Value::Undef);
162 with_host(|h| match h.get(&v) {
163 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
164 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
165 _ => h.str_of(&v).into_bytes(),
166 },
167 _ => h.str_of(&v).into_bytes(),
168 })
169}
170
171fn io_err(e: std::io::Error) -> String {
173 format!("Error: {e}")
174}
175
176fn gzip(input: &[u8]) -> Result<Vec<u8>, String> {
177 let mut enc = GzEncoder::new(Vec::new(), Compression::default());
178 enc.write_all(input).map_err(io_err)?;
179 enc.finish().map_err(io_err)
180}
181
182fn gunzip(input: &[u8]) -> Result<Vec<u8>, String> {
183 let mut out = Vec::new();
184 GzDecoder::new(input)
185 .read_to_end(&mut out)
186 .map_err(io_err)?;
187 Ok(out)
188}
189
190fn deflate(input: &[u8]) -> Result<Vec<u8>, String> {
191 let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
192 enc.write_all(input).map_err(io_err)?;
193 enc.finish().map_err(io_err)
194}
195
196fn inflate(input: &[u8]) -> Result<Vec<u8>, String> {
197 let mut out = Vec::new();
198 ZlibDecoder::new(input)
199 .read_to_end(&mut out)
200 .map_err(io_err)?;
201 Ok(out)
202}
203
204fn deflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
205 let mut enc = DeflateEncoder::new(Vec::new(), Compression::default());
206 enc.write_all(input).map_err(io_err)?;
207 enc.finish().map_err(io_err)
208}
209
210fn inflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
211 let mut out = Vec::new();
212 DeflateDecoder::new(input)
213 .read_to_end(&mut out)
214 .map_err(io_err)?;
215 Ok(out)
216}
217
218fn unzip(input: &[u8]) -> Result<Vec<u8>, String> {
220 if input.starts_with(&[0x1f, 0x8b]) {
221 gunzip(input)
222 } else {
223 inflate(input)
224 }
225}
226
227fn brotli_compress(input: &[u8]) -> Result<Vec<u8>, String> {
228 let mut out = Vec::new();
229 {
230 let mut enc = brotli::CompressorWriter::new(&mut out, 4096, 11, 22);
232 enc.write_all(input).map_err(io_err)?;
233 }
235 Ok(out)
236}
237
238fn brotli_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
239 let mut out = Vec::new();
240 brotli::Decompressor::new(input, 4096)
241 .read_to_end(&mut out)
242 .map_err(io_err)?;
243 Ok(out)
244}
245
246fn zstd_compress(input: &[u8]) -> Result<Vec<u8>, String> {
247 zstd::encode_all(input, 3).map_err(io_err)
249}
250
251fn zstd_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
252 zstd::decode_all(input).map_err(io_err)
253}
254
255fn crc32(data: &[u8], init: u32) -> u32 {
257 let mut h = crc32fast::Hasher::new_with_initial(init);
258 h.update(data);
259 h.finalize()
260}