core: Beginnings of websocket handling in C.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m45s
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m45s
This commit is contained in:
148
src/httpd.app.c
148
src/httpd.app.c
@@ -3,6 +3,7 @@
|
||||
#include "http.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "sha1.h"
|
||||
#include "ssb.db.h"
|
||||
#include "ssb.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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user