]> granicus.if.org Git - esp-idf/commitdiff
HTTP Server : Add uri_match_fn field in config structure which accepts custom URI...
authorAnurag Kar <anurag.kar@espressif.com>
Sun, 6 Jan 2019 14:20:20 +0000 (19:50 +0530)
committerAnurag Kar <anurag.kar@espressif.com>
Mon, 14 Jan 2019 03:29:55 +0000 (08:59 +0530)
Move static uri_matches() function to httpd_uri_match_wildcard() under esp_http_server.h and make it optional.

components/esp_http_server/include/esp_http_server.h
components/esp_http_server/src/httpd_uri.c

index b3da269f0181e8860e120836b862a25f9adc7489..7e26753a4c9b5bcebbcf60106f49854622b2a107 100644 (file)
@@ -49,6 +49,7 @@ initializer that should be kept in sync
         .global_transport_ctx_free_fn = NULL,           \
         .open_fn = NULL,                                \
         .close_fn = NULL,                               \
+        .uri_match_fn = NULL                            \
 }
 
 #define ESP_ERR_HTTPD_BASE              (0x8000)                    /*!< Starting number of HTTPD error codes */
@@ -61,6 +62,10 @@ initializer that should be kept in sync
 #define ESP_ERR_HTTPD_ALLOC_MEM         (ESP_ERR_HTTPD_BASE +  7)   /*!< Failed to dynamically allocate memory for resource */
 #define ESP_ERR_HTTPD_TASK              (ESP_ERR_HTTPD_BASE +  8)   /*!< Failed to launch server task/thread */
 
+/* Symbol to be used as length parameter in httpd_resp_send APIs
+ * for setting buffer length to string length */
+#define HTTPD_RESP_USE_STRLEN -1
+
 /* ************** Group: Initialization ************** */
 /** @name Initialization
  * APIs related to the Initialization of the web server
@@ -82,7 +87,7 @@ typedef enum http_method httpd_method_t;
 
 /**
  * @brief  Prototype for freeing context data (if any)
- * @param[in] ctx : object to free
+ * @param[in] ctx   object to free
  */
 typedef void (*httpd_free_ctx_fn_t)(void *ctx);
 
@@ -92,8 +97,8 @@ typedef void (*httpd_free_ctx_fn_t)(void *ctx);
  * Called immediately after the socket was opened to set up the send/recv functions and
  * other parameters of the socket.
  *
- * @param[in] hd : server instance
- * @param[in] sockfd : session socket file descriptor
+ * @param[in] hd       server instance
+ * @param[in] sockfd   session socket file descriptor
  * @return status
  */
 typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd);
@@ -104,11 +109,26 @@ typedef esp_err_t (*httpd_open_func_t)(httpd_handle_t hd, int sockfd);
  * @note   It's possible that the socket descriptor is invalid at this point, the function
  *         is called for all terminated sessions. Ensure proper handling of return codes.
  *
- * @param[in] hd : server instance
- * @param[in] sockfd : session socket file descriptor
+ * @param[in] hd   server instance
+ * @param[in] sockfd   session socket file descriptor
  */
 typedef void (*httpd_close_func_t)(httpd_handle_t hd, int sockfd);
 
