Move / redirect handling to C

This commit is contained in:
Cory McWilliams 2024-05-05 15:24:15 -04:00
parent 5fdd461159
commit c7ab5447ea
5 changed files with 174 additions and 36 deletions

View File

@ -1425,34 +1425,7 @@ loadSettings()
httpd.all('/app/socket', app.socket); httpd.all('/app/socket', app.socket);
httpd.all('', function default_http_handler(request, response) { httpd.all('', function default_http_handler(request, response) {
let match; let match;
if (request.uri === '/' || request.uri === '') { if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
let host = request.headers['x-forwarded-host'] ?? request.headers.host;
try {
for (let line of (gGlobalSettings.index_map || '').split('\n')) {
let parts = line.split('=');
if (parts.length == 2 && host.match(new RegExp(parts[0], 'i'))) {
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
parts[1],
'Content-Length': '0',
});
return response.end();
}
}
} catch (e) {
print(e);
}
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
gGlobalSettings.index,
'Content-Length': '0',
});
return response.end();
} else if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
return blobHandler(request, response, match[1], match[2]); return blobHandler(request, response, match[1], match[2]);
} else if ( } else if (
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))

View File

@ -67,6 +67,7 @@ typedef struct _tf_http_connection_t
typedef struct _tf_http_handler_t typedef struct _tf_http_handler_t
{ {
const char* pattern; const char* pattern;
bool is_wildcard;
tf_http_callback_t* callback; tf_http_callback_t* callback;
tf_http_cleanup_t* cleanup; tf_http_cleanup_t* cleanup;
void* user_data; void* user_data;
@ -127,12 +128,43 @@ static void _http_allocate_buffer(uv_handle_t* handle, size_t suggested_size, uv
*buf = uv_buf_init(connection->incoming, sizeof(connection->incoming)); *buf = uv_buf_init(connection->incoming, sizeof(connection->incoming));
} }
static bool _http_pattern_matches(const char* pattern, const char* path, bool is_wildcard)
{
if (!pattern || !*pattern || (!is_wildcard && strcmp(path, pattern) == 0))
{
return true;
}
if (is_wildcard)
{
int i = 0;
int j = 0;
while (pattern[i] && path[j] && pattern[i] != '*' && pattern[i] == path[j])
{
i++;
j++;
}
if (pattern[i] == '*')
{
for (; path[j]; j++)
{
if (_http_pattern_matches(pattern + i + 1, path + j, strchr(pattern + i + 1, '*') != NULL))
{
return true;
}
}
}
return !pattern[i] && !path[j];
}
return false;
}
static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, const char** out_trace_name, void** out_user_data) static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, const char** out_trace_name, void** out_user_data)
{ {
for (int i = 0; i < http->handlers_count; i++) for (int i = 0; i < http->handlers_count; i++)
{ {
if (!http->handlers[i].pattern || !*http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0 || if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard))
(*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/'))
{ {
*out_callback = http->handlers[i].callback; *out_callback = http->handlers[i].callback;
*out_trace_name = http->handlers[i].pattern; *out_trace_name = http->handlers[i].pattern;
@ -694,6 +726,7 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
http->handlers[http->handlers_count++] = (tf_http_handler_t) { http->handlers[http->handlers_count++] = (tf_http_handler_t) {
.pattern = tf_strdup(pattern), .pattern = tf_strdup(pattern),
.is_wildcard = pattern && strchr(pattern, '*') != NULL,
.callback = callback, .callback = callback,
.cleanup = cleanup, .cleanup = cleanup,
.user_data = user_data, .user_data = user_data,

View File

@ -783,6 +783,38 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
tf_file_stat(task, path, _httpd_endpoint_static_stat, request); tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
} }
static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{
tf_http_request_t* request = user_data;
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
char url[1024];
snprintf(url, sizeof(url), "%s%s%s", request->is_tls ? "https://" : "http://", host, path ? path : "/~core/apps/");
const char* headers[] = {
"Location",
url,
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_http_request_unref(request);
}
static void _httpd_endpoint_root(tf_http_request_t* request)
{
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
tf_http_request_ref(request);
tf_ssb_db_resolve_index_async(ssb, host, _httpd_endpoint_root_callback, request);
}
static void _httpd_endpoint_robots_txt(tf_http_request_t* request) static void _httpd_endpoint_robots_txt(tf_http_request_t* request)
{ {
if (_httpd_redirect(request)) if (_httpd_redirect(request))
@ -1429,12 +1461,13 @@ void tf_httpd_register(JSContext* context)
tf_http_set_trace(http, tf_task_get_trace(task)); tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http); JS_SetOpaque(httpd, http);
tf_http_add_handler(http, "/codemirror/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/", _httpd_endpoint_root, NULL, task);
tf_http_add_handler(http, "/lit/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/codemirror/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/prettier/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/lit/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/speedscope/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/prettier/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/static/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/.well-known/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);

View File

@ -1754,3 +1754,93 @@ bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* pa
} }
return found; return found;
} }
typedef struct _resolve_index_t
{
uv_work_t work;
tf_ssb_t* ssb;
const char* host;
const char* path;
void (*callback)(const char* path, void* user_data);
void* user_data;
} resolve_index_t;
static void _tf_ssb_db_resolve_index_work(uv_work_t* work)
{
resolve_index_t* request = work->data;
sqlite3* db = tf_ssb_acquire_db_reader(request->ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
const char* index_map = (const char*)sqlite3_column_text(statement, 0);
const char* start = index_map;
while (start)
{
const char* end = strchr(start, '\n');
const char* equals = strchr(start, '=');
if (equals && strncasecmp(request->host, start, equals - start) == 0)
{
size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1);
char* path = tf_malloc(value_length + 1);
memcpy(path, equals + 1, value_length);
path[value_length] = '\0';
request->path = path;
break;
}
start = end ? end + 1 : NULL;
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
if (!request->path)
{
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
request->path = tf_strdup((const char*)sqlite3_column_text(statement, 0));
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
}
tf_ssb_release_db_reader(request->ssb, db);
}
static void _tf_ssb_db_resolve_index_after_work(uv_work_t* work, int status)
{
resolve_index_t* request = work->data;
request->callback(request->path, request->user_data);
tf_free((void*)request->host);
tf_free((void*)request->path);
tf_free(request);
}
void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data)
{
resolve_index_t* request = tf_malloc(sizeof(resolve_index_t));
*request = (resolve_index_t)
{
.work = { .data = request },
.ssb = ssb,
.host = tf_strdup(host),
.callback = callback,
.user_data = user_data,
};
int r = uv_queue_work(tf_ssb_get_loop(ssb), &request->work, _tf_ssb_db_resolve_index_work, _tf_ssb_db_resolve_index_after_work);
if (r)
{
_tf_ssb_db_resolve_index_after_work(&request->work, r);
}
}

View File

@ -370,6 +370,15 @@ const char* tf_ssb_db_get_property(tf_ssb_t* ssb, const char* id, const char* ke
*/ */
bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value); bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);
/**
** Resolve a hostname to its index path by global settings.
** @param ssb The SSB instance.
** @param host The hostname.
** @param callback The callback.
** @param user_data The callback user data.
*/
void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data);
/** /**
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use. ** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer. ** @param user_data User data registered with the authorizer.