|
|
@@ -42,37 +42,50 @@ struct file_server_data {
|
|
|
|
|
|
static const char *TAG = "file_server";
|
|
|
|
|
|
-/* Handler to redirect incoming GET request for /index.html to / */
|
|
|
+/* Handler to redirect incoming GET request for /index.html to /
|
|
|
+ * This can be overridden by uploading file with same name */
|
|
|
static esp_err_t index_html_get_handler(httpd_req_t *req)
|
|
|
{
|
|
|
- httpd_resp_set_status(req, "301 Permanent Redirect");
|
|
|
+ httpd_resp_set_status(req, "307 Temporary Redirect");
|
|
|
httpd_resp_set_hdr(req, "Location", "/");
|
|
|
httpd_resp_send(req, NULL, 0); // Response body can be empty
|
|
|
return ESP_OK;
|
|
|
}
|
|
|
|
|
|
+/* Handler to respond with an icon file embedded in flash.
|
|
|
+ * Browsers expect to GET website icon at URI /favicon.ico.
|
|
|
+ * This can be overridden by uploading file with same name */
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
/* 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)
|
|
|
+ * a list of all files and folders under the requested path.
|
|
|
+ * In case of SPIFFS this returns empty list when path is any
|
|
|
+ * string other than '/', since SPIFFS doesn't support directories */
|
|
|
+static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath)
|
|
|
{
|
|
|
- char fullpath[FILE_PATH_MAX];
|
|
|
+ char entrypath[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);
|
|
|
+ DIR *dir = opendir(dirpath);
|
|
|
+ const size_t dirpath_len = strlen(dirpath);
|
|
|
|
|
|
- /* Concatenate the requested directory path */
|
|
|
- strcat(fullpath, req->uri);
|
|
|
- dir = opendir(fullpath);
|
|
|
- const size_t entrypath_offset = strlen(fullpath);
|
|
|
+ /* Retrieve the base path of file storage to construct the full path */
|
|
|
+ strlcpy(entrypath, dirpath, sizeof(entrypath));
|
|
|
|
|
|
if (!dir) {
|
|
|
- ESP_LOGE(TAG, "Failed to stat dir : %s", fullpath);
|
|
|
+ ESP_LOGE(TAG, "Failed to stat dir : %s", dirpath);
|
|
|
/* Respond with 404 Not Found */
|
|
|
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Directory does not exist");
|
|
|
return ESP_FAIL;
|
|
|
@@ -100,8 +113,8 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req)
|
|
|
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) {
|
|
|
+ strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
|
|
|
+ if (stat(entrypath, &entry_stat) == -1) {
|
|
|
ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name);
|
|
|
continue;
|
|
|
}
|
|
|
@@ -145,33 +158,80 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req)
|
|
|
(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)
|
|
|
+static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
|
|
|
{
|
|
|
- if (IS_FILE_EXT(req->uri, ".pdf")) {
|
|
|
+ if (IS_FILE_EXT(filename, ".pdf")) {
|
|
|
return httpd_resp_set_type(req, "application/pdf");
|
|
|
- } else if (IS_FILE_EXT(req->uri, ".html")) {
|
|
|
+ } else if (IS_FILE_EXT(filename, ".html")) {
|
|
|
return httpd_resp_set_type(req, "text/html");
|
|
|
- } else if (IS_FILE_EXT(req->uri, ".jpeg")) {
|
|
|
+ } else if (IS_FILE_EXT(filename, ".jpeg")) {
|
|
|
return httpd_resp_set_type(req, "image/jpeg");
|
|
|
+ } else if (IS_FILE_EXT(filename, ".ico")) {
|
|
|
+ return httpd_resp_set_type(req, "image/x-icon");
|
|
|
}
|
|
|
/* 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)
|
|
|
+/* Copies the full path into destination buffer and returns
|
|
|
+ * pointer to path (skipping the preceding base path) */
|
|
|
+static const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
|
|
|
+{
|
|
|
+ const size_t base_pathlen = strlen(base_path);
|
|
|
+ size_t pathlen = strlen(uri);
|
|
|
+
|
|
|
+ const char *quest = strchr(uri, '?');
|
|
|
+ if (quest) {
|
|
|
+ pathlen = MIN(pathlen, quest - uri);
|
|
|
+ }
|
|
|
+ const char *hash = strchr(uri, '#');
|
|
|
+ if (hash) {
|
|
|
+ pathlen = MIN(pathlen, hash - uri);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (base_pathlen + pathlen + 1 > destsize) {
|
|
|
+ /* Full path string won't fit into destination buffer */
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Construct full path (base + path) */
|
|
|
+ strcpy(dest, base_path);
|
|
|
+ strlcpy(dest + base_pathlen, uri, pathlen + 1);
|
|
|
+
|
|
|
+ /* Return pointer to path, skipping the base */
|
|
|
+ return dest + base_pathlen;
|
|
|
+}
|
|
|
+
|
|
|
+/* Handler to download a file kept on the server */
|
|
|
+static esp_err_t download_get_handler(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);
|
|
|
+ const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
|
|
+ req->uri, sizeof(filepath));
|
|
|
+ if (!filename) {
|
|
|
+ ESP_LOGE(TAG, "Filename is too long");
|
|
|
+ /* Respond with 500 Internal Server Error */
|
|
|
+ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If name has trailing '/', respond with directory contents */
|
|
|
+ if (filename[strlen(filename) - 1] == '/') {
|
|
|
+ return http_resp_dir_html(req, filepath);
|
|
|
+ }
|
|
|
|
|
|
- /* Concatenate the requested file path */
|
|
|
- strcat(filepath, req->uri);
|
|
|
if (stat(filepath, &file_stat) == -1) {
|
|
|
+ /* If file not present on SPIFFS check if URI
|
|
|
+ * corresponds to one of the hardcoded paths */
|
|
|
+ if (strcmp(filename, "/index.html") == 0) {
|
|
|
+ return index_html_get_handler(req);
|
|
|
+ } else if (strcmp(filename, "/favicon.ico") == 0) {
|
|
|
+ return favicon_get_handler(req);
|
|
|
+ }
|
|
|
ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
|
|
|
/* Respond with 404 Not Found */
|
|
|
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
|
|
|
@@ -186,8 +246,8 @@ static esp_err_t http_resp_file(httpd_req_t *req)
|
|
|
return ESP_FAIL;
|
|
|
}
|
|
|
|
|
|
- ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filepath, file_stat.st_size);
|
|
|
- set_content_type_from_file(req);
|
|
|
+ ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size);
|
|
|
+ set_content_type_from_file(req, filename);
|
|
|
|
|
|
/* Retrieve the pointer to scratch buffer for temporary storage */
|
|
|
char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
|
|
|
@@ -219,20 +279,6 @@ static esp_err_t http_resp_file(httpd_req_t *req)
|
|
|
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)
|
|
|
{
|
|
|
@@ -242,21 +288,21 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
|
|
|
|
|
|
/* 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_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid file name");
|
|
|
+ const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
|
|
+ req->uri + sizeof("/upload") - 1, sizeof(filepath));
|
|
|
+ if (!filename) {
|
|
|
+ /* Respond with 500 Internal Server Error */
|
|
|
+ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
|
|
return ESP_FAIL;
|
|
|
}
|
|
|
|
|
|
- /* Retrieve the base path of file storage to construct the full path */
|
|
|
- strcpy(filepath, ((struct file_server_data *)req->user_ctx)->base_path);
|
|
|
+ /* Filename cannot have a trailing '/' */
|
|
|
+ if (filename[strlen(filename) - 1] == '/') {
|
|
|
+ ESP_LOGE(TAG, "Invalid filename : %s", filename);
|
|
|
+ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
|
|
|
- /* Concatenate the requested file path */
|
|
|
- strcat(filepath, filename);
|
|
|
if (stat(filepath, &file_stat) == 0) {
|
|
|
ESP_LOGE(TAG, "File already exists : %s", filepath);
|
|
|
/* Respond with 400 Bad Request */
|
|
|
@@ -350,23 +396,23 @@ 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 */
|
|
|
+ /* Skip leading "/delete" 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_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid file name");
|
|
|
+ const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
|
|
+ req->uri + sizeof("/delete") - 1, sizeof(filepath));
|
|
|
+ if (!filename) {
|
|
|
+ /* Respond with 500 Internal Server Error */
|
|
|
+ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
|
|
return ESP_FAIL;
|
|
|
}
|
|
|
|
|
|
- /* Retrieve the base path of file storage to construct the full path */
|
|
|
- strcpy(filepath, ((struct file_server_data *)req->user_ctx)->base_path);
|
|
|
+ /* Filename cannot have a trailing '/' */
|
|
|
+ if (filename[strlen(filename) - 1] == '/') {
|
|
|
+ ESP_LOGE(TAG, "Invalid filename : %s", filename);
|
|
|
+ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
|
|
|
- /* Concatenate the requested file path */
|
|
|
- strcat(filepath, filename);
|
|
|
if (stat(filepath, &file_stat) == -1) {
|
|
|
ESP_LOGE(TAG, "File does not exist : %s", filename);
|
|
|
/* Respond with 400 Bad Request */
|
|
|
@@ -385,18 +431,6 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
|
|
|
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)
|
|
|
{
|
|
|
@@ -436,27 +470,9 @@ esp_err_t start_file_server(const char *base_path)
|
|
|
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)
|
|
|
+ .uri = "/*", // Match all URIs of type /path/to/file
|
|
|
.method = HTTP_GET,
|
|
|
.handler = download_get_handler,
|
|
|
.user_ctx = server_data // Pass server data as context
|