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/src/http_request.cpp b/src/http_request.cpp index b0cd46b3..afb7ed8e 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_arg_value http_request::get_arg(std::string_view key) const { + populate_args(); + auto it = cache->unescaped_args.find(key); if (it != cache->unescaped_args.end()) { - return it->second; + 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_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(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.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..e2111081 --- /dev/null +++ b/src/httpserver/http_arg_value.hpp @@ -0,0 +1,65 @@ +/* + 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::string_view() const { + return 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 6228c82f..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" @@ -134,11 +135,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 +181,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_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 +313,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 +323,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 +389,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 +408,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_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 e6b5411f..3a9d6884 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 @@ -299,6 +301,7 @@ class header_comparator { **/ class arg_comparator { public: + using is_transparent = std::true_type; /** * Operator used to compare strings. * @param first string @@ -314,12 +317,19 @@ 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; 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..30598708 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -461,12 +461,18 @@ 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) { - mr->dhr->set_arg(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); + 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 diff --git a/test/integ/basic.cpp b/test/integ/basic.cpp index c6e61d85..e4e63f47 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,48 @@ 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; + // 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); + 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; + 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_plain_content) + 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..0df7d1ee 100644 --- a/test/integ/file_upload.cpp +++ b/test/integ/file_upload.cpp @@ -20,7 +20,10 @@ #include #include +#include +#include #include +#include #include #include @@ -61,6 +64,12 @@ 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"; +// 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"; + static bool file_exists(const string &path) { struct stat sb; @@ -99,6 +108,44 @@ static CURLcode send_file_to_webserver(bool add_second_file, bool append_paramet return res; } +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 + // 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); + + std::ifstream infile(LARGE_CONTENT_FILEPATH); + std::stringstream buffer; + buffer << infile.rdbuf(); + infile.close(); + *content = buffer.str(); + + 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, url.c_str()); + 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); @@ -144,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[std::string(item.first)] = std::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")); @@ -156,14 +205,16 @@ 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); + 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")); return hresp; } - const arg_map get_args() const { + const std::map, httpserver::http::arg_comparator> get_args() const { return args; } @@ -176,7 +227,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 +269,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 +351,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 +405,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 +512,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 +521,92 @@ 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(); + 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 const args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + 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); + + ws->stop(); + 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; @@ -493,7 +630,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/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 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()