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