1use crate::host::with_host;
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) => (
142 with_host(|h| {
143 h.exc
144 .take()
145 .unwrap_or_else(|| crate::builtins::synth_error(h, &e))
146 }),
147 Value::Undef,
148 ),
149 };
150 with_host(|h| h.queue_micro(cb, vec![err, buf]));
151 Ok(Value::Undef)
152}
153
154fn oneshot(op: &str, input: &[u8]) -> Result<Vec<u8>, String> {
156 match op {
157 "gzip" => gzip(input),
158 "gunzip" => gunzip(input),
159 "deflate" => deflate(input),
160 "inflate" => inflate(input),
161 "deflateRaw" => deflate_raw(input),
162 "inflateRaw" => inflate_raw(input),
163 "unzip" => unzip(input),
164 "brotliCompress" => brotli_compress(input),
165 "brotliDecompress" => brotli_decompress(input),
166 "zstdCompress" => zstd_compress(input),
167 "zstdDecompress" => zstd_decompress(input),
168 _ => Err(format!("Error: unknown zlib op '{op}'")),
169 }
170}
171
172fn input_bytes(args: &[Value]) -> Vec<u8> {
175 let v = args.first().cloned().unwrap_or(Value::Undef);
176 match super::buffer::view_bytes(&v) {
180 Some(b) => b,
181 None => with_host(|h| h.str_of(&v)).into_bytes(),
182 }
183}
184
185fn io_err(e: std::io::Error) -> String {
187 format!("Error: {e}")
188}
189
190fn decode_err(truncated: bool) -> String {
199 let (code, errno, msg) = if truncated {
200 ("Z_BUF_ERROR", -5, "unexpected end of file")
201 } else {
202 ("Z_DATA_ERROR", -3, "incorrect header check")
203 };
204 let e = crate::builtins::make_error_pub("Error", msg);
205 for (k, v) in [
206 ("errno", Value::Float(errno as f64)),
207 ("code", with_host(|h| h.new_str(code.to_string()))),
208 ] {
209 let _ = crate::builtins::set_property_pub(&e, k, v);
210 }
211 with_host(|h| h.exc = Some(e));
212 format!("Error: {msg}")
213}
214
215fn header_ok(kind: &str, input: &[u8]) -> bool {
218 match kind {
219 "gzip" => input.len() >= 2 && input[0] == 0x1f && input[1] == 0x8b,
220 "zlib" => {
222 input.len() >= 2
223 && input[0] & 0x0f == 8
224 && (u16::from(input[0]) * 256 + u16::from(input[1])) % 31 == 0
225 }
226 _ => true,
228 }
229}
230
231fn gzip(input: &[u8]) -> Result<Vec<u8>, String> {
232 let mut enc = GzEncoder::new(Vec::new(), Compression::default());
233 enc.write_all(input).map_err(io_err)?;
234 enc.finish().map_err(io_err)
235}
236
237fn gunzip(input: &[u8]) -> Result<Vec<u8>, String> {
238 let mut out = Vec::new();
239 GzDecoder::new(input)
240 .read_to_end(&mut out)
241 .map_err(|_| decode_err(header_ok("gzip", input)))?;
242 Ok(out)
243}
244
245fn deflate(input: &[u8]) -> Result<Vec<u8>, String> {
246 let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
247 enc.write_all(input).map_err(io_err)?;
248 enc.finish().map_err(io_err)
249}
250
251fn inflate(input: &[u8]) -> Result<Vec<u8>, String> {
252 let mut out = Vec::new();
253 ZlibDecoder::new(input)
254 .read_to_end(&mut out)
255 .map_err(|_| decode_err(header_ok("zlib", input)))?;
256 Ok(out)
257}
258
259fn deflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
260 let mut enc = DeflateEncoder::new(Vec::new(), Compression::default());
261 enc.write_all(input).map_err(io_err)?;
262 enc.finish().map_err(io_err)
263}
264
265fn inflate_raw(input: &[u8]) -> Result<Vec<u8>, String> {
266 let mut out = Vec::new();
267 DeflateDecoder::new(input)
268 .read_to_end(&mut out)
269 .map_err(io_err)?;
270 Ok(out)
271}
272
273fn unzip(input: &[u8]) -> Result<Vec<u8>, String> {
275 if input.starts_with(&[0x1f, 0x8b]) {
276 gunzip(input)
277 } else {
278 inflate(input)
279 }
280}
281
282fn brotli_compress(input: &[u8]) -> Result<Vec<u8>, String> {
283 let mut out = Vec::new();
284 {
285 let mut enc = brotli::CompressorWriter::new(&mut out, 4096, 11, 22);
287 enc.write_all(input).map_err(io_err)?;
288 }
290 Ok(out)
291}
292
293fn brotli_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
294 let mut out = Vec::new();
295 brotli::Decompressor::new(input, 4096)
296 .read_to_end(&mut out)
297 .map_err(io_err)?;
298 Ok(out)
299}
300
301fn zstd_compress(input: &[u8]) -> Result<Vec<u8>, String> {
302 zstd::encode_all(input, 3).map_err(io_err)
304}
305
306fn zstd_decompress(input: &[u8]) -> Result<Vec<u8>, String> {
307 zstd::decode_all(input).map_err(io_err)
308}
309
310fn crc32(data: &[u8], init: u32) -> u32 {
312 let mut h = crc32fast::Hasher::new_with_initial(init);
313 h.update(data);
314 h.finalize()
315}