• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/zlib.c

  1 /*
  2  * Copyright (C) 2024 Thibaut VARĂˆNE <hacks@slashdirt.org>
  3  *
  4  * Permission to use, copy, modify, and/or distribute this software for any
  5  * purpose with or without fee is hereby granted, provided that the above
  6  * copyright notice and this permission notice appear in all copies.
  7  *
  8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15  */
 16 
 17 /**
 18  * # Zlib bindings
 19  *
 20  * The `zlib` module provides single-call-oriented functions for interacting with zlib data.
 21  *
 22  * @module zlib
 23  */
 24 
 25 #include <stdio.h>
 26 #include <string.h>
 27 #include <assert.h>
 28 #include <errno.h>
 29 #include <zlib.h>
 30 
 31 #include "ucode/module.h"
 32 #include "ucode/platform.h"
 33 
 34 // https://zlib.net/zlib_how.html
 35 
 36 /*
 37  * CHUNK is simply the buffer size for feeding data to and pulling data from
 38  * the zlib routines. Larger buffer sizes would be more efficient, especially
 39  * for inflate(). If the memory is available, buffers sizes on the order of
 40  * 128K or 256K bytes should be used.
 41  */
 42 #define CHUNK 16384
 43 
 44 
 45 /* zlib init error message */
 46 static const char * ziniterr(int ret)
 47 {
 48         const char * msg;
 49 
 50         switch (ret) {
 51         case Z_ERRNO:
 52                 msg = strerror(errno);
 53                 break;
 54         case Z_STREAM_ERROR:    // can only happen for deflateInit2() by construction
 55                 msg = "invalid compression level";
 56                 break;
 57         case Z_MEM_ERROR:
 58                 msg = "out of memory";
 59                 break;
 60         case Z_VERSION_ERROR:
 61                 msg = "zlib version mismatch!";
 62                 break;
 63         default:
 64                 msg = "unknown error";
 65                 break;
 66         }
 67 
 68         return msg;
 69 }
 70 
 71 static uc_stringbuf_t *
 72 uc_zlib_def_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
 73 {
 74         int ret;
 75         bool eof = false;
 76         uc_value_t *rfn, *rbuf;
 77         uc_stringbuf_t *buf = NULL;
 78 
 79         rfn = ucv_property_get(obj, "read");
 80 
 81         if (!ucv_is_callable(rfn)) {
 82                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
 83                                       "Input object does not implement read() method");
 84                 return NULL;
 85         }
 86 
 87         buf = ucv_stringbuf_new();
 88 
 89         do {
 90                 rbuf = NULL;
 91                 uc_vm_stack_push(vm, ucv_get(obj));
 92                 uc_vm_stack_push(vm, ucv_get(rfn));
 93                 uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
 94 
 95                 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
 96                         goto fail;
 97 
 98                 rbuf = uc_vm_stack_pop(vm);     // read output chunk
 99 
100                 /* we only accept strings */
101                 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
102                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
103                                               "Input object read() method returned non-string value");
104                         goto fail;
105                 }
106 
107                 /* check EOF */
108                 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
109 
110                 strm->next_in = (unsigned char *)ucv_string_get(rbuf);
111                 strm->avail_in = ucv_string_length(rbuf);
112 
113                 /* run deflate() on input until output buffer not full */
114                 do {
115                         // enlarge buf by CHUNK amount as needed
116                         printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
117                         buf->bpos -= CHUNK;
118 
119                         strm->avail_out = CHUNK;
120                         strm->next_out = (unsigned char *)(buf->buf + buf->bpos);;
121 
122                         ret = deflate(strm, eof ? Z_FINISH : Z_NO_FLUSH);       // no bad return value here
123                         assert(ret != Z_STREAM_ERROR);                          // state never clobbered (would be mem corruption)
124                         (void)ret;              // XXX make annoying compiler that ignores assert() happy
125 
126                         // update bpos past data written by deflate()
127                         buf->bpos += CHUNK - strm->avail_out;
128                 } while (strm->avail_out == 0);
129                 assert(strm->avail_in == 0);    // all input will be used
130 
131                 ucv_put(rbuf);  // release rbuf
132         } while (!eof); // finish compression if all of source has been read in
133         assert(ret == Z_STREAM_END);    // stream will be complete
134 
135         return buf;
136 
137 fail:
138         ucv_put(rbuf);
139         printbuf_free(buf);
140         return NULL;
141 }
142 
143 static uc_stringbuf_t *
144 uc_zlib_def_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
145 {
146         int ret;
147         uc_stringbuf_t *buf = NULL;
148 
149         buf = ucv_stringbuf_new();
150 
151         strm->next_in = (unsigned char *)ucv_string_get(str);
152         strm->avail_in = ucv_string_length(str);
153 
154         do {
155                 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
156                 buf->bpos -= CHUNK;
157 
158                 strm->avail_out = CHUNK;
159                 strm->next_out = (unsigned char *)(buf->buf + buf->bpos);
160 
161                 ret = deflate(strm, Z_FINISH);
162                 assert(ret != Z_STREAM_ERROR);
163 
164                 buf->bpos += CHUNK - strm->avail_out;
165         } while (ret != Z_STREAM_END);
166         assert(strm->avail_in == 0);
167 
168         return buf;
169 }
170 
171 /**
172  * Compresses data in Zlib or gzip format.
173  *
174  * If the input argument is a plain string, it is directly compressed.
175  *
176  * If an array, object or resource value is given, this function will attempt to
177  * invoke a `read()` method on it to read chunks of input text to incrementally
178  * compress. Reading will stop if the object's `read()` method returns
179  * either `null` or an empty string.
180  *
181  * Throws an exception on errors.
182  *
183  * Returns the compressed data.
184  *
185  * @function module:zlib#deflate
186  *
187  * @param {string} str_or_resource
188  * The string or resource object to be parsed as JSON.
189  *
190  * @param {?boolean} [gzip=false]
191  * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
192  *
193  * @param {?number} [level=Z_DEFAULT_COMPRESSION]
194  * The compression level (0-9).
195  *
196  * @returns {?string}
197  *
198  * @example
199  * // deflate content using default compression
200  * const deflated = deflate(content);
201  *
202  * // deflate content using fastest compression
203  * const deflated = deflate(content, Z_BEST_SPEED);
204  */
205 static uc_value_t *
206 uc_zlib_deflate(uc_vm_t *vm, size_t nargs)
207 {
208         uc_value_t *rv = NULL;
209         uc_value_t *src = uc_fn_arg(0);
210         uc_value_t *gzip = uc_fn_arg(1);
211         uc_value_t *level = uc_fn_arg(2);
212         uc_stringbuf_t *buf = NULL;
213         int ret, lvl = Z_DEFAULT_COMPRESSION;
214         bool gz = false;
215         z_stream strm = {
216                 .zalloc = Z_NULL,
217                 .zfree = Z_NULL,
218                 .opaque = Z_NULL,
219         };
220 
221         if (gzip) {
222                 if (ucv_type(gzip) != UC_BOOLEAN) {
223                         uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed gzip flag is not a boolean");
224                         goto out;
225                 }
226 
227                 gz = (int)ucv_boolean_get(gzip);
228         }
229 
230         if (level) {
231                 if (ucv_type(level) != UC_INTEGER) {
232                         uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed level is not a number");
233                         goto out;
234                 }
235 
236                 lvl = (int)ucv_int64_get(level);
237         }
238 
239         ret = deflateInit2(&strm, lvl,
240                            Z_DEFLATED,          // only allowed method
241                            gz ? 15+16 : 15,     // 15 Zlib default, +16 for gzip
242                            8,                   // default value
243                            Z_DEFAULT_STRATEGY); // default value
244         if (ret != Z_OK) {
245                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
246                 goto out;
247         }
248 
249         switch (ucv_type(src)) {
250         case UC_STRING:
251                 buf = uc_zlib_def_string(vm, src, &strm);
252                 break;
253 
254         case UC_RESOURCE:
255         case UC_OBJECT:
256         case UC_ARRAY:
257                 buf = uc_zlib_def_object(vm, src, &strm);
258                 break;
259 
260         default:
261                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
262                                       "Passed value is neither a string nor an object");
263                 goto out;
264         }
265 
266         if (!buf) {
267                 if (vm->exception.type == EXCEPTION_NONE)       // do not clobber previous exception
268                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg);
269                 goto out;
270         }
271 
272         rv = ucv_stringbuf_finish(buf);
273 
274 out:
275         (void)deflateEnd(&strm);
276         return rv;
277 }
278 
279 static uc_stringbuf_t *
280 uc_zlib_inf_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
281 {
282         int ret = Z_STREAM_ERROR;       // error out if EOF on first loop
283         bool eof = false;
284         uc_value_t *rfn, *rbuf;
285         uc_stringbuf_t *buf = NULL;
286 
287         rfn = ucv_property_get(obj, "read");
288 
289         if (!ucv_is_callable(rfn)) {
290                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
291                                       "Input object does not implement read() method");
292                 return NULL;
293         }
294 
295         buf = ucv_stringbuf_new();
296 
297         do {
298                 rbuf = NULL;
299                 uc_vm_stack_push(vm, ucv_get(obj));
300                 uc_vm_stack_push(vm, ucv_get(rfn));
301                 uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
302 
303                 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
304                         goto fail;
305 
306                 rbuf = uc_vm_stack_pop(vm);     // read output chunk
307 
308                 /* we only accept strings */
309                 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
310                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
311                                               "Input object read() method returned non-string value");
312                         goto fail;
313                 }
314 
315                 /* check EOF */
316                 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
317                 if (eof)
318                         break;
319 
320                 strm->next_in = (unsigned char *)ucv_string_get(rbuf);
321                 strm->avail_in = ucv_string_length(rbuf);
322 
323                 /* run deflate() on input until output buffer not full */
324                 do {
325                         // enlarge buf by CHUNK amount as needed
326                         printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
327                         buf->bpos -= CHUNK;
328 
329                         strm->avail_out = CHUNK;
330                         strm->next_out = (unsigned char *)(buf->buf + buf->bpos);;
331 
332                         ret = inflate(strm, Z_NO_FLUSH);
333                         assert(ret != Z_STREAM_ERROR);          // state never clobbered (would be mem corruption)
334                         switch (ret) {
335                         case Z_NEED_DICT:
336                         case Z_DATA_ERROR:
337                         case Z_MEM_ERROR:
338                                 goto fail;
339                         }
340 
341                         // update bpos past data written by deflate()
342                         buf->bpos += CHUNK - strm->avail_out;
343                 } while (strm->avail_out == 0);
344 
345                 ucv_put(rbuf);  // release rbuf
346         } while (ret != Z_STREAM_END);  // done when inflate() says it's done
347 
348         if (ret != Z_STREAM_END) {      // data error
349                 printbuf_free(buf);
350                 buf = NULL;
351         }
352 
353         return buf;
354 
355 fail:
356         ucv_put(rbuf);
357         printbuf_free(buf);
358         return NULL;
359 }
360 
361 static uc_stringbuf_t *
362 uc_zlib_inf_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
363 {
364         int ret;
365         uc_stringbuf_t *buf = NULL;
366 
367         buf = ucv_stringbuf_new();
368 
369         strm->next_in = (unsigned char *)ucv_string_get(str);
370         strm->avail_in = ucv_string_length(str);
371 
372         do {
373                 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
374                 buf->bpos -= CHUNK;
375 
376                 strm->avail_out = CHUNK;
377                 strm->next_out = (unsigned char *)(buf->buf + buf->bpos);
378 
379                 ret = inflate(strm, Z_FINISH);
380                 assert(ret != Z_STREAM_ERROR);
381                 switch (ret) {
382                 case Z_NEED_DICT:
383                 case Z_DATA_ERROR:
384                 case Z_MEM_ERROR:
385                         printbuf_free(buf);
386                         return NULL;
387                 }
388 
389                 buf->bpos += CHUNK - strm->avail_out;
390         } while (ret != Z_STREAM_END);
391         assert(strm->avail_in == 0);
392 
393         return buf;
394 }
395 
396 /**
397  * Decompresses data in Zlib or gzip format.
398  *
399  * If the input argument is a plain string, it is directly decompressed.
400  *
401  * If an array, object or resource value is given, this function will attempt to
402  * invoke a `read()` method on it to read chunks of input text to incrementally
403  * decompress. Reading will stop if the object's `read()` method returns
404  * either `null` or an empty string.
405  *
406  * Throws an exception on errors.
407  *
408  * Returns the decompressed data.
409  *
410  * @function module:zlib#inflate
411  *
412  * @param {string} str_or_resource
413  * The string or resource object to be parsed as JSON.
414  *
415  * @returns {?string}
416  */
417 static uc_value_t *
418 uc_zlib_inflate(uc_vm_t *vm, size_t nargs)
419 {
420         uc_value_t *rv = NULL;
421         uc_value_t *src = uc_fn_arg(0);
422         uc_stringbuf_t *buf = NULL;
423         int ret;
424         z_stream strm = {
425                 .zalloc = Z_NULL,
426                 .zfree = Z_NULL,
427                 .opaque = Z_NULL,
428                 .avail_in = 0,          // must be initialized before call to inflateInit
429                 .next_in = Z_NULL,      // must be initialized before call to inflateInit
430         };
431 
432         /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
433         ret = inflateInit2(&strm, 15+32);
434         if (ret != Z_OK) {
435                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
436                 goto out;
437         }
438 
439         switch (ucv_type(src)) {
440         case UC_STRING:
441                 buf = uc_zlib_inf_string(vm, src, &strm);
442                 break;
443 
444         case UC_RESOURCE:
445         case UC_OBJECT:
446         case UC_ARRAY:
447                 buf = uc_zlib_inf_object(vm, src, &strm);
448                 break;
449 
450         default:
451                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
452                                       "Passed value is neither a string nor an object");
453                 goto out;
454         }
455 
456         if (!buf) {
457                 if (vm->exception.type == EXCEPTION_NONE)       // do not clobber previous exception
458                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg);
459                 goto out;
460         }
461 
462         rv = ucv_stringbuf_finish(buf);
463 
464 out:
465         (void)inflateEnd(&strm);
466         return rv;
467 }
468 
469 static const uc_function_list_t global_fns[] = {
470         { "deflate",    uc_zlib_deflate },
471         { "inflate",    uc_zlib_inflate },
472 };
473 
474 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
475 {
476         uc_function_list_register(scope, global_fns);
477 
478 #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
479 
480         /**
481          * @typedef
482          * @name Compression levels
483          * @description Constants representing predefined compression levels.
484          * @property {number} Z_NO_COMPRESSION.
485          * @property {number} Z_BEST_SPEED.
486          * @property {number} Z_BEST_COMPRESSION.
487          * @property {number} Z_DEFAULT_COMPRESSION - default compromise between speed and compression (currently equivalent to level 6).
488          */
489         ADD_CONST(Z_NO_COMPRESSION);
490         ADD_CONST(Z_BEST_SPEED);
491         ADD_CONST(Z_BEST_COMPRESSION);
492         ADD_CONST(Z_DEFAULT_COMPRESSION);
493 }
494 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt