From 90b5d1430fb5d81ce72a395567201e001359cecc Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Jun 2025 08:49:02 -0500 Subject: [PATCH] vendor qjs_miniz --- source/qjs_miniz.c | 291 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 source/qjs_miniz.c diff --git a/source/qjs_miniz.c b/source/qjs_miniz.c new file mode 100644 index 00000000..e62c20f0 --- /dev/null +++ b/source/qjs_miniz.c @@ -0,0 +1,291 @@ +#include "quickjs.h" +#include "miniz.h" +#include "qjs_blob.h" + +static JSClassID js_reader_class_id; +static JSClassID js_writer_class_id; + +static void js_reader_finalizer(JSRuntime *rt, JSValue val) { + mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id); + mz_zip_reader_end(zip); + js_free_rt(rt,zip); +} + +static void js_writer_finalizer(JSRuntime *rt, JSValue val) { + mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id); + mz_zip_writer_finalize_archive(zip); + mz_zip_writer_end(zip); + js_free_rt(rt,zip); +} + +static JSClassDef js_reader_class = { + "zip reader", + .finalizer = js_reader_finalizer, +}; + +static JSClassDef js_writer_class = { + "zip writer", + .finalizer = js_writer_finalizer, +}; + +static mz_zip_archive *js2reader(JSContext *js, JSValue v) +{ + return JS_GetOpaque2(js, v, js_reader_class_id); +} + +static mz_zip_archive *js2writer(JSContext *js, JSValue v) +{ + return JS_GetOpaque2(js, v, js_writer_class_id); +} + +static JSValue js_miniz_read(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (!data) { + printf("Could not create data.\n"); + return JS_UNDEFINED; + } + mz_zip_archive *zip = calloc(sizeof(*zip),1); + int success = mz_zip_reader_init_mem(zip, data, len, 0); + int err = mz_zip_get_last_error(zip); + if (err) { + printf("%s\n", mz_zip_get_error_string(err)); + return JS_UNDEFINED; + } + JSValue jszip = JS_NewObjectClass(js, js_reader_class_id); + JS_SetOpaque(jszip, zip); + return jszip; +} + +static JSValue js_miniz_write(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + const char *file = JS_ToCString(js, argv[0]); + mz_zip_archive *zip = malloc(sizeof(*zip)); + mz_zip_writer_init_file(zip, file, 0); + JS_FreeCString(js,file); + JSValue jszip = JS_NewObjectClass(js, js_writer_class_id); + JS_SetOpaque(jszip, zip); + return jszip; +} + +static JSValue js_miniz_compress(JSContext *js, JSValue this_val, + int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_ThrowTypeError(js, + "compress needs a string or ArrayBuffer"); + + /* ─── 1. Grab the input data ──────────────────────────────── */ + const char *cstring = NULL; + size_t in_len = 0; + const void *in_ptr = NULL; + + if (JS_IsString(argv[0])) { + /* String → UTF-8 bytes without the terminating NUL */ + cstring = JS_ToCStringLen(js, &in_len, argv[0]); + if (!cstring) + return JS_EXCEPTION; + in_ptr = cstring; + } else { /* assume ArrayBuffer / TypedArray */ + in_ptr = js_get_blob_data(js, &in_len, argv[0]); + if (!in_ptr) + return JS_ThrowTypeError(js, + "Argument must be a string or ArrayBuffer"); + } + + /* ─── 2. Allocate an output buffer big enough ────────────── */ + mz_ulong out_len_est = mz_compressBound(in_len); + void *out_buf = js_malloc(js, out_len_est); + if (!out_buf) { + if (cstring) JS_FreeCString(js, cstring); + return JS_EXCEPTION; + } + + /* ─── 3. Do the compression (MZ_DEFAULT_COMPRESSION = level 6) */ + mz_ulong out_len = out_len_est; + int st = mz_compress2(out_buf, &out_len, + in_ptr, in_len, MZ_DEFAULT_COMPRESSION); + + /* clean-up for string input */ + if (cstring) JS_FreeCString(js, cstring); + + if (st != MZ_OK) { + js_free(js, out_buf); + return JS_ThrowInternalError(js, + "miniz: compression failed (%d)", st); + } + + /* ─── 4. Hand JavaScript a copy of the compressed data ────── */ + JSValue abuf = js_new_blob_stoned_copy(js, out_buf, out_len); + js_free(js, out_buf); + return abuf; +} + +static JSValue js_miniz_decompress(JSContext *js, + JSValueConst this_val, + int argc, + JSValueConst *argv) +{ + if (argc < 1) + return JS_ThrowTypeError(js, + "decompress: need compressed ArrayBuffer"); + + /* grab compressed data */ + size_t in_len; + void *in_ptr = js_get_blob_data(js, &in_len, argv[0]); + if (!in_ptr) + return JS_ThrowTypeError(js, + "decompress: first arg must be an ArrayBuffer"); + + /* zlib header present → tell tinfl to parse it */ + size_t out_len = 0; + void *out_ptr = tinfl_decompress_mem_to_heap( + in_ptr, in_len, &out_len, + TINFL_FLAG_PARSE_ZLIB_HEADER); + + if (!out_ptr) + return JS_ThrowInternalError(js, + "miniz: decompression failed"); + + /* wrap for JS */ + JSValue ret; + int asString = (argc > 1) && JS_ToBool(js, argv[1]); + + if (asString) + ret = JS_NewStringLen(js, (const char *)out_ptr, out_len); + else + ret = JS_NewArrayBufferCopy(js, out_ptr, out_len); + +#ifdef MZ_FREE + MZ_FREE(out_ptr); +#else + free(out_ptr); +#endif + return ret; +} + + +static const JSCFunctionListEntry js_miniz_funcs[] = { + JS_CFUNC_DEF("read", 1, js_miniz_read), + JS_CFUNC_DEF("write", 1, js_miniz_write), + JS_CFUNC_DEF("compress", 1, js_miniz_compress), + JS_CFUNC_DEF("decompress", 1, js_miniz_decompress), +}; + +JSValue js_writer_add_file(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + if (argc < 2) + return JS_ThrowTypeError(js, "add_file requires (path, arrayBuffer)"); + + mz_zip_archive *zip = js2writer(js, self); + const char *pathInZip = JS_ToCString(js, argv[0]); + if (!pathInZip) + return JS_ThrowTypeError(js, "Could not parse path argument"); + + size_t dataLen; + void *data = js_get_blob_data(js, &dataLen, argv[1]); + if (!data) { + JS_FreeCString(js, pathInZip); + return JS_ThrowTypeError(js, "Second argument must be an ArrayBuffer"); + } + + int success = mz_zip_writer_add_mem(zip, pathInZip, data, dataLen, MZ_DEFAULT_COMPRESSION); + JS_FreeCString(js, pathInZip); + + if (!success) + return JS_ThrowInternalError(js, "Failed to add memory to zip"); + + return JS_UNDEFINED; +} + + +static const JSCFunctionListEntry js_writer_funcs[] = { + JS_CFUNC_DEF("add_file", 1, js_writer_add_file), +}; + +JSValue js_reader_mod(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + const char *file = JS_ToCString(js,argv[0]); + mz_zip_archive *zip = js2reader(js, self); + mz_zip_archive_file_stat pstat; + mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0); + JS_FreeCString(js,file); + if (index == -1) return JS_UNDEFINED; + + mz_zip_reader_file_stat(zip, index, &pstat); + return JS_NewFloat64(js, pstat.m_time); +} + +JSValue js_reader_exists(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + const char *file = JS_ToCString(js,argv[0]); + mz_zip_archive *zip = js2reader(js, self); + mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0); + JS_FreeCString(js,file); + if (index == -1) return JS_NewBool(js, 0); + return JS_NewBool(js, 1); +} + +JSValue js_reader_slurp(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + const char *file = JS_ToCString(js,argv[0]); + mz_zip_archive *zip = js2reader(js, self); + size_t len; + void *data = mz_zip_reader_extract_file_to_heap(zip, file, &len, 0); + JS_FreeCString(js,file); + + if (!data) + return JS_UNDEFINED; + + JSValue ret; + if (JS_ToBool(js, argv[1])) + ret = JS_NewStringLen(js, data, len); + else + ret = js_new_blob_stoned_copy(js, data, len); + free(data); + return ret; +} + +static const JSCFunctionListEntry js_reader_funcs[] = { + JS_CFUNC_DEF("mod", 1, js_reader_mod), + JS_CFUNC_DEF("exists", 1, js_reader_exists), + JS_CFUNC_DEF("slurp", 2, js_reader_slurp), +}; + +JSValue js_miniz_use(JSContext *js) +{ + JS_NewClassID(&js_reader_class_id); + JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class); + JSValue reader_proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(js, js_reader_class_id, reader_proto); + + JS_NewClassID(&js_writer_class_id); + JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class); + JSValue writer_proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(js, js_writer_class_id, writer_proto); + + JSValue export = JS_NewObject(js); + JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); + return export; +} + +static int js_miniz_init(JSContext *ctx, JSModuleDef *m) { + JS_SetModuleExport(ctx, m, "default",js_miniz_use(ctx)); + return 0; +} + +#ifdef JS_SHARED_LIBRARY +#define JS_INIT_MODULE js_init_module +#else +#define JS_INIT_MODULE js_init_module_miniz +#endif + +JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) { + JSModuleDef *m = JS_NewCModule(ctx, module_name, js_miniz_init); + if (!m) return NULL; + JS_AddModuleExport(ctx, m, "default"); + return m; +}