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