mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-17 07:09:37 +00:00
HTTP Server : Add uri_match_fn field in config structure which accepts custom URI matching functions of type httpd_uri_match_func_t and defaults to basic string compare when set to NULL.
Move static uri_matches() function to httpd_uri_match_wildcard() under esp_http_server.h and make it optional.
This commit is contained in:
@@ -23,30 +23,13 @@
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test if a URI matches the given 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] template - URI template (pattern)
|
||||
* @param[in] uri - tested URI
|
||||
* @param[in] template - how many characters of the URI buffer to test
|
||||
* (there may be trailing query string etc.)
|
||||
*
|
||||
* @return true if a match was found
|
||||
*/
|
||||
static bool uri_matches(const char *template, const char *uri, const unsigned int len)
|
||||
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;
|
||||
@@ -57,12 +40,27 @@ static bool uri_matches(const char *template, const char *uri, const unsigned in
|
||||
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;
|
||||
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 (len < exact_match_chars) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!quest) {
|
||||
if (!asterisk && len != exact_match_chars) {
|
||||
@@ -71,14 +69,14 @@ static bool uri_matches(const char *template, const char *uri, const unsigned in
|
||||
}
|
||||
/* asterisk allows arbitrary trailing characters, we ignore these using
|
||||
* exact_match_chars as the length limit */
|
||||
return 0 == strncmp(template, uri, exact_match_chars);
|
||||
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 (0 != strncmp(template, uri, exact_match_chars)) {
|
||||
if (strncmp(template, uri, exact_match_chars) != 0) {
|
||||
/* the mandatory part differs */
|
||||
return false;
|
||||
}
|
||||
@@ -90,20 +88,47 @@ static bool uri_matches(const char *template, const char *uri, const unsigned in
|
||||
}
|
||||
}
|
||||
|
||||
static int httpd_find_uri_handler(struct httpd_data *hd,
|
||||
const char* uri,
|
||||
httpd_method_t method)
|
||||
/* Find handler with matching URI and method, and set
|
||||
* appropriate error code if URI or method not found */
|
||||
static httpd_uri_t* httpd_find_uri_handler(struct httpd_data *hd,
|
||||
const char *uri, size_t uri_len,
|
||||
httpd_method_t method,
|
||||
httpd_err_resp_t *err)
|
||||
{
|
||||
if (err) {
|
||||
*err = HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
|
||||
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 ((hd->hd_calls[i]->method == method) && // First match methods
|
||||
uri_matches(hd->hd_calls[i]->uri, uri, strlen(uri))) { // Then match uri strings
|
||||
return i;
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
/* Check if custom URI matching function is set,
|
||||
* else use simple string compare */
|
||||
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)) {
|
||||
/* URIs match. Now check if method is supported */
|
||||
if (hd->hd_calls[i]->method == method) {
|
||||
/* Match found! */
|
||||
if (err) {
|
||||
/* Unset any error that may
|
||||
* have been set earlier */
|
||||
*err = 0;
|
||||
}
|
||||
return hd->hd_calls[i];
|
||||
}
|
||||
/* URI found but method not allowed.
|
||||
* If URI is found later then this
|
||||
* error must be set to 0 */
|
||||
if (err) {
|
||||
*err = HTTPD_405_METHOD_NOT_ALLOWED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
|
||||
@@ -115,11 +140,13 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
|
||||
/* Make sure another handler with same URI and method
|
||||
* is not already registered
|
||||
*/
|
||||
/* Make sure another handler with matching URI and method
|
||||
* is not already registered. This will also catch cases
|
||||
* when a registered URI wildcard pattern already accounts
|
||||
* for the new URI being registered */
|
||||
if (httpd_find_uri_handler(handle, uri_handler->uri,
|
||||
uri_handler->method) != -1) {
|
||||
strlen(uri_handler->uri),
|
||||
uri_handler->method, NULL) != NULL) {
|
||||
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
|
||||
uri_handler->uri, uri_handler->method);
|
||||
return ESP_ERR_HTTPD_HANDLER_EXISTS;
|
||||
@@ -162,15 +189,30 @@ esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
|
||||
}
|
||||
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
int i = httpd_find_uri_handler(hd, uri, method);
|
||||
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
if ((hd->hd_calls[i]->method == method) && // First match methods
|
||||
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match URI string
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
if (i != -1) {
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
return ESP_OK;
|
||||
/* Shift the remaining non null handlers in the array
|
||||
* forward by 1 so that order of insertion is maintained */
|
||||
for (i += 1; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
hd->hd_calls[i-1] = hd->hd_calls[i];
|
||||
}
|
||||
/* Nullify the following non null entry */
|
||||
hd->hd_calls[i-1] = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
@@ -185,17 +227,31 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
|
||||
struct httpd_data *hd = (struct httpd_data *) handle;
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if ((hd->hd_calls[i] != NULL) &&
|
||||
(strcmp(hd->hd_calls[i]->uri, uri) == 0)) {
|
||||
int i = 0, j = 0; // For keeping count of removed entries
|
||||
for (; i < hd->config.max_uri_handlers; i++) {
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(hd->hd_calls[i]->uri, uri) == 0) { // Match URI strings
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
found = true;
|
||||
|
||||
j++; // Update count of removed entries
|
||||
} else {
|
||||
/* Shift the remaining non null handlers in the array
|
||||
* forward by j so that order of insertion is maintained */
|
||||
hd->hd_calls[i-j] = hd->hd_calls[i];
|
||||
}
|
||||
}
|
||||
/* Nullify the following non null entries */
|
||||
for (int k = (i - j); k < i; k++) {
|
||||
hd->hd_calls[k] = NULL;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
|
||||
}
|
||||
@@ -205,44 +261,15 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
|
||||
void httpd_unregister_all_uri_handlers(struct httpd_data *hd)
|
||||
{
|
||||
for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) {
|
||||
if (hd->hd_calls[i]) {
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
if (!hd->hd_calls[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
|
||||
|
||||
/* Alternate implmentation of httpd_find_uri_handler()
|
||||
* which takes a uri_len field. This is useful when the URI
|
||||
* string contains extra parameters that are not to be included
|
||||
* while matching with the registered URI_handler strings
|
||||
*/
|
||||
static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err,
|
||||
struct httpd_data *hd,
|
||||
const char *uri, size_t uri_len,
|
||||
httpd_method_t method)
|
||||
{
|
||||
*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 (uri_matches(hd->hd_calls[i]->uri, uri, uri_len)) {
|
||||
if (hd->hd_calls[i]->method == method) { // Match methods
|
||||
return hd->hd_calls[i];
|
||||
}
|
||||
/* URI found but method not allowed.
|
||||
* If URI IS found later then this
|
||||
* error is to be neglected */
|
||||
*err = HTTPD_405_METHOD_NOT_ALLOWED;
|
||||
}
|
||||
}
|
||||
free((char*)hd->hd_calls[i]->uri);
|
||||
free(hd->hd_calls[i]);
|
||||
hd->hd_calls[i] = NULL;
|
||||
}
|
||||
if (*err == 0) {
|
||||
*err = HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t httpd_uri(struct httpd_data *hd)
|
||||
@@ -255,12 +282,11 @@ esp_err_t httpd_uri(struct httpd_data *hd)
|
||||
httpd_err_resp_t err = 0;
|
||||
|
||||
ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
|
||||
|
||||
/* URL parser result contains offset and length of path string */
|
||||
if (res->field_set & (1 << UF_PATH)) {
|
||||
uri = httpd_find_uri_handler2(&err, hd,
|
||||
req->uri + res->field_data[UF_PATH].off,
|
||||
res->field_data[UF_PATH].len,
|
||||
req->method);
|
||||
uri = httpd_find_uri_handler(hd, req->uri + res->field_data[UF_PATH].off,
|
||||
res->field_data[UF_PATH].len, req->method, &err);
|
||||
}
|
||||
|
||||
/* If URI with method not found, respond with error code */
|
||||
@@ -270,7 +296,8 @@ esp_err_t httpd_uri(struct httpd_data *hd)
|
||||
ESP_LOGW(TAG, LOG_FMT("URI '%s' not found"), req->uri);
|
||||
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND);
|
||||
case HTTPD_405_METHOD_NOT_ALLOWED:
|
||||
ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"), req->method, req->uri);
|
||||
ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"),
|
||||
req->method, req->uri);
|
||||
return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
|
||||
default:
|
||||
return ESP_FAIL;
|
||||
|
Reference in New Issue
Block a user