From 54dd5bae127d7dd9ec97e0266fef44416d46fbcd Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Mon, 19 Aug 2013 17:49:36 +0200 Subject: [PATCH] Implemented basic upload functionality --- include/main.h | 37 ++++++++ include/util.h | 4 + src/main.c | 240 ++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 231 insertions(+), 50 deletions(-) create mode 100644 include/main.h diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..b330962 --- /dev/null +++ b/include/main.h @@ -0,0 +1,37 @@ +#ifndef MAIN_H +#define MAIN_H + +#include + +enum ResultCode { + RC_OK = 0, // No problem (so far) + RC_EXISTS = 1, // On upload, the file to be uploaded already existed + RC_OPEN_FAILED = 2, // On upload, opening the file for writing failed + RC_WRONG_TARGET = 3 // On upload, the given URL was not a directory +}; + +enum RequestType { + GET, + POST +}; + +struct ConnectionState { + char cleanedURL[PATH_MAX]; + char localFileName[PATH_MAX]; + struct stat targetStat; + + FILE *upload_fd; + + enum ResultCode result; + enum RequestType requestType; + + struct MHD_PostProcessor *postProcessor; +}; + +struct RequestRange { + off_t start; + off_t length; +}; + + +#endif // MAIN_H diff --git a/include/util.h b/include/util.h index a86a2e9..13838bd 100644 --- a/include/util.h +++ b/include/util.h @@ -11,6 +11,10 @@ #ifndef UTIL_H #define UTIL_H +#ifndef MAX + #define MAX(a, b) ((a > b) ? a : b) +#endif + void remove_trailing_slash(char *str); void urlencode(const char *str, char *result); diff --git a/src/main.c b/src/main.c index 9b9d229..ae8f64c 100644 --- a/src/main.c +++ b/src/main.c @@ -39,24 +39,16 @@ #include "favicon.h" #include "util.h" +#include "main.h" + #define DEFAULT_PORT 8888 -struct ConnectionState { - char cleanedURL[PATH_MAX]; - char localFileName[PATH_MAX]; - struct stat targetStat; -}; - -struct RequestRange { - off_t start; - off_t length; -}; - char *shareRoot; struct MHD_Response *error403Response; struct MHD_Response *error404Response; struct MHD_Response *error500Response; struct MHD_Response *faviconResponse; +struct MHD_Response *uploadFormResponse; #ifdef HAVE_MAGIC magic_t magicCookie; @@ -73,6 +65,15 @@ void request_completed(void *cls, // TODO: show IP+Port in log LOG(LVL_DEBUG, "Freeing state for request of %s", connstate->localFileName); + // close the upload file if open + if(connstate->upload_fd) { + fclose(connstate->upload_fd); + } + + if(connstate->postProcessor) { + MHD_destroy_post_processor(connstate->postProcessor); + } + free(connstate); } @@ -223,6 +224,115 @@ int serv_directory(struct MHD_Connection *connection, struct ConnectionState *co return ret; } +int serv_result_page(struct MHD_Connection *connection, struct ConnectionState *connstate) { + struct MHD_Response *response; + int ret; + + char *message, *title; + unsigned int resultCode; + + switch(connstate->result) { + case RC_OK: + message = "Request was processed successfully."; + title = "Success"; + resultCode = MHD_HTTP_OK; + break; + + case RC_EXISTS: + message = "The file you tried to upload already exists on the server."; + title = "File already exists"; + resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; + break; + + case RC_OPEN_FAILED: + message = "Could not open the file on the server for writing."; + title = "Open failed"; + resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; + break; + + case RC_WRONG_TARGET: + message = "You tried to put a new file into something which is not a directory."; + title = "Target error"; + resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + char *buffer = malloc(strlen(HEADER1 HEADER2 FOOTER) + 1024); + + sprintf(buffer, HEADER1 "%s" HEADER2 "

%s

%s

