diff --git a/README.md b/README.md index 34592205..270a8dd8 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,13 @@ For example, if your connection limit is “1”, a browser may open a first con * _.debug() and .no_debug():_ Enables debug messages from the library. `off` by default. * _.regex_checking() and .no_regex_checking():_ Enables pattern matching for endpoints. Read more [here](#registering-resources). `on` by default. * _.post_process() and .no_post_process():_ Enables/Disables the library to automatically parse the body of the http request as arguments if in querystring format. Read more [here](#parsing-requests). `on` by default. +* _.put_processed_data_to_content() and .no_put_processed_data_to_content():_ Enables/Disables the library to copy parsed body data to the content or to only store it in the arguments map. `on` by default. +* _.file_upload_target(**file_upload_target_T** file_upload_target):_ Controls, how the library stores uploaded files. Default value is `FILE_UPLOAD_MEMORY_ONLY`. + * `FILE_UPLOAD_MEMORY_ONLY`: The content of the file is only stored in memory. Depending on `put_processed_data_to_content` only as part of the arguments map or additionally in the content. + * `FILE_UPLOAD_DISK_ONLY`: The content of the file is stored only in the file system. The path is created from `file_upload_dir` and either a random name (if `generate_random_filename_on_upload` is true) or the actually uploaded file name. + * `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system. +* _.file_upload_dir(**const std::string&** file_upload_dir):_ Specifies the directory to store all uploaded files. Default value is `/tmp`. +* _.generate_random_filename_on_upload() and .no_generate_random_filename_on_upload():_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default. * _.deferred()_ and _.no_deferred():_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default. * _.single_resource() and .no_single_resource:_ Sets or unsets the server in single resource mode. This limits all endpoints to be served from a single resource. The resultant is that the webserver will process the request matching to the endpoint skipping any complex semantic. Because of this, the option is incompatible with `regex_checking` and requires the resource to be registered against an empty endpoint or the root endpoint (`"/"`). The resource will also have to be registered as family. (For more information on resource registration, read more [here](#registering-resources)). `off` by default. @@ -553,6 +560,8 @@ The `http_request` class has a set of methods you will have access to when imple * _**const std::map** get_cookies() **const**:_ Returns a map containing all the cookies present in the HTTP request. * _**const std::map** get_footers() **const**:_ Returns a map containing all the footers present in the HTTP request (only for http 1.1 chunked encodings). * _**const std::map** get_args() **const**:_ Returns all the arguments present in the HTTP request. Arguments can be (1) querystring parameters, (2) path argument (in case of parametric endpoint, (3) parameters parsed from the HTTP request body if the body is in `application/x-www-form-urlencoded` or `multipart/form-data` formats and the postprocessor is enabled in the webserver (enabled by default). +* _**const std::map** get_files() **const**:_ Returns information about all the uploaded files (if the files are stored to disk). This information includes the original file name, the size of the file and the path to the file in the file system. +* _**const std::map>** get_files() **const**:_ Returns information about all the uploaded files (if the files are stored to disk). This information includes the key (as identifier of the outer map), the original file name (as identifier of the inner map) and a class `file_info`, which includes the size of the file and the path to the file in the file system. * _**const std::string&** get_content() **const**:_ Returns the body of the HTTP request. * _**bool** content_too_large() **const**:_ Returns `true` if the body length of the HTTP request sent by the client is longer than the max allowed on the server. * _**const std::string** get_querystring() **const**:_ Returns the `querystring` of the HTTP request. diff --git a/configure.ac b/configure.ac index bac1c1c6..23adba36 100644 --- a/configure.ac +++ b/configure.ac @@ -273,6 +273,7 @@ AC_SUBST(EXT_LIB_PATH) AC_SUBST(EXT_LIBS) AC_CONFIG_FILES([test/test_content:test/test_content]) +AC_CONFIG_FILES([test/test_content_2:test/test_content_2]) AC_CONFIG_FILES([test/test_content_empty:test/test_content_empty]) AC_CONFIG_FILES([test/cert.pem:test/cert.pem]) AC_CONFIG_FILES([test/key.pem:test/key.pem]) diff --git a/examples/Makefile.am b/examples/Makefile.am index 318a7a8d..0fe116af 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,7 +19,7 @@ LDADD = $(top_builddir)/src/libhttpserver.la AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator +noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload hello_world_SOURCES = hello_world.cpp service_SOURCES = service.cpp @@ -41,3 +41,4 @@ minimal_ip_ban_SOURCES = minimal_ip_ban.cpp benchmark_select_SOURCES = benchmark_select.cpp benchmark_threads_SOURCES = benchmark_threads.cpp benchmark_nodelay_SOURCES = benchmark_nodelay.cpp +file_upload_SOURCES = file_upload.cpp diff --git a/examples/file_upload.cpp b/examples/file_upload.cpp new file mode 100644 index 00000000..2c82e2a5 --- /dev/null +++ b/examples/file_upload.cpp @@ -0,0 +1,116 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +class file_upload_resource : public httpserver::http_resource { + public: + const std::shared_ptr render_GET(const httpserver::http_request&) { + std::string get_response = "\n"; + get_response += " \n"; + get_response += "
\n"; + get_response += "

Upload 1 (key is 'files', multiple files can be selected)


\n"; + get_response += " \n"; + get_response += "

\n"; + get_response += "

Upload 2 (key is 'files2', multiple files can be selected)


\n"; + get_response += "

\n"; + get_response += " \n"; + get_response += "
\n"; + get_response += " \n"; + get_response += "\n"; + + return std::shared_ptr(new httpserver::string_response(get_response, 200, "text/html")); + } + + const std::shared_ptr render_POST(const httpserver::http_request& req) { + std::string post_response = "\n"; + post_response += "\n"; + post_response += " \n"; + post_response += "\n"; + post_response += "\n"; + post_response += " Uploaded files:\n"; + post_response += "

\n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + + for (auto &file_key : req.get_files()) { + for (auto &files : file_key.second) { + post_response += " \n"; + } + } + + post_response += "
KeyUploaded filenameFile system pathFile sizeContent typeTransfer encoding
"; + post_response += file_key.first; + post_response += ""; + post_response += files.first; + post_response += ""; + post_response += files.second.get_file_system_file_name(); + post_response += ""; + post_response += std::to_string(files.second.get_file_size()); + post_response += ""; + post_response += files.second.get_content_type(); + post_response += ""; + post_response += files.second.get_transfer_encoding(); + post_response += "


\n"; + post_response += " back\n"; + post_response += "\n"; + return std::shared_ptr(new httpserver::string_response(post_response, 201, "text/html")); + } +}; + +int main(int argc, char** argv) { + // this example needs a directory as parameter + if (2 != argc) { + std::cout << "Usage: file_upload " << std::endl; + std::cout << std::endl; + std::cout << " file_upload: writeable directory where uploaded files will be stored" << std::endl; + return -1; + } + + std::cout << "CAUTION: this example will create files in the directory " << std::string(argv[1]) << std::endl; + std::cout << "These files won't be deleted at termination" << std::endl; + std::cout << "Please make sure, that the given directory exists and is writeable" << std::endl; + + httpserver::webserver ws = httpserver::create_webserver(8080) + .no_put_processed_data_to_content() + .file_upload_dir(std::string(argv[1])) + .generate_random_filename_on_upload() + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY); + + file_upload_resource fur; + ws.register_resource("/", &fur); + ws.start(true); + + return 0; +} + diff --git a/src/Makefile.am b/src/Makefile.am index 5e549bbc..cb4a8209 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,9 +19,9 @@ AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/ METASOURCES = AUTO lib_LTLIBRARIES = libhttpserver.la -libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp details/http_endpoint.cpp +libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp details/http_endpoint.cpp noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h -nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/basic_auth_fail_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp +nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/basic_auth_fail_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp AM_CXXFLAGS += -fPIC -Wall diff --git a/src/file_info.cpp b/src/file_info.cpp new file mode 100644 index 00000000..88a21583 --- /dev/null +++ b/src/file_info.cpp @@ -0,0 +1,56 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include "httpserver/file_info.hpp" + +namespace httpserver { +namespace http { + +void file_info::set_file_system_file_name(const std::string& file_system_file_name) { + _file_system_file_name = file_system_file_name; +} + +void file_info::set_content_type(const std::string& content_type) { + _content_type = content_type; +} + +void file_info::set_transfer_encoding(const std::string& transfer_encoding) { + _transfer_encoding = transfer_encoding; +} + +void file_info::grow_file_size(size_t additional_file_size) { + _file_size += additional_file_size; +} +size_t file_info::get_file_size() const { + return _file_size; +} +const std::string file_info::get_file_system_file_name() const { + return _file_system_file_name; +} +const std::string file_info::get_content_type() const { + return _content_type; +} +const std::string file_info::get_transfer_encoding() const { + return _transfer_encoding; +} + +} // namespace http +} // namespace httpserver diff --git a/src/http_request.cpp b/src/http_request.cpp index 4c3f0c82..3d88a382 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -127,6 +127,10 @@ const std::map http_request::get return arguments; } +http::file_info& http_request::get_or_create_file_info(const std::string& key, const std::string& upload_file_name) { + return files[key][upload_file_name]; +} + const std::string http_request::get_querystring() const { std::string querystring = ""; diff --git a/src/http_utils.cpp b/src/http_utils.cpp index 85a5047a..552bfc19 100644 --- a/src/http_utils.cpp +++ b/src/http_utils.cpp @@ -23,6 +23,10 @@ #if defined(_WIN32) && !defined(__CYGWIN__) #include #include +#include +#include +#include +#include #else // WIN32 check #include #include @@ -33,6 +37,7 @@ #include #include +#include #include #include #include @@ -198,6 +203,14 @@ const char* http_utils::http_post_encoding_multipart_formdata = MHD_HTTP_POST_EN const char* http_utils::application_octet_stream = "application/octet-stream"; const char* http_utils::text_plain = "text/plain"; +const char* http_utils::upload_filename_template = "libhttpserver.XXXXXX"; + +#if defined(_WIN32) + const char http_utils::path_separator = '\\'; +#else // _WIN32 + const char http_utils::path_separator = '/'; +#endif // _WIN32 + std::vector http_utils::tokenize_url(const std::string& str, const char separator) { return string_utilities::string_split(str, separator); } @@ -221,6 +234,45 @@ std::string http_utils::standardize_url(const std::string& url) { return result; } +const std::string http_utils::generate_random_upload_filename(const std::string& directory) { + std::string filename = directory + http_utils::path_separator + http_utils::upload_filename_template; + char *template_filename = strdup(filename.c_str()); + int fd = 0; + +#if defined(_WIN32) + // only function for win32 which creates unique filenames and can handle a given template including a path + // all other functions like tmpnam() always create filenames in the 'temp' directory + if (0 != _mktemp_s(template_filename, filename.size() + 1)) { + free(template_filename); + throw generateFilenameException("Failed to create unique filename"); + } + + // as no existing file should be overwritten the operation should fail if the file already exists + // fstream or ofstream classes don't feature such an option + // with the function _sopen_s this can be achieved by setting the flag _O_EXCL + if (0 != _sopen_s(&fd, template_filename, _O_CREAT | _O_EXCL | _O_NOINHERIT, _SH_DENYNO, _S_IREAD | _S_IWRITE)) { + free(template_filename); + throw generateFilenameException("Failed to create file"); + } + if (fd == -1) { + free(template_filename); + throw generateFilenameException("File descriptor after successful _sopen_s is -1"); + } + _close(fd); +#else // _WIN32 + fd = mkstemp(template_filename); + + if (fd == -1) { + free(template_filename); + throw generateFilenameException("Failed to create unique file"); + } + close(fd); +#endif // _WIN32 + std::string ret_filename = template_filename; + free(template_filename); + return ret_filename; +} + std::string get_ip_str(const struct sockaddr *sa) { if (!sa) throw std::invalid_argument("socket pointer is null"); diff --git a/src/httpserver.hpp b/src/httpserver.hpp index 04eb251a..52f9263c 100644 --- a/src/httpserver.hpp +++ b/src/httpserver.hpp @@ -31,6 +31,7 @@ #include "httpserver/http_resource.hpp" #include "httpserver/http_response.hpp" #include "httpserver/http_utils.hpp" +#include "httpserver/file_info.hpp" #include "httpserver/string_response.hpp" #include "httpserver/webserver.hpp" diff --git a/src/httpserver/create_webserver.hpp b/src/httpserver/create_webserver.hpp index d5e2b07e..99369f60 100644 --- a/src/httpserver/create_webserver.hpp +++ b/src/httpserver/create_webserver.hpp @@ -296,6 +296,36 @@ class create_webserver { return *this; } + create_webserver& no_put_processed_data_to_content() { + _put_processed_data_to_content = false; + return *this; + } + + create_webserver& put_processed_data_to_content() { + _put_processed_data_to_content = true; + return *this; + } + + create_webserver& file_upload_target(const file_upload_target_T& file_upload_target) { + _file_upload_target = file_upload_target; + return *this; + } + + create_webserver& file_upload_dir(const std::string& file_upload_dir) { + _file_upload_dir = file_upload_dir; + return *this; + } + + create_webserver& no_generate_random_filename_on_upload() { + _generate_random_filename_on_upload = false; + return *this; + } + + create_webserver& generate_random_filename_on_upload() { + _generate_random_filename_on_upload = true; + return *this; + } + create_webserver& single_resource() { _single_resource = true; return *this; @@ -360,6 +390,10 @@ class create_webserver { bool _regex_checking = true; bool _ban_system_enabled = true; bool _post_process_enabled = true; + bool _put_processed_data_to_content = true; + file_upload_target_T _file_upload_target = FILE_UPLOAD_MEMORY_ONLY; + std::string _file_upload_dir = "/tmp"; + bool _generate_random_filename_on_upload = false; bool _deferred_enabled = false; bool _single_resource = false; bool _tcp_nodelay = false; diff --git a/src/httpserver/details/modded_request.hpp b/src/httpserver/details/modded_request.hpp index c4f18638..dada29d8 100644 --- a/src/httpserver/details/modded_request.hpp +++ b/src/httpserver/details/modded_request.hpp @@ -27,6 +27,7 @@ #include #include +#include #include "httpserver/http_request.hpp" @@ -47,6 +48,10 @@ struct modded_request { bool second = false; bool has_body = false; + std::string upload_key; + std::string upload_filename; + std::ofstream* upload_ostrm = nullptr; + modded_request() = default; modded_request(const modded_request& b) = default; @@ -64,6 +69,7 @@ struct modded_request { } delete complete_uri; delete standardized_url; + delete upload_ostrm; } }; diff --git a/src/httpserver/file_info.hpp b/src/httpserver/file_info.hpp new file mode 100644 index 00000000..f78c55fa --- /dev/null +++ b/src/httpserver/file_info.hpp @@ -0,0 +1,61 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_FILE_INFO_HPP_ +#define SRC_HTTPSERVER_FILE_INFO_HPP_ + +#include + +namespace httpserver { +class webserver; + +namespace http { + +class file_info { + public: + size_t get_file_size() const; + const std::string get_file_system_file_name() const; + const std::string get_content_type() const; + const std::string get_transfer_encoding() const; + + file_info() = default; + + private: + size_t _file_size; + std::string _file_system_file_name; + std::string _content_type; + std::string _transfer_encoding; + + void set_file_system_file_name(const std::string& file_system_file_name); + void set_content_type(const std::string& content_type); + void set_transfer_encoding(const std::string& transfer_encoding); + void grow_file_size(size_t additional_file_size); + + friend class httpserver::webserver; +}; + +} // namespace http +} // namespace httpserver +#endif // SRC_HTTPSERVER_FILE_INFO_HPP_ + diff --git a/src/httpserver/http_request.hpp b/src/httpserver/http_request.hpp index 00bc2d45..bea32079 100644 --- a/src/httpserver/http_request.hpp +++ b/src/httpserver/http_request.hpp @@ -40,6 +40,7 @@ #include #include "httpserver/http_utils.hpp" +#include "httpserver/file_info.hpp" struct MHD_Connection; @@ -135,6 +136,22 @@ class http_request { **/ const std::map get_args() const; + /** + * Method to get or create a file info struct in the map if the provided filename is already in the map + * return the exiting file info struct, otherwise create one in the map and return it. + * @param upload_file_name the file name the user uploaded (this is the identifier for the map entry) + * @result a file info struct file_info_s + **/ + http::file_info& get_or_create_file_info(const std::string& key, const std::string& upload_file_name); + + /** + * Method used to get all files passed with the request. + * @result result a map > that will be filled with all files + **/ + const std::map> get_files() const { + return files; + } + /** * Method used to get a specific header passed with the request. * @param key the specific header to get the value from @@ -240,6 +257,7 @@ class http_request { std::string path; std::string method; std::map args; + std::map> files; std::string content = ""; size_t content_size_limit = static_cast(-1); std::string version; diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index b768fe6c..6de523ea 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -64,10 +64,29 @@ typedef int MHD_Result; namespace httpserver { +enum file_upload_target_T { + FILE_UPLOAD_MEMORY_ONLY, + FILE_UPLOAD_DISK_ONLY, + FILE_UPLOAD_MEMORY_AND_DISK, +}; + typedef void(*unescaper_ptr)(std::string&); namespace http { +struct generateFilenameException : public std::exception { + public: + explicit generateFilenameException(const std::string& message) noexcept : error_message(message) { + } + + const char* what() const noexcept { + return this->error_message.c_str(); + } + + private: + std::string error_message; +}; + class http_utils { public: enum cred_type_T { @@ -234,8 +253,13 @@ class http_utils { static const char* application_octet_stream; static const char* text_plain; + static const char* upload_filename_template; + static const char path_separator; + static std::vector tokenize_url(const std::string&, const char separator = '/'); static std::string standardize_url(const std::string&); + + static const std::string generate_random_upload_filename(const std::string& directory); }; #define COMPARATOR(x, y, op) { \ diff --git a/src/httpserver/webserver.hpp b/src/httpserver/webserver.hpp index 1a5b1255..ef28cdb6 100644 --- a/src/httpserver/webserver.hpp +++ b/src/httpserver/webserver.hpp @@ -162,6 +162,10 @@ class webserver { const bool regex_checking; const bool ban_system_enabled; const bool post_process_enabled; + const bool put_processed_data_to_content; + const file_upload_target_T file_upload_target; + const std::string file_upload_dir; + const bool generate_random_filename_on_upload; const bool deferred_enabled; bool single_resource; bool tcp_nodelay; diff --git a/src/webserver.cpp b/src/webserver.cpp index 4c4a034a..b345c53e 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -152,6 +151,10 @@ webserver::webserver(const create_webserver& params): regex_checking(params._regex_checking), ban_system_enabled(params._ban_system_enabled), post_process_enabled(params._post_process_enabled), + put_processed_data_to_content(params._put_processed_data_to_content), + file_upload_target(params._file_upload_target), + file_upload_dir(params._file_upload_dir), + generate_random_filename_on_upload(params._generate_random_filename_on_upload), deferred_enabled(params._deferred_enabled), single_resource(params._single_resource), tcp_nodelay(params._tcp_nodelay), @@ -454,14 +457,66 @@ MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, const char *transfer_encoding, const char *data, uint64_t off, size_t size) { // Parameter needed to respect MHD interface, but not needed here. std::ignore = kind; - std::ignore = filename; - std::ignore = content_type; - std::ignore = transfer_encoding; std::ignore = off; struct details::modded_request* mr = (struct details::modded_request*) cls; - mr->dhr->set_arg(key, mr->dhr->get_arg(key) + std::string(data, size)); - return MHD_YES; + + try { + if (filename == nullptr || mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { + mr->dhr->set_arg(key, mr->dhr->get_arg(key) + std::string(data, size)); + } + + if (filename && *filename != '\0' && mr->ws->file_upload_target != FILE_UPLOAD_MEMORY_ONLY) { + // either get the existing file info struct or create a new one in the file map + http::file_info& file = mr->dhr->get_or_create_file_info(key, filename); + // if the file_system_file_name is not filled yet, this is a new entry and the name has to be set + // (either random or copy of the original filename) + if (file.get_file_system_file_name().empty()) { + if (mr->ws->generate_random_filename_on_upload) { + file.set_file_system_file_name(http_utils::generate_random_upload_filename(mr->ws->file_upload_dir)); + } else { + file.set_file_system_file_name(mr->ws->file_upload_dir + "/" + std::string(filename)); + } + // to not append to an already existing file, delete an already existing file + unlink(file.get_file_system_file_name().c_str()); + if (content_type != nullptr) { + file.set_content_type(content_type); + } + if (transfer_encoding != nullptr) { + file.set_transfer_encoding(transfer_encoding); + } + } + + // if multiple files are uploaded, a different filename or a different key indicates + // the start of a new file, so close the previous one + if (mr->upload_filename.empty() || + mr->upload_key.empty() || + 0 != strcmp(filename, mr->upload_filename.c_str()) || + 0 != strcmp(key, mr->upload_key.c_str())) { + if (mr->upload_ostrm != nullptr) { + mr->upload_ostrm->close(); + } + } + + if (mr->upload_ostrm == nullptr || !mr->upload_ostrm->is_open()) { + mr->upload_key = key; + mr->upload_filename = filename; + delete mr->upload_ostrm; + mr->upload_ostrm = new std::ofstream(); + mr->upload_ostrm->open(file.get_file_system_file_name(), std::ios::binary | std::ios::app); + } + + if (size > 0) { + mr->upload_ostrm->write(data, size); + } + + // update the file size in the map + file.grow_file_size(size); + } + return MHD_YES; + } catch(const http::generateFilenameException& e) { + return MHD_NO; + } } void webserver::upgrade_handler(void *cls, struct MHD_Connection* connection, void **con_cls, int upgrade_socket) { @@ -527,9 +582,21 @@ MHD_Result webserver::requests_answer_second_step(MHD_Connection* connection, co #ifdef DEBUG std::cout << "Writing content: " << std::string(upload_data, *upload_data_size) << std::endl; #endif // DEBUG - mr->dhr->grow_content(upload_data, *upload_data_size); + // The post iterator is only created from the libmicrohttpd for content of type + // multipart/form-data and application/x-www-form-urlencoded + // all other content (which is indicated by mr-pp == nullptr) + // has to be put to the content even if put_processed_data_to_content is set to false + if (mr->pp == nullptr || put_processed_data_to_content) { + mr->dhr->grow_content(upload_data, *upload_data_size); + } - if (mr->pp != nullptr) MHD_post_process(mr->pp, upload_data, *upload_data_size); + if (mr->pp != nullptr) { + mr->ws = this; + MHD_post_process(mr->pp, upload_data, *upload_data_size); + if (mr->upload_ostrm != nullptr && mr->upload_ostrm->is_open()) { + mr->upload_ostrm->close(); + } + } } *upload_data_size = 0; diff --git a/test/Makefile.am b/test/Makefile.am index 7b8aba08..68ddb554 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -19,11 +19,12 @@ LDADD = $(top_builddir)/src/libhttpserver.la AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -check_PROGRAMS = basic http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource +check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource MOSTLYCLEANFILES = *.gcda *.gcno *.gcov -basic_SOURCES = integ/basic.cpp +basic_SOURCES = integ/basic.cpp +file_upload_SOURCES = integ/file_upload.cpp threaded_SOURCES = integ/threaded.cpp ban_system_SOURCES = integ/ban_system.cpp ws_start_stop_SOURCES = integ/ws_start_stop.cpp diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp new file mode 100644 index 00000000..00cbd9ab --- /dev/null +++ b/test/integ/file_upload.cpp @@ -0,0 +1,492 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "httpserver/string_utilities.hpp" +#include "./littletest.hpp" + +using std::string; +using std::map; +using std::shared_ptr; +using std::vector; +using std::stringstream; + +using httpserver::http_resource; +using httpserver::http_request; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::file_response; +using httpserver::webserver; +using httpserver::create_webserver; + +static const char* TEST_CONTENT_FILENAME = "test_content"; +static const char* TEST_CONTENT_FILEPATH = "./test_content"; +static const char* FILENAME_IN_GET_CONTENT = "filename=\"test_content\""; +static const char* TEST_CONTENT = "test content of file\n"; +static const char* TEST_KEY = "file"; +static size_t TEST_CONTENT_SIZE = 21; + +static const char* TEST_CONTENT_FILENAME_2 = "test_content_2"; +static const char* TEST_CONTENT_FILEPATH_2 = "./test_content_2"; +static const char* FILENAME_IN_GET_CONTENT_2 = "filename=\"test_content_2\""; +static const char* TEST_CONTENT_2 = "test content of second file\n"; +static const char* TEST_KEY_2 = "file2"; +static size_t TEST_CONTENT_SIZE_2 = 28; + +static const char* TEST_PARAM_KEY = "param_key"; +static const char* TEST_PARAM_VALUE = "Value of test param"; + +static CURLcode send_file_to_webserver(bool add_second_file, bool append_parameters) { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl = curl_easy_init(); + + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY); + curl_mime_filedata(field, TEST_CONTENT_FILEPATH); + if (add_second_file) { + field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY_2); + curl_mime_filedata(field, TEST_CONTENT_FILEPATH_2); + } + + if (append_parameters) { + field = curl_mime_addpart(form); + curl_mime_name(field, TEST_PARAM_KEY); + curl_mime_data(field, TEST_PARAM_VALUE, CURL_ZERO_TERMINATED); + } + + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/upload"); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + res = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl_mime_free(form); + return res; +} + +static bool send_file_via_put() { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl; + CURLcode res; + struct stat file_info; + FILE *fd; + + fd = fopen(TEST_CONTENT_FILEPATH, "rb"); + if (!fd) { + return false; + } + + if (fstat(fileno(fd), &file_info) != 0) { + return false; + } + + curl = curl_easy_init(); + if (!curl) { + fclose(fd); + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/upload"); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(curl, CURLOPT_READDATA, fd); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) file_info.st_size); + + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + fclose(fd); + if (res == CURLE_OK) { + return true; + } + return false; +} + +class print_file_upload_resource : public http_resource { + public: + const shared_ptr render_POST(const http_request& req) { + content = req.get_content(); + args = req.get_args(); + files = req.get_files(); + shared_ptr hresp(new string_response("OK", 201, "text/plain")); + return hresp; + } + + const shared_ptr render_PUT(const http_request& req) { + content = req.get_content(); + args = req.get_args(); + files = req.get_files(); + shared_ptr hresp(new string_response("OK", 200, "text/plain")); + return hresp; + } + + const map get_args() const { + return args; + } + + const map> get_files() const { + return files; + } + + const string get_content() const { + return content; + } + + private: + map args; + map> files; + string content; +}; + +LT_BEGIN_SUITE(file_upload_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(file_upload_suite) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + map::iterator arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second, TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + unlink(file->second.get_file_system_file_name().c_str()); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_and_disk) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_via_put) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + int ret = send_file_via_put(); + LT_ASSERT_EQ(ret, true); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content, TEST_CONTENT); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 0); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 0); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_and_disk_via_put) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_additional_params) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(false, true); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_PARAM_KEY) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_PARAM_VALUE) != string::npos, true); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + map::iterator arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second, TEST_CONTENT); + arg++; + LT_CHECK_EQ(arg->first, TEST_PARAM_KEY); + LT_CHECK_EQ(arg->second, TEST_PARAM_VALUE); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + unlink(file->second.get_file_system_file_name().c_str()); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_and_disk_additional_params) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_two_files) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(true, false); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT_2) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT_2) != string::npos, true); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + map::iterator arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second, TEST_CONTENT); + arg++; + LT_CHECK_EQ(arg->first, TEST_KEY_2); + LT_CHECK_EQ(arg->second, TEST_CONTENT_2); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 2); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + unlink(file->second.get_file_system_file_name().c_str()); + + file_key++; + LT_CHECK_EQ(file_key->first, TEST_KEY_2); + LT_CHECK_EQ(file_key->second.size(), 1); + file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME_2); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE_2); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + unlink(file->second.get_file_system_file_name().c_str()); + + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_and_disk_two_files) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_disk_only) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .no_put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.size(), 0); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 0); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + unlink(file->second.get_file_system_file_name().c_str()); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_disk_only) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_incl_content) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + map::iterator arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second, TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_only_incl_content) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_excl_content) + string upload_directory = "."; + webserver* ws; + + ws = new webserver(create_webserver(8080) + .no_put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + ws->register_resource("upload", &resource); + + CURLcode res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.size(), 0); + + map args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + map::iterator arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second, TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 0); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_memory_only_excl_content) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/test_content_2 b/test/test_content_2 new file mode 100644 index 00000000..18c3afa0 --- /dev/null +++ b/test/test_content_2 @@ -0,0 +1 @@ +test content of second file diff --git a/test/unit/http_utils_test.cpp b/test/unit/http_utils_test.cpp index f1b63afc..71c1a655 100644 --- a/test/unit/http_utils_test.cpp +++ b/test/unit/http_utils_test.cpp @@ -30,6 +30,7 @@ #include #endif +#include #include #include "./littletest.hpp" @@ -154,6 +155,22 @@ LT_BEGIN_AUTO_TEST(http_utils_suite, standardize_url) LT_CHECK_EQ(result, "/abc/pqr"); LT_END_AUTO_TEST(standardize_url) +LT_BEGIN_AUTO_TEST(http_utils_suite, generate_random_upload_filename) + struct stat sb; + string directory = ".", filename = ""; + string expected_output = directory + httpserver::http::http_utils::path_separator + httpserver::http::http_utils::upload_filename_template; + try { + filename = httpserver::http::http_utils::generate_random_upload_filename(directory); + } catch(const httpserver::http::generateFilenameException& e) { + LT_FAIL(e.what()); + } + LT_CHECK_EQ(stat(filename.c_str(), &sb), 0); + // unlink the file again, to not mess up the test directory with files + unlink(filename.c_str()); + // omit the last 6 signs in the check, as the "XXXXXX" is substituted to be random and unique + LT_CHECK_EQ(filename.substr(0, filename.size() - 6), expected_output.substr(0, expected_output.size() - 6)); +LT_END_AUTO_TEST(generate_random_upload_filename) + LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str) struct sockaddr_in ip4addr;