Files
cell/archive/miniz.c

400 lines
11 KiB
C

#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, 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, 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;
}