From 91d6b3b9898392505d3208e47192aaec437094a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 17 Oct 2018 22:57:37 +0200 Subject: [PATCH 1/5] Implement wildcard URI matching for http_server --- components/esp_http_server/src/httpd_uri.c | 74 ++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index e31a6108fb..9a2df51f9c 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -23,6 +23,73 @@ static const char *TAG = "httpd_uri"; + +/** + * @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) +{ + 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 == '*'); + + /* 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 0 == strncmp(template, uri, exact_match_chars); + } 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)) { + /* 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) @@ -31,7 +98,7 @@ static int httpd_find_uri_handler(struct httpd_data *hd, 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 - (strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match uri strings + uri_matches(hd->hd_calls[i]->uri, uri, strlen(uri))) { // Then match uri strings return i; } } @@ -161,9 +228,8 @@ static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err, 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 (hd->hd_calls[i]->method == method) { // Finally match methods + 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. From 107f52c4fc48695e003f4a6e3b1d0eccc3db9cc4 Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Sun, 6 Jan 2019 02:44:45 +0530 Subject: [PATCH 2/5] HTTP Server : Add helper APIs for sending string content Note : In future consider deprecating usage of -1 for setting buffer length equal to string length in APIs httpd_resp_send() and httpd_resp_send_chunk() --- .../esp_http_server/include/esp_http_server.h | 50 ++++++++++++++++++- components/esp_http_server/src/httpd_txrx.c | 8 ++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 793f3dd806..b3da269f01 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -744,6 +744,10 @@ 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 API to send a complete HTTP response. * @@ -772,7 +776,7 @@ esp_err_t httpd_query_key_value(const char *qry, const char *key, char *val, siz * * @param[in] r The request being responded to * @param[in] buf Buffer from where the content is to be fetched - * @param[in] buf_len Length of the buffer, -1 to use strlen() + * @param[in] buf_len Length of the buffer, HTTPD_RESP_USE_STRLEN to use strlen() * * @return * - ESP_OK : On successfully sending the response packet @@ -811,7 +815,7 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len); * * @param[in] r The request being responded to * @param[in] buf Pointer to a buffer that stores the data - * @param[in] buf_len Length of the data from the buffer that should be sent out, -1 to use strlen() + * @param[in] buf_len Length of the buffer, HTTPD_RESP_USE_STRLEN to use strlen() * * @return * - ESP_OK : On successfully sending the response packet chunk @@ -822,6 +826,48 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len); */ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len); +/** + * @brief API to send a complete string as HTTP response. + * + * This API simply calls http_resp_send with buffer length + * set to string length assuming the buffer contains a null + * terminated string + * + * @param[in] r The request being responded to + * @param[in] str String to be sent as response body + * + * @return + * - ESP_OK : On successfully sending the response packet + * - ESP_ERR_INVALID_ARG : Null request pointer + * - ESP_ERR_HTTPD_RESP_HDR : Essential headers are too large for internal buffer + * - ESP_ERR_HTTPD_RESP_SEND : Error in raw send + * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request + */ +inline esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *str) { + return httpd_resp_send(r, str, (str == NULL) ? 0 : strlen(str)); +} + +/** + * @brief API to send a string as an HTTP response chunk. + * + * This API simply calls http_resp_send_chunk with buffer length + * set to string length assuming the buffer contains a null + * terminated string + * + * @param[in] r The request being responded to + * @param[in] str String to be sent as response body (NULL to finish response packet) + * + * @return + * - ESP_OK : On successfully sending the response packet + * - ESP_ERR_INVALID_ARG : Null request pointer + * - ESP_ERR_HTTPD_RESP_HDR : Essential headers are too large for internal buffer + * - ESP_ERR_HTTPD_RESP_SEND : Error in raw send + * - ESP_ERR_HTTPD_INVALID_REQ : Invalid request + */ +inline esp_err_t httpd_resp_sendstr_chunk(httpd_req_t *r, const char *str) { + return httpd_resp_send_chunk(r, str, (str == NULL) ? 0 : strlen(str)); +} + /* Some commonly used status codes */ #define HTTPD_200 "200 OK" /*!< HTTP Response 200 */ #define HTTPD_204 "204 No Content" /*!< HTTP Response 204 */ diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index 69a5ba0048..7721b11b79 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -246,7 +246,9 @@ esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len) const char *colon_separator = ": "; const char *cr_lf_seperator = "\r\n"; - if (buf_len == -1) buf_len = strlen(buf); + if (buf_len == HTTPD_RESP_USE_STRLEN) { + buf_len = strlen(buf); + } /* Request headers are no longer available */ ra->req_hdrs_count = 0; @@ -306,7 +308,9 @@ esp_err_t httpd_resp_send_chunk(httpd_req_t *r, const char *buf, ssize_t buf_len return ESP_ERR_HTTPD_INVALID_REQ; } - if (buf_len == -1) buf_len = strlen(buf); + if (buf_len == HTTPD_RESP_USE_STRLEN) { + buf_len = strlen(buf); + } struct httpd_req_aux *ra = r->aux; const char *httpd_chunked_hdr_str = "HTTP/1.1 %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunked\r\n"; From 416c55e7f065e9456320b6a39456761534332cd3 Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Sun, 6 Jan 2019 19:50:20 +0530 Subject: [PATCH 3/5] 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. --- .../esp_http_server/include/esp_http_server.h | 102 +++++++-- components/esp_http_server/src/httpd_uri.c | 207 ++++++++++-------- 2 files changed, 197 insertions(+), 112 deletions(-) diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index b3da269f01..7e26753a4c 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -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. diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index 9a2df51f9c..4a9841f02a 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -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; From 21878d1bbffb0ba9e12751332cb4dd386452c7dd Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Thu, 10 Jan 2019 02:59:23 +0530 Subject: [PATCH 4/5] HTTP Server : Unit test added for httpd_uri_match_wildcard() function as given in https://github.com/espressif/esp-idf/pull/2581#issuecomment-430788473 --- .../esp_http_server/test/test_http_server.c | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/components/esp_http_server/test/test_http_server.c b/components/esp_http_server/test/test_http_server.c index e343c99ca4..682a54d039 100644 --- a/components/esp_http_server/test/test_http_server.c +++ b/components/esp_http_server/test/test_http_server.c @@ -167,3 +167,68 @@ TEST_CASE("Basic Functionality Tests", "[HTTP SERVER]") test_handler_limit(hd); TEST_ASSERT(httpd_stop(hd) == ESP_OK); } + +TEST_CASE("URI Wildcard Matcher Tests", "[HTTP SERVER]") +{ + struct uritest { + const char *template; + const char *uri; + bool matches; + }; + + struct uritest uris[] = { + {"/", "/", true}, + {"", "", true}, + {"/", "", false}, + {"/wrong", "/", false}, + {"/", "/wrong", false}, + {"/asdfghjkl/qwertrtyyuiuioo", "/asdfghjkl/qwertrtyyuiuioo", true}, + {"/path", "/path", true}, + {"/path", "/path/", false}, + {"/path/", "/path", false}, + + {"?", "", false}, // this is not valid, but should not crash + {"?", "sfsdf", false}, + + {"/path/?", "/pa", false}, + {"/path/?", "/path", true}, + {"/path/?", "/path/", true}, + {"/path/?", "/path/alalal", false}, + + {"/path/*", "/path", false}, + {"/path/*", "/", false}, + {"/path/*", "/path/", true}, + {"/path/*", "/path/blabla", true}, + + {"*", "", true}, + {"*", "/", true}, + {"*", "/aaa", true}, + + {"/path/?*", "/pat", false}, + {"/path/?*", "/pathb", false}, + {"/path/?*", "/pathxx", false}, + {"/path/?*", "/pathblabla", false}, + {"/path/?*", "/path", true}, + {"/path/?*", "/path/", true}, + {"/path/?*", "/path/blabla", true}, + + {"/path/*?", "/pat", false}, + {"/path/*?", "/pathb", false}, + {"/path/*?", "/pathxx", false}, + {"/path/*?", "/path", true}, + {"/path/*?", "/path/", true}, + {"/path/*?", "/path/blabla", true}, + + {"/path/*/xxx", "/path/", false}, + {"/path/*/xxx", "/path/*/xxx", true}, + {} + }; + + struct uritest *ut = &uris[0]; + + while(ut->template != 0) { + bool match = httpd_uri_match_wildcard(ut->template, ut->uri, strlen(ut->uri)); + TEST_ASSERT(match == ut->matches); + ut++; + } +} From 5127aa1976ea0fd2bc87c54efa165636b3fda47b Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Sun, 16 Dec 2018 23:40:24 +0530 Subject: [PATCH 5/5] HTTP Server : File server example added This example demonstrates the capability of wildcard URI matching allowing for a full fledged file server to be created using esp_http_server. --- .../http_server/file_serving/CMakeLists.txt | 6 + .../http_server/file_serving/Makefile | 9 + .../http_server/file_serving/README.md | 36 ++ .../file_serving/main/CMakeLists.txt | 6 + .../file_serving/main/Kconfig.projbuild | 16 + .../file_serving/main/component.mk | 7 + .../http_server/file_serving/main/favicon.ico | Bin 0 -> 6429 bytes .../file_serving/main/file_server.c | 493 ++++++++++++++++++ .../http_server/file_serving/main/main.c | 127 +++++ .../file_serving/main/upload_script.html | 80 +++ .../file_serving/partitions_example.csv | 6 + .../file_serving/sdkconfig.defaults | 6 + 12 files changed, 792 insertions(+) create mode 100644 examples/protocols/http_server/file_serving/CMakeLists.txt create mode 100644 examples/protocols/http_server/file_serving/Makefile create mode 100644 examples/protocols/http_server/file_serving/README.md create mode 100644 examples/protocols/http_server/file_serving/main/CMakeLists.txt create mode 100644 examples/protocols/http_server/file_serving/main/Kconfig.projbuild create mode 100644 examples/protocols/http_server/file_serving/main/component.mk create mode 100644 examples/protocols/http_server/file_serving/main/favicon.ico create mode 100644 examples/protocols/http_server/file_serving/main/file_server.c create mode 100644 examples/protocols/http_server/file_serving/main/main.c create mode 100644 examples/protocols/http_server/file_serving/main/upload_script.html create mode 100644 examples/protocols/http_server/file_serving/partitions_example.csv create mode 100644 examples/protocols/http_server/file_serving/sdkconfig.defaults diff --git a/examples/protocols/http_server/file_serving/CMakeLists.txt b/examples/protocols/http_server/file_serving/CMakeLists.txt new file mode 100644 index 0000000000..17591d30d7 --- /dev/null +++ b/examples/protocols/http_server/file_serving/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(file_server) diff --git a/examples/protocols/http_server/file_serving/Makefile b/examples/protocols/http_server/file_serving/Makefile new file mode 100644 index 0000000000..7b1def8bc8 --- /dev/null +++ b/examples/protocols/http_server/file_serving/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := file_server + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/http_server/file_serving/README.md b/examples/protocols/http_server/file_serving/README.md new file mode 100644 index 0000000000..901d5db6ec --- /dev/null +++ b/examples/protocols/http_server/file_serving/README.md @@ -0,0 +1,36 @@ +# Simple HTTPD File Server Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +HTTP file server example demonstrates file serving using the 'esp_http_server' component of ESP-IDF: + 1. URI `/path/filename` for GET command downloads the corresponding file (if exists) + 2. URI `/upload` POST command for uploading the file onto the device + 3. URI `/delete` POST command for deleting the file from the device + +File server implementation can be found under `main/file_server.c` which uses SPIFFS for file storage. `main/upload_script.html` has some HTML, JavaScript and Ajax content used for file uploading, which is embedded in the flash image and used as it is when generating the home page of the file server. + +## Usage + +* Configure the project using `make menuconfig` and goto : + * Example Configuration -> + 1. WIFI SSID: WIFI network to which your PC is also connected to. + 2. WIFI Password: WIFI password + +* In order to test the file server demo : + 1. compile and burn the firmware `make flash` + 2. run `make monitor` and note down the IP assigned to your ESP module. The default port is 80 + 3. test the example interactively on a web browser (assuming IP is 192.168.43.130): + 1. open path `http://192.168.43.130/` or `http://192.168.43.130/index.html` to see an HTML web page with list of files on the server (initially empty) + 2. use the file upload form on the webpage to select and upload a file to the server + 3. click a file link to download / open the file on browser (if supported) + 4. click the delete link visible next to each file entry to delete them + 4. test the example using curl (assuming IP is 192.168.43.130): + 1. `curl -X POST --data-binary @myfile.html 192.168.43.130:80/upload/path/on/device/myfile_copy.html` + * `myfile.html` is uploaded to `/path/on/device/myfile_copy.html` + 2. `curl 192.168.43.130:80/path/on/device/myfile_copy.html > myfile_copy.html` + * Downloads the uploaded copy back + 3. Compare the copy with the original using `cmp myfile.html myfile_copy.html` + +## Note + +Browsers often send large header fields when an HTML form is submit. Therefore, for the purpose of this example, 'HTTPD_MAX_REQ_HDR_LEN' has been increased to 1024 in `sdkconfig.defaults`. User can adjust this value as per their requirement, keeping in mind the memory constraint of the hardware in use. diff --git a/examples/protocols/http_server/file_serving/main/CMakeLists.txt b/examples/protocols/http_server/file_serving/main/CMakeLists.txt new file mode 100644 index 0000000000..a9493f54a8 --- /dev/null +++ b/examples/protocols/http_server/file_serving/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "main.c" "file_server.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_EMBED_FILES "favicon.ico" "upload_script.html") + +register_component() diff --git a/examples/protocols/http_server/file_serving/main/Kconfig.projbuild b/examples/protocols/http_server/file_serving/main/Kconfig.projbuild new file mode 100644 index 0000000000..9e2813c697 --- /dev/null +++ b/examples/protocols/http_server/file_serving/main/Kconfig.projbuild @@ -0,0 +1,16 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +endmenu diff --git a/examples/protocols/http_server/file_serving/main/component.mk b/examples/protocols/http_server/file_serving/main/component.mk new file mode 100644 index 0000000000..539b9ac1d9 --- /dev/null +++ b/examples/protocols/http_server/file_serving/main/component.mk @@ -0,0 +1,7 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_EMBED_FILES := favicon.ico +COMPONENT_EMBED_FILES += upload_script.html diff --git a/examples/protocols/http_server/file_serving/main/favicon.ico b/examples/protocols/http_server/file_serving/main/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..17b8ff3cb6c3c920c732f563c2507c8472dfb95c GIT binary patch literal 6429 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 +#include +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_log.h" + +#include "esp_vfs.h" +#include "esp_spiffs.h" +#include "esp_http_server.h" + +/* Max length a file path can have on storage */ +#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) + +/* Max size of an individual file. Make sure this + * value is same as that set in upload_script.html */ +#define MAX_FILE_SIZE (200*1024) // 200 KB +#define MAX_FILE_SIZE_STR "200KB" + +/* Scratch buffer size */ +#define SCRATCH_BUFSIZE 8192 + +struct file_server_data { + /* Base path of file storage */ + char base_path[ESP_VFS_PATH_MAX + 1]; + + /* Scratch buffer for temporary storage during file transfer */ + char scratch[SCRATCH_BUFSIZE]; +}; + +static const char *TAG = "file_server"; + +/* Handler to redirect incoming GET request for /index.html to / */ +static esp_err_t index_html_get_handler(httpd_req_t *req) +{ + httpd_resp_set_status(req, "301 Permanent Redirect"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_send(req, NULL, 0); // Response body can be empty + return ESP_OK; +} + +/* Send HTTP response with a run-time generated html consisting of + * a list of all files and folders under the requested path */ +static esp_err_t http_resp_dir_html(httpd_req_t *req) +{ + char fullpath[FILE_PATH_MAX]; + char entrysize[16]; + const char *entrytype; + + DIR *dir = NULL; + struct dirent *entry; + struct stat entry_stat; + + /* Retrieve the base path of file storage to construct the full path */ + strcpy(fullpath, ((struct file_server_data *)req->user_ctx)->base_path); + + /* Concatenate the requested directory path */ + strcat(fullpath, req->uri); + dir = opendir(fullpath); + const size_t entrypath_offset = strlen(fullpath); + + if (!dir) { + /* If opening directory failed then send 404 server error */ + httpd_resp_send_404(req); + return ESP_OK; + } + + /* Send HTML file header */ + httpd_resp_sendstr_chunk(req, ""); + + /* Get handle to embedded file upload script */ + extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start"); + extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end"); + const size_t upload_script_size = (upload_script_end - upload_script_start); + + /* Add file upload form and script which on execution sends a POST request to /upload */ + httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size); + + /* Send file-list table definition and column labels */ + httpd_resp_sendstr_chunk(req, + "" + "" + "" + ""); + + /* Iterate over all files / folders and fetch their names and sizes */ + while ((entry = readdir(dir)) != NULL) { + entrytype = (entry->d_type == DT_DIR ? "directory" : "file"); + + strncpy(fullpath + entrypath_offset, entry->d_name, sizeof(fullpath) - entrypath_offset); + if (stat(fullpath, &entry_stat) == -1) { + ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name); + continue; + } + sprintf(entrysize, "%ld", entry_stat.st_size); + ESP_LOGI(TAG, "Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize); + + /* Send chunk of HTML file containing table entries with file name and size */ + httpd_resp_sendstr_chunk(req, "\n"); + } + closedir(dir); + + /* Finish the file list table */ + httpd_resp_sendstr_chunk(req, "
NameTypeSize (Bytes)Delete
uri); + httpd_resp_sendstr_chunk(req, entry->d_name); + if (entry->d_type == DT_DIR) { + httpd_resp_sendstr_chunk(req, "/"); + } + httpd_resp_sendstr_chunk(req, "\">"); + httpd_resp_sendstr_chunk(req, entry->d_name); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, entrytype); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, entrysize); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, "
uri); + httpd_resp_sendstr_chunk(req, entry->d_name); + httpd_resp_sendstr_chunk(req, "\">
"); + httpd_resp_sendstr_chunk(req, "
"); + + /* Send remaining chunk of HTML file to complete it */ + httpd_resp_sendstr_chunk(req, ""); + + /* Send empty chunk to signal HTTP response completion */ + httpd_resp_sendstr_chunk(req, NULL); + return ESP_OK; +} + +#define IS_FILE_EXT(filename, ext) \ + (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0) + +/* Set HTTP response content type according to file extension */ +static esp_err_t set_content_type_from_file(httpd_req_t *req) +{ + if (IS_FILE_EXT(req->uri, ".pdf")) { + return httpd_resp_set_type(req, "application/pdf"); + } else if (IS_FILE_EXT(req->uri, ".html")) { + return httpd_resp_set_type(req, "text/html"); + } else if (IS_FILE_EXT(req->uri, ".jpeg")) { + return httpd_resp_set_type(req, "image/jpeg"); + } + /* This is a limited set only */ + /* For any other type always set as plain text */ + return httpd_resp_set_type(req, "text/plain"); +} + +/* Send HTTP response with the contents of the requested file */ +static esp_err_t http_resp_file(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + FILE *fd = NULL; + struct stat file_stat; + + /* Retrieve the base path of file storage to construct the full path */ + strcpy(filepath, ((struct file_server_data *)req->user_ctx)->base_path); + + /* Concatenate the requested file path */ + strcat(filepath, req->uri); + if (stat(filepath, &file_stat) == -1) { + ESP_LOGE(TAG, "Failed to stat file : %s", filepath); + /* If file doesn't exist respond with 404 Not Found */ + httpd_resp_send_404(req); + return ESP_OK; + } + + fd = fopen(filepath, "r"); + if (!fd) { + ESP_LOGE(TAG, "Failed to read existing file : %s", filepath); + /* If file exists but unable to open respond with 500 Server Error */ + httpd_resp_set_status(req, "500 Server Error"); + httpd_resp_sendstr(req, "Failed to read existing file!"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filepath, file_stat.st_size); + set_content_type_from_file(req); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *chunk = ((struct file_server_data *)req->user_ctx)->scratch; + size_t chunksize; + do { + /* Read file in chunks into the scratch buffer */ + chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); + + /* Send the buffer contents as HTTP response chunk */ + if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { + fclose(fd); + ESP_LOGE(TAG, "File sending failed!"); + /* Abort sending file */ + httpd_resp_sendstr_chunk(req, NULL); + /* Send error message with status code */ + httpd_resp_set_status(req, "500 Server Error"); + httpd_resp_sendstr(req, "Failed to send file!"); + return ESP_OK; + } + + /* Keep looping till the whole file is sent */ + } while (chunksize != 0); + + /* Close file after sending complete */ + fclose(fd); + ESP_LOGI(TAG, "File sending complete"); + + /* Respond with an empty chunk to signal HTTP response completion */ + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + +/* Handler to download a file kept on the server */ +static esp_err_t download_get_handler(httpd_req_t *req) +{ + // Check if the target is a directory + if (req->uri[strlen(req->uri) - 1] == '/') { + // In so, send an html with directory listing + http_resp_dir_html(req); + } else { + // Else send the file + http_resp_file(req); + } + return ESP_OK; +} + +/* Handler to upload a file onto the server */ +static esp_err_t upload_post_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + FILE *fd = NULL; + struct stat file_stat; + + /* Skip leading "/upload" from URI to get filename */ + /* Note sizeof() counts NULL termination hence the -1 */ + const char *filename = req->uri + sizeof("/upload") - 1; + + /* Filename cannot be empty or have a trailing '/' */ + if (strlen(filename) == 0 || filename[strlen(filename) - 1] == '/') { + ESP_LOGE(TAG, "Invalid file name : %s", filename); + /* Respond with 400 Bad Request */ + httpd_resp_set_status(req, "400 Bad Request"); + /* Send failure reason */ + httpd_resp_sendstr(req, "Invalid file name!"); + return ESP_OK; + } + + /* Retrieve the base path of file storage to construct the full path */ + strcpy(filepath, ((struct file_server_data *)req->user_ctx)->base_path); + + /* Concatenate the requested file path */ + strcat(filepath, filename); + if (stat(filepath, &file_stat) == 0) { + ESP_LOGE(TAG, "File already exists : %s", filepath); + /* If file exists respond with 400 Bad Request */ + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "File already exists!"); + return ESP_OK; + } + + /* File cannot be larger than a limit */ + if (req->content_len > MAX_FILE_SIZE) { + ESP_LOGE(TAG, "File too large : %d bytes", req->content_len); + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "File size must be less than " + MAX_FILE_SIZE_STR "!"); + /* Return failure to close underlying connection else the + * incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + fd = fopen(filepath, "w"); + if (!fd) { + ESP_LOGE(TAG, "Failed to create file : %s", filepath); + /* If file creation failed, respond with 500 Server Error */ + httpd_resp_set_status(req, "500 Server Error"); + httpd_resp_sendstr(req, "Failed to create file!"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Receiving file : %s...", filename); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = ((struct file_server_data *)req->user_ctx)->scratch; + int received; + + /* Content length of the request gives + * the size of the file being uploaded */ + int remaining = req->content_len; + + while (remaining > 0) { + + ESP_LOGI(TAG, "Remaining size : %d", remaining); + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) { + if (received == HTTPD_SOCK_ERR_TIMEOUT) { + /* Retry if timeout occurred */ + continue; + } + + /* In case of unrecoverable error, + * close and delete the unfinished file*/ + fclose(fd); + unlink(filepath); + + ESP_LOGE(TAG, "File reception failed!"); + /* Return failure reason with status code */ + httpd_resp_set_status(req, "500 Server Error"); + httpd_resp_sendstr(req, "Failed to receive file!"); + return ESP_OK; + } + + /* Write buffer content to file on storage */ + if (received && (received != fwrite(buf, 1, received, fd))) { + /* Couldn't write everything to file! + * Storage may be full? */ + fclose(fd); + unlink(filepath); + + ESP_LOGE(TAG, "File write failed!"); + httpd_resp_set_status(req, "500 Server Error"); + httpd_resp_sendstr(req, "Failed to write file to storage!"); + return ESP_OK; + } + + /* Keep track of remaining size of + * the file left to be uploaded */ + remaining -= received; + } + + /* Close file upon upload completion */ + fclose(fd); + ESP_LOGI(TAG, "File reception complete"); + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_sendstr(req, "File uploaded successfully"); + return ESP_OK; +} + +/* Handler to delete a file from the server */ +static esp_err_t delete_post_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + struct stat file_stat; + + /* Skip leading "/upload" from URI to get filename */ + /* Note sizeof() counts NULL termination hence the -1 */ + const char *filename = req->uri + sizeof("/upload") - 1; + + /* Filename cannot be empty or have a trailing '/' */ + if (strlen(filename) == 0 || filename[strlen(filename) - 1] == '/') { + ESP_LOGE(TAG, "Invalid file name : %s", filename); + /* Respond with 400 Bad Request */ + httpd_resp_set_status(req, "400 Bad Request"); + /* Send failure reason */ + httpd_resp_sendstr(req, "Invalid file name!"); + return ESP_OK; + } + + /* Retrieve the base path of file storage to construct the full path */ + strcpy(filepath, ((struct file_server_data *)req->user_ctx)->base_path); + + /* Concatenate the requested file path */ + strcat(filepath, filename); + if (stat(filepath, &file_stat) == -1) { + ESP_LOGE(TAG, "File does not exist : %s", filename); + /* If file does not exist respond with 400 Bad Request */ + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "File does not exist!"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Deleting file : %s", filename); + /* Delete file */ + unlink(filepath); + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_sendstr(req, "File deleted successfully"); + return ESP_OK; +} + +/* Handler to respond with an icon file embedded in flash. + * Browsers expect to GET website icon at URI /favicon.ico */ +static esp_err_t favicon_get_handler(httpd_req_t *req) +{ + extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start"); + extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end"); + const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start); + httpd_resp_set_type(req, "image/x-icon"); + httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size); + return ESP_OK; +} + +/* Function to start the file server */ +esp_err_t start_file_server(const char *base_path) +{ + static struct file_server_data *server_data = NULL; + + /* Validate file storage base path */ + if (!base_path || strcmp(base_path, "/spiffs") != 0) { + ESP_LOGE(TAG, "File server presently supports only '/spiffs' as base path"); + return ESP_ERR_INVALID_ARG; + } + + if (server_data) { + ESP_LOGE(TAG, "File server already started"); + return ESP_ERR_INVALID_STATE; + } + + /* Allocate memory for server data */ + server_data = calloc(1, sizeof(struct file_server_data)); + if (!server_data) { + ESP_LOGE(TAG, "Failed to allocate memory for server data"); + return ESP_ERR_NO_MEM; + } + strlcpy(server_data->base_path, base_path, + sizeof(server_data->base_path)); + + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + /* Use the URI wildcard matching function in order to + * allow the same handler to respond to multiple different + * target URIs which match the wildcard scheme */ + config.uri_match_fn = httpd_uri_match_wildcard; + + ESP_LOGI(TAG, "Starting HTTP Server"); + if (httpd_start(&server, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start file server!"); + return ESP_FAIL; + } + + /* Register handler for index.html which should redirect to / */ + httpd_uri_t index_html = { + .uri = "/index.html", + .method = HTTP_GET, + .handler = index_html_get_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &index_html); + + /* Handler for URI used by browsers to get website icon */ + httpd_uri_t favicon_ico = { + .uri = "/favicon.ico", + .method = HTTP_GET, + .handler = favicon_get_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &favicon_ico); + + /* URI handler for getting uploaded files */ + httpd_uri_t file_download = { + .uri = "/*", // Match all URIs of type /path/to/file (except index.html) + .method = HTTP_GET, + .handler = download_get_handler, + .user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &file_download); + + /* URI handler for uploading files to server */ + httpd_uri_t file_upload = { + .uri = "/upload/*", // Match all URIs of type /upload/path/to/file + .method = HTTP_POST, + .handler = upload_post_handler, + .user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &file_upload); + + /* URI handler for deleting files from server */ + httpd_uri_t file_delete = { + .uri = "/delete/*", // Match all URIs of type /delete/path/to/file + .method = HTTP_POST, + .handler = delete_post_handler, + .user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &file_delete); + + return ESP_OK; +} diff --git a/examples/protocols/http_server/file_serving/main/main.c b/examples/protocols/http_server/file_serving/main/main.c new file mode 100644 index 0000000000..ee1aaeef60 --- /dev/null +++ b/examples/protocols/http_server/file_serving/main/main.c @@ -0,0 +1,127 @@ +/* HTTP File Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include + +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_spiffs.h" +#include "nvs_flash.h" + +/* This example demonstrates how to create file server + * using esp_http_server. This file has only startup code. + * Look in file_server.c for the implementation */ + +/* The example uses simple WiFi configuration that you can set via + * 'make menuconfig'. + * If you'd rather not, just change the below entries to strings + * with the config you want - + * ie. #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +static const char *TAG="example"; + +/* Wi-Fi event handler */ +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + ESP_ERROR_CHECK(esp_wifi_connect()); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + ESP_LOGI(TAG, "Got IP: '%s'", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); + ESP_ERROR_CHECK(esp_wifi_connect()); + break; + default: + break; + } + return ESP_OK; +} + +/* Function to initialize Wi-Fi at station */ +static void initialise_wifi(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + +/* Function to initialize SPIFFS */ +static esp_err_t init_spiffs(void) +{ + ESP_LOGI(TAG, "Initializing SPIFFS"); + + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = NULL, + .max_files = 5, // This decides the maximum number of files that can be created on the storage + .format_if_mount_failed = true + }; + + esp_err_t ret = esp_vfs_spiffs_register(&conf); + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount or format filesystem"); + } else if (ret == ESP_ERR_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to find SPIFFS partition"); + } else { + ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); + } + return ESP_FAIL; + } + + size_t total = 0, used = 0; + ret = esp_spiffs_info(NULL, &total, &used); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); + return ESP_OK; +} + +/* Declare the function which starts the file server. + * Implementation of this function is to be found in + * file_server.c */ +esp_err_t start_file_server(const char *base_path); + +void app_main() +{ + initialise_wifi(); + + /* Initialize file storage */ + ESP_ERROR_CHECK(init_spiffs()); + + /* Start the file server */ + ESP_ERROR_CHECK(start_file_server("/spiffs")); +} diff --git a/examples/protocols/http_server/file_serving/main/upload_script.html b/examples/protocols/http_server/file_serving/main/upload_script.html new file mode 100644 index 0000000000..5513ee89b1 --- /dev/null +++ b/examples/protocols/http_server/file_serving/main/upload_script.html @@ -0,0 +1,80 @@ + + + +
+

ESP32 File Server

+
+ + + + + + + + + + +
+ + + +
+ + + + + +
+
+ diff --git a/examples/protocols/http_server/file_serving/partitions_example.csv b/examples/protocols/http_server/file_serving/partitions_example.csv new file mode 100644 index 0000000000..6d9ee2e745 --- /dev/null +++ b/examples/protocols/http_server/file_serving/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, spiffs, , 0xF0000, diff --git a/examples/protocols/http_server/file_serving/sdkconfig.defaults b/examples/protocols/http_server/file_serving/sdkconfig.defaults new file mode 100644 index 0000000000..e64150b079 --- /dev/null +++ b/examples/protocols/http_server/file_serving/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_APP_OFFSET=0x10000 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024