Implement MIME parsing
This commit adds two core features to bmcweb:
1. A multipart mime parser that can read multipart form requests into
bmcweb. This is implemented as a generic parser that identifies the
content-type strings and parses them into structures.
2. A /login route that can be logged into with a multipart form. This
is to allow changing the login screen to a purely forms based
implementation, thus removing the very large whitelist we currently have
to maintain, and removing javascript from our threat envelope.
More testing is still needed, as this is a parser that exists outside of
the secured areas, but in this simple example, it seems to work well.
Tested: curl -vvvvv --insecure -X POST -F 'username=root' -F
'password=0penBmc' https://<bmc ip address>:18080/login
Returned; { "data": "User 'root' logged in", "message": "200 OK",
"status": "ok" }
Change-Id: Icc3f4c082d584170b65b9e82f7876926cd38035d
Signed-off-by: Ed Tanous<ed@tanous.net>
Signed-off-by: George Liu <liuxiwei@inspur.com>
diff --git a/include/ut/multipart_test.cpp b/include/ut/multipart_test.cpp
new file mode 100644
index 0000000..35e05e2
--- /dev/null
+++ b/include/ut/multipart_test.cpp
@@ -0,0 +1,237 @@
+#include <http_utility.hpp>
+#include <multipart_parser.hpp>
+
+#include <map>
+
+#include "gmock/gmock.h"
+
+class MultipartTest : public ::testing::Test
+{
+ public:
+ boost::beast::http::request<boost::beast::http::string_body> req{};
+ MultipartParser parser;
+ std::error_code ec;
+};
+
+TEST_F(MultipartTest, TestGoodMultipartParser)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "111111111111111111111111112222222222222222222222222222222\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "{\r\n-----------------------------d74496d66958873e123456\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test3\"\r\n\r\n"
+ "{\r\n--------d74496d6695887}\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ ASSERT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ ASSERT_EQ(parser.mime_fields.size(), 3);
+
+ ASSERT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
+ "form-data; name=\"Test1\"");
+ ASSERT_EQ(parser.mime_fields[0].content,
+ "111111111111111111111111112222222222222222222222222222222");
+
+ ASSERT_EQ(parser.mime_fields[1].fields.at("Content-Disposition"),
+ "form-data; name=\"Test2\"");
+ ASSERT_EQ(parser.mime_fields[1].content,
+ "{\r\n-----------------------------d74496d66958873e123456");
+ ASSERT_EQ(parser.mime_fields[2].fields.at("Content-Disposition"),
+ "form-data; name=\"Test3\"");
+ ASSERT_EQ(parser.mime_fields[2].content, "{\r\n--------d74496d6695887}");
+}
+
+TEST_F(MultipartTest, TestBadMultipartParser1)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "1234567890\r\n"
+ "-----------------------------d74496d66958873e\r-\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ ASSERT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ ASSERT_EQ(parser.mime_fields.size(), 1);
+}
+
+TEST_F(MultipartTest, TestBadMultipartParser2)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "abcd\r\n"
+ "-----------------------------d74496d66958873e-\r\n";
+
+ crow::Request reqIn(req, ec);
+ ParserError rc = parser.parse(reqIn);
+ ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
+
+ ASSERT_EQ(parser.boundary,
+ "\r\n-----------------------------d74496d66958873e");
+ ASSERT_EQ(parser.mime_fields.size(), 1);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryFormat)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary+=-----------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 11223333333333333333333333333333333333333333}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_FORMAT);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryCR)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_CR);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryLF)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_LF);
+}
+
+TEST_F(MultipartTest, TestErrorBoundaryData)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d7449sd6d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_DATA);
+}
+
+TEST_F(MultipartTest, TestErrorEmptyHeader)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ ": form-data; name=\"Test1\"\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_EMPTY_HEADER);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderName)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-!!Disposition: form-data; name=\"Test1\"\r\n"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_NAME);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderValue)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_VALUE);
+}
+
+TEST_F(MultipartTest, TestErrorHeaderEnding)
+{
+ req.set("Content-Type",
+ "multipart/form-data; "
+ "boundary=---------------------------d74496d66958873e");
+
+ req.body() = "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
+ "{\"Key1\": 112233}\r\n"
+ "-----------------------------d74496d66958873e\r\n"
+ "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
+ "123456\r\n"
+ "-----------------------------d74496d66958873e--\r\n";
+
+ crow::Request reqIn(req, ec);
+ ASSERT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_ENDING);
+}