From c1a8cadda50d4cb3430c6db64f1d27d246e59dd6 Mon Sep 17 00:00:00 2001 From: Bogdan Pilyugin Date: Tue, 15 Nov 2022 16:19:46 +0200 Subject: [PATCH] debug multi fs http server --- include/HTTPServer.h | 4 +- src/FileServer.c | 229 ++++++++++++++++++++++++++++++++++++------- src/HTTPServer.c | 23 +++-- upload_script.html | 2 +- 4 files changed, 209 insertions(+), 49 deletions(-) diff --git a/include/HTTPServer.h b/include/HTTPServer.h index 2edfcab..6257e16 100644 --- a/include/HTTPServer.h +++ b/include/HTTPServer.h @@ -93,9 +93,9 @@ esp_err_t start_file_server(void); HTTP_IO_RESULT HTTPPostApp(httpd_req_t *req, const char *filename, char *PostData); int HTTPPrint(httpd_req_t *req, char* buf, char* var); - -esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath); +esp_err_t download_get_handler(httpd_req_t *req); esp_err_t upload_post_handler(httpd_req_t *req); esp_err_t delete_post_handler(httpd_req_t *req); + #endif /* COMPONENTS_WEB_GUI_APPLICATION_INCLUDE_HTTPSERVER_H_ */ diff --git a/src/FileServer.c b/src/FileServer.c index 4fe25c6..6984c56 100644 --- a/src/FileServer.c +++ b/src/FileServer.c @@ -1,4 +1,4 @@ - /*! Copyright 2022 Bogdan Pilyugin +/*! Copyright 2022 Bogdan Pilyugin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,60 @@ * \copyright Apache License, Version 2.0 */ - #include "HTTPServer.h" static const char *TAG = "FileServer"; +/* 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, "307 Temporary Redirect"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_send(req, NULL, 0); // Response body can be empty + 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, + const char *filename) +{ + if (IS_FILE_EXT(filename, ".pdf")) + { + return httpd_resp_set_type(req, "application/pdf"); + } + else if (IS_FILE_EXT(filename, ".html")) + { + return httpd_resp_set_type(req, "text/html"); + } + else if (IS_FILE_EXT(filename, ".jpeg")) + { + return httpd_resp_set_type(req, "image/jpeg"); + } + else if (IS_FILE_EXT(filename, ".png")) + { + return httpd_resp_set_type(req, "image/png"); + } + else if (IS_FILE_EXT(filename, ".ico")) + { + return httpd_resp_set_type(req, "image/x-icon"); + } + else if (IS_FILE_EXT(filename, ".css")) + { + return httpd_resp_set_type(req, "text/css"); + } + else if (IS_FILE_EXT(filename, ".woff2")) + { + return httpd_resp_set_type(req, "font/woff2"); + } + /* This is a limited set only */ + /* For any other type always set as plain text */ + return httpd_resp_set_type(req, "text/plain"); +} + /* 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, @@ -64,7 +113,7 @@ static const char* get_path_from_uri(char *dest, const char *base_path, * 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 */ -esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) +static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) { char entrypath[FILE_PATH_MAX]; char entrysize[16]; @@ -79,39 +128,41 @@ esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) /* Retrieve the base path of file storage to construct the full path */ strlcpy(entrypath, dirpath, sizeof(entrypath)); - if (!dir) { + if (!dir) + { 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"); + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Files directory does not exist"); return ESP_FAIL; } - - /* 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"); + 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); + 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, - "" - "" - "" - ""); + httpd_resp_sendstr_chunk( + req, + "
NameTypeSize (Bytes)Delete
" + "" + "" + ""); /* Iterate over all files / folders and fetch their names and sizes */ - while ((entry = readdir(dir)) != NULL) { + while ((entry = readdir(dir)) != NULL) + { entrytype = (entry->d_type == DT_DIR ? "directory" : "file"); strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len); - if (stat(entrypath, &entry_stat) == -1) { + if (stat(entrypath, &entry_stat) == -1) + { ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name); continue; } @@ -122,7 +173,8 @@ esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) httpd_resp_sendstr_chunk(req, "
NameTypeSize (Bytes)Delete
uri); httpd_resp_sendstr_chunk(req, entry->d_name); - if (entry->d_type == DT_DIR) { + if (entry->d_type == DT_DIR) + { httpd_resp_sendstr_chunk(req, "/"); } httpd_resp_sendstr_chunk(req, "\">"); @@ -151,6 +203,99 @@ esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) return ESP_OK; } +/* Handler to download a file kept on the server */ +esp_err_t download_get_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + FILE *fd = NULL; + struct stat file_stat; + + 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, "/data"); + } + + 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, "/") == 0) + { + return index_html_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"); + return ESP_FAIL; + } + + fd = fopen(filepath, "r"); + if (!fd) + { + ESP_LOGE(TAG, "Failed to read existing file : %s", filepath); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file"); + return ESP_FAIL; + } + + 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; + size_t chunksize; + do + { + /* Read file in chunks into the scratch buffer */ + chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); + + if (chunksize > 0) + { + /* 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); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + return ESP_FAIL; + } + } + + /* 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 */ +#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER + httpd_resp_set_hdr(req, "Connection", "close"); +#endif + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + /* Handler to upload a file onto the server */ esp_err_t upload_post_handler(httpd_req_t *req) { @@ -160,22 +305,26 @@ 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 = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path2, - req->uri + sizeof("/upload") - 1, sizeof(filepath)); - if (!filename) { + const char *filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path, + req->uri + sizeof("/files/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; } /* Filename cannot have a trailing '/' */ - if (filename[strlen(filename) - 1] == '/') { + 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; } - if (stat(filepath, &file_stat) == 0) { + if (stat(filepath, &file_stat) == 0) + { ESP_LOGE(TAG, "File already exists : %s", filepath); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists"); @@ -183,7 +332,8 @@ esp_err_t upload_post_handler(httpd_req_t *req) } /* File cannot be larger than a limit */ - if (req->content_len > MAX_FILE_SIZE) { + if (req->content_len > MAX_FILE_SIZE) + { ESP_LOGE(TAG, "File too large : %d bytes", req->content_len); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, @@ -195,7 +345,8 @@ esp_err_t upload_post_handler(httpd_req_t *req) } fd = fopen(filepath, "w"); - if (!fd) { + if (!fd) + { ESP_LOGE(TAG, "Failed to create file : %s", filepath); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file"); @@ -205,19 +356,22 @@ esp_err_t upload_post_handler(httpd_req_t *req) 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; + 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) { + 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) { + if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) + { + if (received == HTTPD_SOCK_ERR_TIMEOUT) + { /* Retry if timeout occurred */ continue; } @@ -234,7 +388,8 @@ esp_err_t upload_post_handler(httpd_req_t *req) } /* Write buffer content to file on storage */ - if (received && (received != fwrite(buf, 1, received, fd))) { + if (received && (received != fwrite(buf, 1, received, fd))) + { /* Couldn't write everything to file! * Storage may be full? */ fclose(fd); @@ -273,22 +428,26 @@ esp_err_t delete_post_handler(httpd_req_t *req) /* Skip leading "/delete" from URI to get filename */ /* Note sizeof() counts NULL termination hence the -1 */ - const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path2, - req->uri + sizeof("/delete") - 1, sizeof(filepath)); - if (!filename) { + const char *filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path2, + 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; } /* Filename cannot have a trailing '/' */ - if (filename[strlen(filename) - 1] == '/') { + 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; } - if (stat(filepath, &file_stat) == -1) { + if (stat(filepath, &file_stat) == -1) + { ESP_LOGE(TAG, "File does not exist : %s", filename); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist"); @@ -309,5 +468,3 @@ esp_err_t delete_post_handler(httpd_req_t *req) return ESP_OK; } - - diff --git a/src/HTTPServer.c b/src/HTTPServer.c index 9c90587..1ebcdd9 100644 --- a/src/HTTPServer.c +++ b/src/HTTPServer.c @@ -211,7 +211,13 @@ static esp_err_t POSTHandler(httpd_req_t *req) ((struct file_server_data*) req->user_ctx)->base_path, req->uri, sizeof(filepath)); - ESP_LOGW(TAG, "filepath %s", filepath); + + ESP_LOGW(TAG, "URI %s", req->uri); + if (memmem(filename, strlen(filename), "/files/upload/", sizeof("/files/upload/"))) + { + ESP_LOGW(TAG, "filepath %s", filepath); + return upload_post_handler(req); + } filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path, @@ -219,8 +225,6 @@ static esp_err_t POSTHandler(httpd_req_t *req) sizeof(filepath)); - - if (!memcmp(filename, "/api", 4)) { return RestApiHandler(req); @@ -284,6 +288,12 @@ static esp_err_t GETHandler(httpd_req_t *req) return ESP_FAIL; } + ESP_LOGW(TAG, "URI %s", req->uri); + if (!strcmp(filename, "/files/")) + { + return download_get_handler(req); + } + /* Redirect request to /index.html */ if (filename[strlen(filename) - 1] == '/') { @@ -296,13 +306,6 @@ static esp_err_t GETHandler(httpd_req_t *req) return ESP_OK; } - - if (!strcmp(filename, "/files.html")) - { - return http_resp_dir_html(req, "/data"); - } - - //check auth for all files except status.json if (strcmp(filename, "/status.json")) { diff --git a/upload_script.html b/upload_script.html index 5513ee8..9efe632 100644 --- a/upload_script.html +++ b/upload_script.html @@ -33,7 +33,7 @@ function setpath() { } function upload() { var filePath = document.getElementById("filepath").value; - var upload_path = "/upload/" + filePath; + var upload_path = "files/upload/" + filePath; var fileInput = document.getElementById("newfile").files; /* Max size of an individual file. Make sure this