From 00ba74a6c4dd685a998f35c645803640edf5c60d Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 25 Jan 2024 18:00:23 +0000 Subject: [PATCH] This simplifies upgrading an HTTP request to a websocket, I believe, and fixes sending refresh auth tokens. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4791 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/app.js | 8 ++- core/core.js | 2 +- src/httpd.js.c | 141 ++++++++++++++++++++++++++----------------------- 3 files changed, 78 insertions(+), 73 deletions(-) diff --git a/core/app.js b/core/app.js index a2136b61..67d58c20 100644 --- a/core/app.js +++ b/core/app.js @@ -202,11 +202,9 @@ function socket(request, response, client) { } } - if (refresh) { - return { - 'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`, - }; - } + response.upgrade(100, refresh ? { + 'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`, + } : {}); } export { socket, App }; diff --git a/core/core.js b/core/core.js index a71ff6e0..6ececd9a 100644 --- a/core/core.js +++ b/core/core.js @@ -982,7 +982,7 @@ loadSettings().then(function() { httpd.set_http_redirect(gGlobalSettings.http_redirect); } httpd.all("/login", auth.handler); - httpd.registerSocketHandler("/app/socket", app.socket); + httpd.all("/app/socket", app.socket); httpd.all("", function(request, response) { let match; if (request.uri === "/" || request.uri === "") { diff --git a/src/httpd.js.c b/src/httpd.js.c index cb79af12..506aab15 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -21,6 +21,8 @@ #define tf_countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) +static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); + static JSClassID _httpd_class_id; static JSClassID _httpd_request_class_id; @@ -42,6 +44,34 @@ static JSValue _httpd_response_write_head(JSContext* context, JSValueConst this_ return JS_UNDEFINED; } +static int _object_to_headers(JSContext* context, JSValue object, const char** headers, int headers_length) +{ + int count = 0; + JSPropertyEnum* ptab = NULL; + uint32_t plen = 0; + JS_GetOwnPropertyNames(context, &ptab, &plen, object, JS_GPN_STRING_MASK); + for (; count < (int)plen && count < headers_length / 2; ++count) + { + JSValue key = JS_AtomToString(context, ptab[count].atom); + JSPropertyDescriptor desc; + JSValue key_value = JS_NULL; + if (JS_GetOwnProperty(context, &desc, object, ptab[count].atom) == 1) + { + key_value = desc.value; + JS_FreeValue(context, desc.setter); + JS_FreeValue(context, desc.getter); + } + headers[count * 2 + 0] = JS_ToCString(context, key); + headers[count * 2 + 1] = JS_ToCString(context, key_value); + } + for (uint32_t i = 0; i < plen; ++i) + { + JS_FreeAtom(context, ptab[i].atom); + } + js_free(context, ptab); + return count; +} + static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); @@ -71,40 +101,16 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in JS_ToInt32(context, &status, response_status); JS_FreeValue(context, response_status); - const char** headers = NULL; - int headers_length = 0; + const char* headers[64] = { 0 }; JSValue response_headers = JS_GetPropertyStr(context, this_val, "response_headers"); - JSPropertyEnum* ptab; - uint32_t plen; - JS_GetOwnPropertyNames(context, &ptab, &plen, response_headers, JS_GPN_STRING_MASK); - headers = alloca(sizeof(const char*) * plen * 2); - headers_length = plen; - for (uint32_t i = 0; i < plen; ++i) - { - JSValue key = JS_AtomToString(context, ptab[i].atom); - JSPropertyDescriptor desc; - JSValue key_value = JS_NULL; - if (JS_GetOwnProperty(context, &desc, response_headers, ptab[i].atom) == 1) - { - key_value = desc.value; - JS_FreeValue(context, desc.setter); - JS_FreeValue(context, desc.getter); - } - headers[i * 2 + 0] = JS_ToCString(context, key); - headers[i * 2 + 1] = JS_ToCString(context, key_value); - } + int headers_count = _object_to_headers(context, response_headers, headers, tf_countof(headers)); - tf_http_respond(request, status, headers, headers_length, data, length); + tf_http_respond(request, status, headers, headers_count, data, length); - for (int i = 0; i < headers_length * 2; i++) + for (int i = 0; i < headers_count * 2; i++) { JS_FreeCString(context, headers[i]); } - for (uint32_t i = 0; i < plen; ++i) - { - JS_FreeAtom(context, ptab[i].atom); - } - js_free(context, ptab); JS_FreeValue(context, buffer); return JS_UNDEFINED; } @@ -204,6 +210,7 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2)); + JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2)); JSValue args[] = { request_object, @@ -213,17 +220,7 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock tf_util_report_error(context, response); JS_FreeValue(context, request_object); JS_FreeValue(context, response); - - if (is_websocket) - { - request->on_message = _httpd_message_callback; - request->context = context; - request->user_data = JS_VALUE_GET_PTR(response_object); - } - else - { - JS_FreeValue(context, response_object); - } + JS_FreeValue(context, response_object); } static bool _httpd_redirect(tf_http_request_t* request) @@ -255,13 +252,10 @@ static void _httpd_callback(tf_http_request_t* request) _httpd_callback_internal(request, false); } -static void _httpd_websocket_callback(tf_http_request_t* request) +static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { - if (_httpd_redirect(request)) - { - return; - } - + tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); + tf_http_request_ref(request); const char* header_connection = tf_http_request_get_header(request, "connection"); const char* header_upgrade = tf_http_request_get_header(request, "upgrade"); const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key"); @@ -281,26 +275,51 @@ static void _httpd_websocket_callback(tf_http_request_t* request) SHA1(key_magic, size, digest); char key[41] = { 0 }; tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); - enum { k_headers_count = 4 }; - const char* headers[k_headers_count * 2] = - { - "Upgrade", "websocket", - "Connection", "Upgrade", - "Sec-WebSocket-Accept", key, - "Sec-WebSocket-Version", "13", - }; + + const char* headers[64] = { 0 }; + int headers_count = 0; + + headers[headers_count * 2 + 0] = "Upgrade"; + headers[headers_count * 2 + 1] = "websocket"; + headers_count++; + + headers[headers_count * 2 + 0] = "Connection"; + headers[headers_count * 2 + 1] = "Upgrade"; + headers_count++; + + headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept"; + headers[headers_count * 2 + 1] = key; + headers_count++; + bool send_version = !tf_http_request_get_header(request, "sec-websocket-version") || strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; + if (send_version) + { + headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept"; + headers[headers_count * 2 + 1] = key; + headers_count++; + } + headers_count += _object_to_headers(context, argv[1], headers + headers_count * 2, tf_countof(headers) - headers_count * 2); + + for (int i = 0; i < headers_count; i += 2) + { + tf_printf("[%d] %s = %s\n", i / 2, headers[i * 2 + 0], headers[i * 2 + 1]); + } + tf_http_request_websocket_upgrade(request); - tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0); + tf_http_respond(request, 101, headers, headers_count, NULL, 0); + + request->on_message = _httpd_message_callback; + request->context = context; + request->user_data = JS_VALUE_GET_PTR(JS_DupValue(context, this_val)); } else { tf_http_respond(request, 400, NULL, 0, NULL, 0); } - _httpd_callback_internal(request, true); + return JS_UNDEFINED; } static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) @@ -314,17 +333,6 @@ static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, in return JS_UNDEFINED; } -static JSValue _httpd_register_socket_handler(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_websocket_callback, data); - JS_FreeCString(context, pattern); - return JS_UNDEFINED; -} - 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); @@ -513,7 +521,6 @@ void tf_httpd_register(JSContext* context) JS_SetPropertyStr(context, httpd, "handlers", JS_NewObject(context)); JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2)); - JS_SetPropertyStr(context, httpd, "registerSocketHandler", JS_NewCFunction(context, _httpd_register_socket_handler, "register_socket_handler", 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)); JS_SetPropertyStr(context, global, "httpd", httpd);