#include "util.js.h" #include "log.h" #include "mem.h" #include "task.h" #include "trace.h" #include "backtrace.h" #include "openssl/sha.h" #include "picohttpparser.h" #include "sodium/utils.h" #include "uv.h" #include #if defined(__ANDROID__) #include #elif !defined(_WIN32) && !defined(__HAIKU__) #include #endif static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { size_t length = 0; const char* value = JS_ToCStringLen(context, &length, argv[0]); JSValue typed_array = tf_util_new_uint8_array(context, (const uint8_t*)value, length); JS_FreeCString(context, value); return typed_array; } static JSValue _util_utf8_decode(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_util_try_get_array_buffer(context, &length, argv[0]); if (array) { result = JS_NewStringLen(context, (const char*)array, length); } else { size_t offset; size_t element_size; JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); size_t size; if (!JS_IsException(buffer)) { array = tf_util_try_get_array_buffer(context, &size, buffer); if (array) { result = JS_NewStringLen(context, (const char*)array, size); } } JS_FreeValue(context, buffer); } } return result; } JSValue tf_util_utf8_decode(JSContext* context, JSValue value) { return _util_utf8_decode(context, JS_NULL, 1, &value); } static JSValue _util_base64_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; size_t length = 0; uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]); if (array) { char* encoded = tf_malloc(length * 4); int r = tf_base64_encode(array, length, encoded, length * 4); if (r >= 0) { result = JS_NewStringLen(context, encoded, r); } tf_free(encoded); } else { const char* value = JS_ToCStringLen(context, &length, argv[0]); char* encoded = tf_malloc(length * 4); int r = tf_base64_encode((const uint8_t*)value, length, encoded, length * 4); if (r >= 0) { result = JS_NewStringLen(context, encoded, r); } tf_free(encoded); JS_FreeCString(context, value); } return result; } static JSValue _util_base64_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; size_t length = 0; const char* value = JS_ToCStringLen(context, &length, argv[0]); uint8_t* encoded = tf_malloc(length); int r = tf_base64_decode(value, length, encoded, length); if (r >= 0) { result = JS_NewStringLen(context, (const char*)encoded, r); } tf_free(encoded); JS_FreeCString(context, value); return result; } uint8_t* tf_util_try_get_array_buffer(JSContext* context, size_t* psize, JSValueConst obj) { uint8_t* result = JS_GetArrayBuffer(context, psize, obj); JS_FreeValue(context, JS_GetException(context)); return result; } JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element) { JSValue result = JS_GetTypedArrayBuffer(context, obj, pbyte_offset, pbyte_length, pbytes_per_element); JS_FreeValue(context, JS_GetException(context)); return result; } JSValue _util_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); if (task) { tf_printf("Task[%p:%s]>", task, tf_task_get_name(task)); tf_task_print(task, argc, argv); } for (int i = 0; i < argc; ++i) { if (JS_IsNull(argv[i])) { tf_printf(" null"); } else { const char* value = JS_ToCString(context, argv[i]); tf_printf(" %s", value); JS_FreeCString(context, value); } } tf_printf("\n"); return JS_NULL; } bool tf_util_report_error(JSContext* context, JSValue value) { bool is_error = false; if (JS_IsError(context, value)) { const char* string = JS_ToCString(context, value); tf_printf("ERROR: %s\n", string); JS_FreeCString(context, string); JSValue stack = JS_GetPropertyStr(context, value, "stack"); if (!JS_IsUndefined(stack)) { const char* stack_str = JS_ToCString(context, stack); tf_printf("%s\n", stack_str); JS_FreeCString(context, stack_str); } JS_FreeValue(context, stack); tf_task_t* task = tf_task_get(context); tf_task_send_error_to_parent(task, value); is_error = true; } else if (JS_IsException(value)) { tf_task_t* task = tf_task_get(context); tf_task_send_error_to_parent(task, value); is_error = true; } return is_error; } typedef struct _timeout_t { uv_timer_t _timer; tf_task_t* _task; JSValue _callback; } timeout_t; static void _handle_closed(uv_handle_t* handle) { timeout_t* timeout = handle->data; tf_free(timeout); } static void _util_timeoutCallback(uv_timer_t* handle) { timeout_t* timeout = handle->data; tf_trace_begin(tf_task_get_trace(timeout->_task), "_util_timeoutCallback"); JSContext* context = tf_task_get_context(timeout->_task); JSValue result = JS_Call( context, timeout->_callback, JS_NULL, 0, NULL); tf_util_report_error(context, result); JS_FreeValue(context, result); JS_FreeValue(context, timeout->_callback); tf_trace_end(tf_task_get_trace(timeout->_task)); uv_close((uv_handle_t*)handle, _handle_closed); } static JSValue _util_setTimeout(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_task_t* task = JS_GetContextOpaque(context); timeout_t* timeout = tf_malloc(sizeof(timeout_t)); *timeout = (timeout_t) { ._task = task, ._callback = JS_DupValue(context, argv[0]), ._timer = { .data = timeout }, }; uv_timer_init(tf_task_get_loop(task), &timeout->_timer); int64_t duration; JS_ToInt64(context, &duration, argv[1]); if (uv_timer_start(&timeout->_timer, _util_timeoutCallback, duration, 0) != 0) { tf_free(timeout); } return JS_NULL; } static JSValue _util_parseHttpRequest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; const char* method = NULL; size_t method_length = 0; const char* path = NULL; size_t path_length = 0; int minor_version = 0; struct phr_header headers[100]; size_t header_count = sizeof(headers) / sizeof(*headers); int previous_length = 0; JS_ToInt32(context, &previous_length, argv[1]); JSValue buffer = JS_UNDEFINED; size_t length; uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]); if (!array) { size_t offset; size_t element_size; buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); if (!JS_IsException(buffer)) { array = tf_util_try_get_array_buffer(context, &length, buffer); } } if (array) { int parse_result = phr_parse_request((const char*)array, length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, previous_length); if (parse_result > 0) { result = JS_NewObject(context); JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result)); JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version)); JS_SetPropertyStr(context, result, "method", JS_NewStringLen(context, method, method_length)); JS_SetPropertyStr(context, result, "path", JS_NewStringLen(context, path, path_length)); JSValue header_object = JS_NewObject(context); for (int i = 0; i < (int)header_count; i++) { char name[256]; snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name); JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len)); } JS_SetPropertyStr(context, result, "headers", header_object); } else { result = JS_NewInt32(context, parse_result); } } else { result = JS_ThrowTypeError(context, "Could not convert argument to array."); } JS_FreeValue(context, buffer); return result; } static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; int status = 0; int minor_version = 0; const char* message = NULL; size_t message_length = 0; struct phr_header headers[100]; size_t header_count = sizeof(headers) / sizeof(*headers); int previous_length = 0; JS_ToInt32(context, &previous_length, argv[1]); JSValue buffer = JS_UNDEFINED; size_t length; uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]); if (!array) { size_t offset; size_t element_size; buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); if (!JS_IsException(buffer)) { array = tf_util_try_get_array_buffer(context, &length, buffer); } } if (array) { int parse_result = phr_parse_response((const char*)array, length, &minor_version, &status, &message, &message_length, headers, &header_count, previous_length); if (parse_result > 0) { result = JS_NewObject(context); JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result)); JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version)); JS_SetPropertyStr(context, result, "status", JS_NewInt32(context, status)); JS_SetPropertyStr(context, result, "message", JS_NewStringLen(context, message, message_length)); JSValue header_object = JS_NewObject(context); for (int i = 0; i < (int)header_count; i++) { char name[256]; snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name); JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len)); } JS_SetPropertyStr(context, result, "headers", header_object); } else { result = JS_NewInt32(context, parse_result); } } else { result = JS_ThrowTypeError(context, "Could not convert argument to array."); } JS_FreeValue(context, buffer); return result; } static JSValue _util_sha1_digest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { size_t length = 0; const char* value = JS_ToCStringLen(context, &length, argv[0]); unsigned char digest[SHA_DIGEST_LENGTH] = { 0 }; SHA1((const unsigned char*)value, length, digest); JS_FreeCString(context, value); return JS_NewArrayBufferCopy(context, digest, sizeof(digest)); } JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size) { JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size); JSValue global = JS_GetGlobalObject(context); JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array"); JSValue result = JS_CallConstructor(context, constructor, 1, &array_buffer); JS_FreeValue(context, constructor); JS_FreeValue(context, global); JS_FreeValue(context, array_buffer); return result; } static JSValue _util_mask_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; uint32_t mask = 0; JS_ToUint32(context, &mask, argv[1]); uint64_t double_mask = ((uint64_t)mask << 32) | mask; size_t offset = 0; size_t length = 0; size_t element_size = 0; JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size); if (!JS_IsException(buffer)) { size_t size = 0; const uint8_t* array = tf_util_try_get_array_buffer(context, &size, buffer); if (array) { uint8_t* copy = tf_malloc(size); size_t i = 0; for (; i + sizeof(double_mask) < size; i += sizeof(double_mask)) { ((uint64_t*)copy)[i / sizeof(double_mask)] = ((const uint64_t*)array)[i / sizeof(double_mask)] ^ double_mask; } for (; i + sizeof(mask) < size; i += sizeof(mask)) { ((uint32_t*)copy)[i / sizeof(mask)] = ((const uint32_t*)array)[i / sizeof(mask)] ^ mask; } for (; i < size; i++) { copy[i] = array[i] ^ ((mask >> (8 * (i % 4))) & 0xff); } result = tf_util_new_uint8_array(context, copy, size); tf_free(copy); } } JS_FreeValue(context, buffer); return result; } void tf_util_register(JSContext* context) { JSValue global = JS_GetGlobalObject(context); JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _util_utf8_decode, "utf8Decode", 1)); JS_SetPropertyStr(context, global, "utf8Encode", JS_NewCFunction(context, _util_utf8_encode, "utf8Encode", 1)); JS_SetPropertyStr(context, global, "base64Decode", JS_NewCFunction(context, _util_base64_decode, "base64Decode", 1)); JS_SetPropertyStr(context, global, "base64Encode", JS_NewCFunction(context, _util_base64_encode, "base64Encode", 1)); JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1)); JS_SetPropertyStr(context, global, "setTimeout", JS_NewCFunction(context, _util_setTimeout, "setTimeout", 2)); JS_SetPropertyStr(context, global, "parseHttpRequest", JS_NewCFunction(context, _util_parseHttpRequest, "parseHttpRequest", 2)); JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2)); JS_SetPropertyStr(context, global, "sha1Digest", JS_NewCFunction(context, _util_sha1_digest, "sha1Digest", 1)); JS_SetPropertyStr(context, global, "maskBytes", JS_NewCFunction(context, _util_mask_bytes, "maskBytes", 2)); JS_FreeValue(context, global); } int tf_util_get_length(JSContext* context, JSValue value) { JSValue length = JS_GetPropertyStr(context, value, "length"); int result = 0; JS_ToInt32(context, &result, length); JS_FreeValue(context, length); return result; } int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*)) { int lower = 0; int upper = count; while (lower < upper && lower < (int)count) { int guess = (lower + upper) / 2; int result = compare(key, ((char*)base) + size * guess); if (result < 0) { upper = guess; } else if (result > 0) { lower = guess + 1; } else { return guess; } }; return lower; } size_t tf_base64_encode(const uint8_t* source, size_t source_length, char* out, size_t out_length) { sodium_bin2base64(out, out_length, source, source_length, sodium_base64_VARIANT_ORIGINAL); return sodium_base64_ENCODED_LEN(source_length, sodium_base64_VARIANT_ORIGINAL) - 1; } size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out, size_t out_length) { size_t actual_length = 0; return sodium_base642bin(out, out_length, source, source_length, NULL, &actual_length, NULL, sodium_base64_VARIANT_ORIGINAL) == 0 ? actual_length : 0; } static int _tf_util_backtrace_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function) { char** stack = data; char line[256]; int length = snprintf(line, sizeof(line), "%p %s:%d %s\n", (void*)pc, filename, line_number, function); int current = *stack ? strlen(*stack) : 0; *stack = tf_resize_vec(*stack, current + length + 1); memcpy(*stack + current, line, length + 1); return 0; } static void _tf_util_backtrace_error(void* data, const char* message, int error) { char** stack = data; if (message) { int length = strlen(message); int current = *stack ? strlen(*stack) : 0; *stack = tf_resize_vec(*stack, current + length + 1); memcpy(*stack + current, message, length + 1); } } const char* tf_util_backtrace_to_string(void* const* buffer, int count) { extern struct backtrace_state* g_backtrace_state; char* string = NULL; for (int i = 0; i < count; i++) { backtrace_pcinfo( g_backtrace_state, (uintptr_t)buffer[i], _tf_util_backtrace_callback, _tf_util_backtrace_error, &string); } return string; } static int _tf_util_backtrace_single_callback(void* data, uintptr_t pc, const char* filename, int line_number, const char* function) { char** stack = data; char line[256]; int length = snprintf(line, sizeof(line), "%s", function); int current = *stack ? strlen(*stack) : 0; *stack = tf_resize_vec(*stack, current + length + 1); memcpy(*stack + current, line, length + 1); return 0; } const char* tf_util_function_to_string(void* function) { extern struct backtrace_state* g_backtrace_state; char* string = NULL; backtrace_pcinfo( g_backtrace_state, (uintptr_t)function, _tf_util_backtrace_single_callback, _tf_util_backtrace_error, &string); return string; } const char* tf_util_backtrace_string() { void* buffer[32]; int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer)); return tf_util_backtrace_to_string(buffer, count); } #if defined(__ANDROID__) typedef struct _android_backtrace_t { void** current; void** end; } android_backtrace_t; static _Unwind_Reason_Code _android_unwind_callback(struct _Unwind_Context* context, void* arg) { android_backtrace_t* state = arg; uintptr_t pc = _Unwind_GetIP(context); if (pc) { if (state->current == state->end) { return _URC_END_OF_STACK; } else { *state->current++ = (void*)pc; } } return _URC_NO_REASON; } #endif int tf_util_backtrace(void** buffer, int count) { #ifdef _WIN32 return CaptureStackBackTrace(0, count, buffer, NULL); #elif defined(__ANDROID__) android_backtrace_t state = { .current = buffer, .end = buffer + count }; _Unwind_Backtrace(_android_unwind_callback, &state); return state.current - buffer; #elif defined(__HAIKU__) return 0; #else return backtrace(buffer, count); #endif }