diff --git a/examples/example.cpp b/examples/example.cpp index 1355b03ec..a2739a827 100644 --- a/examples/example.cpp +++ b/examples/example.cpp @@ -163,6 +163,14 @@ int main() return std::string(512*1024, ' '); }); + // Take a multipart/form-data request and print out its body + CROW_ROUTE(app,"/multipart") + ([](const crow::request& req){ + crow::multipart::message msg(req); + CROW_LOG_INFO << "body of the first part " << msg.parts[0].body; + return "it works!"; + }); + // enables all log app.loglevel(crow::LogLevel::DEBUG); //crow::logger::setHandler(std::make_shared()); diff --git a/include/crow.h b/include/crow.h index 9a0c22e11..b57bb6632 100644 --- a/include/crow.h +++ b/include/crow.h @@ -15,6 +15,7 @@ #include "crow/websocket.h" #include "crow/parser.h" #include "crow/http_response.h" +#include "crow/multipart.h" #include "crow/middleware.h" #include "crow/routing.h" #include "crow/middleware_context.h" diff --git a/include/crow/multipart.h b/include/crow/multipart.h new file mode 100644 index 000000000..7deb8d45d --- /dev/null +++ b/include/crow/multipart.h @@ -0,0 +1,195 @@ +#pragma once +#include +#include +#include +#include "crow/http_request.h" +#include "crow/http_response.h" + +namespace crow +{ + ///Encapsulates anything related to processing and organizing `multipart/xyz` messages + namespace multipart + { + const std::string dd = "--"; + const std::string crlf = "\r\n"; + + ///The first part in a section, contains metadata about the part + struct header + { + std::pair value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition` + std::unordered_map params; ///< The parameters of the header, come after the `value` + }; + + ///One part of the multipart message + + ///It is usually separated from other sections by a `boundary` + /// + struct part + { + std::vector
headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding + std::string body; ///< The actual data in the part + }; + + ///The parsed multipart request/response + struct message + { + ci_map headers; + std::string boundary; ///< The text boundary that separates different `parts` + std::vector parts; ///< The individual parts of the message + + const std::string& get_header_value(const std::string& key) const + { + return crow::get_header_value(headers, key); + } + + ///Represent all parts as a string (**does not include message headers**) + const std::string dump() + { + std::stringstream str; + std::string delimiter = dd + boundary; + + for (uint i=0 ; i(header.substr(0, header_split), header.substr(header_split+2)); + } + + //add the parameters + while (!line.empty()) + { + size_t found = line.find("; "); + std::string param = line.substr(0, found); + if (found != std::string::npos) + line.erase(0, found+2); + else + line = std::string(); + + size_t param_split = param.find('='); + + std::string value = param.substr(param_split+1); + + to_add.params.emplace(param.substr(0, param_split), trim(value)); + } + part.headers.emplace_back(to_add); + } + } + + inline std::string trim (std::string& string, const char& excess = '"') + { + if (string.length() > 1 && string[0] == excess && string[string.length()-1] == excess) + return string.substr(1, string.length()-2); + return string; + } + + inline std::string pad (std::string& string, const char& padding = '"') + { + return (padding + string + padding); + } + + }; + } +} diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 09fe97201..022398b1e 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -1234,14 +1234,46 @@ TEST_CASE("send_file") res.headers.find("Content-Length")->second); } + { + request req; + response res; + + req.url = "/jpg2"; + + app.handle(req, res); + + + REQUIRE(404 == res.code); + } +} + +TEST_CASE("multipart") +{ + std::string test_string = "--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"hello\"\r\n\r\nworld\r\n--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--CROW-BOUNDARY\r\nContent-Disposition: form-data; name=\"multiline\"\r\n\r\ntext\ntext\ntext\r\n--CROW-BOUNDARY--\r\n"; + + SimpleApp app; + + CROW_ROUTE(app, "/multipart") + ([](const crow::request& req, crow::response& res) + { + multipart::message msg(req); + res.add_header("Content-Type", "multipart/form-data; boundary=CROW-BOUNDARY"); + res.body = msg.dump(); + res.end(); + }); + + app.validate(); + { request req; response res; - req.url = "/jpg2"; + req.url = "/multipart"; + req.add_header("Content-Type", "multipart/form-data; boundary=CROW-BOUNDARY"); + req.body = test_string; app.handle(req, res); - REQUIRE(404 == res.code); + REQUIRE(test_string == res.body); } }