#include "quickjs.h" #include "miniz.h" #include "cell.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(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(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_GetOpaque(v, js_reader_class_id); } static mz_zip_archive *js2writer(JSContext *js, JSValue v) { return JS_GetOpaque(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 == -1) return JS_EXCEPTION; mz_zip_archive *zip = calloc(sizeof(*zip), 1); if (!zip) return JS_ThrowOutOfMemory(js); mz_bool success = mz_zip_reader_init_mem(zip, data, len, 0); if (!success) { int err = mz_zip_get_last_error(zip); free(zip); return JS_ThrowInternalError(js, "Failed to initialize zip reader: %s", mz_zip_get_error_string(err)); } 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]); if (!file) return JS_EXCEPTION; mz_zip_archive *zip = calloc(sizeof(*zip), 1); if (!zip) { JS_FreeCString(js, file); return JS_ThrowOutOfMemory(js); } mz_bool success = mz_zip_writer_init_file(zip, file, 0); JS_FreeCString(js, file); if (!success) { int err = mz_zip_get_last_error(zip); mz_zip_writer_end(zip); free(zip); return JS_ThrowInternalError(js, "Failed to initialize zip writer: %s", mz_zip_get_error_string(err)); } 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_IsText(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 { in_ptr = js_get_blob_data(js, &in_len, argv[0]); if (in_ptr == -1) return JS_EXCEPTION; } /* ─── 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 == -1) return JS_EXCEPTION; /* 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"); JSValue ret; ret = JS_NewStringLen(js, (const char *)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 == -1) { JS_FreeCString(js, pathInZip); return JS_EXCEPTION; } 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_NULL; } 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) { #ifndef MINIZ_NO_TIME const char *file = JS_ToCString(js,argv[0]); if (!file) return JS_EXCEPTION; mz_zip_archive *zip = js2reader(js, self); if (!zip) { JS_FreeCString(js, file); return JS_ThrowInternalError(js, "Invalid zip reader"); } mz_zip_archive_file_stat pstat; mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0); if (index == (mz_uint)-1) { JS_FreeCString(js, file); return JS_ThrowReferenceError(js, "File '%s' not found in archive", file); } JS_FreeCString(js, file); if (!mz_zip_reader_file_stat(zip, index, &pstat)) { int err = mz_zip_get_last_error(zip); return JS_ThrowInternalError(js, "Failed to get file stats: %s", mz_zip_get_error_string(err)); } return JS_NewFloat64(js, pstat.m_time); #else return JS_ThrowInternalError(js, "MINIZ_NO_TIME is defined"); #endif } JSValue js_reader_exists(JSContext *js, JSValue self, int argc, JSValue *argv) { const char *file = JS_ToCString(js,argv[0]); if (!file) return JS_EXCEPTION; mz_zip_archive *zip = js2reader(js, self); if (!zip) { JS_FreeCString(js, file); return JS_ThrowInternalError(js, "Invalid zip reader"); } mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0); JS_FreeCString(js,file); if (index == (mz_uint)-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]); if (!file) return JS_EXCEPTION; mz_zip_archive *zip = js2reader(js, self); if (!zip) { JS_FreeCString(js, file); return JS_ThrowInternalError(js, "Invalid zip reader"); } size_t len; void *data = mz_zip_reader_extract_file_to_heap(zip, file, &len, 0); if (!data) { int err = mz_zip_get_last_error(zip); const char *filename = file; JS_FreeCString(js, file); return JS_ThrowInternalError(js, "Failed to extract file '%s': %s", filename, mz_zip_get_error_string(err)); } JS_FreeCString(js, file); JSValue ret = js_new_blob_stoned_copy(js, data, len); free(data); return ret; } JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv) { mz_zip_archive *zip = js2reader(js, self); if (!zip) return JS_ThrowInternalError(js, "Invalid zip reader"); mz_uint num_files = mz_zip_reader_get_num_files(zip); JSValue arr = JS_NewArray(js); if (JS_IsException(arr)) return arr; mz_uint arr_index = 0; for (mz_uint i = 0; i < num_files; i++) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(zip, i, &file_stat)) continue; JSValue filename = JS_NewString(js, file_stat.m_filename); if (JS_IsException(filename)) { JS_FreeValue(js, arr); return filename; } JS_SetPropertyUint32(js, arr, arr_index++, filename); } return arr; } JSValue js_reader_is_directory(JSContext *js, JSValue self, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError(js, "is_directory requires a file index"); int32_t index; if (JS_ToInt32(js, &index, argv[0])) return JS_EXCEPTION; mz_zip_archive *zip = js2reader(js, self); if (!zip) return JS_ThrowInternalError(js, "Invalid zip reader"); return JS_NewBool(js, mz_zip_reader_is_file_a_directory(zip, index)); } JSValue js_reader_get_filename(JSContext *js, JSValue self, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError(js, "get_filename requires a file index"); int32_t index; if (JS_ToInt32(js, &index, argv[0])) return JS_EXCEPTION; mz_zip_archive *zip = js2reader(js, self); if (!zip) return JS_ThrowInternalError(js, "Invalid zip reader"); mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(zip, index, &file_stat)) return JS_ThrowInternalError(js, "Failed to get file stats"); return JS_NewString(js, file_stat.m_filename); } JSValue js_reader_count(JSContext *js, JSValue self, int argc, JSValue *argv) { mz_zip_archive *zip = js2reader(js, self); if (!zip) return JS_ThrowInternalError(js, "Invalid zip reader"); return JS_NewUint32(js, mz_zip_reader_get_num_files(zip)); } 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", 1, js_reader_slurp), JS_CFUNC_DEF("list", 0, js_reader_list), JS_CFUNC_DEF("is_directory", 1, js_reader_is_directory), JS_CFUNC_DEF("get_filename", 1, js_reader_get_filename), JS_CFUNC_DEF("count", 0, js_reader_count), }; 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; }