From 8bffbe3ff2af36c400e7e7c30dddf550d5084079 Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 2 Mar 2026 21:46:31 -0500 Subject: [PATCH] Merge commit from fork --- httplib.h | 15 ++++++++------- test/test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/httplib.h b/httplib.h index a5e2e893..1dd73501 100644 --- a/httplib.h +++ b/httplib.h @@ -7770,13 +7770,14 @@ public: it = params.find("filename*"); if (it != params.end()) { - // Only allow UTF-8 encoding... - thread_local const std::regex re_rfc5987_encoding( - R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); - - std::smatch m2; - if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { - file_.filename = decode_path_component(m2[1]); // override... + // RFC 5987: only UTF-8 encoding is allowed + const auto &val = it->second; + constexpr size_t prefix_len = 7; // length of "UTF-8''" + if (val.size() > prefix_len && + case_ignore::to_lower(val.substr(0, prefix_len)) == + "utf-8''") { + file_.filename = decode_path_component( + val.substr(prefix_len)); // override... } else { is_valid_ = false; return false; diff --git a/test/test.cc b/test/test.cc index 21650ca2..d31aaefa 100644 --- a/test/test.cc +++ b/test/test.cc @@ -11609,6 +11609,52 @@ TEST(MultipartFormDataTest, AlternateFilename) { ASSERT_TRUE(send_request(1, req)); } +TEST(MultipartFormDataTest, AlternateFilenameLongValueAndCaseInsensitive) { + auto handled = false; + + Server svr; + svr.Post("/test", [&](const Request &req, Response &res) { + // Case-insensitive "utf-8''" prefix with a long value + const auto &file = req.form.get_file("file1"); + ASSERT_EQ("file1", file.name); + std::string expected_filename(2000, 'A'); + ASSERT_EQ(expected_filename, file.filename); + + res.set_content("ok", "text/plain"); + handled = true; + }); + + thread t = thread([&] { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + ASSERT_TRUE(handled); + }); + + svr.wait_until_ready(); + + // Build body with a long filename* value using mixed-case prefix "Utf-8''" + // Prior to the fix, this would cause O(N) stack recursion in std::regex + std::string long_filename(2000, 'A'); + std::string body = "----------\r\n" + "Content-Disposition: form-data; name=\"file1\"; " + "filename*=\"Utf-8''" + + long_filename + + "\"\r\n" + "\r\n" + "hello\r\n" + "------------\r\n"; + + auto req = "POST /test HTTP/1.1\r\n" + "Content-Type: multipart/form-data;boundary=--------\r\n" + "Content-Length: " + + std::to_string(body.size()) + + "\r\n\r\n" + body; + + ASSERT_TRUE(send_request(1, req)); +} + TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) { auto handled = false;