#include "qjs_qop.h" #include "qop.h" #include "qjs_blob.h" #include "jsffi.h" static JSClassID js_qop_archive_class_id; static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) { qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id); if (qop) { if (qop->hashmap) { js_free_rt(rt, qop->hashmap); } qop_close(qop); js_free_rt(rt, qop); } } static JSClassDef js_qop_archive_class = { "qop archive", .finalizer = js_qop_archive_finalizer, }; static qop_desc *js2qop(JSContext *js, JSValue v) { return JS_GetOpaque(v, js_qop_archive_class_id); } static int js_qop_ensure_index(JSContext *js, qop_desc *qop) { if (qop->hashmap != NULL) return 1; void *buffer = js_malloc(js, qop->hashmap_size); if (!buffer) return 0; int num = qop_read_index(qop, buffer); if (num == 0) { js_free(js, buffer); return 0; } return 1; } JSC_SCALL(qop_open, size_t len; void *data = js_get_blob_data(js, &len, argv[0]); if (!data) ret = JS_ThrowReferenceError(js, "Could not get blob data.\n"); else { qop_desc *qop = js_malloc(js, sizeof(qop_desc)); if (!qop) ret = JS_ThrowOutOfMemory(js); else { int size = qop_open_data((const unsigned char *)data, len, qop); if (size == 0) { js_free(js, qop); ret = JS_ThrowReferenceError(js, "Failed to open QOP archive from blob"); } else { JSValue obj = JS_NewObjectClass(js, js_qop_archive_class_id); JS_SetOpaque(obj, qop); ret = obj; } } } ) static JSValue js_qop_close(JSContext *js, JSValue self, int argc, JSValue *argv) { qop_desc *qop = js2qop(js, self); if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive"); qop_close(qop); JS_SetOpaque(self, NULL); // Prevent double free return JS_NULL; } static JSValue js_qop_read(JSContext *js, JSValue self, int argc, JSValue *argv) { qop_desc *qop = js2qop(js, self); if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive"); const char *path = JS_ToCString(js, argv[0]); if (!path) return JS_EXCEPTION; if (!js_qop_ensure_index(js, qop)) { JS_FreeCString(js, path); return JS_ThrowReferenceError(js, "Failed to read QOP index"); } qop_file *file = qop_find(qop, path); JS_FreeCString(js, path); if (!file) return JS_NULL; unsigned char *dest = js_malloc(js, file->size); if (!dest) return JS_ThrowOutOfMemory(js); int bytes = qop_read(qop, file, dest); if (bytes == 0) { js_free(js, dest); return JS_ThrowReferenceError(js, "Failed to read file"); } JSValue blob = js_new_blob_stoned_copy(js, dest, bytes); js_free(js, dest); return blob; } static JSValue js_qop_read_ex(JSContext *js, JSValue self, int argc, JSValue *argv) { qop_desc *qop = js2qop(js, self); if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive"); const char *path = JS_ToCString(js, argv[0]); if (!path) return JS_EXCEPTION; if (!js_qop_ensure_index(js, qop)) { JS_FreeCString(js, path); return JS_ThrowReferenceError(js, "Failed to read QOP index"); } qop_file *file = qop_find(qop, path); JS_FreeCString(js, path); if (!file) return JS_NULL; unsigned int start; unsigned int len; if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0) return JS_ThrowTypeError(js, "Invalid start or len"); unsigned char *dest = js_malloc(js, len); if (!dest) return JS_ThrowOutOfMemory(js); int bytes = qop_read_ex(qop, file, dest, start, len); if (bytes == 0) { js_free(js, dest); return JS_ThrowReferenceError(js, "Failed to read file part"); } JSValue blob = js_new_blob_stoned_copy(js, dest, bytes); js_free(js, dest); return blob; } static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv) { qop_desc *qop = js2qop(js, self); if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive"); if (!js_qop_ensure_index(js, qop)) { return JS_ThrowReferenceError(js, "Failed to read QOP index"); } JSValue arr = JS_NewArray(js); int count = 0; for (unsigned int i = 0; i < qop->hashmap_len; i++) { qop_file *file = &qop->hashmap[i]; if (file->size == 0) continue; // empty slot char *path = js_malloc(js, file->path_len); if (!path) { return JS_ThrowOutOfMemory(js); } int len = qop_read_path(qop, file, path); if (len == 0) { js_free(js, path); continue; // skip on error } JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator js_free(js, path); JS_SetPropertyUint32(js, arr, count++, str); } return arr; } static const JSCFunctionListEntry js_qop_archive_funcs[] = { JS_CFUNC_DEF("close", 0, js_qop_close), JS_CFUNC_DEF("list", 0, js_qop_list), JS_CFUNC_DEF("read", 1, js_qop_read), JS_CFUNC_DEF("read_ex", 3, js_qop_read_ex), }; static const JSCFunctionListEntry js_qop_funcs[] = { MIST_FUNC_DEF(qop, open, 1), JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE), JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE), JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE), JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, JS_PROP_ENUMERABLE), }; JSValue js_qop_use(JSContext *js) { JS_NewClassID(&js_qop_archive_class_id); JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class); JSValue archive_proto = JS_NewObject(js); JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs)); return mod; }