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:
Anurag Kar
2019-01-06 19:50:20 +05:30
parent 107f52c4fc
commit 416c55e7f0
2 changed files with 197 additions and 112 deletions

View File

@@ -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;