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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt