xref: /openbmc/bmcweb/test/include/multipart_test.cpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include "http_request.hpp"
4 #include "multipart_parser.hpp"
5 
6 #include <boost/beast/http/fields.hpp>
7 
8 #include <iterator>
9 #include <string_view>
10 #include <system_error>
11 #include <vector>
12 
13 #include <gtest/gtest.h>
14 
15 namespace
16 {
17 using ::testing::Test;
18 
19 class MultipartTest : public Test
20 {
21   public:
22     MultipartParser parser;
23     std::error_code ec;
24 };
25 
TEST_F(MultipartTest,TestGoodMultipartParser)26 TEST_F(MultipartTest, TestGoodMultipartParser)
27 {
28     std::string_view body =
29         "-----------------------------d74496d66958873e\r\n"
30         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
31         "111111111111111111111111112222222222222222222222222222222\r\n"
32         "-----------------------------d74496d66958873e\r\n"
33         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
34         "{\r\n-----------------------------d74496d66958873e123456\r\n"
35         "-----------------------------d74496d66958873e\r\n"
36         "Content-Disposition: form-data; name=\"Test3\"\r\n\r\n"
37         "{\r\n--------d74496d6695887}\r\n"
38         "-----------------------------d74496d66958873e--\r\n";
39 
40     crow::Request reqIn(body, ec);
41 
42     reqIn.addHeader("Content-Type",
43                     "multipart/form-data; "
44                     "boundary=---------------------------d74496d66958873e");
45 
46     ParserError rc = parser.parse(reqIn);
47     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
48 
49     EXPECT_EQ(parser.boundary,
50               "\r\n-----------------------------d74496d66958873e");
51     EXPECT_EQ(parser.mime_fields.size(), 3);
52 
53     EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
54               "form-data; name=\"Test1\"");
55     EXPECT_EQ(parser.mime_fields[0].content,
56               "111111111111111111111111112222222222222222222222222222222");
57 
58     EXPECT_EQ(parser.mime_fields[1].fields.at("Content-Disposition"),
59               "form-data; name=\"Test2\"");
60     EXPECT_EQ(parser.mime_fields[1].content,
61               "{\r\n-----------------------------d74496d66958873e123456");
62     EXPECT_EQ(parser.mime_fields[2].fields.at("Content-Disposition"),
63               "form-data; name=\"Test3\"");
64     EXPECT_EQ(parser.mime_fields[2].content, "{\r\n--------d74496d6695887}");
65 }
66 
TEST_F(MultipartTest,TestBadMultipartParser1)67 TEST_F(MultipartTest, TestBadMultipartParser1)
68 {
69     std::string_view body =
70         "-----------------------------d74496d66958873e\r\n"
71         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
72         "1234567890\r\n"
73         "-----------------------------d74496d66958873e\r-\r\n";
74 
75     crow::Request reqIn(body, ec);
76 
77     reqIn.addHeader("Content-Type",
78                     "multipart/form-data; "
79                     "boundary=---------------------------d74496d66958873e");
80 
81     ParserError rc = parser.parse(reqIn);
82 
83     EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
84 }
85 
TEST_F(MultipartTest,TestBadMultipartParser2)86 TEST_F(MultipartTest, TestBadMultipartParser2)
87 {
88     std::string_view body =
89         "-----------------------------d74496d66958873e\r\n"
90         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
91         "abcd\r\n"
92         "-----------------------------d74496d66958873e-\r\n";
93     crow::Request reqIn(body, ec);
94 
95     reqIn.addHeader("Content-Type",
96                     "multipart/form-data; "
97                     "boundary=---------------------------d74496d66958873e");
98 
99     ParserError rc = parser.parse(reqIn);
100 
101     EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
102 }
103 
TEST_F(MultipartTest,TestErrorBoundaryFormat)104 TEST_F(MultipartTest, TestErrorBoundaryFormat)
105 {
106     std::string_view body =
107         "-----------------------------d74496d66958873e\r\n"
108         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
109         "{\"Key1\": 11223333333333333333333333333333333333333333}\r\n"
110         "-----------------------------d74496d66958873e\r\n"
111         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
112         "123456\r\n"
113         "-----------------------------d74496d66958873e--\r\n";
114 
115     crow::Request reqIn(body, ec);
116 
117     reqIn.addHeader("Content-Type",
118                     "multipart/form-data; "
119                     "boundary+=-----------------------------d74496d66958873e");
120 
121     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_FORMAT);
122 }
123 
TEST_F(MultipartTest,TestErrorBoundaryCR)124 TEST_F(MultipartTest, TestErrorBoundaryCR)
125 {
126     std::string_view body =
127         "-----------------------------d74496d66958873e"
128         "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
129         "{\"Key1\": 112233}\r\n"
130         "-----------------------------d74496d66958873e\r\n"
131         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
132         "123456\r\n"
133         "-----------------------------d74496d66958873e--\r\n";
134     crow::Request reqIn(body, ec);
135 
136     reqIn.addHeader("Content-Type",
137                     "multipart/form-data; "
138                     "boundary=---------------------------d74496d66958873e");
139 
140     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_CR);
141 }
142 
TEST_F(MultipartTest,TestErrorBoundaryLF)143 TEST_F(MultipartTest, TestErrorBoundaryLF)
144 {
145     std::string_view body =
146         "-----------------------------d74496d66958873e\r"
147         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
148         "{\"Key1\": 112233}\r\n"
149         "-----------------------------d74496d66958873e\r\n"
150         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
151         "123456\r\n"
152         "-----------------------------d74496d66958873e--\r\n";
153 
154     crow::Request reqIn(body, ec);
155 
156     reqIn.addHeader("Content-Type",
157                     "multipart/form-data; "
158                     "boundary=---------------------------d74496d66958873e");
159 
160     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_LF);
161 }
162 
TEST_F(MultipartTest,TestErrorBoundaryData)163 TEST_F(MultipartTest, TestErrorBoundaryData)
164 {
165     std::string_view body =
166         "-----------------------------d74496d66958873e\r\n"
167         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
168         "{\"Key1\": 112233}\r\n"
169         "-----------------------------d74496d66958873e\r\n"
170         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
171         "123456\r\n"
172         "-----------------------------d74496d66958873e--\r\n";
173 
174     crow::Request reqIn(body, ec);
175 
176     reqIn.addHeader("Content-Type",
177                     "multipart/form-data; "
178                     "boundary=---------------------------d7449sd6d66958873e");
179 
180     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_BOUNDARY_DATA);
181 }
182 
TEST_F(MultipartTest,TestErrorEmptyHeader)183 TEST_F(MultipartTest, TestErrorEmptyHeader)
184 {
185     std::string_view body =
186         "-----------------------------d74496d66958873e\r\n"
187         ": form-data; name=\"Test1\"\r\n"
188         "{\"Key1\": 112233}\r\n"
189         "-----------------------------d74496d66958873e\r\n"
190         "Content-Disposition: form-data; name=\"Test2\"\r\n"
191         "123456\r\n"
192         "-----------------------------d74496d66958873e--\r\n";
193     crow::Request reqIn(body, ec);
194 
195     reqIn.addHeader("Content-Type",
196                     "multipart/form-data; "
197                     "boundary=---------------------------d74496d66958873e");
198 
199     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_EMPTY_HEADER);
200 }
201 
TEST_F(MultipartTest,TestErrorHeaderName)202 TEST_F(MultipartTest, TestErrorHeaderName)
203 {
204     std::string_view body =
205         "-----------------------------d74496d66958873e\r\n"
206         "Content-!!Disposition: form-data; name=\"Test1\"\r\n"
207         "{\"Key1\": 112233}\r\n"
208         "-----------------------------d74496d66958873e\r\n"
209         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
210         "123456\r\n"
211         "-----------------------------d74496d66958873e--\r\n";
212     crow::Request reqIn(body, ec);
213 
214     reqIn.addHeader("Content-Type",
215                     "multipart/form-data; "
216                     "boundary=---------------------------d74496d66958873e");
217 
218     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_NAME);
219 }
220 
TEST_F(MultipartTest,TestErrorHeaderValue)221 TEST_F(MultipartTest, TestErrorHeaderValue)
222 {
223     std::string_view body =
224         "-----------------------------d74496d66958873e\r\n"
225         "Content-Disposition: form-data; name=\"Test1\"\r"
226         "{\"Key1\": 112233}\r\n"
227         "-----------------------------d74496d66958873e\r\n"
228         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
229         "123456\r\n"
230         "-----------------------------d74496d66958873e--\r\n";
231 
232     crow::Request reqIn(body, ec);
233 
234     reqIn.addHeader("Content-Type",
235                     "multipart/form-data; "
236                     "boundary=---------------------------d74496d66958873e");
237 
238     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_VALUE);
239 }
240 
TEST_F(MultipartTest,TestErrorHeaderEnding)241 TEST_F(MultipartTest, TestErrorHeaderEnding)
242 {
243     std::string_view body =
244         "-----------------------------d74496d66958873e\r\n"
245         "Content-Disposition: form-data; name=\"Test1\"\r\n\r"
246         "{\"Key1\": 112233}\r\n"
247         "-----------------------------d74496d66958873e\r\n"
248         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
249         "123456\r\n"
250         "-----------------------------d74496d66958873e--\r\n";
251 
252     crow::Request reqIn(body, ec);
253 
254     reqIn.addHeader("Content-Type",
255                     "multipart/form-data; "
256                     "boundary=---------------------------d74496d66958873e");
257 
258     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_HEADER_ENDING);
259 }
260 
TEST_F(MultipartTest,TestGoodMultipartParserMultipleHeaders)261 TEST_F(MultipartTest, TestGoodMultipartParserMultipleHeaders)
262 {
263     std::string_view body =
264         "-----------------------------d74496d66958873e\r\n"
265         "Content-Disposition: form-data; name=\"Test1\"\r\n"
266         "Other-Header: value=\"v1\"\r\n"
267         "\r\n"
268         "Data1\r\n"
269         "-----------------------------d74496d66958873e--";
270 
271     crow::Request reqIn(body, ec);
272 
273     reqIn.addHeader("Content-Type",
274                     "multipart/form-data; "
275                     "boundary=---------------------------d74496d66958873e");
276 
277     ParserError rc = parser.parse(reqIn);
278     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
279 
280     EXPECT_EQ(parser.boundary,
281               "\r\n-----------------------------d74496d66958873e");
282     ASSERT_EQ(parser.mime_fields.size(), 1);
283 
284     EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
285               "form-data; name=\"Test1\"");
286     EXPECT_EQ(parser.mime_fields[0].fields.at("Other-Header"), "value=\"v1\"");
287     EXPECT_EQ(parser.mime_fields[0].content, "Data1");
288 }
289 
TEST_F(MultipartTest,TestErrorHeaderWithoutColon)290 TEST_F(MultipartTest, TestErrorHeaderWithoutColon)
291 {
292     std::string_view body =
293         "----end\r\n"
294         "abc\r\n"
295         "\r\n"
296         "Data1\r\n"
297         "----end--\r\n";
298     crow::Request reqIn(body, ec);
299 
300     reqIn.addHeader("Content-Type", "multipart/form-data; "
301                                     "boundary=--end");
302 
303     EXPECT_EQ(parser.parse(reqIn), ParserError::ERROR_UNEXPECTED_END_OF_HEADER);
304 }
305 
TEST_F(MultipartTest,TestUnknownHeaderIsCorrectlyParsed)306 TEST_F(MultipartTest, TestUnknownHeaderIsCorrectlyParsed)
307 {
308     std::string_view body =
309         "----end\r\n"
310         "t-DiPpcccc:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa\r\n"
311         "\r\n"
312         "Data1\r\n"
313         "----end--\r\n";
314 
315     crow::Request reqIn(body, ec);
316 
317     reqIn.addHeader("Content-Type", "multipart/form-data; "
318                                     "boundary=--end");
319     ParserError rc = parser.parse(reqIn);
320 
321     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
322 
323     EXPECT_EQ(parser.boundary, "\r\n----end");
324     ASSERT_EQ(parser.mime_fields.size(), 1);
325 
326     EXPECT_EQ(
327         parser.mime_fields[0].fields.at("t-DiPpcccc"),
328         "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa");
329     EXPECT_EQ(parser.mime_fields[0].content, "Data1");
330 }
331 
TEST_F(MultipartTest,TestErrorMissingSeparatorBetweenMimeFieldsAndData)332 TEST_F(MultipartTest, TestErrorMissingSeparatorBetweenMimeFieldsAndData)
333 {
334     std::string_view body =
335         "-----------------------------d74496d66958873e\r\n"
336         "t-DiPpcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccgcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa\r\n"
337         "Data1"
338         "-----------------------------d74496d66958873e--";
339 
340     crow::Request reqIn(body, ec);
341 
342     reqIn.addHeader(
343         "Content-Type",
344         "multipart/form-data; boundary=---------------------------d74496d66958873e");
345 
346     ParserError rc = parser.parse(reqIn);
347 
348     EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_HEADER);
349 }
350 
TEST_F(MultipartTest,TestDataWithoutMimeFields)351 TEST_F(MultipartTest, TestDataWithoutMimeFields)
352 {
353     std::string_view body =
354         "-----------------------------d74496d66958873e\r\n"
355         "\r\n"
356         "Data1\r\n"
357         "-----------------------------d74496d66958873e--";
358 
359     crow::Request reqIn(body, ec);
360 
361     reqIn.addHeader(
362         "Content-Type",
363         "multipart/form-data; boundary=---------------------------d74496d66958873e");
364 
365     ParserError rc = parser.parse(reqIn);
366 
367     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
368 
369     EXPECT_EQ(parser.boundary,
370               "\r\n-----------------------------d74496d66958873e");
371     ASSERT_EQ(parser.mime_fields.size(), 1);
372 
373     EXPECT_EQ(std::distance(parser.mime_fields[0].fields.begin(),
374                             parser.mime_fields[0].fields.end()),
375               0);
376     EXPECT_EQ(parser.mime_fields[0].content, "Data1");
377 }
378 
TEST_F(MultipartTest,TestErrorMissingFinalBoundry)379 TEST_F(MultipartTest, TestErrorMissingFinalBoundry)
380 {
381     std::string_view body =
382         "----XX\r\n"
383         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
384         "t-DiPpccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccAAAAAAAAAAAAAAABCDz\r\n"
385         "\335\r\n\r\n";
386 
387     crow::Request reqIn(body, ec);
388 
389     reqIn.addHeader("Content-Type", "multipart/form-data; boundary=--XX");
390 
391     ParserError rc = parser.parse(reqIn);
392 
393     EXPECT_EQ(rc, ParserError::ERROR_UNEXPECTED_END_OF_INPUT);
394 }
395 
TEST_F(MultipartTest,TestIgnoreDataAfterFinalBoundary)396 TEST_F(MultipartTest, TestIgnoreDataAfterFinalBoundary)
397 {
398     std::string_view body =
399         "----XX\r\n"
400         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
401         "Data1\r\n"
402         "----XX--\r\n"
403         "Content-Disposition: form-data; name=\"Test2\"\r\n\r\n"
404         "Data2\r\n"
405         "----XX--\r\n";
406 
407     crow::Request reqIn(body, ec);
408 
409     reqIn.addHeader("Content-Type", "multipart/form-data; boundary=--XX");
410 
411     ParserError rc = parser.parse(reqIn);
412 
413     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
414 
415     EXPECT_EQ(parser.boundary, "\r\n----XX");
416     EXPECT_EQ(parser.mime_fields.size(), 1);
417 
418     EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
419               "form-data; name=\"Test1\"");
420     EXPECT_EQ(parser.mime_fields[0].content, "Data1");
421 }
422 
TEST_F(MultipartTest,TestFinalBoundaryIsCorrectlyRecognized)423 TEST_F(MultipartTest, TestFinalBoundaryIsCorrectlyRecognized)
424 {
425     std::string_view body =
426         "----XX\r\n"
427         "Content-Disposition: form-data; name=\"Test1\"\r\n\r\n"
428         "Data1\r\n"
429         "----XX-abc-\r\n"
430         "StillData1\r\n"
431         "----XX--\r\n";
432 
433     crow::Request reqIn(body, ec);
434 
435     reqIn.addHeader("Content-Type", "multipart/form-data; boundary=--XX");
436 
437     ParserError rc = parser.parse(reqIn);
438 
439     ASSERT_EQ(rc, ParserError::PARSER_SUCCESS);
440 
441     EXPECT_EQ(parser.boundary, "\r\n----XX");
442     EXPECT_EQ(parser.mime_fields.size(), 1);
443 
444     EXPECT_EQ(parser.mime_fields[0].fields.at("Content-Disposition"),
445               "form-data; name=\"Test1\"");
446     EXPECT_EQ(parser.mime_fields[0].content,
447               "Data1\r\n"
448               "----XX-abc-\r\n"
449               "StillData1");
450 }
451 
452 } // namespace
453