diff --git a/CVE-2025-0825.patch b/CVE-2025-0825.patch deleted file mode 100644 index 2e19a3308609b69e531af58ace89685bc01f2cec..0000000000000000000000000000000000000000 --- a/CVE-2025-0825.patch +++ /dev/null @@ -1,195 +0,0 @@ -From 9c36aae4b73e2b6e493f4133e4173103c9266289 Mon Sep 17 00:00:00 2001 -From: yhirose -Date: Thu, 16 Jan 2025 00:04:17 -0500 -Subject: [PATCH] Fix HTTP Response Splitting Vulnerability - ---- - httplib.h | 62 +++++++++++++++++++++++++++++++++++++-- - test/test.cc | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 141 insertions(+), 3 deletions(-) - -diff --git a/httplib.h b/httplib.h -index 7743f9fd6f..27141f0bf4 100644 ---- a/httplib.h -+++ b/httplib.h -@@ -2506,6 +2506,60 @@ class mmap { - bool is_open_empty_file = false; - }; - -+// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 -+namespace fields { -+ -+inline bool is_token_char(char c) { -+ return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || -+ c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || -+ c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; -+} -+ -+inline bool is_token(const std::string &s) { -+ if (s.empty()) { return false; } -+ for (auto c : s) { -+ if (!is_token_char(c)) { return false; } -+ } -+ return true; -+} -+ -+inline bool is_field_name(const std::string &s) { return is_token(s); } -+ -+inline bool is_vchar(char c) { return c >= 33 && c <= 126; } -+ -+inline bool is_obs_text(char c) { return 128 <= static_cast(c); } -+ -+inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); } -+ -+inline bool is_field_content(const std::string &s) { -+ if (s.empty()) { return false; } -+ -+ if (s.size() == 1) { -+ return is_field_vchar(s[0]); -+ } else if (s.size() == 2) { -+ return is_field_vchar(s[0]) && is_field_vchar(s[1]); -+ } else { -+ size_t i = 0; -+ -+ if (!is_field_vchar(s[i])) { return false; } -+ i++; -+ -+ while (i < s.size() - 1) { -+ auto c = s[i++]; -+ if (c == ' ' || c == '\t' || is_field_vchar(c)) { -+ } else { -+ return false; -+ } -+ } -+ -+ return is_field_vchar(s[i]); -+ } -+} -+ -+inline bool is_field_value(const std::string &s) { return is_field_content(s); } -+ -+}; // namespace fields -+ - } // namespace detail - - // ---------------------------------------------------------------------------- -@@ -5699,7 +5753,8 @@ inline size_t Request::get_header_value_count(const std::string &key) const { - - inline void Request::set_header(const std::string &key, - const std::string &val) { -- if (!detail::has_crlf(key) && !detail::has_crlf(val)) { -+ if (detail::fields::is_field_name(key) && -+ detail::fields::is_field_value(val)) { - headers.emplace(key, val); - } - } -@@ -5765,13 +5820,14 @@ inline size_t Response::get_header_value_count(const std::string &key) const { - - inline void Response::set_header(const std::string &key, - const std::string &val) { -- if (!detail::has_crlf(key) && !detail::has_crlf(val)) { -+ if (detail::fields::is_field_name(key) && -+ detail::fields::is_field_value(val)) { - headers.emplace(key, val); - } - } - - inline void Response::set_redirect(const std::string &url, int stat) { -- if (!detail::has_crlf(url)) { -+ if (detail::fields::is_field_value(url)) { - set_header("Location", url); - if (300 <= stat && stat < 400) { - this->status = stat; -diff --git a/test/test.cc b/test/test.cc -index 6ec4b6fc63..ebc50f6f01 100644 ---- a/test/test.cc -+++ b/test/test.cc -@@ -7925,6 +7925,88 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) { - cli.Get("/test", {{"Test", "_\n\r_\n\r_"}}); - } - -+TEST(InvalidHeaderCharsTest, is_field_name) { -+ EXPECT_TRUE(detail::fields::is_field_name("exampleToken")); -+ EXPECT_TRUE(detail::fields::is_field_name("token123")); -+ EXPECT_TRUE(detail::fields::is_field_name("!#$%&'*+-.^_`|~")); -+ -+ EXPECT_FALSE(detail::fields::is_field_name("example token")); -+ EXPECT_FALSE(detail::fields::is_field_name(" example_token")); -+ EXPECT_FALSE(detail::fields::is_field_name("example_token ")); -+ EXPECT_FALSE(detail::fields::is_field_name("token@123")); -+ EXPECT_FALSE(detail::fields::is_field_name("")); -+ EXPECT_FALSE(detail::fields::is_field_name("example\rtoken")); -+ EXPECT_FALSE(detail::fields::is_field_name("example\ntoken")); -+ EXPECT_FALSE(detail::fields::is_field_name(std::string("\0", 1))); -+ EXPECT_FALSE(detail::fields::is_field_name("example\ttoken")); -+} -+ -+TEST(InvalidHeaderCharsTest, is_field_value) { -+ EXPECT_TRUE(detail::fields::is_field_value("exampleToken")); -+ EXPECT_TRUE(detail::fields::is_field_value("token123")); -+ EXPECT_TRUE(detail::fields::is_field_value("!#$%&'*+-.^_`|~")); -+ -+ EXPECT_TRUE(detail::fields::is_field_value("example token")); -+ EXPECT_FALSE(detail::fields::is_field_value(" example_token")); -+ EXPECT_FALSE(detail::fields::is_field_value("example_token ")); -+ EXPECT_TRUE(detail::fields::is_field_value("token@123")); -+ EXPECT_FALSE(detail::fields::is_field_value("")); -+ EXPECT_FALSE(detail::fields::is_field_value("example\rtoken")); -+ EXPECT_FALSE(detail::fields::is_field_value("example\ntoken")); -+ EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1))); -+ EXPECT_TRUE(detail::fields::is_field_value("example\ttoken")); -+ -+ EXPECT_TRUE(detail::fields::is_field_value("0")); -+} -+ -+TEST(InvalidHeaderCharsTest, OnServer) { -+ Server svr; -+ -+ svr.Get("/test_name", [&](const Request &req, Response &res) { -+ std::string header = "Not Set"; -+ if (req.has_param("header")) { header = req.get_param_value("header"); } -+ -+ res.set_header(header, "value"); -+ res.set_content("Page Content Page Content", "text/plain"); -+ }); -+ -+ svr.Get("/test_value", [&](const Request &req, Response &res) { -+ std::string header = "Not Set"; -+ if (req.has_param("header")) { header = req.get_param_value("header"); } -+ -+ res.set_header("X-Test", header); -+ res.set_content("Page Content Page Content", "text/plain"); -+ }); -+ -+ auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); -+ -+ auto se = detail::scope_exit([&] { -+ svr.stop(); -+ thread.join(); -+ ASSERT_FALSE(svr.is_running()); -+ }); -+ -+ svr.wait_until_ready(); -+ -+ Client cli(HOST, PORT); -+ { -+ auto res = cli.Get( -+ R"(/test_name?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)"); -+ -+ ASSERT_TRUE(res); -+ EXPECT_EQ("Page Content Page Content", res->body); -+ EXPECT_FALSE(res->has_header("HEADER_KEY")); -+ } -+ { -+ auto res = cli.Get( -+ R"(/test_value?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)"); -+ -+ ASSERT_TRUE(res); -+ EXPECT_EQ("Page Content Page Content", res->body); -+ EXPECT_FALSE(res->has_header("HEADER_KEY")); -+ } -+} -+ - #ifndef _WIN32 - TEST(Expect100ContinueTest, ServerClosesConnection) { - static constexpr char reject[] = "Unauthorized"; diff --git a/cpp-httplib.spec b/cpp-httplib.spec index 198961f54aad397d6c09a0566b39bf61f98d9532..56b1cde823ee270270a689c37e7659c2ed98d2bb 100644 --- a/cpp-httplib.spec +++ b/cpp-httplib.spec @@ -1,11 +1,10 @@ Name: cpp-httplib -Version: 0.18.3 -Release: 2 +Version: 0.20.1 +Release: 1 Summary: A C++ header-only HTTP/HTTPS server and client library License: MIT URL: https://github.com/yhirose/cpp-httplib Source0: https://github.com/yhirose/cpp-httplib/archive/refs/tags/v%{version}.tar.gz -Patch0: CVE-2025-0825.patch BuildRequires: gcc-c++ BuildRequires: cmake BuildRequires: pkgconfig(libcurl) @@ -60,6 +59,9 @@ rm -r $RPM_BUILD_ROOT%{_licensedir}/httplib %{_libdir}/cmake/httplib %changelog +* Wed May 07 2025 yaoxin <1024769339@qq.com> - 0.20.1-1 +- Update to 0.20.1 for fix CVE-2025-46728 + * Wed Feb 05 2025 yaoxin <1024769339@qq.com> - 0.18.3-2 - Fix CVE-2025-0825 diff --git a/v0.18.3.tar.gz b/v0.18.3.tar.gz deleted file mode 100644 index 8834be4ba100e794bdf5297428712817f24b2688..0000000000000000000000000000000000000000 Binary files a/v0.18.3.tar.gz and /dev/null differ diff --git a/v0.20.1.tar.gz b/v0.20.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5a686b88846dee82822f344de4d6ba73b3739b52 Binary files /dev/null and b/v0.20.1.tar.gz differ