diff --git a/apps/ssb/tf-message.js b/apps/ssb/tf-message.js index 5a48472a..fc161345 100644 --- a/apps/ssb/tf-message.js +++ b/apps/ssb/tf-message.js @@ -775,7 +775,9 @@ class TfMessageElement extends LitElement { } } else { return this.render_small_frame( - html`

type: ${content.type}

` + html`
+

type: ${content.type}

+
` ); } } else if (typeof this.message.content == 'string') { diff --git a/core/app.js b/core/app.js index 48e8b4c6..6a535a64 100644 --- a/core/app.js +++ b/core/app.js @@ -81,9 +81,8 @@ App.prototype.send = function (message) { * TODOC * @param {*} request * @param {*} response - * @param {*} client */ -async function socket(request, response, client) { +exports.app_socket = async function socket(request, response) { let process; let options = {}; let credentials = await httpd.auth_query(request.headers); @@ -245,6 +244,6 @@ async function socket(request, response, client) { }; response.upgrade(100, {}); -} +}; -export {socket, App}; +export {App}; diff --git a/core/core.js b/core/core.js index 67a71e3f..f7899314 100644 --- a/core/core.js +++ b/core/core.js @@ -835,61 +835,4 @@ exports.callAppHandler = async function callAppHandler( response.end(answer?.data); }; -/** - * TODOC - */ -loadSettings() - .then(function (settings) { - if (tildefriends.https_port && settings.http_redirect) { - httpd.set_http_redirect(settings.http_redirect); - } - httpd.all('/app/socket', app.socket); - if (tildefriends.http_port > 0 || tildefriends.args.out_http_port_file) { - let port = httpd.start(tildefriends.http_port); - if (tildefriends.args.out_http_port_file) { - print('Writing the port file.'); - File.writeFile( - tildefriends.args.out_http_port_file, - port.toString() + '\n' - ) - .then(function (r) { - print( - 'Wrote the port file:', - tildefriends.args.out_http_port_file, - r - ); - }) - .catch(function () { - print('Failed to write the port file.'); - }); - } - } - - if (tildefriends.https_port) { - async function start_tls() { - const kCertificatePath = 'data/httpd/certificate.pem'; - const kPrivateKeyPath = 'data/httpd/privatekey.pem'; - let privateKey; - let certificate; - try { - privateKey = utf8Decode(await File.readFile(kPrivateKeyPath)); - certificate = utf8Decode(await File.readFile(kCertificatePath)); - } catch (e) { - print(`TLS disabled (${e.message}).`); - return; - } - let context = new TlsContext(); - context.setPrivateKey(privateKey); - context.setCertificate(certificate); - httpd.start(tildefriends.https_port, context); - } - start_tls(); - } - }) - .catch(function (error) { - print('Failed to load settings.'); - printError({print: print}, error); - exit(1); - }); - export {invoke, getProcessBlob}; diff --git a/src/file.js.c b/src/file.js.c index 1df953f5..5f1e7c49 100644 --- a/src/file.js.c +++ b/src/file.js.c @@ -212,14 +212,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar { tf_task_t* task = JS_GetContextOpaque(context); const char* file_name = JS_ToCString(context, argv[0]); - const char* actual = file_name; - if (tf_task_get_root_path(task)) - { - size_t size = strlen(tf_task_get_root_path(task)) + strlen(file_name) + 2; - char* buffer = alloca(size); - snprintf(buffer, size, "%s/%s", tf_task_get_root_path(task), file_name); - actual = buffer; - } + const char* actual = tf_task_get_path_with_root(task, file_name); promiseid_t promise = -1; JSValue promise_value = tf_task_allocate_promise(task, &promise); @@ -241,6 +234,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar tf_free(req); } JS_FreeCString(context, file_name); + tf_free((char*)actual); return promise_value; } diff --git a/src/httpd.js.c b/src/httpd.js.c index 340306c4..ce4e9d85 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -7,6 +7,7 @@ #include "ssb.db.h" #include "ssb.h" #include "task.h" +#include "tls.h" #include "tlscontext.js.h" #include "trace.h" #include "util.js.h" @@ -233,46 +234,6 @@ static JSValue _httpd_make_response_object(JSContext* context, tf_http_request_t return response_object; } -static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket) -{ - http_handler_data_t* data = request->user_data; - JSContext* context = data->context; - JSValue request_object = JS_NewObject(context); - JS_SetPropertyStr(context, request_object, "method", JS_NewString(context, request->method)); - JS_SetPropertyStr(context, request_object, "uri", JS_NewString(context, request->path)); - JSValue headers = JS_NewObject(context); - for (int i = 0; i < request->headers_count; i++) - { - JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value)); - } - JS_SetPropertyStr(context, request_object, "headers", headers); - if (request->query) - { - JS_SetPropertyStr(context, request_object, "query", JS_NewString(context, request->query)); - } - if (request->body) - { - JS_SetPropertyStr(context, request_object, "body", tf_util_new_uint8_array(context, request->body, request->content_length)); - } - - JSValue client = JS_NewObject(context); - JS_SetPropertyStr(context, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE); - JS_SetPropertyStr(context, request_object, "client", client); - - JSValue response_object = _httpd_make_response_object(context, request); - /* The ref is owned by the JS object and will be released by the finalizer. */ - tf_http_request_ref(request); - JSValue args[] = { - request_object, - response_object, - }; - JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args); - tf_util_report_error(context, response); - JS_FreeValue(context, request_object); - JS_FreeValue(context, response); - JS_FreeValue(context, response_object); -} - static bool _httpd_redirect(tf_http_request_t* request) { if (request->is_tls) @@ -292,16 +253,6 @@ static bool _httpd_redirect(tf_http_request_t* request) return true; } -static void _httpd_callback(tf_http_request_t* request) -{ - if (_httpd_redirect(request)) - { - return; - } - - _httpd_callback_internal(request, false); -} - static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); @@ -388,71 +339,19 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va return JS_UNDEFINED; } -static void _httpd_cleanup_callback(void* user_data) -{ - http_handler_data_t* data = user_data; - JS_FreeValue(data->context, data->callback); - tf_free(data); -} - -static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); - const char* pattern = JS_ToCString(context, argv[0]); - http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); - *data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; - tf_http_add_handler(http, pattern, _httpd_callback, _httpd_cleanup_callback, data); - JS_FreeCString(context, pattern); - return JS_UNDEFINED; -} - typedef struct _httpd_listener_t { - JSContext* context; - JSValue tls; + tf_tls_context_t* tls; } httpd_listener_t; static void _httpd_listener_cleanup(void* user_data) { httpd_listener_t* listener = user_data; - JS_FreeValue(listener->context, listener->tls); - tf_free(listener); -} - -static JSValue _httpd_endpoint_start(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); - int port = 0; - JS_ToInt32(context, &port, argv[0]); - - httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); - *listener = (httpd_listener_t) { .context = context, .tls = JS_DupValue(context, argv[1]) }; - tf_tls_context_t* tls = tf_tls_context_get(listener->tls); - int assigned_port = tf_http_listen(http, port, tls, _httpd_listener_cleanup, listener); - tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http%s://127.0.0.1:%d/" RESET ".\n", tls ? "s" : "", assigned_port); - return JS_NewInt32(context, assigned_port); -} - -static void _httpd_free_user_data(void* user_data) -{ - tf_free(user_data); -} - -static JSValue _httpd_set_http_redirect(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); - http_user_data_t* user_data = tf_http_get_user_data(http); - if (!user_data) + if (listener->tls) { - user_data = tf_malloc(sizeof(http_user_data_t)); - memset(user_data, 0, sizeof(http_user_data_t)); - tf_http_set_user_data(http, user_data, _httpd_free_user_data); + tf_tls_context_destroy(listener->tls); } - - const char* redirect = JS_ToCString(context, argv[0]); - snprintf(user_data->redirect, sizeof(user_data->redirect), "%s", redirect ? redirect : ""); - JS_FreeCString(context, redirect); - return JS_UNDEFINED; + tf_free(listener); } typedef struct _auth_query_work_t @@ -2267,6 +2166,100 @@ static void _httpd_endpoint_logout(tf_http_request_t* request) tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); } +static void _httpd_endpoint_app_socket(tf_http_request_t* request) +{ + tf_task_t* task = request->user_data; + tf_ssb_t* ssb = tf_task_get_ssb(task); + + JSContext* context = tf_ssb_get_context(ssb); + JSValue global = JS_GetGlobalObject(context); + JSValue exports = JS_GetPropertyStr(context, global, "exports"); + JSValue app_socket = JS_GetPropertyStr(context, exports, "app_socket"); + + JSValue request_object = JS_NewObject(context); + JSValue headers = JS_NewObject(context); + for (int i = 0; i < request->headers_count; i++) + { + JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value)); + } + JS_SetPropertyStr(context, request_object, "headers", headers); + + JSValue response = _httpd_make_response_object(context, request); + tf_http_request_ref(request); + + JSValue args[] = { + request_object, + response, + }; + + JSValue result = JS_Call(context, app_socket, JS_NULL, tf_countof(args), args); + tf_util_report_error(context, result); + JS_FreeValue(context, result); + + for (int i = 0; i < tf_countof(args); i++) + { + JS_FreeValue(context, args[i]); + } + + JS_FreeValue(context, app_socket); + JS_FreeValue(context, exports); + JS_FreeValue(context, global); +} + +static int _tf_httpd_get_tildefriends_int(JSContext* context, const char* arg) +{ + JSValue global = JS_GetGlobalObject(context); + JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends"); + JSValue arg_value = JS_GetPropertyStr(context, tildefriends, arg); + int value = 0; + JS_ToInt32(context, &value, arg_value); + JS_FreeValue(context, arg_value); + JS_FreeValue(context, tildefriends); + JS_FreeValue(context, global); + return value; +} + +static const char* _tf_httpd_get_tildefriends_arg_string(JSContext* context, const char* arg) +{ + JSValue global = JS_GetGlobalObject(context); + JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends"); + JSValue args = JS_GetPropertyStr(context, tildefriends, "args"); + JSValue arg_value = JS_GetPropertyStr(context, args, arg); + const char* value = !JS_IsUndefined(arg_value) ? JS_ToCString(context, arg_value) : NULL; + const char* result = value ? tf_strdup(value) : NULL; + JS_FreeCString(context, value); + JS_FreeValue(context, arg_value); + JS_FreeValue(context, args); + JS_FreeValue(context, tildefriends); + JS_FreeValue(context, global); + return result; +} + +static void _httpd_free_user_data(void* user_data) +{ + tf_free(user_data); +} + +static const char* _httpd_read_file(tf_task_t* task, const char* path) +{ + const char* actual = tf_task_get_path_with_root(task, path); + const size_t k_max_read = 8 * 1024 * 1024; + char* result = NULL; + char* buffer = tf_malloc(k_max_read); + FILE* file = fopen(actual, "rb"); + if (file) + { + size_t size = fread(buffer, 1, k_max_read, file); + result = tf_malloc(size + 1); + memcpy(result, buffer, size); + result[size] = '\0'; + fclose(file); + } + tf_free(buffer); + tf_free((char*)actual); + return result; +} + void tf_httpd_register(JSContext* context) { JS_NewClassID(&_httpd_class_id); @@ -2287,15 +2280,41 @@ void tf_httpd_register(JSContext* context) { fprintf(stderr, "Failed to register Request.\n"); } + + int http_port = _tf_httpd_get_tildefriends_int(context, "http_port"); + int https_port = _tf_httpd_get_tildefriends_int(context, "https_port"); + const char* out_http_port_file = _tf_httpd_get_tildefriends_arg_string(context, "out_http_port_file"); + JSValue global = JS_GetGlobalObject(context); JSValue httpd = JS_NewObjectClass(context, _httpd_class_id); tf_task_t* task = tf_task_get(context); + tf_ssb_t* ssb = tf_task_get_ssb(task); uv_loop_t* loop = tf_task_get_loop(task); tf_http_t* http = tf_http_create(loop); tf_http_set_trace(http, tf_task_get_trace(task)); JS_SetOpaque(httpd, http); + if (https_port) + { + http_user_data_t* user_data = tf_http_get_user_data(http); + if (!user_data) + { + user_data = tf_malloc(sizeof(http_user_data_t)); + memset(user_data, 0, sizeof(http_user_data_t)); + tf_http_set_user_data(http, user_data, _httpd_free_user_data); + } + sqlite3* db = tf_ssb_acquire_db_reader(ssb); + tf_ssb_db_get_global_setting_string(db, "http_redirect", user_data->redirect, sizeof(user_data->redirect)); + tf_ssb_release_db_reader(ssb, db); + + /* Workaround. */ + if (strcmp(user_data->redirect, "0") == 0) + { + *user_data->redirect = '\0'; + } + } + tf_http_add_handler(http, "/", _httpd_endpoint_root, NULL, task); tf_http_add_handler(http, "/codemirror/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/lit/*", _httpd_endpoint_static, NULL, task); @@ -2324,10 +2343,56 @@ void tf_httpd_register(JSContext* context) tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task); tf_http_add_handler(http, "/login", _httpd_endpoint_login, NULL, task); - JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2)); - JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2)); - JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1)); + tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task); + JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1)); JS_SetPropertyStr(context, global, "httpd", httpd); JS_FreeValue(context, global); + + if (http_port > 0 || out_http_port_file) + { + httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); + *listener = (httpd_listener_t) { 0 }; + int assigned_port = tf_http_listen(http, http_port, NULL, _httpd_listener_cleanup, listener); + tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port); + + if (out_http_port_file) + { + const char* actual_http_port_file = tf_task_get_path_with_root(task, out_http_port_file); + FILE* file = fopen(actual_http_port_file, "wb"); + if (file) + { + fprintf(file, "%d", assigned_port); + fclose(file); + tf_printf("Wrote the port file: %s.\n", out_http_port_file); + } + else + { + tf_printf("Failed to open %s for write: %s.\n", out_http_port_file, strerror(errno)); + } + tf_free((char*)actual_http_port_file); + } + + if (https_port) + { + const char* k_certificate = "data/httpd/certificate.pem"; + const char* k_private_key = "data/httpd/privatekey.pem"; + const char* certificate = _httpd_read_file(task, k_certificate); + const char* private_key = _httpd_read_file(task, k_private_key); + if (certificate && private_key) + { + tf_tls_context_t* tls = tf_tls_context_create(); + tf_tls_context_set_certificate(tls, certificate); + tf_tls_context_set_private_key(tls, private_key); + httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); + *listener = (httpd_listener_t) { .tls = tls }; + int assigned_port = tf_http_listen(http, https_port, tls, _httpd_listener_cleanup, listener); + tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port); + } + tf_free((char*)certificate); + tf_free((char*)private_key); + } + } + + tf_free((void*)out_http_port_file); } diff --git a/src/task.c b/src/task.c index 79813a2b..0d7ba272 100644 --- a/src/task.c +++ b/src/task.c @@ -376,7 +376,6 @@ static const char* _task_loadFile(tf_task_t* task, const char* fileName, size_t* snprintf(buffer, size, "%s/%s", task->_root_path, fileName); actual = fileName; } - tf_printf("opening %s\n", actual); FILE* file = fopen(actual, "rb"); if (file) { @@ -1727,7 +1726,6 @@ void tf_task_activate(tf_task_t* task) JS_SetPropertyStr(context, global, "TlsContext", tf_tls_context_register(context)); tf_file_register(context); tf_database_register(context); - tf_httpd_register(context); task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path, task->_network_key); tf_ssb_set_trace(task->_ssb, task->_trace); @@ -1744,10 +1742,18 @@ void tf_task_activate(tf_task_t* task) } JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, actual_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)); + if (task->_http_port) + { + JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port)); + } + if (task->_https_port) + { + JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port)); + } JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0)); + + tf_httpd_register(context); } else { @@ -2043,6 +2049,19 @@ const char* tf_task_get_root_path(tf_task_t* task) return *task->_root_path ? task->_root_path : NULL; } +const char* tf_task_get_path_with_root(tf_task_t* task, const char* path) +{ + if (!*task->_root_path) + { + return tf_strdup(path); + } + + size_t size = strlen(task->_root_path) + 1 + strlen(path) + 1; + char* result = tf_malloc(size); + snprintf(result, size, "%s/%s", task->_root_path, path); + return result; +} + void tf_task_set_args(tf_task_t* task, const char* args) { task->_args = args; diff --git a/src/task.h b/src/task.h index 3d5b4d5c..1579ec81 100644 --- a/src/task.h +++ b/src/task.h @@ -132,6 +132,14 @@ const char* tf_task_get_zip_path(tf_task_t* task); */ const char* tf_task_get_root_path(tf_task_t* task); +/** +** Get the path to use for reading a given loose file. +** @param task The task. +** @param path The path to the file. +** @return The path or NULL. Free with tf_free(). +*/ +const char* tf_task_get_path_with_root(tf_task_t* task, const char* path); + /** ** Set arbitrary named arguments that will be made available to the task. ** @param task The task.