core: Beginnings of websocket handling in C.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m45s

This commit is contained in:
2025-12-04 12:49:35 -05:00
parent f756d1e5b2
commit 1972ce7091

View File

@@ -3,6 +3,7 @@
#include "http.h" #include "http.h"
#include "log.h" #include "log.h"
#include "mem.h" #include "mem.h"
#include "sha1.h"
#include "ssb.db.h" #include "ssb.db.h"
#include "ssb.h" #include "ssb.h"
#include "task.h" #include "task.h"
@@ -213,9 +214,154 @@ void tf_httpd_endpoint_app(tf_http_request_t* request)
tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data); tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data);
} }
typedef struct _app_t
{
tf_http_request_t* request;
const char* settings;
JSValue credentials;
} app_t;
static void _httpd_auth_query_work(tf_ssb_t* ssb, void* user_data)
{
app_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _httpd_app_on_message(tf_http_request_t* request, int op_code, const void* data, size_t size)
{
tf_printf("REQUEST MESSAGE %.*s\n", (int)size, (const char*)data);
}
static void _httpd_app_on_close(tf_http_request_t* request)
{
tf_printf("REQUEST CLOSE\n");
tf_http_request_unref(request);
}
static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
app_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue session = JS_GetPropertyStr(context, work->credentials, "session");
JSValue name = JS_GetPropertyStr(context, session, "name");
JS_FreeValue(context, session);
const char* name_string = JS_ToCString(context, name);
JSValue settings_value = work->settings ? JS_ParseJSON(context, work->settings, strlen(work->settings), NULL) : JS_UNDEFINED;
tf_free((void*)work->settings);
work->settings = NULL;
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, work->credentials, "permissions", out_permissions);
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
JS_FreeValue(context, name);
tf_http_request_t* request = work->request;
const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key");
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_CTX sha1 = { 0 };
SHA1Init(&sha1);
SHA1Update(&sha1, key_magic, size);
SHA1Final(digest, &sha1);
char key[41] = { 0 };
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
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++;
const char* session_token = tf_httpd_make_session_jwt(tf_ssb_get_context(ssb), ssb, name_string);
const char* cookie = tf_httpd_make_set_session_cookie_header(request, session_token);
tf_free((void*)session_token);
headers[headers_count * 2 + 0] = "Set-Cookie";
headers[headers_count * 2 + 1] = cookie ? cookie : "";
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++;
}
tf_http_request_websocket_upgrade(request);
tf_http_respond(request, 101, headers, headers_count, NULL, 0);
/* What now? */
tf_free((void*)cookie);
JS_FreeCString(context, name_string);
JS_FreeValue(context, work->credentials);
tf_free(work);
// tf_http_request_unref(request);
request->on_message = _httpd_app_on_message;
request->on_close = _httpd_app_on_close;
}
static void _tf_httpd_endpoint_app_socket_c(tf_http_request_t* request) static void _tf_httpd_endpoint_app_socket_c(tf_http_request_t* request)
{ {
tf_printf("Unimplemented.\n"); 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");
if (!header_connection || !header_upgrade || !header_sec_websocket_key || !strstr(header_connection, "Upgrade") || strcasecmp(header_upgrade, "websocket"))
{
tf_http_respond(request, 500, NULL, 0, NULL, 0);
return;
}
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSContext* context = tf_task_get_context(task);
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
tf_free((void*)session);
if (!JS_IsUndefined(jwt))
{
JSValue credentials = JS_NewObject(context);
JS_SetPropertyStr(context, credentials, "session", jwt);
tf_http_request_ref(request);
app_t* work = tf_malloc(sizeof(app_t));
*work = (app_t) {
.request = request,
.credentials = credentials,
};
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work);
}
} }
static void _tf_httpd_endpoint_app_socket_js(tf_http_request_t* request) static void _tf_httpd_endpoint_app_socket_js(tf_http_request_t* request)