+/**
+ * @brief  Function prototype for URI matching.
+ *
+ * @param[in] reference_uri   URI/template with respect to which the other URI is matched
+ * @param[in] uri_to_match    URI/template being matched to the reference URI/template
+ * @param[in] match_upto      For specifying the actual length of `uri_to_match` up to
+ *                            which the matching algorithm is to be applied (The maximum
+ *                            value is `strlen(uri_to_match)`, independent of the length
+ *                            of `reference_uri`)
+ * @return true on match
+ */
+typedef bool (*httpd_uri_match_func_t)(const char *reference_uri,
+                                       const char *uri_to_match,
+                                       size_t match_upto);
+
 /**
  * @brief   HTTP Server Configuration Structure
  *
@@ -195,6 +215,24 @@ 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 matcher function.
+     *
+     * Called when searching for a matching URI:
+     *     1) whose request handler is to be executed right
+     *        after an HTTP request is successfully parsed
+     *     2) in order to prevent duplication while registering
+     *        a new URI handler using `httpd_register_uri_handler()`
+     *
+     * Available options are:
+     *     1) NULL : Internally do basic matching using `strncmp()`
+     *     2) `httpd_uri_match_wildcard()` : URI wildcard matcher
+     *
+     * Users can implement their own matching functions (See description
+     * of the `httpd_uri_match_func_t` function prototype)
+     */
+    httpd_uri_match_func_t uri_match_fn;
 } httpd_config_t;
 
 /**
@@ -227,8 +265,8 @@ typedef struct httpd_config {
  *
  * @endcode
  *
- * @param[in]  config : Configuration for new instance of the server
- * @param[out] handle : Handle to newly created instance of the server. NULL on error
+ * @param[in]  config   Configuration for new instance of the server
+ * @param[out] handle   Handle to newly created instance of the server. NULL on error
  * @return
  *  - ESP_OK    : Instance created successfully
  *  - ESP_ERR_INVALID_ARG      : Null argument(s)
@@ -451,11 +489,11 @@ esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char* uri);
  *         HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as
  *         return value of httpd_send() function
  *
- * @param[in] hd      : server instance
- * @param[in] sockfd  : session socket file descriptor
- * @param[in] buf     : buffer with bytes to send
- * @param[in] buf_len : data size
- * @param[in] flags   : flags for the send() function
+ * @param[in] hd        server instance
+ * @param[in] sockfd    session socket file descriptor
+ * @param[in] buf       buffer with bytes to send
+ * @param[in] buf_len   data size
+ * @param[in] flags     flags for the send() function
  * @return
  *  - Bytes : The number of bytes sent successfully
  *  - HTTPD_SOCK_ERR_INVALID  : Invalid arguments
@@ -472,11 +510,11 @@ typedef int (*httpd_send_func_t)(httpd_handle_t hd, int sockfd, const char *buf,
  *         HTTPD_SOCK_ERR_ codes, which will eventually be conveyed as
  *         return value of httpd_req_recv() function
  *
- * @param[in] hd      : server instance
- * @param[in] sockfd  : session socket file descriptor
- * @param[in] buf     : buffer with bytes to send
- * @param[in] buf_len : data size
- * @param[in] flags   : flags for the send() function
+ * @param[in] hd        server instance
+ * @param[in] sockfd    session socket file descriptor
+ * @param[in] buf       buffer with bytes to send
+ * @param[in] buf_len   data size
+ * @param[in] flags     flags for the send() function
  * @return
  *  - Bytes : The number of bytes received successfully
  *  - 0     : Buffer length parameter is zero / connection closed by peer
@@ -494,8 +532,8 @@ typedef int (*httpd_recv_func_t)(httpd_handle_t hd, int sockfd, char *buf, size_
  *         HTTPD_SOCK_ERR_ codes, which will be handled accordingly in
  *         the server task.
  *
- * @param[in] hd : server instance
- * @param[in] sockfd : session socket file descriptor
+ * @param[in] hd       server instance
+ * @param[in] sockfd   session socket file descriptor
  * @return
  *  - Bytes : The number of bytes waiting to be received
  *  - HTTPD_SOCK_ERR_INVALID  : Invalid arguments
@@ -744,9 +782,29 @@ esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
  */
 esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, size_t val_size);
 
-/* Symbol to be used as length parameter in httpd_resp_send APIs
- * for setting buffer length to string length */
-#define HTTPD_RESP_USE_STRLEN -1
+/**
+ * @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] template   URI template (pattern)
+ * @param[in] uri        URI to be matched
+ * @param[in] len        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 *template, const char *uri, size_t len);
 
 /**
  * @brief   API to send a complete HTTP response.
index 9a2df51f9c0e714155cf9217660de6cfd01e30c7..4a9841f02a1d54b3513f593787fbf8b893180f42 100644 (file)
 
 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;
-            }
-        }
-    }
-    if (*err == 0) {
-        *err = HTTPD_404_NOT_FOUND;
+        free((char*)hd->hd_calls[i]->uri);
+        free(hd->hd_calls[i]);
+        hd->hd_calls[i] = NULL;
     }
-    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;