@@ -295,6 +295,28 @@ class print_file_upload_resource : public http_resource {
295295 string content;
296296};
297297
298+ // Raw multipart bytes preserve the missing name attribute; curl_mime would
299+ // supply a name and conceal this malformed-input regression.
300+ static std::pair<CURLcode, long > send_raw_multipart (int port, const string& body) {
301+ CURL * curl = curl_easy_init ();
302+ const string url = " http://127.0.0.1:" + std::to_string (port) + " /anything" ;
303+ curl_slist* headers = curl_slist_append (nullptr ,
304+ " Content-Type: multipart/form-data; boundary=BOUNDARY" );
305+ curl_easy_setopt (curl, CURLOPT_URL , url.c_str ());
306+ curl_easy_setopt (curl, CURLOPT_HTTPHEADER , headers);
307+ curl_easy_setopt (curl, CURLOPT_POSTFIELDS , body.data ());
308+ curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE , static_cast <long >(body.size ()));
309+ curl_easy_setopt (curl, CURLOPT_TIMEOUT , 5L );
310+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION ,
311+ +[](char *, size_t size, size_t count, void *) -> size_t { return size * count; });
312+ const CURLcode result = curl_easy_perform (curl);
313+ long status = 0 ;
314+ curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE , &status);
315+ curl_easy_cleanup (curl);
316+ curl_slist_free_all (headers);
317+ return {result, status};
318+ }
319+
298320LT_BEGIN_SUITE (file_upload_suite)
299321 void set_up () {
300322 }
@@ -1069,6 +1091,33 @@ LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_sanitize_keeps_basename)
10691091 rmdir (upload_directory.c_str());
10701092LT_END_AUTO_TEST (file_upload_sanitize_keeps_basename)
10711093
1094+ LT_BEGIN_AUTO_TEST (file_upload_suite, unnamed_multipart_file_keeps_server_alive)
1095+ curl_global_init (CURL_GLOBAL_ALL );
1096+ webserver ws{create_webserver (0 )};
1097+ ws.on_get(" /hello" , [](const http_request&) {
1098+ return http_response::string (" hello" );
1099+ });
1100+ ws.start(false );
1101+ LT_CHECK_EQ (ws.is_running(), true);
1102+ const int port = ws.get_bound_port();
1103+ for (const char * disposition : {" filename=\" x\" " , " filename=\"\" " ,
1104+ " name=\" f\" ; filename=\" x\" " }) {
1105+ const string body = string (" --BOUNDARY\r\n Content-Disposition: form-data; " )
1106+ + disposition + " \r\n\r\n data\r\n --BOUNDARY--\r\n " ;
1107+ const auto result = send_raw_multipart (port, body);
1108+ LT_CHECK_EQ (result.first , CURLE_OK );
1109+ LT_CHECK_EQ (result.second , 404L );
1110+ // A second HTTP request proves the daemon still handles connections.
1111+ const auto followup = send_raw_multipart (port,
1112+ " --BOUNDARY\r\n Content-Disposition: form-data; name=\" f\"\r\n "
1113+ " \r\n ok\r\n --BOUNDARY--\r\n " );
1114+ LT_CHECK_EQ (followup.first , CURLE_OK );
1115+ LT_CHECK_EQ (followup.second , 404L );
1116+ }
1117+ ws.stop();
1118+ curl_global_cleanup ();
1119+ LT_END_AUTO_TEST (unnamed_multipart_file_keeps_server_alive)
1120+
10721121LT_BEGIN_AUTO_TEST_ENV ()
10731122 AUTORUN_TESTS ()
10741123LT_END_AUTO_TEST_ENV()
0 commit comments