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;
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;
}
(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");
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;
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)
{
/* 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 */
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 */
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)
{
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