diff --git a/core/core.js b/core/core.js index a055ae45..3a042aef 100644 --- a/core/core.js +++ b/core/core.js @@ -1425,34 +1425,7 @@ loadSettings() httpd.all('/app/socket', app.socket); httpd.all('', function default_http_handler(request, response) { let match; - if (request.uri === '/' || 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))) { + if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) { return blobHandler(request, response, match[1], match[2]); } else if ( (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) diff --git a/src/http.c b/src/http.c index 05baae18..a5149a18 100644 --- a/src/http.c +++ b/src/http.c @@ -67,6 +67,7 @@ typedef struct _tf_http_connection_t typedef struct _tf_http_handler_t { const char* pattern; + bool is_wildcard; tf_http_callback_t* callback; tf_http_cleanup_t* cleanup; 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)); } +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) { 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 || - (*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/')) + if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard)) { *out_callback = http->handlers[i].callback; *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[http->handlers_count++] = (tf_http_handler_t) { .pattern = tf_strdup(pattern), + .is_wildcard = pattern && strchr(pattern, '*') != NULL, .callback = callback, .cleanup = cleanup, .user_data = user_data, diff --git a/src/httpd.js.c b/src/httpd.js.c index 2845119c..fbddf92e 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -783,6 +783,38 @@ static void _httpd_endpoint_static(tf_http_request_t* 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) { if (_httpd_redirect(request)) @@ -1429,12 +1461,13 @@ void tf_httpd_register(JSContext* context) tf_http_set_trace(http, tf_task_get_trace(task)); JS_SetOpaque(httpd, http); - tf_http_add_handler(http, "/codemirror/", _httpd_endpoint_static, NULL, task); - tf_http_add_handler(http, "/lit/", _httpd_endpoint_static, NULL, task); - tf_http_add_handler(http, "/prettier/", _httpd_endpoint_static, NULL, task); - tf_http_add_handler(http, "/speedscope/", _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, "/", _httpd_endpoint_root, NULL, task); + tf_http_add_handler(http, "/codemirror/*", _httpd_endpoint_static, NULL, task); + tf_http_add_handler(http, "/lit/*", _httpd_endpoint_static, NULL, task); + tf_http_add_handler(http, "/prettier/*", _httpd_endpoint_static, NULL, task); + tf_http_add_handler(http, "/speedscope/*", _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, "/debug", _httpd_endpoint_debug, NULL, task); diff --git a/src/ssb.db.c b/src/ssb.db.c index c58fdb95..103f7917 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -1754,3 +1754,93 @@ bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* pa } 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); + } +} diff --git a/src/ssb.db.h b/src/ssb.db.h index 9016a749..7bacc344 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -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); +/** +** 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. ** @param user_data User data registered with the authorizer.