418 lines
12 KiB
C
418 lines
12 KiB
C
#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)
|
|
return JS_ThrowReferenceError(js, "Could not create data.\n");
|
|
|
|
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)
|
|
return JS_ThrowInternalError(js, "miniz error: %s\n", 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_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]);
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|