From 2d7c6f8d90987d49b510c913e9935aca621c8617 Mon Sep 17 00:00:00 2001 From: FradZGenius Date: Fri, 26 May 2023 08:57:29 -0400 Subject: [PATCH] Added URI Matching Methods Added URI Matching system with "simple" and "wildcard" methods like the ESP-IDF has. Code taken from ESP-IDF --- .../esp_http_server/include/esp_http_server.h | 41 ++++++++++ components/esp_http_server/src/httpd_uri.c | 75 ++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 93dae4dc7..0e2396f65 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -98,6 +98,15 @@ typedef void (*httpd_free_ctx_fn_t)(void *ctx); */ typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd); +/** + * @brief Function prototype for URI matching + * + */ + +typedef bool (*httpd_uri_match_func_t)(const char *reference_uri, + const char *uri_to_match, + size_t match_upto); + /** * @brief Function prototype for closing a session. * @@ -195,6 +204,12 @@ typedef struct httpd_config { * was closed by the network stack - that is, the file descriptor may not be valid anymore. */ httpd_close_func_t close_fn; + + /** + * Uri matching function + * + */ + httpd_uri_match_func_t uri_match_fn; } httpd_config_t; /** @@ -979,6 +994,32 @@ esp_err_t httpd_resp_send_408(httpd_req_t *r); */ esp_err_t httpd_resp_send_500(httpd_req_t *r); + +/** + * @brief Test if a URI matches the given wildcard template. + * + * Template may end with "?" to make the previous character optional (typically a slash), + * "*" for a wildcard match, and "?*" to make the previous character optional, and if present, + * allow anything to follow. + * + * Example: + * - * matches everything + * - /foo/? matches /foo and /foo/ + * - /foo/\* (sans the backslash) matches /foo/ and /foo/bar, but not /foo or /fo + * - /foo/?* or /foo/\*? (sans the backslash) matches /foo/, /foo/bar, and also /foo, but not /foox or /fo + * + * The special characters "?" and "*" anywhere else in the template will be taken literally. + * + * @param[in] uri_template URI template (pattern) + * @param[in] uri_to_match URI to be matched + * @param[in] match_upto how many characters of the URI buffer to test + * (there may be trailing query string etc.) + * + * @return true if a match was found + */ +bool httpd_uri_match_wildcard(const char *uri_template, const char *uri_to_match, size_t match_upto); + + /** * @brief Raw HTTP send * diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index e31a6108f..b831eef83 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -23,6 +23,71 @@ static const char *TAG = "httpd_uri"; +static bool httpd_uri_match_simple(const char *uri1, const char *uri2, size_t len2) +{ + return strlen(uri1) == len2 && // First match lengths + (strncmp(uri1, uri2, len2) == 0); // Then match actual URIs +} + +bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len) +{ + const size_t tpl_len = strlen(template); + size_t exact_match_chars = tpl_len; + + /* Check for trailing question mark and asterisk */ + const char last = (const char) (tpl_len > 0 ? template[tpl_len - 1] : 0); + const char prevlast = (const char) (tpl_len > 1 ? template[tpl_len - 2] : 0); + const bool asterisk = last == '*' || (prevlast == '*' && last == '?'); + const bool quest = last == '?' || (prevlast == '?' && last == '*'); + + /* Minimum template string length must be: + * 0 : if neither of '*' and '?' are present + * 1 : if only '*' is present + * 2 : if only '?' is present + * 3 : if both are present + * + * The expression (asterisk + quest*2) serves as a + * case wise generator of these length values + */ + + /* abort in cases such as "?" with no preceding character (invalid template) */ + if (exact_match_chars < asterisk + quest*2) { + return false; + } + + /* account for special characters and the optional character if "?" is used */ + exact_match_chars -= asterisk + quest*2; + + if (len < exact_match_chars) { + return false; + } + + if (!quest) { + if (!asterisk && len != exact_match_chars) { + /* no special characters and different length - strncmp would return false */ + return false; + } + /* asterisk allows arbitrary trailing characters, we ignore these using + * exact_match_chars as the length limit */ + return (strncmp(template, uri, exact_match_chars) == 0); + } else { + /* question mark present */ + if (len > exact_match_chars && template[exact_match_chars] != uri[exact_match_chars]) { + /* the optional character is present, but different */ + return false; + } + if (strncmp(template, uri, exact_match_chars) != 0) { + /* the mandatory part differs */ + return false; + } + /* Now we know the URI is longer than the required part of template, + * the mandatory part matches, and if the optional character is present, it is correct. + * Match is OK if we have asterisk, i.e. any trailing characters are OK, or if + * there are no characters beyond the optional character. */ + return asterisk || len <= exact_match_chars + 1; + } +} + static int httpd_find_uri_handler(struct httpd_data *hd, const char* uri, httpd_method_t method) @@ -157,12 +222,18 @@ static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err, const char *uri, size_t uri_len, httpd_method_t method) { + //if (hd->config.uri_match_fn ? + // hd->config.uri_match_fn(hd->hd_calls[i]->uri, uri, strlen(uri)) : + // httpd_uri_match_simple(hd->hd_calls[i]->uri, uri, strlen(uri))) { *err = 0; for (int i = 0; i < hd->config.max_uri_handlers; i++) { if (hd->hd_calls[i]) { ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri); - if ((strlen(hd->hd_calls[i]->uri) == uri_len) && // First match uri length - (strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0)) { // Then match uri strings + //if ((strlen(hd->hd_calls[i]->uri) == uri_len) && // First match uri length + // (strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0)) { // Then match uri strings + if (hd->config.uri_match_fn ? + hd->config.uri_match_fn(hd->hd_calls[i]->uri, uri, uri_len) : + httpd_uri_match_simple(hd->hd_calls[i]->uri, uri, uri_len)) { if (hd->hd_calls[i]->method == method) { // Finally match methods return hd->hd_calls[i]; }