diff options
author | Thibaut VARÈNE <hacks@slashdirt.org> | 2024-06-27 19:27:28 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2025-02-20 09:09:56 +0100 |
commit | d20f7bb87d478a9bba1a9ee7d4074dfc1297bebb (patch) | |
tree | 0b1dfeb7c0e44755b179479580402700e2cba31b | |
parent | ee6a6c3ca5797a5e0c3add05714e0326f576d732 (diff) |
zlib: add stream-oriented deflate operations
Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
-rw-r--r-- | lib/zlib.c | 275 |
1 files changed, 273 insertions, 2 deletions
@@ -17,7 +17,7 @@ /** * # Zlib bindings * - * The `zlib` module provides single-call-oriented functions for interacting with zlib data. + * The `zlib` module provides single-call and stream-oriented functions for interacting with zlib data. * * @module zlib */ @@ -41,6 +41,10 @@ */ #define CHUNK 16384 +static uc_resource_type_t *zstrmd_type; + +static int last_error = 0; +#define err_return(err) do { last_error = err; return NULL; } while(0) typedef struct { z_stream strm; @@ -157,7 +161,7 @@ uc_zlib_def_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const z zstrm->strm.next_in = (unsigned char *)ucv_string_get(str); zstrm->strm.avail_in = ucv_string_length(str); - def_chunks(zstrm); + last_error = def_chunks(zstrm); return true; } @@ -461,15 +465,266 @@ out: return rv; } +/** + * Represents a handle for interacting with a deflate stream initiated by defnew(). + * + * @class module:zlib.zstrmd + * @hideconstructor + * + * @see {@link module:zlib#defnew()} + * + * @example + * + * const zstrmd = defnew(…); + * + * for (let data = ...; data; data = ...) { + * zstrmd.write(data, Z_PARTIAL_FLUSH); // write uncompressed data to stream + * if (foo) + * let defl = zstrmd.read(); // read back compressed stream content + * } + * + * // terminate the stream if needed (for e.g. file output) + * zstrmd.write('', Z_FINISH); + * defl = ztrmd.read(); + * + * zstrmd.error(); + */ + +/** + * Initializes a deflate stream. + * + * Returns a stream handle on success. + * + * Returns `null` if an error occurred. + * + * @function module:zlib#defnew + * + * @param {?boolean} [gzip=false] + * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib) + * + * @param {?number} [level=Z_DEFAULT_COMPRESSION] + * The compression level (0-9). + * + * @returns {?module:zlib.zstrmd} + * + * @example + * // initialize a Zlib deflate stream using default compression + * const zstrmd = defnew(); + * + * // initialize a gzip deflate stream using fastest compression + * const zstrmd = defnew(true, Z_BEST_SPEED); + */ + static uc_value_t * +uc_zlib_defnew(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *gzip = uc_fn_arg(0); + uc_value_t *level = uc_fn_arg(1); + int ret, lvl = Z_DEFAULT_COMPRESSION; + bool gz = false; + 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; + + if (gzip) { + if (ucv_type(gzip) != UC_BOOLEAN) { + last_error = EINVAL; + goto fail; + } + + gz = (int)ucv_boolean_get(gzip); + } + + if (level) { + if (ucv_type(level) != UC_INTEGER) { + last_error = EINVAL; + goto fail; + } + + lvl = (int)ucv_int64_get(level); + } + + ret = deflateInit2(&zstrm->strm, lvl, + Z_DEFLATED, // only allowed method + gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip + 8, // default value + Z_DEFAULT_STRATEGY); // default value + if (ret != Z_OK) { + last_error = ret; + goto fail; + } + + return uc_resource_new(zstrmd_type, zstrm); + +fail: + free(zstrm); + return NULL; +} + +/** + * Writes a chunk of data to the deflate stream. + * + * Input data must be a string, it is internally compressed 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_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH` + * + * Returns `true` on success. + * + * Returns `null` if an error occurred. + * + * @function module:zlib.zstrmd#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_defwrite(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.strmd"); + 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_PARTIAL_FLUSH: + case Z_FULL_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_def_string(vm, src, zstrm)); +} + +/** + * Reads a chunk of compressed data from the deflate stream. + * + * Returns the current content of the deflate buffer, fed through + * @see {@link module:zlib.zstrmd#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_defread(uc_vm_t *vm, size_t nargs) +{ + zstrm_t **z = uc_fn_this("zlib.strmd"); + 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)deflateEnd(&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 + * `null` if there is no error information. + * + * @function module:zlib.zstrmd#error + * + * + * @returns {?string} + */ +static uc_value_t * +uc_zlib_error(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *errmsg; + + if (!last_error) + return NULL; + + // negative last_error only happens for zlib init returns + errmsg = ucv_string_new(last_error < 0 ? ziniterr(last_error) : strerror(last_error)); + last_error = 0; + return errmsg; +} + +static const uc_function_list_t strmd_fns[] = { + { "write", uc_zlib_defwrite }, + { "read", uc_zlib_defread }, + { "error", uc_zlib_error }, +}; + static const uc_function_list_t global_fns[] = { { "deflate", uc_zlib_deflate }, { "inflate", uc_zlib_inflate }, + { "defnew", uc_zlib_defnew }, }; +static void destroy_zstrmd(void *z) +{ + zstrm_t *zstrm = z; + + if (zstrm) { + (void)deflateEnd(&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); + #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) /** @@ -485,4 +740,20 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope) ADD_CONST(Z_BEST_SPEED); ADD_CONST(Z_BEST_COMPRESSION); ADD_CONST(Z_DEFAULT_COMPRESSION); + + /** + * @typedef + * @name flush options + * @description Constants representing flush options. + * @property {number} Z_NO_FLUSH. + * @property {number} Z_PARTIAL_FLUSH. + * @property {number} Z_SYNC_FLUSH. + * @property {number} Z_FULL_FLUSH. + * @property {number} Z_FINISH. + */ + ADD_CONST(Z_NO_FLUSH); + ADD_CONST(Z_PARTIAL_FLUSH); + ADD_CONST(Z_SYNC_FLUSH); + ADD_CONST(Z_FULL_FLUSH); + ADD_CONST(Z_FINISH); } |