From 76261d680b43e05e314ea04a71070221cfeaafb5 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 01/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- examples/hello_world.cpp | 2 +- src/http_request.cpp | 64 +++++++++++++------ src/http_utils.cpp | 24 +++++-- src/httpserver/http_request.hpp | 39 +++++++++--- src/httpserver/http_utils.hpp | 35 +++++++++- src/webserver.cpp | 2 +- test/integ/basic.cpp | 42 ++++++++++++ test/integ/file_upload.cpp | 109 ++++++++++++++++++++++++++++---- test/unit/http_utils_test.cpp | 20 +++--- 9 files changed, 279 insertions(+), 58 deletions(-) diff --git a/examples/hello_world.cpp b/examples/hello_world.cpp index 391a600f..9af22f14 100755 --- a/examples/hello_world.cpp +++ b/examples/hello_world.cpp @@ -33,7 +33,7 @@ class hello_world_resource : public httpserver::http_resource { std::shared_ptr hello_world_resource::render(const httpserver::http_request& req) { // It is possible to store data inside the resource object that can be altered through the requests std::cout << "Data was: " << data << std::endl; - std::string_view datapar = req.get_arg("data"); + std::string_view datapar = req.get_arg_flat("data"); set_some_data(datapar == "" ? "no data passed!!!" : std::string(datapar)); std::cout << "Now data is:" << data << std::endl; diff --git a/src/http_request.cpp b/src/http_request.cpp index b0cd46b3..0bad64a3 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -32,7 +32,7 @@ const char http_request::EMPTY[] = ""; struct arguments_accumulator { unescaper_ptr unescaper; - http::arg_map* arguments; + std::map, http::arg_comparator>* arguments; }; void http_request::set_method(const std::string& method) { @@ -104,35 +104,63 @@ const http::header_view_map http_request::get_cookies() const { return get_headerlike_values(MHD_COOKIE_KIND); } -std::string_view http_request::get_arg(std::string_view key) const { - std::map::const_iterator it = cache->unescaped_args.find(std::string(key)); +void http_request::populate_args() const { + if (!cache->unescaped_args.empty()) { + return; + } + arguments_accumulator aa; + aa.unescaper = unescaper; + aa.arguments = &cache->unescaped_args; + MHD_get_connection_values(underlying_connection, MHD_GET_ARGUMENT_KIND, &build_request_args, reinterpret_cast(&aa)); +} + +http::http_arg_value http_request::get_arg(std::string_view key) const { + populate_args(); + auto it = cache->unescaped_args.find(std::string(key)); if (it != cache->unescaped_args.end()) { - return it->second; + http::http_arg_value arg; + arg.values.reserve(it->second.size()); + for (const auto& value : it->second) { + arg.values.push_back(value); + } + return arg; } - - return get_connection_value(key, MHD_GET_ARGUMENT_KIND); + return http::http_arg_value(); } -const http::arg_view_map http_request::get_args() const { - http::arg_view_map arguments; +std::string_view http_request::get_arg_flat(std::string_view key) const { + auto const it = cache->unescaped_args.find(std::string(key)); - if (!cache->unescaped_args.empty()) { - arguments.insert(cache->unescaped_args.begin(), cache->unescaped_args.end()); - return arguments; + if (it != cache->unescaped_args.end()) { + return it->second[0]; } - arguments_accumulator aa; - aa.unescaper = unescaper; - aa.arguments = &cache->unescaped_args; - - MHD_get_connection_values(underlying_connection, MHD_GET_ARGUMENT_KIND, &build_request_args, reinterpret_cast(&aa)); + return get_connection_value(key, MHD_GET_ARGUMENT_KIND); +} - arguments.insert(cache->unescaped_args.begin(), cache->unescaped_args.end()); +const http::arg_view_map http_request::get_args() const { + populate_args(); + http::arg_view_map arguments; + for (const auto& [key, value] : cache->unescaped_args) { + auto& arg_values = arguments[key]; + for (const auto& v : value) { + arg_values.values.push_back(v); + } + } return arguments; } +const std::map http_request::get_args_flat() const { + populate_args(); + std::map ret; + for (const auto&[key, val] : cache->unescaped_args) { + ret[key] = val[0]; + } + return ret; +} + 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]; } @@ -155,7 +183,7 @@ MHD_Result http_request::build_request_args(void *cls, enum MHD_ValueKind kind, std::string value = ((arg_value == nullptr) ? "" : arg_value); http::base_unescaper(&value, aa->unescaper); - (*aa->arguments)[key] = value; + (*aa->arguments)[key].push_back(value); return MHD_YES; } diff --git a/src/http_utils.cpp b/src/http_utils.cpp index d34e196b..975f1a8f 100644 --- a/src/http_utils.cpp +++ b/src/http_utils.cpp @@ -510,8 +510,7 @@ const std::string load_file(const std::string& filename) { } } -template -void dump_map(std::ostream& os, const std::string& prefix, const map_t& map) { +void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_view_map &map) { auto it = map.begin(); auto end = map.end(); @@ -524,12 +523,23 @@ void dump_map(std::ostream& os, const std::string& prefix, const map_t& map) { } } -void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_view_map &map) { - dump_map(os, prefix, map); -} - void dump_arg_map(std::ostream& os, const std::string& prefix, const http::arg_view_map &map) { - dump_map(os, prefix, map); + auto it = map.begin(); + auto end = map.end(); + + if (map.size()) { + os << " " << prefix << " ["; + for (; it != end; ++it) { + os << (*it).first << ":["; + std::string sep = ""; + for (const auto& v : it->second.values) { + os << sep << "\"" << v << "\""; + sep = ", "; + } + os << "] "; + } + os << "]" << std::endl; + } } size_t base_unescaper(std::string* s, unescaper_ptr unescaper) { diff --git a/src/httpserver/http_request.hpp b/src/httpserver/http_request.hpp index 6228c82f..ec1dda5f 100644 --- a/src/httpserver/http_request.hpp +++ b/src/httpserver/http_request.hpp @@ -134,11 +134,17 @@ class http_request { /** * Method used to get all args passed with the request. - * @param result a map > that will be filled with all args * @result the size of the map **/ const http::arg_view_map get_args() const; + /** + * Method used to get all args passed with the request. If any key has multiple + * values, one value is chosen and returned. + * @result the size of the map + **/ + const std::map get_args_flat() 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. @@ -174,9 +180,17 @@ class http_request { /** * Method used to get a specific argument passed with the request. * @param ket the specific argument to get the value from + * @return the value(s) of the arg. + **/ + http::http_arg_value get_arg(std::string_view key) const; + + /** + * Method used to get a specific argument passed with the request. + * If the arg key has more than one value, only one is returned. + * @param ket the specific argument to get the value from * @return the value of the arg. **/ - std::string_view get_arg(std::string_view key) const; + std::string_view get_arg_flat(std::string_view key) const; /** * Method used to get the content of the request. @@ -298,7 +312,7 @@ class http_request { * @param value The value assumed by the argument **/ void set_arg(const std::string& key, const std::string& value) { - cache->unescaped_args[key] = value.substr(0, content_size_limit); + cache->unescaped_args[key].push_back(value.substr(0, content_size_limit)); } /** @@ -308,9 +322,17 @@ class http_request { * @param size The size in number of char of the value parameter. **/ void set_arg(const char* key, const char* value, size_t size) { - cache->unescaped_args[key] = std::string(value, std::min(size, content_size_limit)); + cache->unescaped_args[key].push_back(std::string(value, std::min(size, content_size_limit))); } + /** + * Method used to set an argument value by key. If a key already exists, overwrites it. + * @param key The name identifying the argument + * @param value The value assumed by the argument + **/ + void set_arg_flat(const std::string& key, const std::string& value) { + cache->unescaped_args[key] = { (value.substr(0, content_size_limit)) }; + } /** * Method used to set the content of the request * @param content The content to set. @@ -366,9 +388,8 @@ class http_request { * @param args The args key-value map to set for the request. **/ void set_args(const std::map& args) { - std::map::const_iterator it; - for (it = args.begin(); it != args.end(); ++it) { - this->cache->unescaped_args[it->first] = it->second.substr(0, content_size_limit); + for (auto const& [key, value] : args) { + this->cache->unescaped_args[key].push_back(value.substr(0, content_size_limit)); } } @@ -386,9 +407,11 @@ class http_request { std::string querystring; std::string requestor_ip; std::string digested_user; - http::arg_map unescaped_args; + std::map, http::arg_comparator> unescaped_args; }; std::unique_ptr cache; + // Populate the data cache unescaped_args + void populate_args() const; friend class webserver; friend struct details::modded_request; diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index e6b5411f..9a461207 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -316,10 +316,41 @@ class arg_comparator { } }; +class http_arg_value { + public: + + std::string_view get_flat_value() const + { + return values.empty() ? "" : values[0]; + } + + std::vector get_all_values() const + { + return values; + } + + operator std::string() const + { + return std::string(get_flat_value()); + } + + operator std::vector() const + { + std::vector result; + for (auto const & value : values) { + result.push_back(std::string(value)); + } + return result; + } + + std::vector values; +}; + using header_map = std::map; using header_view_map = std::map; -using arg_map = std::map; -using arg_view_map = std::map; +using arg_map = std::map; +using arg_view_map = std::map; + struct ip_representation { http_utils::IP_version_T ip_version; diff --git a/src/webserver.cpp b/src/webserver.cpp index 5a1f4324..eed8343a 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -463,7 +463,7 @@ MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, try { if (filename == nullptr || mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { - mr->dhr->set_arg(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); + mr->dhr->set_arg_flat(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); } if (filename && *filename != '\0' && mr->ws->file_upload_target != FILE_UPLOAD_MEMORY_ONLY) { diff --git a/test/integ/basic.cpp b/test/integ/basic.cpp index c6e61d85..569f39f9 100644 --- a/test/integ/basic.cpp +++ b/test/integ/basic.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -67,6 +68,23 @@ class simple_resource : public http_resource { } }; +class arg_value_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return shared_ptr(new string_response("OK", 200, "text/plain")); + } + shared_ptr render_POST(const http_request& req) { + auto const arg_value = req.get_arg("arg").get_all_values(); + for (auto const & a : arg_value) { + std::cerr << a << std::endl; + } + std::string all_values = std::accumulate(std::next(arg_value.begin()), arg_value.end(), std::string(arg_value[0]), [](std::string a, std::string_view in) { + return std::move(a) + std::string(in); + }); + return shared_ptr(new string_response(all_values, 200, "text/plain")); + } +}; + class args_resource : public http_resource { public: shared_ptr render_GET(const http_request& req) { @@ -627,6 +645,30 @@ LT_BEGIN_AUTO_TEST(basic_suite, postprocessor) curl_easy_cleanup(curl); LT_END_AUTO_TEST(postprocessor) +LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value) + arg_value_resource resource; + ws->register_resource("base", &resource); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base?arg=beep&arg=boop&arg=hello&arg=what"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg=beep&arg=boop&arg=hello&arg=what"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + struct curl_slist *list = NULL; + // Change the content type to trigger "normal" POST processing, + // otherwise the file processing logic is triggered which does + // not set the multiple arg values as expected. + list = curl_slist_append(list, "content-type: text/plain"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + res = curl_easy_perform(curl); + curl_slist_free_all(list); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "beepboophellowhat"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(same_key_different_value) + LT_BEGIN_AUTO_TEST(basic_suite, empty_arg) simple_resource resource; ws->register_resource("base", &resource); diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 2082f9c7..3ae92a2e 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -19,8 +19,11 @@ */ #include +#include +#include #include #include +#include #include #include @@ -61,6 +64,13 @@ 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 const char* LARGE_FILENAME_IN_GET_CONTENT = "filename=\"large_content\""; +static const char* LARGE_CONTENT_FILEPATH = "./large_content"; +static const char* LARGE_KEY = "large_file"; +using random_bytes_engine = std::independent_bits_engine< + std::default_random_engine, CHAR_BIT, unsigned char>; + static bool file_exists(const string &path) { struct stat sb; @@ -99,6 +109,45 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet return res; } +static CURLcode send_large_file(string* content) { + // Generate a large (100K) file of random bytes. Upload the file with + // a curl request, then delete the file. The default chunk size of MHD + // appears to be around 16K, so 100K should be enough to trigger the + // behavior. Return the content via the pointer parameter so that test + // cases can make required checks for the content. + 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); + + random_bytes_engine rbe; + std::vector data(100000); // 1_MB + std::generate(begin(data), end(data), std::ref(rbe)); + *content = string(reinterpret_cast(&data[0]), data.size()); + + std::ofstream outfile(LARGE_CONTENT_FILEPATH, std::ios::out); + outfile << *content; + outfile.close(); + + curl_mime_name(field, LARGE_KEY); + curl_mime_filedata(field, LARGE_CONTENT_FILEPATH); + + 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); + + std::filesystem::remove(LARGE_CONTENT_FILEPATH); + + return res; +} + static bool send_file_via_put() { curl_global_init(CURL_GLOBAL_ALL); @@ -144,7 +193,7 @@ class print_file_upload_resource : public http_resource { auto args_view = req.get_args(); // req may go out of scope, so we need to copy the values. for (auto const& item : args_view) { - args[std::string(item.first)] = std::string(item.second); + args[string(item.first)].push_back(string(item.second)); } files = req.get_files(); shared_ptr hresp(new string_response("OK", 201, "text/plain")); @@ -156,14 +205,14 @@ class print_file_upload_resource : public http_resource { auto args_view = req.get_args(); // req may go out of scope, so we need to copy the values. for (auto const& item : args_view) { - args[std::string(item.first)] = std::string(item.second); + args[string(item.first)].push_back(string(item.second)); } files = req.get_files(); shared_ptr hresp(new string_response("OK", 200, "text/plain")); return hresp; } - const arg_map get_args() const { + const std::map, httpserver::http::arg_comparator> get_args() const { return args; } @@ -176,7 +225,7 @@ class print_file_upload_resource : public http_resource { } private: - arg_map args; + std::map, httpserver::http::arg_comparator> args; map> files; string content; }; @@ -218,7 +267,7 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk) LT_CHECK_EQ(args.size(), 1); auto arg = args.begin(); LT_CHECK_EQ(arg->first, TEST_KEY); - LT_CHECK_EQ(arg->second, TEST_CONTENT); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); map> files = resource.get_files(); LT_CHECK_EQ(files.size(), 1); @@ -300,10 +349,10 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_additional_par LT_CHECK_EQ(args.size(), 2); auto arg = args.begin(); LT_CHECK_EQ(arg->first, TEST_KEY); - LT_CHECK_EQ(arg->second, TEST_CONTENT); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); arg++; LT_CHECK_EQ(arg->first, TEST_PARAM_KEY); - LT_CHECK_EQ(arg->second, TEST_PARAM_VALUE); + LT_CHECK_EQ(arg->second[0], TEST_PARAM_VALUE); map> files = resource.get_files(); LT_CHECK_EQ(files.size(), 1); @@ -354,10 +403,10 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_two_files) LT_CHECK_EQ(args.size(), 2); auto arg = args.begin(); LT_CHECK_EQ(arg->first, TEST_KEY); - LT_CHECK_EQ(arg->second, TEST_CONTENT); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); arg++; LT_CHECK_EQ(arg->first, TEST_KEY_2); - LT_CHECK_EQ(arg->second, TEST_CONTENT_2); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT_2); map> files = resource.get_files(); LT_CHECK_EQ(files.size(), 2); @@ -461,7 +510,7 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_incl_content) LT_CHECK_EQ(args.size(), 1); auto arg = args.begin(); LT_CHECK_EQ(arg->first, TEST_KEY); - LT_CHECK_EQ(arg->second, TEST_CONTENT); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); map> files = resource.get_files(); LT_CHECK_EQ(resource.get_files().size(), 0); @@ -470,6 +519,44 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_incl_content) delete ws; LT_END_AUTO_TEST(file_upload_memory_only_incl_content) +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_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); + + // Upload a large file to trigger the chunking behavior of MHD. + std::string file_content; + CURLcode res = send_large_file(&file_content); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + //std::cerr << "actual content: " << actual_content << std::endl; + LT_CHECK_EQ(actual_content.find(LARGE_FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(file_content) != string::npos, true); + + // The chunks of the file should be concatenated into the first + // arg value of the key. + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, LARGE_KEY); + LT_CHECK_EQ(arg->second[0], file_content); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_large_content) + LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_excl_content) string upload_directory = "."; webserver* ws; @@ -493,7 +580,7 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_excl_content) LT_CHECK_EQ(args.size(), 1); auto arg = args.begin(); LT_CHECK_EQ(arg->first, TEST_KEY); - LT_CHECK_EQ(arg->second, TEST_CONTENT); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); map> files = resource.get_files(); LT_CHECK_EQ(files.size(), 0); diff --git a/test/unit/http_utils_test.cpp b/test/unit/http_utils_test.cpp index 1b7486ac..b12196a9 100644 --- a/test/unit/http_utils_test.cpp +++ b/test/unit/http_utils_test.cpp @@ -618,25 +618,25 @@ LT_BEGIN_AUTO_TEST(http_utils_suite, dump_header_map_no_prefix) LT_END_AUTO_TEST(dump_header_map_no_prefix) LT_BEGIN_AUTO_TEST(http_utils_suite, dump_arg_map) - std::map arg_map; - arg_map["ARG_ONE"] = "VALUE_ONE"; - arg_map["ARG_TWO"] = "VALUE_TWO"; - arg_map["ARG_THREE"] = "VALUE_THREE"; + httpserver::http::arg_view_map arg_map; + arg_map["ARG_ONE"].values.push_back("VALUE_ONE"); + arg_map["ARG_TWO"].values.push_back("VALUE_TWO"); + arg_map["ARG_THREE"].values.push_back("VALUE_THREE"); std::stringstream ss; httpserver::http::dump_arg_map(ss, "prefix", arg_map); - LT_CHECK_EQ(ss.str(), " prefix [ARG_ONE:\"VALUE_ONE\" ARG_TWO:\"VALUE_TWO\" ARG_THREE:\"VALUE_THREE\" ]\n"); + LT_CHECK_EQ(ss.str(), " prefix [ARG_ONE:[\"VALUE_ONE\"] ARG_TWO:[\"VALUE_TWO\"] ARG_THREE:[\"VALUE_THREE\"] ]\n"); LT_END_AUTO_TEST(dump_arg_map) LT_BEGIN_AUTO_TEST(http_utils_suite, dump_arg_map_no_prefix) - std::map arg_map; - arg_map["ARG_ONE"] = "VALUE_ONE"; - arg_map["ARG_TWO"] = "VALUE_TWO"; - arg_map["ARG_THREE"] = "VALUE_THREE"; + httpserver::http::arg_view_map arg_map; + arg_map["ARG_ONE"].values.push_back("VALUE_ONE"); + arg_map["ARG_TWO"].values.push_back("VALUE_TWO"); + arg_map["ARG_THREE"].values.push_back("VALUE_THREE"); std::stringstream ss; httpserver::http::dump_arg_map(ss, "", arg_map); - LT_CHECK_EQ(ss.str(), " [ARG_ONE:\"VALUE_ONE\" ARG_TWO:\"VALUE_TWO\" ARG_THREE:\"VALUE_THREE\" ]\n"); + LT_CHECK_EQ(ss.str(), " [ARG_ONE:[\"VALUE_ONE\"] ARG_TWO:[\"VALUE_TWO\"] ARG_THREE:[\"VALUE_THREE\"] ]\n"); LT_END_AUTO_TEST(dump_arg_map_no_prefix) LT_BEGIN_AUTO_TEST_ENV() From 77b9f04257e90e9259931a1c2ea5f93046be52ad Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 02/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/httpserver/http_utils.hpp | 13 ++++--------- test/integ/file_upload.cpp | 8 +++++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index 9a461207..83c9cf12 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -318,24 +318,19 @@ class arg_comparator { class http_arg_value { public: - - std::string_view get_flat_value() const - { + std::string_view get_flat_value() const { return values.empty() ? "" : values[0]; } - std::vector get_all_values() const - { + std::vector get_all_values() const { return values; } - operator std::string() const - { + operator std::string() const { return std::string(get_flat_value()); } - operator std::vector() const - { + operator std::vector() const { std::vector result; for (auto const & value : values) { result.push_back(std::string(value)); diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 3ae92a2e..0e60b5a6 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -123,7 +125,7 @@ static CURLcode send_large_file(string* content) { curl_mimepart *field = curl_mime_addpart(form); random_bytes_engine rbe; - std::vector data(100000); // 1_MB + std::vector data(100000); std::generate(begin(data), end(data), std::ref(rbe)); *content = string(reinterpret_cast(&data[0]), data.size()); @@ -143,7 +145,8 @@ static CURLcode send_large_file(string* content) { curl_easy_cleanup(curl); curl_mime_free(form); - std::filesystem::remove(LARGE_CONTENT_FILEPATH); + auto const remove_result = remove(LARGE_CONTENT_FILEPATH); + assert(remove_result == 0); return res; } @@ -538,7 +541,6 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content) LT_ASSERT_EQ(res, 0); string actual_content = resource.get_content(); - //std::cerr << "actual content: " << actual_content << std::endl; LT_CHECK_EQ(actual_content.find(LARGE_FILENAME_IN_GET_CONTENT) != string::npos, true); LT_CHECK_EQ(actual_content.find(file_content) != string::npos, true); From c2a085094c4f27053bd2eda1c107475056a53114 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 03/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- test/integ/file_upload.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 0e60b5a6..908c6172 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -19,8 +19,6 @@ */ #include -#include -#include #include #include #include From 92bbd19af8f41d8fc5d1a3ec33eb4bc1a1659c14 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 04/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- configure.ac | 5 +- test/integ/file_upload.cpp | 30 ++--- test/test_content_large | 260 +++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 18 deletions(-) create mode 100644 test/test_content_large diff --git a/configure.ac b/configure.ac index b40e3067..9d5c63e2 100644 --- a/configure.ac +++ b/configure.ac @@ -57,7 +57,7 @@ AC_ARG_ENABLE([same-directory-build], AC_MSG_RESULT([$samedirectory]) if test x"$samedirectory" = x"no"; then - if test "`cd $srcdir; /bin/pwd`" = "`/bin/pwd`"; then + if test "`cd $srcdir; /bin/pwd`" = "`/bin/pwd`"; then AC_MSG_ERROR("you must configure in a separate build directory") fi fi @@ -275,6 +275,7 @@ 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/test_content_large:test/test_content_large]) AC_CONFIG_FILES([test/cert.pem:test/cert.pem]) AC_CONFIG_FILES([test/key.pem:test/key.pem]) AC_CONFIG_FILES([test/test_root_ca.pem:test/test_root_ca.pem]) @@ -292,7 +293,7 @@ AC_OUTPUT( examples/Makefile ) -AC_MSG_NOTICE([Configuration Summary: +AC_MSG_NOTICE([Configuration Summary: Operating System: ${host_os} Target directory: ${prefix} License : LGPL only diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 908c6172..30bb8d2f 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -64,12 +64,11 @@ 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 const char* LARGE_FILENAME_IN_GET_CONTENT = "filename=\"large_content\""; -static const char* LARGE_CONTENT_FILEPATH = "./large_content"; +// The large file test_content_large is large enough to ensure +// that MHD splits the underlying request into several chunks. +static const char* LARGE_FILENAME_IN_GET_CONTENT = "filename=\"test_content_large\""; +static const char* LARGE_CONTENT_FILEPATH = "./test_content_large"; static const char* LARGE_KEY = "large_file"; -using random_bytes_engine = std::independent_bits_engine< - std::default_random_engine, CHAR_BIT, unsigned char>; static bool file_exists(const string &path) { struct stat sb; @@ -109,6 +108,11 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet return res; } +static size_t file_size(const char* filename) { + std::ifstream infile(LARGE_CONTENT_FILEPATH, std::ifstream::ate | std::ifstream::binary); + return infile.tellg(); +} + static CURLcode send_large_file(string* content) { // Generate a large (100K) file of random bytes. Upload the file with // a curl request, then delete the file. The default chunk size of MHD @@ -122,14 +126,11 @@ static CURLcode send_large_file(string* content) { curl_mime *form = curl_mime_init(curl); curl_mimepart *field = curl_mime_addpart(form); - random_bytes_engine rbe; - std::vector data(100000); - std::generate(begin(data), end(data), std::ref(rbe)); - *content = string(reinterpret_cast(&data[0]), data.size()); - - std::ofstream outfile(LARGE_CONTENT_FILEPATH, std::ios::out); - outfile << *content; - outfile.close(); + auto const file_bytes = file_size(LARGE_CONTENT_FILEPATH); + content->resize(file_bytes); + std::ifstream infile(LARGE_CONTENT_FILEPATH); + infile.read(content->data(), content->size()); + infile.close(); curl_mime_name(field, LARGE_KEY); curl_mime_filedata(field, LARGE_CONTENT_FILEPATH); @@ -143,9 +144,6 @@ static CURLcode send_large_file(string* content) { curl_easy_cleanup(curl); curl_mime_free(form); - auto const remove_result = remove(LARGE_CONTENT_FILEPATH); - assert(remove_result == 0); - return res; } diff --git a/test/test_content_large b/test/test_content_large new file mode 100644 index 00000000..0c8026f1 --- /dev/null +++ b/test/test_content_large @@ -0,0 +1,260 @@ +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 \ No newline at end of file From 9ff21a03fa6d57060420495f8059917fd28b9ad6 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 05/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- test/integ/file_upload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 30bb8d2f..d086b4f5 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -109,7 +109,7 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet } static size_t file_size(const char* filename) { - std::ifstream infile(LARGE_CONTENT_FILEPATH, std::ifstream::ate | std::ifstream::binary); + std::ifstream infile(filename, std::ifstream::ate | std::ifstream::binary); return infile.tellg(); } From 1d01b2b39af99564f502d1fbb78da7362b4db9f0 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 06/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- test/integ/file_upload.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index d086b4f5..5921baee 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -108,11 +108,6 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet return res; } -static size_t file_size(const char* filename) { - std::ifstream infile(filename, std::ifstream::ate | std::ifstream::binary); - return infile.tellg(); -} - static CURLcode send_large_file(string* content) { // Generate a large (100K) file of random bytes. Upload the file with // a curl request, then delete the file. The default chunk size of MHD @@ -126,11 +121,11 @@ static CURLcode send_large_file(string* content) { curl_mime *form = curl_mime_init(curl); curl_mimepart *field = curl_mime_addpart(form); - auto const file_bytes = file_size(LARGE_CONTENT_FILEPATH); - content->resize(file_bytes); std::ifstream infile(LARGE_CONTENT_FILEPATH); - infile.read(content->data(), content->size()); + std::stringstream buffer; + buffer << infile.rdbuf(); infile.close(); + *content = buffer.str(); curl_mime_name(field, LARGE_KEY); curl_mime_filedata(field, LARGE_CONTENT_FILEPATH); From 64a204675977cd66ade2c3ddbf8f2cfb890760aa Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 07/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/http_request.cpp | 6 +-- src/httpserver.hpp | 1 + src/httpserver/http_arg_value.hpp | 61 +++++++++++++++++++++++++++++++ src/httpserver/http_request.hpp | 3 +- src/httpserver/http_response.hpp | 1 + src/httpserver/http_utils.hpp | 27 +------------- 6 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 src/httpserver/http_arg_value.hpp diff --git a/src/http_request.cpp b/src/http_request.cpp index 0bad64a3..cb5949dd 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -114,19 +114,19 @@ void http_request::populate_args() const { MHD_get_connection_values(underlying_connection, MHD_GET_ARGUMENT_KIND, &build_request_args, reinterpret_cast(&aa)); } -http::http_arg_value http_request::get_arg(std::string_view key) const { +http_arg_value http_request::get_arg(std::string_view key) const { populate_args(); auto it = cache->unescaped_args.find(std::string(key)); if (it != cache->unescaped_args.end()) { - http::http_arg_value arg; + http_arg_value arg; arg.values.reserve(it->second.size()); for (const auto& value : it->second) { arg.values.push_back(value); } return arg; } - return http::http_arg_value(); + return http_arg_value(); } std::string_view http_request::get_arg_flat(std::string_view key) const { diff --git a/src/httpserver.hpp b/src/httpserver.hpp index 52f9263c..100e7276 100644 --- a/src/httpserver.hpp +++ b/src/httpserver.hpp @@ -27,6 +27,7 @@ #include "httpserver/deferred_response.hpp" #include "httpserver/digest_auth_fail_response.hpp" #include "httpserver/file_response.hpp" +#include "httpserver/http_arg_value.hpp" #include "httpserver/http_request.hpp" #include "httpserver/http_resource.hpp" #include "httpserver/http_response.hpp" diff --git a/src/httpserver/http_arg_value.hpp b/src/httpserver/http_arg_value.hpp new file mode 100644 index 00000000..5df24420 --- /dev/null +++ b/src/httpserver/http_arg_value.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_HTTP_ARG_VALUE_HPP_ +#define SRC_HTTPSERVER_HTTP_ARG_VALUE_HPP_ + +#include +#include +#include + +namespace httpserver { + +class http_arg_value { + public: + std::string_view get_flat_value() const { + return values.empty() ? "" : values[0]; + } + + std::vector get_all_values() const { + return values; + } + + operator std::string() const { + return std::string(get_flat_value()); + } + + operator std::vector() const { + std::vector result; + for (auto const & value : values) { + result.push_back(std::string(value)); + } + return result; + } + + std::vector values; +}; + +} // end namespace httpserver + +#endif // SRC_HTTPSERVER_HTTP_ARG_VALUE_HPP_ diff --git a/src/httpserver/http_request.hpp b/src/httpserver/http_request.hpp index ec1dda5f..4773d90b 100644 --- a/src/httpserver/http_request.hpp +++ b/src/httpserver/http_request.hpp @@ -40,6 +40,7 @@ #include #include +#include "httpserver/http_arg_value.hpp" #include "httpserver/http_utils.hpp" #include "httpserver/file_info.hpp" @@ -182,7 +183,7 @@ class http_request { * @param ket the specific argument to get the value from * @return the value(s) of the arg. **/ - http::http_arg_value get_arg(std::string_view key) const; + http_arg_value get_arg(std::string_view key) const; /** * Method used to get a specific argument passed with the request. diff --git a/src/httpserver/http_response.hpp b/src/httpserver/http_response.hpp index 4c7988ea..81593b36 100644 --- a/src/httpserver/http_response.hpp +++ b/src/httpserver/http_response.hpp @@ -28,6 +28,7 @@ #include #include #include +#include "httpserver/http_arg_value.hpp" #include "httpserver/http_utils.hpp" struct MHD_Connection; diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index 83c9cf12..f32359e6 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -56,6 +56,8 @@ #include #include +#include "httpserver/http_arg_value.hpp" + #define DEFAULT_MASK_VALUE 0xFFFF #if MHD_VERSION < 0x00097002 @@ -316,31 +318,6 @@ class arg_comparator { } }; -class http_arg_value { - public: - std::string_view get_flat_value() const { - return values.empty() ? "" : values[0]; - } - - std::vector get_all_values() const { - return values; - } - - operator std::string() const { - return std::string(get_flat_value()); - } - - operator std::vector() const { - std::vector result; - for (auto const & value : values) { - result.push_back(std::string(value)); - } - return result; - } - - std::vector values; -}; - using header_map = std::map; using header_view_map = std::map; using arg_map = std::map; From eb50b88e7ad9f9c2cf0969f7c11b0917c4d18180 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 08/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- examples/hello_world.cpp | 2 +- src/http_request.cpp | 2 +- src/httpserver/http_utils.hpp | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/hello_world.cpp b/examples/hello_world.cpp index 9af22f14..391a600f 100755 --- a/examples/hello_world.cpp +++ b/examples/hello_world.cpp @@ -33,7 +33,7 @@ class hello_world_resource : public httpserver::http_resource { std::shared_ptr hello_world_resource::render(const httpserver::http_request& req) { // It is possible to store data inside the resource object that can be altered through the requests std::cout << "Data was: " << data << std::endl; - std::string_view datapar = req.get_arg_flat("data"); + std::string_view datapar = req.get_arg("data"); set_some_data(datapar == "" ? "no data passed!!!" : std::string(datapar)); std::cout << "Now data is:" << data << std::endl; diff --git a/src/http_request.cpp b/src/http_request.cpp index cb5949dd..6f5c8111 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -117,7 +117,7 @@ void http_request::populate_args() const { http_arg_value http_request::get_arg(std::string_view key) const { populate_args(); - auto it = cache->unescaped_args.find(std::string(key)); + auto it = cache->unescaped_args.find(key); if (it != cache->unescaped_args.end()) { http_arg_value arg; arg.values.reserve(it->second.size()); diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index f32359e6..3a9d6884 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -301,6 +301,7 @@ class header_comparator { **/ class arg_comparator { public: + using is_transparent = std::true_type; /** * Operator used to compare strings. * @param first string @@ -316,6 +317,12 @@ class arg_comparator { bool operator()(const std::string& x, const std::string& y) const { return operator()(std::string_view(x), std::string_view(y)); } + bool operator()(std::string_view x, const std::string& y) const { + return operator()(x, std::string_view(y)); + } + bool operator()(const std::string& x, std::string_view y) const { + return operator()(std::string_view(x), std::string(y)); + } }; using header_map = std::map; From c41ed6a5afe28111243d0845bf7f075583c2bea8 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 09/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/httpserver/http_arg_value.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/httpserver/http_arg_value.hpp b/src/httpserver/http_arg_value.hpp index 5df24420..e2111081 100644 --- a/src/httpserver/http_arg_value.hpp +++ b/src/httpserver/http_arg_value.hpp @@ -45,6 +45,10 @@ class http_arg_value { return std::string(get_flat_value()); } + operator std::string_view() const { + return get_flat_value(); + } + operator std::vector() const { std::vector result; for (auto const & value : values) { From 2b93e2335f05e4a077d64cdf1abd01ea5aa3e9cf Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 10/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/http_request.cpp | 2 +- src/webserver.cpp | 8 +++++++- test/integ/basic.cpp | 26 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/http_request.cpp b/src/http_request.cpp index 6f5c8111..afb7ed8e 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -130,7 +130,7 @@ http_arg_value http_request::get_arg(std::string_view key) const { } std::string_view http_request::get_arg_flat(std::string_view key) const { - auto const it = cache->unescaped_args.find(std::string(key)); + auto const it = cache->unescaped_args.find(key); if (it != cache->unescaped_args.end()) { return it->second[0]; diff --git a/src/webserver.cpp b/src/webserver.cpp index eed8343a..ffef4ed5 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -461,8 +461,14 @@ MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, struct details::modded_request* mr = (struct details::modded_request*) cls; + if (!filename) { + // There is no actual file, just set the arg key/value and return. + mr->dhr->set_arg(key, std::string(data, size)); + return MHD_YES; + } + try { - if (filename == nullptr || mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { + if (filename && mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { mr->dhr->set_arg_flat(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); } diff --git a/test/integ/basic.cpp b/test/integ/basic.cpp index 569f39f9..e4e63f47 100644 --- a/test/integ/basic.cpp +++ b/test/integ/basic.cpp @@ -646,6 +646,27 @@ LT_BEGIN_AUTO_TEST(basic_suite, postprocessor) LT_END_AUTO_TEST(postprocessor) LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value) + arg_value_resource resource; + ws->register_resource("base", &resource); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + // The curl default content type triggers the file processing + // logic in the webserver. However, since there is no actual + // file, the arg handling should be the same. + curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg=inertia&arg=isaproperty&arg=ofmatter"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "inertiaisapropertyofmatter"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(same_key_different_value) + + +LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value_plain_content) arg_value_resource resource; ws->register_resource("base", &resource); curl_global_init(CURL_GLOBAL_ALL); @@ -657,9 +678,6 @@ LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value) curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); struct curl_slist *list = NULL; - // Change the content type to trigger "normal" POST processing, - // otherwise the file processing logic is triggered which does - // not set the multiple arg values as expected. list = curl_slist_append(list, "content-type: text/plain"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); res = curl_easy_perform(curl); @@ -667,7 +685,7 @@ LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value) LT_ASSERT_EQ(res, 0); LT_CHECK_EQ(s, "beepboophellowhat"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(same_key_different_value) +LT_END_AUTO_TEST(same_key_different_value_plain_content) LT_BEGIN_AUTO_TEST(basic_suite, empty_arg) simple_resource resource; From 576d0810e507868e55b93bff1de65330cf6e76f0 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 11/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/webserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webserver.cpp b/src/webserver.cpp index ffef4ed5..4d4fb03d 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -468,7 +468,7 @@ MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, } try { - if (filename && mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { + if (filename != nullptr && mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { mr->dhr->set_arg_flat(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); } From 9fece76e085e789dddec89ca79617bf6469e47c8 Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 12/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- src/webserver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webserver.cpp b/src/webserver.cpp index 4d4fb03d..30598708 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -468,11 +468,11 @@ MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, } try { - if (filename != nullptr && mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { + if (mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { mr->dhr->set_arg_flat(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); } - if (filename && *filename != '\0' && mr->ws->file_upload_target != FILE_UPLOAD_MEMORY_ONLY) { + if (*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 From da2eb979a34605abbbf8a19abaea2a5246eeaeaf Mon Sep 17 00:00:00 2001 From: Stuart Byma Date: Sun, 5 Mar 2023 09:50:54 +0000 Subject: [PATCH 13/13] fix #292 handle argument keys with multiple values This change allows the server to handle argument keys with multiple values e.g. url?arg=param&arg=what Previous, the arg key would just be overwritten with the next value. This change adds a http_arg_value type to house all the values for a given key. Adjust existing get_arg(s) methods, and add some get_arg_flat type methods that still just return the first value for a given key. Add a test case for multiple valued keys. Add a test case for a large file upload that triggers chunking behavior of MHD. This causes the server to concatenate file chunks within the first value of the given arg key, so the test ensures that this behavior is not affected by the changes. --- test/integ/file_upload.cpp | 73 +++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp index 5921baee..0df7d1ee 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -108,7 +108,7 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet return res; } -static CURLcode send_large_file(string* content) { +static CURLcode send_large_file(string* content, std::string args = "") { // Generate a large (100K) file of random bytes. Upload the file with // a curl request, then delete the file. The default chunk size of MHD // appears to be around 16K, so 100K should be enough to trigger the @@ -130,8 +130,12 @@ static CURLcode send_large_file(string* content) { curl_mime_name(field, LARGE_KEY); curl_mime_filedata(field, LARGE_CONTENT_FILEPATH); + std::string url = "localhost:8080/upload"; + if (!args.empty()) { + url.append(args); + } CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/upload"); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); res = curl_easy_perform(curl); @@ -187,7 +191,9 @@ class print_file_upload_resource : public http_resource { auto args_view = req.get_args(); // req may go out of scope, so we need to copy the values. for (auto const& item : args_view) { - args[string(item.first)].push_back(string(item.second)); + for (auto const & value : item.second.get_all_values()) { + args[string(item.first)].push_back(string(value)); + } } files = req.get_files(); shared_ptr hresp(new string_response("OK", 201, "text/plain")); @@ -199,7 +205,9 @@ class print_file_upload_resource : public http_resource { auto args_view = req.get_args(); // req may go out of scope, so we need to copy the values. for (auto const& item : args_view) { - args[string(item.first)].push_back(string(item.second)); + for (auto const & value : item.second.get_all_values()) { + args[string(item.first)].push_back(string(value)); + } } files = req.get_files(); shared_ptr hresp(new string_response("OK", 200, "text/plain")); @@ -537,11 +545,14 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content) // The chunks of the file should be concatenated into the first // arg value of the key. - auto args = resource.get_args(); + auto const args = resource.get_args(); LT_CHECK_EQ(args.size(), 1); - auto arg = args.begin(); - LT_CHECK_EQ(arg->first, LARGE_KEY); - LT_CHECK_EQ(arg->second[0], file_content); + auto const file_arg_iter = args.find(std::string_view(LARGE_KEY)); + if (file_arg_iter == args.end()) { + LT_FAIL("file arg not found"); + } + LT_CHECK_EQ(file_arg_iter->second.size(), 1); + LT_CHECK_EQ(file_arg_iter->second[0], file_content); map> files = resource.get_files(); LT_CHECK_EQ(resource.get_files().size(), 0); @@ -550,6 +561,52 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content) delete ws; LT_END_AUTO_TEST(file_upload_large_content) +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content_with_args) + 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); + + // Upload a large file to trigger the chunking behavior of MHD. + // Include some additional args to make sure those are processed as well. + std::string file_content; + CURLcode res = send_large_file(&file_content, "?arg1=hello&arg1=world"); + LT_ASSERT_EQ(res, 0); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(LARGE_FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(file_content) != string::npos, true); + + auto const args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + auto const file_arg_iter = args.find(std::string_view(LARGE_KEY)); + if (file_arg_iter == args.end()) { + LT_FAIL("file arg not found"); + } + LT_CHECK_EQ(file_arg_iter->second.size(), 1); + LT_CHECK_EQ(file_arg_iter->second[0], file_content); + auto const other_arg_iter = args.find(std::string_view("arg1")); + if (other_arg_iter == args.end()) { + LT_FAIL("other arg(s) not found"); + } + LT_CHECK_EQ(other_arg_iter->second.size(), 2); + LT_CHECK_EQ(other_arg_iter->second[0], "hello"); + LT_CHECK_EQ(other_arg_iter->second[1], "world"); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); + delete ws; +LT_END_AUTO_TEST(file_upload_large_content_with_args) + LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_excl_content) string upload_directory = "."; webserver* ws;