#include "task.h" #include "bcrypt.h" #include "database.h" #include "file.h" #include "packetstream.h" #include "serialize.h" #include "socket.h" #include "ssb.h" #include "ssb.qjs.h" #include "taskstub.h" #include "tlscontextwrapper.h" #include "trace.h" #include #include #include #include #include #include #include "quickjs.h" #include "quickjs-libc.h" #ifdef _WIN32 static const int STDIN_FILENO = 0; #else #include #endif static const char* k_version = "1.0"; static JSClassID _import_class_id; static int _count; static void _task_timeoutCallback(uv_timer_t* handle); typedef struct _export_record_t export_record_t; typedef struct _import_record_t import_record_t; typedef struct _task_child_node_t task_child_node_t; typedef struct _task_child_node_t { taskid_t id; tf_taskstub_t* stub; task_child_node_t* next; } task_child_node_t; typedef struct _script_export_t script_export_t; typedef struct _script_export_t { const char* name; JSValue value; script_export_t* next; } script_export_t; typedef struct _promise_t promise_t; typedef struct _promise_t { promiseid_t id; JSValue values[3]; promise_t* next; } promise_t; typedef struct _tf_task_t { taskid_t _nextTask; task_child_node_t* _children; int _child_count; tf_taskstub_t* _stub; tf_taskstub_t* _parent; const char* _path; script_export_t* _scriptExports; bool _trusted; bool _killed; int32_t _exitCode; char _scriptName[256]; JSRuntime* _runtime; JSContext* _context; sqlite3* _db; tf_ssb_t* _ssb; tf_trace_t* _trace; promise_t* _promises; int _promise_count; promiseid_t _nextPromise; uv_loop_t* _loop; export_record_t* _exports; int _export_count; exportid_t _nextExport; import_record_t* _imports; int _import_count; JSValue _requires; JSValue _loadedFiles; int _ssb_port; int _http_port; int _https_port; const char* _db_path; const char* _secrets_path; } tf_task_t; typedef struct _export_record_t { exportid_t _export_id; taskid_t _taskid; JSValue _function; int _useCount; export_record_t* _next; } export_record_t; static void _export_record_ref(export_record_t* export) { export->_useCount++; } static bool _export_record_release(export_record_t* export) { return --export->_useCount == 0; } static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_utf8Decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_get_parent(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_sandbox_require(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_trace(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static void _tf_task_sendPromiseMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, JSValue result); static void _tf_task_sendPromiseResolve(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result); static void _tf_task_sendPromiseReject(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result); static void _tf_task_sendPromiseExportMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promiseId, exportid_t exportId, JSValue result); static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name); static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id); static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId); typedef struct _import_record_t { JSValue _function; exportid_t _export; taskid_t _task; tf_task_t* _owner; int _useCount; import_record_t* _next; } import_record_t; static void _tf_task_trace_imports(tf_task_t* task) { tf_trace_counter(task->_trace, "imports", 1, (const char*[]) { "count" }, (int64_t[]) { task->_import_count }); } static void _import_record_release(import_record_t* import) { JSContext* context = import->_owner->_context; if (--import->_useCount > 0) { return; } JS_SetOpaque(import->_function, NULL); JS_FreeValue(context, import->_function); import->_function = JS_UNDEFINED; _tf_task_release_export(_tf_task_get_stub(import->_owner, import->_task), import->_export); for (import_record_t** it = &import->_owner->_imports; *it; it = &(*it)->_next) { if (*it == import) { *it = import->_next; import->_owner->_import_count--; _tf_task_trace_imports(import->_owner); import->_next = NULL; break; } } free(import); } static void _import_record_release_for_task(tf_task_t* task, taskid_t task_id) { bool any = false; import_record_t** next = NULL; for (import_record_t** it = &task->_imports; *it; it = next) { next = &(*it)->_next; if ((*it)->_task == task_id) { import_record_t* import = *it; _import_record_release(import); next = it; any = true; } } if (any) { _tf_task_trace_imports(task); } } static void _tf_task_trace_exports(tf_task_t* task) { tf_trace_counter(task->_trace, "exports", 1, (const char*[]) { "count" }, (int64_t[]) { task->_export_count }); } static void _export_record_release_for_task(tf_task_t* task, taskid_t task_id) { bool any = false; export_record_t** next = NULL; for (export_record_t** it = &task->_exports; *it; it = next) { next = &(*it)->_next; if ((*it)->_taskid == task_id) { export_record_t* export = *it; if (_export_record_release(export)) { *it = export->_next; JS_FreeValue(task->_context, export->_function); free(export); task->_export_count--; _tf_task_trace_exports(task); next = it; any = true; } } } if (any) { _tf_task_trace_exports(task); } } static void _tf_task_send_error_to_parent(tf_task_t* task, JSValue error) { if (task->_parent) { void* buffer = NULL; size_t size = 0; tf_serialize_store(task, task->_parent, &buffer, &size, error); tf_packetstream_send(tf_taskstub_get_stream(task->_parent), kTaskError, buffer, size); free(buffer); } } void tf_task_report_error(tf_task_t* task, JSValue error) { JSContext* context = task->_context; if (JS_IsError(context, error)) { const char* value = JS_ToCString(context, error); printf("ERROR: %s\n", value); JS_FreeCString(context, value); JSValue stack = JS_GetPropertyStr(context, error, "stack"); if (!JS_IsUndefined(stack)) { const char* stack_str = JS_ToCString(context, stack); printf("%s\n", stack_str); JS_FreeCString(context, stack_str); } JS_FreeValue(context, stack); _tf_task_send_error_to_parent(task, error); } else if (JS_IsException(error)) { error = JS_GetException(context); const char* value = JS_ToCString(context, error); printf("Exception: %s\n", value); JS_FreeCString(context, value); _tf_task_send_error_to_parent(task, error); } } static const char* _task_loadFile(const char* fileName) { char* result = NULL; FILE* file = fopen(fileName, "rb"); if (file) { fseek(file, 0, SEEK_END); long fileSize = ftell(file); fseek(file, 0, SEEK_SET); result = malloc(fileSize + 1); fread(result, 1, fileSize, file); result[fileSize] = '\0'; fclose(file); } return result; } JSValue _tf_task_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); printf("Task[%p:%s]>", task, task->_scriptName); for (int i = 0; i < argc; ++i) { if (JS_IsNull(argv[i])) { printf(" null"); } else { const char* value = JS_ToCString(task->_context, argv[i]); printf(" %s", value); JS_FreeCString(task->_context, value); } } printf("\n"); return JS_NULL; } typedef struct _timeout_t { tf_task_t* _task; JSValue _callback; } timeout_t; static JSValue _task_setTimeout(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); timeout_t* timeout = malloc(sizeof(timeout_t)); *timeout = (timeout_t) { ._task = task, ._callback = JS_DupValue(context, argv[0]), }; uv_timer_t* timer = malloc(sizeof(uv_timer_t)); memset(timer, 0, sizeof(uv_timer_t)); uv_timer_init(task->_loop, timer); timer->data = timeout; int64_t duration; JS_ToInt64(task->_context, &duration, argv[1]); uv_timer_start(timer, _task_timeoutCallback, duration, 0); return JS_NULL; } static void _handle_closed(uv_handle_t* handle) { free(handle); } static void _task_timeoutCallback(uv_timer_t* handle) { timeout_t* timeout = handle->data; tf_trace_begin(timeout->_task->_trace, "_task_timeoutCallback"); JSValue result = JS_Call( timeout->_task->_context, timeout->_callback, JS_NULL, 0, NULL); tf_task_report_error(timeout->_task, result); JS_FreeValue(timeout->_task->_context, result); tf_task_run_jobs(timeout->_task); tf_trace_end(timeout->_task->_trace); free(timeout); uv_close((uv_handle_t*)handle, _handle_closed); } JSValue _tf_task_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); int exitCode = 0; JS_ToInt32(task->_context, &exitCode, argv[0]); exit(exitCode); return JS_UNDEFINED; } int tf_task_execute(tf_task_t* task, const char* fileName) { bool executed = false; tf_trace_begin(task->_trace, "tf_task_execute"); const char* source = _task_loadFile(fileName); printf("Running script %s\n", fileName); if (!*task->_scriptName) { strncpy(task->_scriptName, fileName, sizeof(task->_scriptName) - 1); } if (!task->_path) { char* path = strdup(fileName); char* slash = strrchr(path, '/'); if (slash) { *slash = '\0'; task->_path = strdup(path); } else { task->_path = strdup("./"); } free(path); } if (source) { JSValue result = JS_Eval(task->_context, source, strlen(source), fileName, 0); tf_task_report_error(task, result); if (!JS_IsError(task->_context, result) && !JS_IsException(result)) { executed = true; } JS_FreeValue(task->_context, result); tf_task_run_jobs(task); free((void*)source); } else { printf("Failed to load file: %s.\n", fileName); } tf_trace_end(task->_trace); return executed; } static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id) { if (id == k_task_parent_id) { return task->_parent; } for (task_child_node_t* node = task->_children; node; node = node->next) { if (node->id == id) { return node->stub; } } return NULL; } static JSValue _import_call(JSContext* context, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst *argv, int flags) { import_record_t* import = JS_GetOpaque(func_obj, _import_class_id); if (!import) { return JS_ThrowInternalError(context, "Invoking a function that has been released."); } import->_useCount++; JSValue array = JS_NewArray(context); JS_SetPropertyUint32(context, array, 0, JS_DupValue(context, this_val)); for (int i = 0; i < argc; ++i) { JS_SetPropertyUint32(context, array, i + 1, JS_DupValue(context, argv[i])); } tf_task_t* sender = JS_GetContextOpaque(context); JSValue result; tf_taskstub_t* recipient = _tf_task_get_stub(sender, import->_task); if (recipient) { promiseid_t promise = tf_task_allocate_promise(sender); _tf_task_sendPromiseExportMessage(sender, recipient, kInvokeExport, promise, import->_export, array); result = tf_task_get_promise(sender, promise); } else { result = JS_ThrowInternalError(context, "Invoking a function on a nonexistent task."); } JS_FreeValue(context, array); return result; } static export_record_t* _task_get_export(tf_task_t* task, exportid_t export_id) { for (export_record_t* it = task->_exports; it; it = it->_next) { if (it->_export_id == export_id) { return it; } } return NULL; } JSValue _task_invokeExport_internal(tf_taskstub_t* from, tf_task_t* to, exportid_t exportId, const char* buffer, size_t size) { JSValue result = JS_NULL; export_record_t* export = _task_get_export(to, exportId); if (export) { JSValue arguments = tf_serialize_load((tf_task_t*)to, from, buffer, size); JSValue* argument_array = NULL; JSValue length_val = JS_GetPropertyStr(to->_context, arguments, "length"); int length; JSValue this_val = JS_NULL; if (JS_ToInt32(to->_context, &length, length_val) == 0) { if (length > 0) { this_val = JS_GetPropertyUint32(to->_context, arguments, 0); argument_array = alloca(sizeof(JSValue) * (length - 1)); } for (int i = 1; i < length; ++i) { argument_array[i - 1] = JS_GetPropertyUint32(to->_context, arguments, i); } } JSValue function = export->_function; JSPropertyDescriptor desc; const char* name = NULL; JSAtom atom = JS_NewAtom(to->_context, "name"); if (JS_GetOwnProperty(to->_context, &desc, function, atom) == 1) { name = JS_ToCString(to->_context, desc.value); JS_FreeValue(to->_context, desc.value); } JS_FreeAtom(to->_context, atom); tf_trace_begin(to->_trace, name ? name : "_task_invokeExport_internal"); if (name) { JS_FreeCString(to->_context, name); } result = JS_Call(to->_context, function, this_val, length - 1, argument_array); tf_trace_end(to->_trace); tf_task_report_error(to, result); tf_task_run_jobs(to); } else { printf("%s: That's not an export we have (exportId=%d, exports=%d)\n", to->_scriptName, exportId, to->_export_count); } tf_packetstream_send(tf_taskstub_get_stream(from), kReleaseImport, (void*)&exportId, sizeof(exportId)); return result; } static JSValue _invokeThen(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { taskid_t taskid = 0; JS_ToInt32(context, &taskid, func_data[0]); tf_task_t* from = JS_GetContextOpaque(context); tf_taskstub_t* to = _tf_task_get_stub(from, taskid); promiseid_t promiseid = 0; JS_ToInt32(context, &promiseid, func_data[1]); _tf_task_sendPromiseMessage(from, to, kResolvePromise, promiseid, argv[0]); return JS_DupValue(context, this_val); } static JSValue _invokeCatch(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { taskid_t taskid = 0; JS_ToInt32(context, &taskid, func_data[0]); tf_task_t* from = JS_GetContextOpaque(context); tf_taskstub_t* to = _tf_task_get_stub(from, taskid); promiseid_t promiseid = 0; JS_ToInt32(context, &promiseid, func_data[1]); _tf_task_sendPromiseMessage(from, to, kRejectPromise, promiseid, argv[0]); return JS_DupValue(context, this_val); } static void _forward_promise(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) { // We're not going to serialize/deserialize a promise... JSValue data[] = { JS_NewInt32(from->_context, tf_taskstub_get_id(to)), JS_NewInt32(from->_context, promise), }; JSValue promise_then = JS_GetPropertyStr(from->_context, result, "then"); JSValue promise_catch = JS_GetPropertyStr(from->_context, result, "catch"); JSValue then_handler = JS_NewCFunctionData(from->_context, _invokeThen, 0, 0, 2, data); JSValue catch_handler = JS_NewCFunctionData(from->_context, _invokeCatch, 0, 0, 2, data); JSValue error = JS_Call(from->_context, promise_then, result, 1, &then_handler); tf_task_report_error(from, error); JS_FreeValue(from->_context, error); error = JS_Call(from->_context, promise_catch, result, 1, &catch_handler); tf_task_report_error(from, error); JS_FreeValue(from->_context, error); tf_task_run_jobs(from); } static void _tf_task_sendPromiseResolve(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) { JSValue global = JS_GetGlobalObject(from->_context); JSValue promiseType = JS_GetPropertyStr(from->_context, global, "Promise"); JS_FreeValue(from->_context, global); if (JS_IsInstanceOf(from->_context, result, promiseType)) { _forward_promise(from, to, promise, result); } else { _tf_task_sendPromiseMessage(from, to, kResolvePromise, promise, result); } } static void _tf_task_sendPromiseReject(tf_task_t* from, tf_taskstub_t* to, promiseid_t promise, JSValue result) { JSValue global = JS_GetGlobalObject(from->_context); JSValue promiseType = JS_GetPropertyStr(from->_context, global, "Promise"); JS_FreeValue(from->_context, global); if (JS_IsInstanceOf(from->_context, result, promiseType)) { _forward_promise(from, to, promise, result); } else { _tf_task_sendPromiseMessage(from, to, kRejectPromise, promise, result); } } static void _tf_task_sendPromiseMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, JSValue result) { if (to) { void* buffer; size_t size; tf_serialize_store(from, to, &buffer, &size, result); char* copy = (char*)malloc(sizeof(promise) + size); memcpy(copy, &promise, sizeof(promise)); memcpy(copy + sizeof(promise), buffer, size); tf_packetstream_send(tf_taskstub_get_stream(to), messageType, copy, size + sizeof(promise)); free(buffer); free(copy); } else { printf("Sending to a NULL task.\n"); } } static void _tf_task_sendPromiseExportMessage(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t messageType, promiseid_t promise, exportid_t exportId, JSValue result) { void* buffer; size_t size; tf_serialize_store(from, to, &buffer, &size, result); char* copy = (char*)malloc(sizeof(promise) + sizeof(exportId) + size); memcpy(copy, &promise, sizeof(promise)); memcpy(copy + sizeof(promise), &exportId, sizeof(exportId)); memcpy(copy + sizeof(promise) + sizeof(exportId), buffer, size); tf_packetstream_send(tf_taskstub_get_stream(to), messageType, copy, sizeof(promise) + sizeof(exportId) + size); free(buffer); free(copy); } JSValue _tf_task_get_parent(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); return task->_parent ? tf_taskstub_get_task_object(task->_parent) : JS_UNDEFINED; } static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); return JS_NewString(task->_context, k_version); } exportid_t tf_task_export_function(tf_task_t* task, tf_taskstub_t* to, JSValue function) { export_record_t* export = NULL; for (export_record_t* it = task->_exports; it; it = it->_next) { if (JS_VALUE_GET_PTR(it->_function) == JS_VALUE_GET_PTR(function) && it->_taskid == tf_taskstub_get_id(to)) { export = it; break; } } if (!export) { int id = -1; do { id = task->_nextExport++; } while (_task_get_export(task, id)); export = malloc(sizeof(export_record_t)); *export = (export_record_t) { ._export_id = id, ._taskid = tf_taskstub_get_id(to), ._function = JS_DupValue(task->_context, function), ._next = task->_exports, }; task->_exports = export; task->_export_count++; _tf_task_trace_exports(task); } if (export) { _export_record_ref(export); } return export ? export->_export_id : -1; } static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId) { if (stub) { tf_packetstream_t* stream = tf_taskstub_get_stream(stub); if (stream) { tf_packetstream_send(stream, kReleaseExport, (void*)&exportId, sizeof(exportId)); } } } static JSValue _tf_task_trace(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv){ tf_task_t* task = JS_GetContextOpaque(context); if (!task->_trace) { return JS_UNDEFINED; } char* trace = tf_trace_export(task->_trace); JSValue result = JS_NewString(context, trace); free(trace); return result; } static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); const char* name = JS_ToCString(context, argv[0]); JSValue result = JS_GetPropertyStr(context, task->_loadedFiles, name); JS_FreeCString(context, name); return result; } void tf_task_on_receive_packet(int packetType, const char* begin, size_t length, void* userData) { tf_taskstub_t* stub = userData; tf_taskstub_t* from = stub; tf_task_t* to = tf_taskstub_get_owner(stub); tf_trace_begin(to->_trace, "tf_task_on_receive_packet"); switch (packetType) { case kInvokeExport: { promiseid_t promise; exportid_t exportId; memcpy(&promise, begin, sizeof(promise)); memcpy(&exportId, begin + sizeof(promise), sizeof(exportId)); JSValue result = _task_invokeExport_internal(from, to, exportId, begin + sizeof(promiseid_t) + sizeof(exportid_t), length); if (JS_IsException(result)) { _tf_task_sendPromiseReject(to, from, promise, result); } else { _tf_task_sendPromiseResolve(to, from, promise, result); } } break; case kResolvePromise: case kRejectPromise: { JSValue arg = JS_UNDEFINED; promiseid_t promise; memcpy(&promise, begin, sizeof(promiseid_t)); if (length > sizeof(promiseid_t)) { arg = tf_serialize_load((tf_task_t*)to, from, begin + sizeof(promiseid_t), length - sizeof(promiseid_t)); } if (packetType == kResolvePromise) { tf_task_resolve_promise(to, promise, arg); } else { tf_task_reject_promise(to, promise, arg); } JS_FreeValue(to->_context, arg); } break; case kReleaseExport: { assert(length == sizeof(exportid_t)); exportid_t exportId; memcpy(&exportId, begin, sizeof(exportId)); for (export_record_t** it = &to->_exports; *it; it = &(*it)->_next) { export_record_t* export = *it; if (export->_export_id == exportId && export->_taskid == tf_taskstub_get_id(from)) { if (_export_record_release(export)) { *it = export->_next; JS_FreeValue(to->_context, export->_function); free(export); to->_export_count--; _tf_task_trace_exports(to); } break; } } } break; case kReleaseImport: { assert(length == sizeof(exportid_t)); exportid_t exportId; memcpy(&exportId, begin, sizeof(exportId)); for (import_record_t* it = to->_imports; it; it = it->_next) { if (it->_task == tf_taskstub_get_id(from) && it->_export == exportId) { _import_record_release(it); break; } } } break; case kSetRequires: to->_requires = tf_serialize_load((tf_task_t*)to, from, begin, length); break; case kLoadFile: { JSValue args = tf_serialize_load((tf_task_t*)to, from, begin, length); JSValue key = JS_GetPropertyUint32(to->_context, args, 0); JSValue content = JS_GetPropertyUint32(to->_context, args, 1); const char* name = JS_ToCString(to->_context, key); JS_SetPropertyStr(to->_context, to->_loadedFiles, name, JS_DupValue(to->_context, content)); JS_FreeCString(to->_context, name); JS_FreeValue(to->_context, key); JS_FreeValue(to->_context, content); JS_FreeValue(to->_context, args); } break; case kActivate: tf_task_activate(to); break; case kExecute: { assert(length >= sizeof(promiseid_t)); promiseid_t promise; memcpy(&promise, begin, sizeof(promiseid_t)); JSValue value = tf_serialize_load((tf_task_t*)to, from, begin + sizeof(promiseid_t), length - sizeof(promiseid_t)); JSValue source = JS_GetPropertyStr(to->_context, value, "source"); JSValue name_val = JS_GetPropertyStr(to->_context, value, "name"); const char* name = JS_ToCString(to->_context, name_val); JS_FreeValue(to->_context, name_val); JS_FreeValue(to->_context, value); JSValue utf8 = _tf_task_utf8Decode(to->_context, JS_NULL, 1, &source); const char* source_str = JS_ToCString(to->_context, utf8); JS_FreeValue(to->_context, utf8); _tf_task_executeSource(to, source_str, name); JS_FreeCString(to->_context, source_str); JS_FreeCString(to->_context, name); _tf_task_sendPromiseResolve(to, from, promise, JS_UNDEFINED); } break; case kKill: exit(1); break; case kSetImports: { JSValue global = JS_GetGlobalObject(to->_context); JSValue imports = tf_serialize_load((tf_task_t*)to, from, begin, length); JSPropertyEnum* ptab; uint32_t plen; JS_GetOwnPropertyNames(to->_context, &ptab, &plen, imports, JS_GPN_STRING_MASK); for (uint32_t i = 0; i < plen; ++i) { JSPropertyDescriptor desc; if (JS_GetOwnProperty(to->_context, &desc, imports, ptab[i].atom) == 1) { JS_SetProperty(to->_context, global, ptab[i].atom, desc.value); } JS_FreeAtom(to->_context, ptab[i].atom); } js_free(to->_context, ptab); JS_SetPropertyStr(to->_context, global, "imports", imports); JS_FreeValue(to->_context, global); } break; case kGetExports: { promiseid_t promise; assert(length >= sizeof(promise)); memcpy(&promise, begin, sizeof(promiseid_t)); JSValue global = JS_GetGlobalObject(to->_context); JSValue exportObject = JS_GetPropertyStr(to->_context, global, "exports"); _tf_task_sendPromiseResolve(to, from, promise, exportObject); JS_FreeValue(to->_context, exportObject); JS_FreeValue(to->_context, global); } break; case kTaskError: { JSValue error = tf_serialize_load((tf_task_t*)to, from, begin, length); tf_taskstub_on_error(from, error); JS_FreeValue(to->_context, error); } break; } tf_trace_end(to->_trace); } static const char* _tf_task_resolveRequire(tf_task_t* task, const char* require) { printf("Looking in %s for %s\n", task->_path, require); if (strstr(require, "..") || strstr(require, "/") || strstr(require, "\\")) { return NULL; } char test[1024]; snprintf(test, sizeof(test), "%s/%s%s", task->_path, require, strstr(require, ".js") ? "" : ".js"); printf("Testing %s\n", test); uv_fs_t request; if (uv_fs_access(task->_loop, &request, test, R_OK, 0) == 0) { return strdup(test); } return NULL; } static script_export_t* _task_find_script_export(tf_task_t* task, const char* path) { for (script_export_t* it = task->_scriptExports; it; it = it->next) { if (strcmp(it->name, path) == 0) { return it; } } return NULL; } JSValue _tf_task_require(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_NULL; tf_task_t* task = JS_GetContextOpaque(context); tf_trace_begin(task->_trace, "_tf_task_require"); const char* in_path = JS_ToCString(context, argv[0]); if (in_path) { const char* path = _tf_task_resolveRequire(task, in_path); if (!path) { result = JS_ThrowReferenceError(task->_context, "require(): Unable to resolve module: %s.", in_path); } else { script_export_t* it = _task_find_script_export(task, path); if (it) { result = JS_DupValue(task->_context, it->value); free((void*)path); } else { JSValue exports = JS_NewObject(task->_context); script_export_t* export = malloc(sizeof(script_export_t)); *export = (script_export_t) { .name = path, .value = JS_DupValue(task->_context, exports), .next = task->_scriptExports, }; task->_scriptExports = export; const char* source = _task_loadFile(path); printf("Requiring script %s\n", path); if (source) { JSValue global = JS_GetGlobalObject(task->_context); JSValue oldExports = JS_GetPropertyStr(task->_context, global, "exports"); JS_SetPropertyStr(task->_context, global, "exports", JS_DupValue(task->_context, exports)); JSValue eval = JS_Eval(task->_context, source, strlen(source), path, 0); tf_task_report_error(task, eval); tf_task_run_jobs(task); if (JS_IsError(task->_context, eval) || JS_IsException(eval)) { result = JS_DupValue(task->_context, eval); } else { result = JS_DupValue(task->_context, exports); } JS_FreeValue(task->_context, eval); JS_SetPropertyStr(task->_context, global, "exports", oldExports); JS_FreeValue(task->_context, global); free((void*)source); } else { printf("Failed to load %s.\n", path); } JS_FreeValue(task->_context, exports); } } } else { result = JS_ThrowReferenceError(task->_context, "require(): No module specified."); } JS_FreeCString(context, in_path); tf_trace_end(task->_trace); return result; } static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name) { tf_trace_begin(task->_trace, "_tf_task_executeSource"); JSValue result = JS_Eval(task->_context, source, strlen(source), name, 0); tf_task_report_error(task, result); if (!*task->_scriptName) { snprintf(task->_scriptName, sizeof(task->_scriptName), "%s", name); } tf_task_run_jobs(task); tf_trace_end(task->_trace); return result; } JSValue _tf_task_sandbox_require(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); if (JS_IsObject(task->_loadedFiles)) { const char* name = JS_ToCString(context, argv[0]); script_export_t* it = _task_find_script_export(task, name); if (it) { return it->value; } else { JSValue exports = JS_NewObject(context); const char* name = JS_ToCString(context, argv[0]); JSValue value = JS_GetPropertyStr(context, task->_loadedFiles, name); size_t length; uint8_t* array = tf_try_get_array_buffer(context, &length, value); if (array) { char* source = malloc(length + 1); memcpy(source, array, length); source[length] = '\0'; JSValue global = JS_GetGlobalObject(context); JSValue oldExports = JS_GetPropertyStr(context, global, "exports"); JS_SetPropertyStr(context, global, "exports", JS_DupValue(context, exports)); JSValue result = JS_Eval(context, source, length, name, 0); tf_task_report_error(task, result); JS_SetPropertyStr(context, global, "exports", oldExports); JS_FreeValue(context, global); tf_task_run_jobs(task); free(source); return exports; } else if (JS_IsString(value)) { size_t length; const char* source = JS_ToCStringLen(context, &length, value); JSValue global = JS_GetGlobalObject(context); JSValue oldExports = JS_GetPropertyStr(context, global, "exports"); JS_SetPropertyStr(context, global, "exports", JS_DupValue(context, exports)); JSValue result = JS_Eval(context, source, length, name, 0); tf_task_report_error(task, result); JS_SetPropertyStr(context, global, "exports", oldExports); JS_FreeValue(context, global); tf_task_run_jobs(task); return exports; } else { return JS_ThrowInternalError(context, "Failed to load %s %d %d %d.", name, JS_IsNull(value), JS_IsUndefined(value), JS_IsException(value)); } } } return JS_UNDEFINED; } static JSValue _utf8Decode(JSContext* context, uint8_t* data, size_t length) { return JS_NewStringLen(context, (const char*)data, length); } static JSValue _tf_task_utf8Decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_NULL; size_t length; if (JS_IsString(argv[0])) { result = JS_DupValue(context, argv[0]); } else { uint8_t* array = tf_try_get_array_buffer(context, &length, argv[0]); if (array) { result = _utf8Decode(context, array, length); } else { size_t offset; size_t element_size; JSValue buffer = tf_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); size_t size; if (!JS_IsException(buffer)) { array = tf_try_get_array_buffer(context, &size, buffer); if (array) { result = _utf8Decode(context, array, size); } } JS_FreeValue(context, buffer); } } return result; } uv_loop_t* tf_task_get_loop(tf_task_t* task) { return task->_loop; } static promise_t* _tf_task_find_promise(tf_task_t* task, promiseid_t id) { for (promise_t* it = task->_promises; it; it = it->next) { if (it->id == id) { return it; } } return NULL; } static void _tf_task_trace_promises(tf_task_t* task) { tf_trace_counter(task->_trace, "promises", 1, (const char*[]) { "count" }, (int64_t[]) { task->_promise_count }); } static void _tf_task_free_promise(tf_task_t* task, promiseid_t id) { for (promise_t** it = &task->_promises; *it; it = &(*it)->next) { if ((*it)->id == id) { promise_t* promise = *it; *it = (*it)->next; free(promise); task->_promise_count--; _tf_task_trace_promises(task); break; } } } promiseid_t tf_task_allocate_promise(tf_task_t* task) { promiseid_t promiseId; do { promiseId = task->_nextPromise++; } while (_tf_task_find_promise(task, promiseId)); promise_t* promise = malloc(sizeof(promise_t)); *promise = (promise_t) { .id = promiseId, .next = task->_promises, .values = { JS_NULL, JS_NULL, JS_NULL }, }; promise->values[0] = JS_NewPromiseCapability(task->_context, &promise->values[1]); task->_promises = promise; task->_promise_count++; _tf_task_trace_promises(task); return promiseId; } void tf_task_resolve_promise(tf_task_t* task, promiseid_t promise, JSValue value) { promise_t* it = _tf_task_find_promise(task, promise); if (it) { JSValue result = JS_Call(task->_context, it->values[1], JS_UNDEFINED, 1, &value); tf_task_report_error(task, result); JS_FreeValue(task->_context, it->values[1]); JS_FreeValue(task->_context, it->values[2]); JS_FreeValue(task->_context, result); _tf_task_free_promise(task, promise); tf_task_run_jobs(task); } else { printf("Didn't find promise %d to resolve.\n", promise); abort(); } } void tf_task_reject_promise(tf_task_t* task, promiseid_t promise, JSValue value) { promise_t* it = _tf_task_find_promise(task, promise); if (it) { JSValue result = JS_Call(task->_context, it->values[2], JS_UNDEFINED, 1, &value); tf_task_report_error(task, result); JS_FreeValue(task->_context, it->values[1]); JS_FreeValue(task->_context, it->values[2]); JS_FreeValue(task->_context, result); _tf_task_free_promise(task, promise); tf_task_run_jobs(task); } } JSValue tf_task_get_promise(tf_task_t* task, promiseid_t promise) { promise_t* it = _tf_task_find_promise(task, promise); return it ? it->values[0] : JS_NULL; } static void _tf_task_trace_children(tf_task_t* task) { tf_trace_counter(task->_trace, "child_tasks", 1, (const char*[]) { "count" }, (int64_t[]) { task->_child_count }); } taskid_t tf_task_allocate_task_id(tf_task_t* task, tf_taskstub_t* stub) { taskid_t id = 0; do { id = task->_nextTask++; } while (id == k_task_parent_id || _tf_task_get_stub(task, id)); task_child_node_t* node = malloc(sizeof(task_child_node_t)); *node = (task_child_node_t) { .id = id, .stub = stub, .next = task->_children, }; task->_children = node; task->_child_count++; _tf_task_trace_children(task); return id; } void tf_task_remove_child(tf_task_t* task, tf_taskstub_t* child) { _import_record_release_for_task(task, tf_taskstub_get_id(child)); _export_record_release_for_task(task, tf_taskstub_get_id(child)); for (task_child_node_t** it = &task->_children; *it; it = &(*it)->next) { if ((*it)->stub == child) { task_child_node_t* node = *it; *it = node->next; free(node); task->_child_count--; _tf_task_trace_children(task); break; } } } static void _import_finalizer(JSRuntime* runtime, JSValue value) { import_record_t* import = JS_GetOpaque(value, _import_class_id); _import_record_release(import); } static void _import_mark_func(JSRuntime* runtime, JSValueConst value, JS_MarkFunc mark_func) { import_record_t* import = JS_GetOpaque(value, _import_class_id); if (import && import->_useCount > 0) { JS_MarkValue(runtime, import->_function, mark_func); } } static void _tf_task_promise_rejection_tracker(JSContext* context, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* user_data) { tf_task_t* task = user_data; if (!is_handled) { tf_task_report_error(task, reason); } } tf_task_t* tf_task_create() { tf_task_t* task = malloc(sizeof(tf_task_t)); *task = (tf_task_t) { 0 }; task->_loop = uv_loop_new(); ++_count; task->_runtime = JS_NewRuntime(); task->_context = JS_NewContext(task->_runtime); JS_SetContextOpaque(task->_context, task); JS_SetHostPromiseRejectionTracker(task->_runtime, _tf_task_promise_rejection_tracker, task); JS_NewClassID(&_import_class_id); JSClassDef def = { .class_name = "imported_function", .finalizer = _import_finalizer, .gc_mark = _import_mark_func, .call = _import_call, }; JS_NewClass(task->_runtime, _import_class_id, &def); task->_loadedFiles = JS_NewObject(task->_context); return task; } void tf_task_configure_from_stdin(tf_task_t* task) { task->_parent = tf_taskstub_create_parent(task, STDIN_FILENO); } void tf_task_activate(tf_task_t* task) { JSContext* context = task->_context; JSValue global = JS_GetGlobalObject(context); JS_SetPropertyStr(context, global, "exports", JS_NewObject(context)); JSAtom atom = JS_NewAtom(context, "parent"); JS_DefinePropertyGetSet(context, global, atom, JS_NewCFunction(context, _tf_task_get_parent, "parent", 0), JS_UNDEFINED, 0); JS_FreeAtom(context, atom); JSValue tildefriends = JS_NewObject(context); JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, task->_ssb_port)); JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port)); JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port)); JS_SetPropertyStr(context, global, "tildefriends", tildefriends); if (task->_trusted) { sqlite3_open(task->_db_path ? task->_db_path : "db.sqlite", &task->_db); JS_SetPropertyStr(context, global, "require", JS_NewCFunction(context, _tf_task_require, "require", 1)); JS_SetPropertyStr(context, global, "Task", tf_taskstub_init(context)); JS_SetPropertyStr(context, global, "Socket", tf_socket_init(context)); JS_SetPropertyStr(context, global, "TlsContext", tf_tls_context_wrapper_init(context)); tf_database_init(context, task->_db); tf_file_init(context); task->_trace = tf_trace_create(); task->_ssb = tf_ssb_create(task->_loop, task->_context, task->_db, task->_secrets_path); tf_ssb_set_trace(task->_ssb, task->_trace); tf_ssb_broadcast_listener_start(task->_ssb, false); tf_ssb_init(context, task->_ssb); if (task->_ssb_port) { tf_ssb_server_open(task->_ssb, task->_ssb_port); } JS_SetPropertyStr(context, global, "trace", JS_NewCFunction(context, _tf_task_trace, "trace", 1)); } else { JS_SetPropertyStr(context, global, "require", JS_NewCFunction(context, _tf_task_sandbox_require, "sandboxRequire", 0)); } tf_bcrypt_init(context); JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _tf_task_utf8Decode, "utf8Decode", 1)); JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _tf_task_print, "print", 0)); JS_SetPropertyStr(context, global, "exit", JS_NewCFunction(context, _tf_task_exit, "exit", 1)); JS_SetPropertyStr(context, global, "version", JS_NewCFunction(context, _tf_task_version, "version", 0)); JS_SetPropertyStr(context, global, "setTimeout", JS_NewCFunction(context, _task_setTimeout, "setTimeout", 2)); JS_SetPropertyStr(context, global, "getFile", JS_NewCFunction(context, _tf_task_getFile, "getFile", 1)); JS_FreeValue(context, global); } void tf_task_run(tf_task_t* task) { uv_run(task->_loop, UV_RUN_DEFAULT); } void tf_task_set_trusted(tf_task_t* task, bool trusted) { task->_trusted = trusted; } JSContext* tf_task_get_context(tf_task_t* task) { return task->_context; } void tf_task_destroy(tf_task_t* task) { import_record_t* it = task->_imports; while (it) { import_record_t* next = it->_next; JS_FreeValue(task->_context, it->_function); free(it); it = next; } task->_imports = NULL; while (task->_exports) { export_record_t* export = task->_exports; JS_FreeValue(task->_context, export->_function); task->_exports = export->_next; free(export); } while (task->_children) { task_child_node_t* node = task->_children; tf_taskstub_destroy(node->stub); task->_children = node->next; free(node); } if (task->_parent) { JS_FreeValue(task->_context, tf_taskstub_get_task_object(task->_parent)); } while (task->_promises) { tf_task_reject_promise(task, task->_promises->id, JS_NULL); } JS_FreeValue(task->_context, task->_requires); JS_FreeValue(task->_context, task->_loadedFiles); while (task->_scriptExports) { script_export_t* export = task->_scriptExports; JS_FreeValue(task->_context, export->value); task->_scriptExports = export->next; free((void*)export->name); free(export); } if (task->_trace) { tf_trace_destroy(task->_trace); } if (task->_ssb) { tf_ssb_destroy(task->_ssb); } JS_FreeContext(task->_context); JS_FreeRuntime(task->_runtime); if (task->_db) { sqlite3_close(task->_db); } uv_print_all_handles(task->_loop, stdout); uv_loop_delete(task->_loop); --_count; free((void*)task->_path); free(task); } JSValue tf_task_add_import(tf_task_t* task, taskid_t stub_id, exportid_t export_id) { JSValue function = JS_NewObjectClass(task->_context, _import_class_id); import_record_t* import = malloc(sizeof(import_record_t)); JS_SetOpaque(function, import); *import = (import_record_t) { ._function = JS_DupValue(task->_context, function), ._export = export_id, ._owner = task, ._task = stub_id, ._useCount = 1, ._next = task->_imports, }; task->_imports = import; task->_import_count++; _tf_task_trace_imports(task); return function; } tf_task_t* tf_task_get(JSContext* context) { return JS_GetContextOpaque(context); } void tf_task_run_jobs(tf_task_t* task) { while (JS_IsJobPending(task->_runtime)) { JSContext* context = NULL; int r = JS_ExecutePendingJob(task->_runtime, &context); JSValue result = JS_GetException(context); tf_task_report_error(task, result); if (r < 0) { js_std_dump_error(context); } else if (r == 0) { break; } } } void tf_task_send_promise_message(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t type, promiseid_t promise, JSValue payload) { _tf_task_sendPromiseMessage(from, to, type, promise, payload); } JSValue tf_try_get_typed_array_buffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, size_t *pbytes_per_element) { JSValue result = JS_GetTypedArrayBuffer(ctx, obj, pbyte_offset, pbyte_length, pbytes_per_element); JS_FreeValue(ctx, JS_GetException(ctx)); return result; } uint8_t *tf_try_get_array_buffer(JSContext *ctx, size_t *psize, JSValueConst obj) { uint8_t* result = JS_GetArrayBuffer(ctx, psize, obj); JS_FreeValue(ctx, JS_GetException(ctx)); return result; } void tf_task_set_ssb_port(tf_task_t* task, int port) { task->_ssb_port = port; } void tf_task_set_http_port(tf_task_t* task, int port) { task->_http_port = port; } void tf_task_set_https_port(tf_task_t* task, int port) { task->_https_port = port; } void tf_task_set_db_path(tf_task_t* task, const char* db_path) { task->_db_path = db_path; } void tf_task_set_secrets_path(tf_task_t* task, const char* secrets_path) { task->_secrets_path = secrets_path; }