• 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 and stream-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 static uc_resource_type_t *zstrmd_type, *zstrmi_type;
 45 
 46 static int last_error = 0;
 47 #define err_return(err) do { last_error = err; return NULL; } while(0)
 48 
 49 typedef struct {
 50         z_stream strm;
 51         uc_stringbuf_t *outbuf;
 52         int flush;
 53 } zstrm_t;
 54 
 55 /* zlib init error message */
 56 static const char * ziniterr(int ret)
 57 {
 58         const char * msg;
 59 
 60         switch (ret) {
 61         case Z_ERRNO:
 62                 msg = strerror(errno);
 63                 break;
 64         case Z_STREAM_ERROR:    // can only happen for deflateInit2() by construction
 65                 msg = "invalid compression level";
 66                 break;
 67         case Z_MEM_ERROR:
 68                 msg = "out of memory";
 69                 break;
 70         case Z_VERSION_ERROR:
 71                 msg = "zlib version mismatch!";
 72                 break;
 73         default:
 74                 msg = "unknown error";
 75                 break;
 76         }
 77 
 78         return msg;
 79 }
 80 
 81 static int
 82 def_chunks(zstrm_t * const zstrm)
 83 {
 84         int ret;
 85 
 86         /* run deflate() on input until output buffer not full */
 87         do {
 88                 printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
 89                 zstrm->outbuf->bpos -= CHUNK;
 90 
 91                 zstrm->strm.avail_out = CHUNK;
 92                 zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
 93 
 94                 ret = deflate(&zstrm->strm, zstrm->flush);
 95                 assert(ret != Z_STREAM_ERROR);
 96 
 97                 zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
 98         } while (zstrm->strm.avail_out == 0);
 99         assert(zstrm->strm.avail_in == 0);      // all input will be used
100 
101         return ret;
102 }
103 
104 static bool
105 uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
106 {
107         int ret;
108         bool eof = false;
109         uc_value_t *rfn, *rbuf;
110 
111         rfn = ucv_property_get(obj, "read");
112 
113         if (!ucv_is_callable(rfn)) {
114                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
115                                       "Input object does not implement read() method");
116                 return false;
117         }
118 
119         do {
120                 rbuf = NULL;
121                 uc_vm_stack_push(vm, ucv_get(obj));
122                 uc_vm_stack_push(vm, ucv_get(rfn));
123                 uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
124 
125                 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
126                         goto fail;
127 
128                 rbuf = uc_vm_stack_pop(vm);     // read output chunk
129 
130                 /* we only accept strings */
131                 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
132                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
133                                               "Input object read() method returned non-string value");
134                         goto fail;
135                 }
136 
137                 /* check EOF */
138                 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
139 
140                 zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
141                 zstrm->strm.avail_in = ucv_string_length(rbuf);
142 
143                 zstrm->flush = eof ? Z_FINISH : Z_NO_FLUSH;
144                 ret = def_chunks(zstrm);
145                 (void)ret;      // XXX make annoying compiler that ignores assert() happy
146 
147                 ucv_put(rbuf);  // release rbuf
148         } while (!eof); // finish compression if all of source has been read in
149         assert(ret == Z_STREAM_END);    // stream will be complete
150 
151         return true;
152 
153 fail:
154         ucv_put(rbuf);
155         return false;
156 }
157 
158 static bool
159 uc_zlib_def_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
160 {
161         zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
162         zstrm->strm.avail_in = ucv_string_length(str);
163 
164         last_error = def_chunks(zstrm);
165 
166         return true;
167 }
168 
169 /**
170  * Compresses data in Zlib or gzip format.
171  *
172  * If the input argument is a plain string, it is directly compressed.
173  *
174  * If an array, object or resource value is given, this function will attempt to
175  * invoke a `read()` method on it to read chunks of input text to incrementally
176  * compress. Reading will stop if the object's `read()` method returns
177  * either `null` or an empty string.
178  *
179  * Throws an exception on errors.
180  *
181  * Returns the compressed data.
182  *
183  * @function module:zlib#deflate
184  *
185  * @param {string} str_or_resource
186  * The string or resource object to be compressed.
187  *
188  * @param {?boolean} [gzip=false]
189  * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
190  *
191  * @param {?number} [level=Z_DEFAULT_COMPRESSION]
192  * The compression level (0-9).
193  *
194  * @returns {?string}
195  *
196  * @example
197  * // deflate content using default compression
198  * const deflated = deflate(content);
199  *
200  * // deflate content using fastest compression
201  * const deflated = deflate(content, Z_BEST_SPEED);
202  */
203 static uc_value_t *
204 uc_zlib_deflate(uc_vm_t * const vm, const size_t nargs)
205 {
206         uc_value_t *rv = NULL;
207         uc_value_t *src = uc_fn_arg(0);
208         uc_value_t *gzip = uc_fn_arg(1);
209         uc_value_t *level = uc_fn_arg(2);
210         int ret, lvl = Z_DEFAULT_COMPRESSION;
211         bool success, gz = false;
212         zstrm_t zstrm = {
213                 .strm = {
214                         .zalloc = Z_NULL,
215                         .zfree = Z_NULL,
216                         .opaque = Z_NULL,
217                 },
218                 .outbuf = NULL,
219                 .flush = Z_FINISH,
220         };
221 
222         if (gzip) {
223                 if (ucv_type(gzip) != UC_BOOLEAN) {
224                         uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed gzip flag is not a boolean");
225                         goto out;
226                 }
227 
228                 gz = (int)ucv_boolean_get(gzip);
229         }
230 
231         if (level) {
232                 if (ucv_type(level) != UC_INTEGER) {
233                         uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed level is not a number");
234                         goto out;
235                 }
236 
237                 lvl = (int)ucv_int64_get(level);
238         }
239 
240         ret = deflateInit2(&zstrm.strm, lvl,
241                            Z_DEFLATED,          // only allowed method
242                            gz ? 15+16 : 15,     // 15 Zlib default, +16 for gzip
243                            8,                   // default value
244                            Z_DEFAULT_STRATEGY); // default value
245         if (ret != Z_OK) {
246                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
247                 goto out;
248         }
249 
250         zstrm.outbuf = ucv_stringbuf_new();
251 
252         switch (ucv_type(src)) {
253         case UC_STRING:
254                 success = uc_zlib_def_string(vm, src, &zstrm);
255                 break;
256 
257         case UC_RESOURCE:
258         case UC_OBJECT:
259         case UC_ARRAY:
260                 success = uc_zlib_def_object(vm, src, &zstrm);
261                 break;
262 
263         default:
264                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
265                                       "Passed value is neither a string nor an object");
266                 printbuf_free(zstrm.outbuf);
267                 goto out;
268         }
269 
270         if (!success) {
271                 if (vm->exception.type == EXCEPTION_NONE)       // do not clobber previous exception
272                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
273                 printbuf_free(zstrm.outbuf);
274                 goto out;
275         }
276 
277         rv = ucv_stringbuf_finish(zstrm.outbuf);
278 
279 out:
280         (void)deflateEnd(&zstrm.strm);
281         return rv;
282 }
283 
284 static int
285 inf_chunks(zstrm_t * const zstrm)
286 {
287         int ret;
288 
289         /* run inflate() on input until output buffer not full */
290         do {
291                 printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
292                 zstrm->outbuf->bpos -= CHUNK;
293 
294                 zstrm->strm.avail_out = CHUNK;
295                 zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
296 
297                 ret = inflate(&zstrm->strm, zstrm->flush);
298                 assert(ret != Z_STREAM_ERROR);
299                 switch (ret) {
300                 case Z_NEED_DICT:
301                 case Z_DATA_ERROR:
302                 case Z_MEM_ERROR:
303                         return ret;
304                 }
305 
306                 zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
307         } while (zstrm->strm.avail_out == 0);
308 
309         return ret;
310 }
311 
312 static bool
313 uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
314 {
315         int ret = Z_STREAM_ERROR;       // error out if EOF on first loop
316         bool eof = false;
317         uc_value_t *rfn, *rbuf;
318 
319         rfn = ucv_property_get(obj, "read");
320 
321         if (!ucv_is_callable(rfn)) {
322                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
323                                       "Input object does not implement read() method");
324                 return false;
325         }
326 
327         do {
328                 rbuf = NULL;
329                 uc_vm_stack_push(vm, ucv_get(obj));
330                 uc_vm_stack_push(vm, ucv_get(rfn));
331                 uc_vm_stack_push(vm, ucv_int64_new(CHUNK));
332 
333                 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE)
334                         goto fail;
335 
336                 rbuf = uc_vm_stack_pop(vm);     // read output chunk
337 
338                 /* we only accept strings */
339                 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) {
340                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
341                                               "Input object read() method returned non-string value");
342                         goto fail;
343                 }
344 
345                 /* check EOF */
346                 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
347                 if (eof)
348                         break;
349 
350                 zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
351                 zstrm->strm.avail_in = ucv_string_length(rbuf);
352 
353                 ret = inf_chunks(zstrm);
354                 switch (ret) {
355                 case Z_NEED_DICT:
356                 case Z_DATA_ERROR:
357                 case Z_MEM_ERROR:
358                         goto fail;
359                 }
360 
361                 ucv_put(rbuf);  // release rbuf
362         } while (ret != Z_STREAM_END);  // done when inflate() says it's done
363 
364         if (ret != Z_STREAM_END)        // data error
365                 return false;
366 
367         return true;
368 
369 fail:
370         ucv_put(rbuf);
371         return false;
372 }
373 
374 static bool
375 uc_zlib_inf_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
376 {
377         int ret;
378 
379         zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
380         zstrm->strm.avail_in = ucv_string_length(str);
381 
382         ret = inf_chunks(zstrm);
383         assert(zstrm->strm.avail_in == 0);
384         last_error = ret;
385 
386         return Z_STREAM_END == ret;
387 }
388 
389 /**
390  * Decompresses data in Zlib or gzip format.
391  *
392  * If the input argument is a plain string, it is directly decompressed.
393  *
394  * If an array, object or resource value is given, this function will attempt to
395  * invoke a `read()` method on it to read chunks of input text to incrementally
396  * decompress. Reading will stop if the object's `read()` method returns
397  * either `null` or an empty string.
398  *
399  * Throws an exception on errors.
400  *
401  * Returns the decompressed data.
402  *
403  * @function module:zlib#inflate
404  *
405  * @param {string} str_or_resource
406  * The string or resource object to be parsed as JSON.
407  *
408  * @returns {?string}
409  */
410 static uc_value_t *
411 uc_zlib_inflate(uc_vm_t * const vm, const size_t nargs)
412 {
413         uc_value_t *rv = NULL;
414         uc_value_t *src = uc_fn_arg(0);
415         bool success;
416         int ret;
417         zstrm_t zstrm = {
418                 .strm = {
419                         .zalloc = Z_NULL,
420                         .zfree = Z_NULL,
421                         .opaque = Z_NULL,
422                         .avail_in = 0,          // must be initialized before call to inflateInit
423                         .next_in = Z_NULL,      // must be initialized before call to inflateInit
424                 },
425                 .outbuf = NULL,
426         };
427 
428         /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
429         ret = inflateInit2(&zstrm.strm, 15+32);
430         if (ret != Z_OK) {
431                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
432                 goto out;
433         }
434 
435         zstrm.outbuf = ucv_stringbuf_new();
436 
437         switch (ucv_type(src)) {
438         case UC_STRING:
439                 zstrm.flush = Z_FINISH;
440                 success = uc_zlib_inf_string(vm, src, &zstrm);
441                 break;
442 
443         case UC_RESOURCE:
444         case UC_OBJECT:
445         case UC_ARRAY:
446                 zstrm.flush = Z_NO_FLUSH;
447                 success = uc_zlib_inf_object(vm, src, &zstrm);
448                 break;
449 
450         default:
451                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
452                                       "Passed value is neither a string nor an object");
453                 printbuf_free(zstrm.outbuf);
454                 goto out;
455         }
456 
457         if (!success) {
458                 if (vm->exception.type == EXCEPTION_NONE)       // do not clobber previous exception
459                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
460                 printbuf_free(zstrm.outbuf);
461                 goto out;
462         }
463 
464         rv = ucv_stringbuf_finish(zstrm.outbuf);
465 
466 out:
467         (void)inflateEnd(&zstrm.strm);
468         return rv;
469 }
470 
471 /**
472  * Represents a handle for interacting with a deflate stream initiated by deflater().
473  *
474  * @class module:zlib.deflate
475  * @hideconstructor
476  *
477  * @see {@link module:zlib#deflater()}
478  *
479  * @example
480  *
481  * const zstrmd = deflater(…);
482  *
483  * for (let data = ...; data; data = ...) {
484  *      zstrmd.write(data, Z_PARTIAL_FLUSH);    // write uncompressed data to stream
485  *      if (foo)
486         *       let defl = zstrmd.read();       // read back compressed stream content
487  * }
488  *
489  * // terminate the stream at the end of data input to complete a valid archive
490  * zstrmd.write(last_data, Z_FINISH);
491  * defl = ztrmd.read();
492  *
493  * zstrmd.error();
494  */
495 
496 /**
497  * Initializes a deflate stream.
498  *
499  * Returns a stream handle on success.
500  *
501  * Returns `null` if an error occurred.
502  *
503  * @function module:zlib#deflater
504  *
505  * @param {?boolean} [gzip=false]
506  * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
507  *
508  * @param {?number} [level=Z_DEFAULT_COMPRESSION]
509  * The compression level (0-9).
510  *
511  * @returns {?module:zlib.deflate}
512  *
513  * @example
514  * // initialize a Zlib deflate stream using default compression
515  * const zstrmd = deflater();
516  *
517  * // initialize a gzip deflate stream using fastest compression
518  * const zstrmd = deflater(true, Z_BEST_SPEED);
519  */
520  static uc_value_t *
521 uc_zlib_deflater(uc_vm_t *vm, size_t nargs)
522 {
523         uc_value_t *gzip = uc_fn_arg(0);
524         uc_value_t *level = uc_fn_arg(1);
525         int ret, lvl = Z_DEFAULT_COMPRESSION;
526         bool gz = false;
527         zstrm_t *zstrm;
528 
529         zstrm = calloc(1, sizeof(*zstrm));
530         if (!zstrm)
531                 err_return(ENOMEM);
532 
533         zstrm->strm.zalloc = Z_NULL;
534         zstrm->strm.zfree = Z_NULL;
535         zstrm->strm.opaque = Z_NULL;
536 
537         if (gzip) {
538                 if (ucv_type(gzip) != UC_BOOLEAN) {
539                         last_error = EINVAL;
540                         goto fail;
541                 }
542 
543                 gz = (int)ucv_boolean_get(gzip);
544         }
545 
546         if (level) {
547                 if (ucv_type(level) != UC_INTEGER) {
548                         last_error = EINVAL;
549                         goto fail;
550                 }
551 
552                 lvl = (int)ucv_int64_get(level);
553         }
554 
555         ret = deflateInit2(&zstrm->strm, lvl,
556                            Z_DEFLATED,          // only allowed method
557                            gz ? 15+16 : 15,     // 15 Zlib default, +16 for gzip
558                            8,                   // default value
559                            Z_DEFAULT_STRATEGY); // default value
560         if (ret != Z_OK) {
561                 last_error = ret;
562                 goto fail;
563         }
564 
565         return uc_resource_new(zstrmd_type, zstrm);
566 
567 fail:
568         free(zstrm);
569         return NULL;
570 }
571 
572 /**
573  * Writes a chunk of data to the deflate stream.
574  *
575  * Input data must be a string, it is internally compressed by the zlib `deflate()` routine,
576  * the end result is buffered according to the requested `flush` mode until read via
577  * {@link module:zlib.zstrmd#read}.
578  * Valid `flush`values are `Z_NO_FLUSH` (the default),
579  * `Z_SYNC_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH`.
580  * If `flush` is `Z_FINISH` then no more data can be written to the stream.
581  * Refer to the {@link https://zlib.net/manual.html Zlib manual} for details
582  * on each flush mode.
583  *
584  * Returns `true` on success.
585  *
586  * Returns `null` if an error occurred.
587  *
588  * @function module:zlib.deflate#write
589  *
590  * @param {string} src
591  * The string of data to deflate.
592  *
593  * @param {?number} [flush=Z_NO_FLUSH]
594  * The zlib flush mode.
595  *
596  * @returns {?boolean}
597  */
598 static uc_value_t *
599 uc_zlib_defwrite(uc_vm_t *vm, size_t nargs)
600 {
601         uc_value_t *src = uc_fn_arg(0);
602         uc_value_t *flush = uc_fn_arg(1);
603         zstrm_t **z = uc_fn_this("zlib.deflate");
604         zstrm_t *zstrm;
605 
606         if (!z || !*z)
607                 err_return(EBADF);
608 
609         zstrm = *z;
610 
611         if (Z_FINISH == zstrm->flush)
612                 err_return(EPIPE);      // can't reuse a finished stream
613 
614         if (flush) {
615                 if (ucv_type(flush) != UC_INTEGER)
616                         err_return(EINVAL);
617 
618                 zstrm->flush = (int)ucv_int64_get(flush);
619                 switch (zstrm->flush) {
620                 case Z_NO_FLUSH:
621                 case Z_SYNC_FLUSH:
622                 case Z_PARTIAL_FLUSH:
623                 case Z_FULL_FLUSH:
624                 case Z_FINISH:
625                         break;
626                 default:
627                         err_return(EINVAL);
628                 }
629         }
630         else
631                 zstrm->flush = Z_NO_FLUSH;
632 
633         /* we only accept strings */
634         if (!src || ucv_type(src) != UC_STRING)
635                 err_return(EINVAL);
636 
637         if (!zstrm->outbuf)
638                 zstrm->outbuf = ucv_stringbuf_new();
639 
640         return ucv_boolean_new(uc_zlib_def_string(vm, src, zstrm));
641 }
642 
643 /**
644  * Reads a chunk of compressed data from the deflate stream.
645  *
646  * Returns the current content of the deflate buffer, fed through
647  * {@link module:zlib.deflate#write}.
648  *
649  * Returns compressed chunk on success.
650  *
651  * Returns `null` if an error occurred.
652  *
653  * @function module:zlib.deflate#read
654  *
655  * @returns {?string}
656  */
657 static uc_value_t *
658 uc_zlib_defread(uc_vm_t *vm, size_t nargs)
659 {
660         zstrm_t **z = uc_fn_this("zlib.deflate");
661         zstrm_t *zstrm;
662         uc_value_t *rv;
663 
664         if (!z || !*z)
665                 err_return(EBADF);
666 
667         zstrm = *z;
668 
669         if (!zstrm->outbuf)
670                 err_return(ENODATA);
671 
672         if (Z_FINISH == zstrm->flush)
673                 (void)deflateEnd(&zstrm->strm);
674 
675         rv = ucv_stringbuf_finish(zstrm->outbuf);
676         zstrm->outbuf = NULL;   // outbuf is now unuseable
677         return rv;
678 }
679 
680 /**
681  * Represents a handle for interacting with an inflate stream initiated by inflater().
682  *
683  * @class module:zlib.inflate
684  * @hideconstructor
685  *
686  * @borrows module:zlib.deflate#error as module:fs.inflate#error
687  *
688  * @see {@link module:zlib#inflater()}
689  *
690  * @example
691  *
692  * const zstrmi = inflater();
693  *
694  * for (let data = ...; data; data = ...) {
695  *      zstrmi.write(data, Z_SYNC_FLUSH);       // write compressed data to stream
696  *      if (foo)
697         *       let defl = zstrmi.read();       // read back decompressed stream content
698  * }
699  *
700  * zstrmi.error();
701  */
702 
703 /**
704  * Initializes an inflate stream. Can process either Zlib or gzip data.
705  *
706  * Returns a stream handle on success.
707  *
708  * Returns `null` if an error occurred.
709  *
710  * @function module:zlib#inflater
711  *
712  * @returns {?module:zlib.inflate}
713  *
714  * @example
715  * // initialize an inflate stream
716  * const zstrmi = inflater();
717  */
718  static uc_value_t *
719 uc_zlib_inflater(uc_vm_t *vm, size_t nargs)
720 {
721         int ret;
722         zstrm_t *zstrm;
723 
724         zstrm = calloc(1, sizeof(*zstrm));
725         if (!zstrm)
726                 err_return(ENOMEM);
727 
728         zstrm->strm.zalloc = Z_NULL;
729         zstrm->strm.zfree = Z_NULL;
730         zstrm->strm.opaque = Z_NULL;
731         zstrm->strm.avail_in = 0;
732         zstrm->strm.next_in = Z_NULL;
733 
734         /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
735         ret = inflateInit2(&zstrm->strm, 15+32);
736         if (ret != Z_OK) {
737                 last_error = ret;
738                 goto fail;
739         }
740 
741         return uc_resource_new(zstrmi_type, zstrm);
742 
743 fail:
744         free(zstrm);
745         return NULL;
746 }
747 
748 /**
749  * Writes a chunk of data to the inflate stream.
750  *
751  * Input data must be a string, it is internally decompressed by the zlib `inflate()` routine,
752  * the end result is buffered according to the requested `flush` mode until read via
753  * {@link module:zlib.inflate#read}.
754  * Valid `flush` values are `Z_NO_FLUSH` (the default), `Z_SYNC_FLUSH, Z_FINISH`.
755  * If `flush` is `Z_FINISH` then no more data can be written to the stream.
756  * Refer to the {@link https://zlib.net/manual.html Zlib manual} for details
757  * on each flush mode.
758  *
759  * Returns `true` on success.
760  *
761  * Returns `null` if an error occurred.
762  *
763  * @function module:zlib.inflate#write
764  *
765  * @param {string} src
766  * The string of data to inflate.
767  *
768  * @param {?number} [flush=Z_NO_FLUSH]
769  * The zlib flush mode.
770  *
771  * @returns {?boolean}
772  */
773 static uc_value_t *
774 uc_zlib_infwrite(uc_vm_t *vm, size_t nargs)
775 {
776         uc_value_t *src = uc_fn_arg(0);
777         uc_value_t *flush = uc_fn_arg(1);
778         zstrm_t **z = uc_fn_this("zlib.inflate");
779         zstrm_t *zstrm;
780 
781         if (!z || !*z)
782                 err_return(EBADF);
783 
784         zstrm = *z;
785 
786         if (Z_FINISH == zstrm->flush)
787                 err_return(EPIPE);      // can't reuse a finished stream
788 
789         if (flush) {
790                 if (ucv_type(flush) != UC_INTEGER)
791                         err_return(EINVAL);
792 
793                 zstrm->flush = (int)ucv_int64_get(flush);
794                 switch (zstrm->flush) {
795                 case Z_NO_FLUSH:
796                 case Z_SYNC_FLUSH:
797                 case Z_FINISH:
798                         break;
799                 default:
800                         err_return(EINVAL);
801                 }
802         }
803         else
804                 zstrm->flush = Z_NO_FLUSH;
805 
806         /* we only accept strings */
807         if (!src || ucv_type(src) != UC_STRING)
808                 err_return(EINVAL);
809 
810         if (!zstrm->outbuf)
811                 zstrm->outbuf = ucv_stringbuf_new();
812 
813         return ucv_boolean_new(uc_zlib_inf_string(vm, src, zstrm));
814 }
815 
816 /**
817  * Reads a chunk of decompressed data from the inflate stream.
818  *
819  * Returns the current content of the inflate buffer, fed through
820  * {@link module:zlib.inflate#write}.
821  *
822  * Returns decompressed chunk on success.
823  *
824  * Returns `null` if an error occurred.
825  *
826  * @function module:zlib.inflate#read
827  *
828  * @returns {?string}
829  */
830 static uc_value_t *
831 uc_zlib_infread(uc_vm_t *vm, size_t nargs)
832 {
833         zstrm_t **z = uc_fn_this("zlib.inflate");
834         zstrm_t *zstrm;
835         uc_value_t *rv;
836 
837         if (!z || !*z)
838                 err_return(EBADF);
839 
840         zstrm = *z;
841 
842         if (!zstrm->outbuf)
843                 err_return(ENODATA);
844 
845         if (Z_FINISH == zstrm->flush)
846                 (void)inflateEnd(&zstrm->strm);
847 
848         rv = ucv_stringbuf_finish(zstrm->outbuf);
849         zstrm->outbuf = NULL;   // outbuf is now unuseable
850         return rv;
851 }
852 
853 /**
854  * Queries error information.
855  *
856  * Returns a string containing a description of the last occurred error or
857  * `null` if there is no error information.
858  *
859  * @function module:zlib.deflate#error
860  *
861  *
862  * @returns {?string}
863  */
864 static uc_value_t *
865 uc_zlib_error(uc_vm_t *vm, size_t nargs)
866 {
867         uc_value_t *errmsg;
868 
869         if (!last_error)
870                 return NULL;
871 
872         // negative last_error only happens for zlib init returns
873         errmsg = ucv_string_new(last_error < 0 ? ziniterr(last_error) : strerror(last_error));
874         last_error = 0;
875         return errmsg;
876 }
877 
878 static const uc_function_list_t strmd_fns[] = {
879         { "write",      uc_zlib_defwrite },
880         { "read",       uc_zlib_defread },
881         { "error",      uc_zlib_error },
882 };
883 
884 static const uc_function_list_t strmi_fns[] = {
885         { "write",      uc_zlib_infwrite },
886         { "read",       uc_zlib_infread },
887         { "error",      uc_zlib_error },
888 };
889 
890 static const uc_function_list_t global_fns[] = {
891         { "deflate",    uc_zlib_deflate },
892         { "inflate",    uc_zlib_inflate },
893         { "deflater",   uc_zlib_deflater },
894         { "inflater",   uc_zlib_inflater },
895 };
896 
897 static void destroy_zstrmd(void *z)
898 {
899         zstrm_t *zstrm = z;
900 
901         if (zstrm) {
902                 (void)deflateEnd(&zstrm->strm);
903                 printbuf_free(zstrm->outbuf);
904                 free(zstrm);
905         }
906 }
907 
908 static void destroy_zstrmi(void *z)
909 {
910         zstrm_t *zstrm = z;
911 
912         if (zstrm) {
913                 (void)inflateEnd(&zstrm->strm);
914                 printbuf_free(zstrm->outbuf);
915                 free(zstrm);
916         }
917 }
918 
919 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
920 {
921         uc_function_list_register(scope, global_fns);
922 
923         zstrmd_type = uc_type_declare(vm, "zlib.deflate", strmd_fns, destroy_zstrmd);
924         zstrmi_type = uc_type_declare(vm, "zlib.inflate", strmi_fns, destroy_zstrmi);
925 
926 #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
927 
928         /**
929          * @typedef
930          * @name Compression levels
931          * @description Constants representing predefined compression levels.
932          * @property {number} Z_NO_COMPRESSION.
933          * @property {number} Z_BEST_SPEED.
934          * @property {number} Z_BEST_COMPRESSION.
935          * @property {number} Z_DEFAULT_COMPRESSION - default compromise between speed and compression (currently equivalent to level 6).
936          */
937         ADD_CONST(Z_NO_COMPRESSION);
938         ADD_CONST(Z_BEST_SPEED);
939         ADD_CONST(Z_BEST_COMPRESSION);
940         ADD_CONST(Z_DEFAULT_COMPRESSION);
941 
942         /**
943          * @typedef
944          * @name flush options
945          * @description Constants representing flush options.
946          * @property {number} Z_NO_FLUSH.
947          * @property {number} Z_PARTIAL_FLUSH.
948          * @property {number} Z_SYNC_FLUSH.
949          * @property {number} Z_FULL_FLUSH.
950          * @property {number} Z_FINISH.
951          */
952         ADD_CONST(Z_NO_FLUSH);
953         ADD_CONST(Z_PARTIAL_FLUSH);
954         ADD_CONST(Z_SYNC_FLUSH);
955         ADD_CONST(Z_FULL_FLUSH);
956         ADD_CONST(Z_FINISH);
957 }
958 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt