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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt