summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorThibaut VARÈNE <hacks@slashdirt.org>2024-06-27 19:27:28 +0200
committerJo-Philipp Wich <jo@mein.io>2025-02-20 09:09:56 +0100
commitd20f7bb87d478a9bba1a9ee7d4074dfc1297bebb (patch)
tree0b1dfeb7c0e44755b179479580402700e2cba31b
parentee6a6c3ca5797a5e0c3add05714e0326f576d732 (diff)
zlib: add stream-oriented deflate operations
Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
-rw-r--r--lib/zlib.c275
1 files changed, 273 insertions, 2 deletions
diff --git a/lib/zlib.c b/lib/zlib.c
index 8bd6b79..c1a1e03 100644
--- a/lib/zlib.c
+++ b/lib/zlib.c
@@ -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);
}