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