" FOOTER, + title, title, message); + + response = MHD_create_response_from_buffer( + strlen(buffer), buffer, MHD_RESPMEM_MUST_FREE); + + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); + + ret = MHD_queue_response(connection, resultCode, response); + MHD_destroy_response(response); + return ret; +} + + +static int post_processor(void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *filename, + const char *content_type, + const char *transfer_encoding, + const char *data, + uint64_t off, + size_t size) { + LOG(LVL_DUMP, "Entering POST handler (key=%s, filename=%s, contentType=%s, transferEncoding=%s, size=%u)", + key, filename, content_type, transfer_encoding, size); + + struct ConnectionState *ci = (struct ConnectionState*)cls; + + if(kind == MHD_POSTDATA_KIND) { + if(strcmp("data", key) == 0) { + if(ci->upload_fd == NULL) { + // file pointer not allocated yet + // determine the path where to store the uploaded file + char uploadFilename[PATH_MAX]; + + if(!S_ISDIR(ci->targetStat.st_mode)) { + LOG(LVL_WARN, "Cannot upload %s: Target path is not an existing directory.", filename); + ci->result = RC_WRONG_TARGET; + return MHD_NO; + } + + strncpy(uploadFilename, ci->localFileName, PATH_MAX); + strncat(uploadFilename, "/", PATH_MAX); + strncat(uploadFilename, filename, PATH_MAX); + + // check if the file already exists + FILE *tmp = fopen(uploadFilename, "rb"); + if(tmp) { + LOG(LVL_WARN, "Cannot upload to %s: file exists.", uploadFilename); + ci->result = RC_EXISTS; + fclose(tmp); + return MHD_NO; + } + + // file does not exist and can therefore be opened for writing + ci->upload_fd = fopen(uploadFilename, "wb"); + if(!ci->upload_fd) { + LOG(LVL_WARN, "Cannot upload to %s: file cannot be created.", uploadFilename); + ci->result = RC_OPEN_FAILED; + return MHD_NO; + } + + fwrite(data, size, 1, ci->upload_fd); + } else { + // file descriptor is already open + fwrite(data, size, 1, ci->upload_fd); + } + } + return MHD_YES; + } + + return MHD_NO; +} + static int connection_handler(void * cls, struct MHD_Connection *connection, const char *url, @@ -233,17 +343,15 @@ static int connection_handler(void * cls, void **ptr) { struct ConnectionState *connstate; - if (0 != strcmp(method, "GET") && 0 != strcmp(method, "HEAD")) { - LOG(LVL_WARN, "Unexpected method: %s.", method); - return MHD_NO; - } - if (*ptr == NULL) { // This is the first time this connection is seen. // Initialize the connection state connstate = malloc(sizeof(struct ConnectionState)); *ptr = connstate; + connstate->result = RC_OK; + connstate->upload_fd = NULL; + // set the local file name strncpy(connstate->localFileName, shareRoot, PATH_MAX); strncat(connstate->localFileName, url, @@ -257,49 +365,71 @@ static int connection_handler(void * cls, LOG(LVL_INFO, "%s %s (local: %s)", method, url, connstate->localFileName); + if (0 == strcmp(method, "GET") || 0 == strcmp(method, "HEAD")) { + connstate->postProcessor = NULL; + connstate->requestType = GET; + } else if(0 == strcmp(method, "POST")) { + connstate->postProcessor = MHD_create_post_processor(connection, 1024*1024, post_processor, connstate); + connstate->requestType = POST; + } else { + LOG(LVL_WARN, "Unexpected method: %s.", method); + return MHD_NO; + } + + // check if url contains a "/.." sequence. If so, block the request as it is + // trying to access the parent directory (and may be an attempt to leave the + // shared space. + if(NULL != strstr(url, "/..")) { + LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.", + url); + + return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response); + } + + // check properties of the target file/dir + if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) { + LOG(LVL_ERR, "Cannot stat %s: %s", + connstate->localFileName, strerror(errno)); + + return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response); + } + return MHD_YES; } else { connstate = *ptr; } - if (0 != *upload_data_size) { - return MHD_NO; // upload data in a GET!? - } + if(connstate->requestType == GET) { + if (0 != *upload_data_size) { + return MHD_NO; // upload data in a GET!? + } - // serv the favicon, if requested - if(strcmp(url, "/favicon.png") == 0) { - LOG(LVL_INFO, "Serving FavIcon request."); - return MHD_queue_response(connection, MHD_HTTP_OK, faviconResponse); - } + // serv the favicon, if requested + if(strcmp(url, "/favicon.png") == 0) { + LOG(LVL_INFO, "Serving FavIcon request."); + return MHD_queue_response(connection, MHD_HTTP_OK, faviconResponse); + } - // check if url contains a "/.." sequence. If so, block the request as it is - // trying to access the parent directory (and may be an attempt to leave the - // shared space. - if(NULL != strstr(url, "/..")) { - LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.", - url); + if(S_ISREG(connstate->targetStat.st_mode)) { + return serv_regular_file(connection, connstate); + } else if(S_ISDIR(connstate->targetStat.st_mode)) { + return serv_directory(connection, connstate); + } else { + LOG(LVL_WARN, + "%s is neither a directory nor a regular file. Don't allow the access.", + connstate->localFileName); - return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response); - } + return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response); + } + } else if(connstate->requestType == POST) { + if (*upload_data_size != 0) { + MHD_post_process (connstate->postProcessor, upload_data, *upload_data_size); + *upload_data_size = 0; - // check properties of the target file/dir - if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) { - LOG(LVL_ERR, "Cannot stat %s: %s", - connstate->localFileName, strerror(errno)); - - return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response); - } - - if(S_ISREG(connstate->targetStat.st_mode)) { - return serv_regular_file(connection, connstate); - } else if(S_ISDIR(connstate->targetStat.st_mode)) { - return serv_directory(connection, connstate); - } else { - LOG(LVL_WARN, - "%s is neither a directory nor a regular file. Don't allow the access.", - connstate->localFileName); - - return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response); + return MHD_YES; + } else { + return serv_result_page(connection, connstate); + } } // generate a 500 if request was not handled properly @@ -477,6 +607,15 @@ int main(int argc, char ** argv) { MHD_add_response_header(error404Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); MHD_add_response_header(error500Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); + // static response for upload form + uploadFormResponse = MHD_create_response_from_data( + strlen(UPLOAD_FORM), + (void*) UPLOAD_FORM, + MHD_NO, + MHD_NO); + + MHD_add_response_header(uploadFormResponse, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); + // static response for favicon faviconResponse = create_favicon_response(); @@ -527,6 +666,7 @@ int main(int argc, char ** argv) { MHD_destroy_response(error404Response); MHD_destroy_response(error500Response); MHD_destroy_response(faviconResponse); + MHD_destroy_response(uploadFormResponse); #ifdef HAVE_MAGIC magic_close(magicCookie);