1use base64::{Engine, engine::general_purpose::STANDARD};
28use flate2::Compression;
29use flate2::read::{GzDecoder, GzEncoder};
30use seq_core::seqstring::global_string;
31use seq_core::stack::{Stack, pop, push};
32use seq_core::value::Value;
33use std::io::Read;
34
35#[unsafe(no_mangle)]
44pub unsafe extern "C" fn patch_seq_compress_gzip(stack: Stack) -> Stack {
45 assert!(!stack.is_null(), "compress.gzip: stack is null");
46
47 let (stack, data_val) = unsafe { pop(stack) };
48
49 match data_val {
50 Value::String(data) => {
51 match gzip_compress(data.as_str().as_bytes(), Compression::default()) {
52 Some(compressed) => {
53 let encoded = STANDARD.encode(&compressed);
54 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
55 unsafe { push(stack, Value::Bool(true)) }
56 }
57 None => {
58 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
59 unsafe { push(stack, Value::Bool(false)) }
60 }
61 }
62 }
63 _ => panic!("compress.gzip: expected String on stack"),
64 }
65}
66
67#[unsafe(no_mangle)]
77pub unsafe extern "C" fn patch_seq_compress_gzip_level(stack: Stack) -> Stack {
78 assert!(!stack.is_null(), "compress.gzip-level: stack is null");
79
80 let (stack, level_val) = unsafe { pop(stack) };
81 let (stack, data_val) = unsafe { pop(stack) };
82
83 match (data_val, level_val) {
84 (Value::String(data), Value::Int(level)) => {
85 let level = level.clamp(1, 9) as u32;
86 match gzip_compress(data.as_str().as_bytes(), Compression::new(level)) {
87 Some(compressed) => {
88 let encoded = STANDARD.encode(&compressed);
89 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
90 unsafe { push(stack, Value::Bool(true)) }
91 }
92 None => {
93 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
94 unsafe { push(stack, Value::Bool(false)) }
95 }
96 }
97 }
98 _ => panic!("compress.gzip-level: expected String and Int on stack"),
99 }
100}
101
102#[unsafe(no_mangle)]
112pub unsafe extern "C" fn patch_seq_compress_gunzip(stack: Stack) -> Stack {
113 assert!(!stack.is_null(), "compress.gunzip: stack is null");
114
115 let (stack, data_val) = unsafe { pop(stack) };
116
117 match data_val {
118 Value::String(data) => {
119 let decoded = match STANDARD.decode(data.as_str()) {
121 Ok(d) => d,
122 Err(_) => {
123 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
124 return unsafe { push(stack, Value::Bool(false)) };
125 }
126 };
127
128 match gzip_decompress(&decoded) {
130 Some(decompressed) => {
131 let stack = unsafe { push(stack, Value::String(global_string(decompressed))) };
132 unsafe { push(stack, Value::Bool(true)) }
133 }
134 None => {
135 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
136 unsafe { push(stack, Value::Bool(false)) }
137 }
138 }
139 }
140 _ => panic!("compress.gunzip: expected String on stack"),
141 }
142}
143
144#[unsafe(no_mangle)]
153pub unsafe extern "C" fn patch_seq_compress_zstd(stack: Stack) -> Stack {
154 assert!(!stack.is_null(), "compress.zstd: stack is null");
155
156 let (stack, data_val) = unsafe { pop(stack) };
157
158 match data_val {
159 Value::String(data) => match zstd::encode_all(data.as_str().as_bytes(), 3) {
160 Ok(compressed) => {
161 let encoded = STANDARD.encode(&compressed);
162 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
163 unsafe { push(stack, Value::Bool(true)) }
164 }
165 Err(_) => {
166 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
167 unsafe { push(stack, Value::Bool(false)) }
168 }
169 },
170 _ => panic!("compress.zstd: expected String on stack"),
171 }
172}
173
174#[unsafe(no_mangle)]
184pub unsafe extern "C" fn patch_seq_compress_zstd_level(stack: Stack) -> Stack {
185 assert!(!stack.is_null(), "compress.zstd-level: stack is null");
186
187 let (stack, level_val) = unsafe { pop(stack) };
188 let (stack, data_val) = unsafe { pop(stack) };
189
190 match (data_val, level_val) {
191 (Value::String(data), Value::Int(level)) => {
192 let level = level.clamp(1, 22) as i32;
193 match zstd::encode_all(data.as_str().as_bytes(), level) {
194 Ok(compressed) => {
195 let encoded = STANDARD.encode(&compressed);
196 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
197 unsafe { push(stack, Value::Bool(true)) }
198 }
199 Err(_) => {
200 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
201 unsafe { push(stack, Value::Bool(false)) }
202 }
203 }
204 }
205 _ => panic!("compress.zstd-level: expected String and Int on stack"),
206 }
207}
208
209#[unsafe(no_mangle)]
219pub unsafe extern "C" fn patch_seq_compress_unzstd(stack: Stack) -> Stack {
220 assert!(!stack.is_null(), "compress.unzstd: stack is null");
221
222 let (stack, data_val) = unsafe { pop(stack) };
223
224 match data_val {
225 Value::String(data) => {
226 let decoded = match STANDARD.decode(data.as_str()) {
228 Ok(d) => d,
229 Err(_) => {
230 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
231 return unsafe { push(stack, Value::Bool(false)) };
232 }
233 };
234
235 match zstd::decode_all(decoded.as_slice()) {
237 Ok(decompressed) => match String::from_utf8(decompressed) {
238 Ok(s) => {
239 let stack = unsafe { push(stack, Value::String(global_string(s))) };
240 unsafe { push(stack, Value::Bool(true)) }
241 }
242 Err(_) => {
243 let stack =
244 unsafe { push(stack, Value::String(global_string(String::new()))) };
245 unsafe { push(stack, Value::Bool(false)) }
246 }
247 },
248 Err(_) => {
249 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
250 unsafe { push(stack, Value::Bool(false)) }
251 }
252 }
253 }
254 _ => panic!("compress.unzstd: expected String on stack"),
255 }
256}
257
258fn gzip_compress(data: &[u8], level: Compression) -> Option<Vec<u8>> {
261 let mut encoder = GzEncoder::new(data, level);
262 let mut compressed = Vec::new();
263 match encoder.read_to_end(&mut compressed) {
264 Ok(_) => Some(compressed),
265 Err(_) => None,
266 }
267}
268
269fn gzip_decompress(data: &[u8]) -> Option<String> {
270 let mut decoder = GzDecoder::new(data);
271 let mut decompressed = String::new();
272 match decoder.read_to_string(&mut decompressed) {
273 Ok(_) => Some(decompressed),
274 Err(_) => None,
275 }
276}
277
278#[cfg(test)]
279mod tests;