diff options
author | Thibaut VARÈNE <hacks@slashdirt.org> | 2024-07-17 18:01:09 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2025-02-20 09:09:56 +0100 |
commit | 0255af462be3c4b2aeaa77014376e22e9300ffc3 (patch) | |
tree | 0bef56c93bb38f8db117905486dfd65b0ccdfdf4 | |
parent | d20f7bb87d478a9bba1a9ee7d4074dfc1297bebb (diff) |
zlib: add stream-oriented inflate operations
Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
-rw-r--r-- | lib/zlib.c | 195 |
1 files changed, 194 insertions, 1 deletions
@@ -41,7 +41,7 @@ */ #define CHUNK 16384 -static uc_resource_type_t *zstrmd_type; +static uc_resource_type_t *zstrmd_type, *zstrmi_type; static int last_error = 0; #define err_return(err) do { last_error = err; return NULL; } while(0) @@ -380,6 +380,7 @@ uc_zlib_inf_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const z ret = inf_chunks(zstrm); assert(zstrm->strm.avail_in == 0); + last_error = ret; return Z_STREAM_END == ret; } @@ -672,6 +673,179 @@ uc_zlib_defread(uc_vm_t *vm, size_t nargs) } /** + * Represents a handle for interacting with an inflate stream initiated by infnew(). + * + * @class module:zlib.zstrmi + * @hideconstructor + * + * @see {@link module:zlib#infnew()} + * + * @example + * + * const zstrmi = infnew(); + * + * for (let data = ...; data; data = ...) { + * zstrmi.write(data, Z_SYNC_FLUSH); // write compressed data to stream + * if (foo) + * let defl = zstrmi.read(); // read back decompressed stream content + * } + * + * // terminate the stream if needed (for e.g. file output) + * zstrmi.write('', Z_FINISH); + * defl = ztrmi.read(); + * + * zstrmi.error(); + */ + +/** + * Initializes an inflate stream. Can process either Zlib or gzip data. + * + * Returns a stream handle on success. + * + * Returns `null` if an error occurred. + * + * @function module:zlib#infnew + * + * @returns {?module:zlib.zstrmi} + * + * @example + * // initialize an inflate stream + * const zstrmi = infnew(); + */ + static uc_value_t * +uc_zlib_infnew(uc_vm_t *vm, size_t nargs) +{ + int ret; + zstrm_t *zstrm; + + zstrm = calloc(1, sizeof(*zstrm)); + if (!zstrm) + err_return(ENOMEM); + + zstrm->strm.zalloc = Z_NULL; + zstrm->strm.zfree = Z_NULL; + zstrm->strm.opaque = Z_NULL; + zstrm->strm.avail_in = 0; + zstrm->strm.next_in = Z_NULL; + + /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */ + ret = inflateInit2(&zstrm->strm, 15+32); + if (ret != Z_OK) { + last_error = ret; + goto fail; + } + + return uc_resource_new(zstrmi_type, zstrm); + +fail: + free(zstrm); + return NULL; +} + +/** + * Writes a chunk of data to the inflate stream. + * + * Input data must be a string, it is internally decompressed by the zlib deflate() routine, + * the end is buffered according to the requested `flush` mode until read via + * @see {@link module:zlib.zstrmd#read}. + * If `flush` is `Z_FINISH` (the default) then no more data can be written to the stream. + * Valid `flush` values are `Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH` + * + * Returns `true` on success. + * + * Returns `null` if an error occurred. + * + * @function module:zlib.zstrmi#write + * + * @param {string} src + * The string of data to deflate. + * + * @param {?number} [flush=Z_FINISH] + * The zlib flush mode. + * + * @returns {?boolean} + */ +static uc_value_t * +uc_zlib_infwrite(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *src = uc_fn_arg(0); + uc_value_t *flush = uc_fn_arg(1); + zstrm_t **z = uc_fn_this("zlib.strmi"); + zstrm_t *zstrm; + + if (!z || !*z) + err_return(EBADF); + + zstrm = *z; + + if (Z_FINISH == zstrm->flush) + err_return(EPIPE); // can't reuse a finished stream + + if (flush) { + if (ucv_type(flush) != UC_INTEGER) + err_return(EINVAL); + + zstrm->flush = (int)ucv_int64_get(flush); + switch (zstrm->flush) { + case Z_NO_FLUSH: + case Z_SYNC_FLUSH: + case Z_FINISH: + break; + default: + err_return(EINVAL); + } + } + else + zstrm->flush = Z_FINISH; + + /* we only accept strings */ + if (!src || ucv_type(src) != UC_STRING) + err_return(EINVAL); + + if (!zstrm->outbuf) + zstrm->outbuf = ucv_stringbuf_new(); + + return ucv_boolean_new(uc_zlib_inf_string(vm, src, zstrm)); +} + +/** + * Reads a chunk of decompressed data from the inflate stream. + * + * Returns the current content of the inflate buffer, fed through + * @see {@link module:zlib.zstrmi#write}. + * + * Returns compressed chunk on success. + * + * Returns `null` if an error occurred. + * + * @function module:zlib.zstrmd#read + * + * @returns {?string} + */ +static uc_value_t * +uc_zlib_infread(uc_vm_t *vm, size_t nargs) +{ + zstrm_t **z = uc_fn_this("zlib.strmi"); + zstrm_t *zstrm; + uc_value_t *rv; + + if (!z || !*z) + err_return(EBADF); + + zstrm = *z; + + if (!zstrm->outbuf) + err_return(ENODATA); + + if (Z_FINISH == zstrm->flush) + (void)inflateEnd(&zstrm->strm); + + rv = ucv_stringbuf_finish(zstrm->outbuf); + zstrm->outbuf = NULL; // outbuf is now unuseable + return rv; +} + +/** * Query error information. * * Returns a string containing a description of the last occurred error or @@ -702,10 +876,17 @@ static const uc_function_list_t strmd_fns[] = { { "error", uc_zlib_error }, }; +static const uc_function_list_t strmi_fns[] = { + { "write", uc_zlib_infwrite }, + { "read", uc_zlib_infread }, + { "error", uc_zlib_error }, +}; + static const uc_function_list_t global_fns[] = { { "deflate", uc_zlib_deflate }, { "inflate", uc_zlib_inflate }, { "defnew", uc_zlib_defnew }, + { "infnew", uc_zlib_infnew }, }; static void destroy_zstrmd(void *z) @@ -719,11 +900,23 @@ static void destroy_zstrmd(void *z) } } +static void destroy_zstrmi(void *z) +{ + zstrm_t *zstrm = z; + + if (zstrm) { + (void)inflateEnd(&zstrm->strm); + printbuf_free(zstrm->outbuf); + free(zstrm); + } +} + void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); zstrmd_type = uc_type_declare(vm, "zlib.strmd", strmd_fns, destroy_zstrmd); + zstrmi_type = uc_type_declare(vm, "zlib.strmi", strmi_fns, destroy_zstrmi); #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) |