mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-09 12:35:28 +00:00
File Server Example : Check longer than allowed filenames when converting from URIs to filepaths
This change prevents buffer overflows in case of really long file paths. Other changes: * Remove query (?) and fragment (#) component from URI when converting to file path * /index.html and favicon.ico can be overridden by files with same name and path in SPIFFS * README.md updated
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
/* 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)
|
||||
{
|
||||
char fullpath[FILE_PATH_MAX];
|
||||
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.
|
||||
* 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 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
|
||||
|
Reference in New Issue
Block a user