From a09fefab5eda2a7a9f7e440b09f35f135fe13b86 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sat, 2 Nov 2024 19:22:04 -0400 Subject: [PATCH] http: Add a more expressive but still nowhere near regex URL pattern matcher. --- src/http.c | 37 +++++++++++++++++++++++++++++-------- src/http.h | 8 ++++++++ src/tests.c | 22 +++++++++++++++++----- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/http.c b/src/http.c index dcefc3e0..947535d0 100644 --- a/src/http.c +++ b/src/http.c @@ -67,7 +67,6 @@ 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; @@ -129,9 +128,15 @@ 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) +bool tf_http_pattern_matches(const char* pattern, const char* path) { - if (!pattern || !*pattern || (!is_wildcard && strcmp(path, pattern) == 0)) + if (!pattern || !*pattern) + { + return true; + } + const char* k_word = "{word}"; + bool is_wildcard = strchr(pattern, '*') || strstr(pattern, k_word); + if (!is_wildcard && strcmp(path, pattern) == 0) { return true; } @@ -140,17 +145,34 @@ static bool _http_pattern_matches(const char* pattern, const char* path, bool is { int i = 0; int j = 0; - while (pattern[i] && path[j] && pattern[i] != '*' && pattern[i] == path[j]) + while (pattern[i] && path[j] && pattern[i] == path[j]) { i++; j++; } - if (pattern[i] == '*') + size_t k_word_len = strlen(k_word); + if (strncmp(pattern + i, k_word, k_word_len) == 0 && ((path[j] >= 'a' && path[j] <= 'z') || (path[j] >= 'A' && path[j] <= 'Z'))) { while (true) { - if (_http_pattern_matches(pattern + i + 1, path + j, strchr(pattern + i + 1, '*') != NULL)) + if (((path[j] >= 'a' && path[j] <= 'z') || (path[j] >= 'A' && path[j] <= 'Z') || (path[j] >= '0' && path[j] <= '9')) && + tf_http_pattern_matches(pattern + i + k_word_len, path + j + 1)) + { + return true; + } + if (!path[j]) + { + break; + } + j++; + } + } + else if (pattern[i] == '*') + { + while (true) + { + if (tf_http_pattern_matches(pattern + i + 1, path + j)) { return true; } @@ -170,7 +192,7 @@ static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callba { for (int i = 0; i < http->handlers_count; i++) { - if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard)) + if (tf_http_pattern_matches(http->handlers[i].pattern, path)) { *out_callback = http->handlers[i].callback; *out_trace_name = http->handlers[i].pattern; @@ -741,7 +763,6 @@ 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/http.h b/src/http.h index 64588c1e..6eaba0be 100644 --- a/src/http.h +++ b/src/http.h @@ -228,4 +228,12 @@ void tf_http_request_websocket_upgrade(tf_http_request_t* request); */ const char* tf_http_status_text(int status); +/** +** Match URL patterns. "*" matches anything, and "{word}" matches [a-zA-Z][a-zA-Z0-9]*". +** @param pattern The pattern to match. +** @param path The path to test. +** @return true if the path matches the pattern. +*/ +bool tf_http_pattern_matches(const char* pattern, const char* path); + /** @} */ diff --git a/src/tests.c b/src/tests.c index cddc18a1..b7df802b 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4,8 +4,8 @@ #include "http.h" #include "log.h" #include "mem.h" -#include "ssb.h" #include "ssb.db.h" +#include "ssb.h" #include "ssb.tests.h" #include "util.js.h" @@ -850,16 +850,14 @@ static void _test_httpd(const tf_test_options_t* options) char command[256]; snprintf(command, sizeof(command), "%s run -b 0 --db-path=out/test_db0.sqlite" TEST_ARGS, options->exe_path); - uv_stdio_container_t stdio[] = - { + uv_stdio_container_t stdio[] = { [STDIN_FILENO] = { .flags = UV_IGNORE }, [STDOUT_FILENO] = { .flags = UV_INHERIT_FD }, [STDERR_FILENO] = { .flags = UV_INHERIT_FD }, }; uv_process_t process = { 0 }; uv_spawn(&loop, &process, - &(uv_process_options_t) - { + &(uv_process_options_t) { .file = options->exe_path, .args = (char*[]) { (char*)options->exe_path, "run", "-b0", "--db-path=out/test_db0.sqlite", "--http-port=8080", "--https-port=0", NULL }, .stdio_count = sizeof(stdio) / sizeof(*stdio), @@ -906,6 +904,19 @@ static void _test_httpd(const tf_test_options_t* options) uv_loop_close(&loop); } +static void _test_pattern(const tf_test_options_t* options) +{ + assert(tf_http_pattern_matches("/~core/test/", "/~core/test/")); + assert(tf_http_pattern_matches("/~core/test/*", "/~core/test/")); + assert(tf_http_pattern_matches("/~core/test/*", "/~core/test/blah")); + assert(tf_http_pattern_matches("*/~core/test/", "/~core/test/")); + assert(tf_http_pattern_matches("*/~core/test/", "blah/~core/test/")); + assert(tf_http_pattern_matches("/~*/*/", "/~core/test/")); + assert(tf_http_pattern_matches("/~{word}/*", "/~core/test")); + assert(tf_http_pattern_matches("/~{word}/{word}/", "/~core/test/")); + assert(tf_http_pattern_matches("/~{word}/{word}", "/~core/test")); +} + static void _test_auto_process_exit(uv_process_t* process, int64_t status, int termination_signal) { tf_printf("Process exit %d signal=%d.\n", (int)WEXITSTATUS(status), termination_signal); @@ -1012,6 +1023,7 @@ void tf_tests(const tf_test_options_t* options) _tf_test_run(options, "bip39", _test_bip39, false); _tf_test_run(options, "http", _test_http, false); _tf_test_run(options, "httpd", _test_httpd, false); + _tf_test_run(options, "pattern", _test_pattern, false); _tf_test_run(options, "ssb", tf_ssb_test_ssb, false); _tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false); _tf_test_run(options, "ssb_following", tf_ssb_test_following, false);