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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt