From 1621f1753a0272e23c97f0e922d05987b68771b5 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 24 Dec 2023 17:43:33 +0000 Subject: [PATCH] WebSocket request/response header dance. Feels like the loop is getting close to closed, but I want to refactor everything. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4692 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/httpd.js | 1 - src/http.c | 38 ++++++++++++++++++++++++++++++++------ src/http.h | 10 +++++++++- src/httpd.js.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- src/tests.c | 4 ++-- 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/core/httpd.js b/core/httpd.js index 3e97e5d1..34780a62 100644 --- a/core/httpd.js +++ b/core/httpd.js @@ -342,7 +342,6 @@ function handleWebSocketRequest(request, response, client) { function webSocketAcceptResponse(key) { let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; return base64Encode(sha1Digest(key + kMagic)); } diff --git a/src/http.c b/src/http.c index 4ea717b9..1f86c3ec 100644 --- a/src/http.c +++ b/src/http.c @@ -39,6 +39,7 @@ typedef struct _tf_http_connection_t int headers_length; bool headers_done; + int flags; tf_http_callback_t* callback; void* user_data; @@ -51,6 +52,7 @@ typedef struct _tf_http_connection_t typedef struct _tf_http_handler_t { const char* pattern; + int flags; tf_http_callback_t* callback; void* user_data; } tf_http_handler_t; @@ -70,6 +72,7 @@ typedef struct _tf_http_t uv_loop_t* loop; } tf_http_t; +static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name); static void _http_connection_destroy(tf_http_connection_t* connection); static const char* _http_status_text(int status); @@ -96,13 +99,14 @@ void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* } } -bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, void** out_user_data) +bool _http_find_handler(tf_http_t* http, const char* path, int flags, tf_http_callback_t** out_callback, void** out_user_data) { for (int i = 0; i < http->handlers_count; i++) { - if (!http->handlers[i].pattern || + if (http->handlers[i].flags == flags && + (!http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0 || - (strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/')) + (strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/'))) { *out_callback = http->handlers[i].callback; *out_user_data = http->handlers[i].user_data; @@ -182,6 +186,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d .phase = k_http_callback_phase_headers_received, .method = connection->method, .path = connection->path, + .flags = connection->flags, .query = connection->query, .body = connection->body, .content_length = connection->content_length, @@ -189,6 +194,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d .headers_count = connection->headers_length, .user_data = connection->user_data, }; + tf_http_request_ref(request); connection->callback(request); tf_http_request_release(request); @@ -260,7 +266,9 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t connection->body = tf_malloc(connection->content_length); } - if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback) + int flags = _http_connection_get_header(connection, "upgrade") ? k_tf_http_handler_flag_websocket : 0; + connection->flags = flags; + if (!_http_find_handler(connection->http, connection->path, flags, &connection->callback, &connection->user_data) || !connection->callback) { connection->callback = _http_builtin_404_handler; } @@ -365,12 +373,13 @@ void tf_http_listen(tf_http_t* http, int port) } } -void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data) +void tf_http_add_handler(tf_http_t* http, const char* pattern, int flags, tf_http_callback_t* callback, void* user_data) { http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); http->handlers[http->handlers_count++] = (tf_http_handler_t) { .pattern = tf_strdup(pattern), + .flags = flags, .callback = callback, .user_data = user_data, }; @@ -451,7 +460,7 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header char content_length_buffer[32] = { 0 }; int content_length_buffer_length = 0; - if (!sent_content_length) + if (!sent_content_length && status != 101) { content_length_buffer_length = snprintf(content_length_buffer, sizeof(content_length_buffer), "Content-Length: %zd\r\n", content_length); headers_length += content_length_buffer_length; @@ -513,3 +522,20 @@ void tf_http_request_release(tf_http_request_t* request) tf_free(request); } } + +static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name) +{ + for (int i = 0; i < connection->headers_length; i++) + { + if (strcasecmp(connection->headers[i].name, name) == 0) + { + return connection->headers[i].value; + } + } + return NULL; +} + +const char* tf_http_request_get_header(tf_http_request_t* request, const char* name) +{ + return _http_connection_get_header(request->connection, name); +} diff --git a/src/http.h b/src/http.h index dda5afd9..bf2e446e 100644 --- a/src/http.h +++ b/src/http.h @@ -18,6 +18,7 @@ typedef struct _tf_http_request_t tf_http_connection_t* connection; const char* method; const char* path; + int flags; const char* query; void* body; size_t content_length; @@ -27,14 +28,21 @@ typedef struct _tf_http_request_t int ref_count; } tf_http_request_t; +typedef enum _tf_http_handler_flags_t +{ + k_tf_http_handler_flag_none = 0, + k_tf_http_handler_flag_websocket = 1, +} tf_http_handler_flags_t; + typedef void (tf_http_callback_t)(tf_http_request_t* request); tf_http_t* tf_http_create(uv_loop_t* loop); void tf_http_listen(tf_http_t* http, int port); -void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data); +void tf_http_add_handler(tf_http_t* http, const char* pattern, int flags, tf_http_callback_t* callback, void* user_data); void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length); size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data); void tf_http_destroy(tf_http_t* http); void tf_http_request_ref(tf_http_request_t* request); void tf_http_request_release(tf_http_request_t* request); +const char* tf_http_request_get_header(tf_http_request_t* request, const char* name); diff --git a/src/httpd.js.c b/src/httpd.js.c index fd868bc6..aee9f275 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -9,6 +9,9 @@ #include "picohttpparser.h" #include +#include + +#include #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) #include @@ -99,6 +102,43 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in static void _httpd_callback(tf_http_request_t* request) { + if (request->flags & k_tf_http_handler_flag_websocket) + { + 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"); + tf_printf("\n%s\n%s\n%s\n\n", header_connection, header_upgrade, header_sec_websocket_key); + if (header_connection && + header_upgrade && + header_sec_websocket_key && + strstr(header_connection, "Upgrade") && + strcasecmp(header_upgrade, "websocket") == 0) + { + static const char* k_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + size_t key_length = strlen(header_sec_websocket_key); + size_t size = key_length + 36; + uint8_t* key_magic = alloca(size); + memcpy(key_magic, header_sec_websocket_key, key_length); + memcpy(key_magic + key_length, k_magic, 36); + uint8_t digest[20]; + SHA1(key_magic, size, digest); + char key[41] = { 0 }; + tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); + tf_printf("ACCEPT %s\n", 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", + }; + tf_http_respond(request, 101, headers, k_headers_count, NULL, 0); + return; + } + } + http_handler_data_t* data = request->user_data; JSContext* context = data->context; JSValue request_object = JS_NewObject(context); @@ -146,15 +186,19 @@ static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, J 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, data); + tf_http_add_handler(http, pattern, k_tf_http_handler_flag_none, _httpd_callback, data); JS_FreeCString(context, pattern); - return JS_UNDEFINED; } static JSValue _httpd_register_socket_handler(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { - tf_printf("HTTPD_REGISTER_SOCKET_HANDLER UNIMPLEMENTED\n"); + 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, k_tf_http_handler_flag_websocket, _httpd_callback, data); + JS_FreeCString(context, pattern); return JS_UNDEFINED; } diff --git a/src/tests.c b/src/tests.c index 0763c6fc..c79c93e6 100644 --- a/src/tests.c +++ b/src/tests.c @@ -737,8 +737,8 @@ static void _test_http(const tf_test_options_t* options) uv_loop_t loop = { 0 }; uv_loop_init(&loop); tf_http_t* http = tf_http_create(&loop); - tf_http_add_handler(http, "/hello", _test_http_handler, NULL); - tf_http_add_handler(http, "/post", _test_http_handler_post, NULL); + tf_http_add_handler(http, "/hello", k_tf_http_handler_flag_none, _test_http_handler, NULL); + tf_http_add_handler(http, "/post", k_tf_http_handler_flag_none, _test_http_handler_post, NULL); tf_http_listen(http, 23456); test_http_t test = { .loop = &loop };