Skip to main content

ferrijs_std/zlib/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use crate::utils::module::{export_default, ModuleInfo};
4use rquickjs::{
5    function::Func,
6    module::{Declarations, Exports, ModuleDef},
7    Ctx, Result,
8};
9
10mod brotli;
11mod codec;
12mod zstd;
13
14use std::io::Read;
15
16use crate::utils::object::ObjectExt;
17use rquickjs::{prelude::Opt, Exception, Value};
18
19/// Reads the `maxOutputLength` option, which `node:zlib` uses to cap the output
20/// of the convenience methods.
21pub(crate) fn max_output_length<'js>(options: &Opt<Value<'js>>) -> Result<Option<usize>> {
22    match options.0.as_ref() {
23        Some(options) => options.get_optional::<_, usize>("maxOutputLength"),
24        None => Ok(None),
25    }
26}
27
28/// Drains `reader` into a buffer, rejecting output longer than `limit` bytes
29/// with the same `RangeError` Node.js raises for `maxOutputLength`.
30///
31/// Reading stops one byte past the limit, so an over-long result is detected
32/// without decompressing (or allocating) the rest of the payload.
33pub(crate) fn read_to_end_limited<R: Read>(
34    ctx: &Ctx<'_>,
35    reader: R,
36    limit: Option<usize>,
37    capacity: usize,
38) -> Result<Vec<u8>> {
39    let Some(limit) = limit else {
40        let mut dst = Vec::with_capacity(capacity);
41        let mut reader = reader;
42        reader.read_to_end(&mut dst)?;
43        return Ok(dst);
44    };
45
46    let cutoff = limit.saturating_add(1);
47    let mut dst = Vec::with_capacity(capacity.min(cutoff));
48    reader.take(cutoff as u64).read_to_end(&mut dst)?;
49
50    if dst.len() > limit {
51        return Err(Exception::throw_range(
52            ctx,
53            &[
54                "Cannot create a Buffer larger than ",
55                &limit.to_string(),
56                " bytes",
57            ]
58            .concat(),
59        ));
60    }
61
62    Ok(dst)
63}
64
65use self::brotli::{br_comp, br_comp_sync, br_decomp, br_decomp_sync};
66use self::codec::{
67    deflate, deflate_raw, deflate_raw_sync, deflate_sync, gunzip, gunzip_sync, gzip, gzip_sync,
68    inflate, inflate_raw, inflate_raw_sync, inflate_sync,
69};
70use self::zstd::{zstd_comp, zstd_comp_sync, zstd_decomp, zstd_decomp_sync};
71
72#[macro_export]
73macro_rules! define_sync_function {
74    ($fn_name:ident, $converter:expr, $command:expr) => {
75        pub(crate) fn $fn_name<'js>(
76            ctx: Ctx<'js>,
77            value: ObjectBytes<'js>,
78            options: Opt<Value<'js>>,
79        ) -> Result<Value<'js>> {
80            $converter(ctx.clone(), value, options, $command)
81        }
82    };
83}
84
85#[macro_export]
86macro_rules! define_cb_function {
87    ($fn_name:ident, $converter:expr, $command:expr) => {
88        pub(crate) fn $fn_name<'js>(
89            ctx: Ctx<'js>,
90            value: ObjectBytes<'js>,
91            args: Rest<Value<'js>>,
92        ) -> Result<()> {
93            let mut args_iter = args.0.into_iter().rev();
94            let cb: Function = args_iter
95                .next()
96                .and_then(|v| v.into_function())
97                .or_throw_msg(&ctx, "Callback parameter is not a function")?;
98            let options = match args_iter.next() {
99                Some(v) => Opt(Some(v)),
100                None => Opt(None),
101            };
102
103            ctx.clone().spawn_exit(async move {
104                match $converter(ctx.clone(), value, options, $command) {
105                    Ok(obj) => {
106                        () = cb.call((Null.into_js(&ctx), obj))?;
107                        Ok::<_, Error>(())
108                    },
109                    Err(err) => {
110                        // `Error::Exception` is only a marker; the thrown value
111                        // (and therefore the real message) lives in ctx.catch().
112                        let err = if matches!(err, Error::Exception) {
113                            ctx.catch()
114                        } else {
115                            Exception::from_message(ctx.clone(), &err.to_string())?.into_value()
116                        };
117                        () = cb.call((err,))?;
118                        Ok(())
119                    },
120                }
121            })?;
122            Ok(())
123        }
124    };
125}
126pub struct ZlibModule;
127
128impl ModuleDef for ZlibModule {
129    fn declare(declare: &Declarations) -> Result<()> {
130        declare.declare("deflate")?;
131        declare.declare("deflateSync")?;
132
133        declare.declare("deflateRaw")?;
134        declare.declare("deflateRawSync")?;
135
136        declare.declare("gzip")?;
137        declare.declare("gzipSync")?;
138
139        declare.declare("inflate")?;
140        declare.declare("inflateSync")?;
141
142        declare.declare("inflateRaw")?;
143        declare.declare("inflateRawSync")?;
144
145        declare.declare("gunzip")?;
146        declare.declare("gunzipSync")?;
147
148        declare.declare("brotliCompress")?;
149        declare.declare("brotliCompressSync")?;
150
151        declare.declare("brotliDecompress")?;
152        declare.declare("brotliDecompressSync")?;
153
154        declare.declare("zstdCompress")?;
155        declare.declare("zstdCompressSync")?;
156
157        declare.declare("zstdDecompress")?;
158        declare.declare("zstdDecompressSync")?;
159
160        declare.declare("default")?;
161        Ok(())
162    }
163
164    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
165        export_default(ctx, exports, |default| {
166            default.set("deflate", Func::from(deflate))?;
167            default.set("deflateSync", Func::from(deflate_sync))?;
168
169            default.set("deflateRaw", Func::from(deflate_raw))?;
170            default.set("deflateRawSync", Func::from(deflate_raw_sync))?;
171
172            default.set("gzip", Func::from(gzip))?;
173            default.set("gzipSync", Func::from(gzip_sync))?;
174
175            default.set("inflate", Func::from(inflate))?;
176            default.set("inflateSync", Func::from(inflate_sync))?;
177
178            default.set("inflateRaw", Func::from(inflate_raw))?;
179            default.set("inflateRawSync", Func::from(inflate_raw_sync))?;
180
181            default.set("gunzip", Func::from(gunzip))?;
182            default.set("gunzipSync", Func::from(gunzip_sync))?;
183
184            default.set("brotliCompress", Func::from(br_comp))?;
185            default.set("brotliCompressSync", Func::from(br_comp_sync))?;
186
187            default.set("brotliDecompress", Func::from(br_decomp))?;
188            default.set("brotliDecompressSync", Func::from(br_decomp_sync))?;
189
190            default.set("zstdCompress", Func::from(zstd_comp))?;
191            default.set("zstdCompressSync", Func::from(zstd_comp_sync))?;
192
193            default.set("zstdDecompress", Func::from(zstd_decomp))?;
194            default.set("zstdDecompressSync", Func::from(zstd_decomp_sync))?;
195
196            Ok(())
197        })
198    }
199}
200
201impl From<ZlibModule> for ModuleInfo<ZlibModule> {
202    fn from(val: ZlibModule) -> Self {
203        ModuleInfo {
204            name: "zlib",
205            module: val,
206        }
207    }
208}