diff --git a/CMakeLists.txt b/CMakeLists.txt index 64d03d531..f3a38ece0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ if(CROW_AMALGAMATE) include/crow/ci_map.h include/crow/common.h include/crow/compression.h + include/crow/exceptions.h include/crow/http_connection.h include/crow/http_parser_merged.h include/crow/http_request.h diff --git a/include/crow/exceptions.h b/include/crow/exceptions.h new file mode 100644 index 000000000..63a6e8e49 --- /dev/null +++ b/include/crow/exceptions.h @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace crow +{ + struct bad_request : public std::runtime_error + { + bad_request(const std::string& what_arg) + : std::runtime_error(what_arg) {} + + bad_request(const char* what_arg) + : std::runtime_error(what_arg) {} + }; +} \ No newline at end of file diff --git a/include/crow/multipart.h b/include/crow/multipart.h index 3f4c1d2c3..24040c962 100644 --- a/include/crow/multipart.h +++ b/include/crow/multipart.h @@ -7,6 +7,7 @@ #include "crow/http_request.h" #include "crow/returnable.h" #include "crow/ci_map.h" +#include "crow/exceptions.h" namespace crow { @@ -147,8 +148,14 @@ namespace crow boundary(get_boundary(get_header_value("Content-Type"))) { if (!boundary.empty()) + { content_type = "multipart/form-data; boundary=" + boundary; - parse_body(req.body); + parse_body(req.body); + } + else + { + throw bad_request("Empty boundary in multipart message"); + } } private: @@ -178,8 +185,8 @@ namespace crow size_t found = body.find(delimiter); if (found == std::string::npos) { - // did not find delimiter; probably an ill-formed body; ignore the rest - break; + // did not find delimiter; probably an ill-formed body; throw to indicate the issue to user + throw bad_request("Unable to find delimiter in multipart message. Probably ill-formed body"); } std::string section = body.substr(0, found); diff --git a/include/crow/routing.h b/include/crow/routing.h index 31d8b7cf5..e19561332 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -14,6 +14,7 @@ #include "crow/http_request.h" #include "crow/utility.h" #include "crow/logging.h" +#include "crow/exceptions.h" #include "crow/websocket.h" #include "crow/mustache.h" #include "crow/middleware.h" @@ -1813,6 +1814,11 @@ namespace crow // NOTE: Already documented in "crow/app.h" { throw; } + catch (const bad_request& e) + { + res = response (400); + res.body = e.what(); + } catch (const std::exception& e) { CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what(); diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 316d5b1fc..53f083c07 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -2510,6 +2510,54 @@ TEST_CASE("multipart") CHECK(test_string == res.body); } + + //Test against empty boundary + { + request req; + response res; + req.url = "/multipart"; + req.add_header("Content-Type", "multipart/form-data; boundary="); + req.body = test_string; + + app.handle_full(req, res); + + CHECK(res.code == 400); + CHECK(res.body == "Empty boundary in multipart message"); + + } + + //Boundary that differs from actual boundary + { + const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(0, sizeof(alphabet) - 2); + std::uniform_int_distribution boundary_sizes(1, 50); + std::array test_boundaries; + + for (auto& boundary : test_boundaries) + { + const size_t boundary_size = boundary_sizes(rng); + boundary.reserve(boundary_size); + for (size_t idx = 0; idx < boundary_size; idx++) + boundary.push_back(alphabet[dist(rng)]); + } + + for (const auto& boundary : test_boundaries) + { + request req; + response res; + + req.url = "/multipart"; + req.add_header("Content-Type", "multipart/form-data; boundary=" + boundary); + req.body = test_string; + + app.handle_full(req, res); + + CHECK(res.code == 400); + CHECK(res.body == "Unable to find delimiter in multipart message. Probably ill-formed body"); + } + } } // multipart TEST_CASE("multipart_view")