From aaccde2122c591446976d9d16401b8923dd963f4 Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 16 Oct 2026 16:39:00 -0400 Subject: [PATCH] Reject userinfo and backslashes in a URL authority parse_url took http://user@evil.com/ for the host "user@evil.com", where parsers that honour userinfo, or read a backslash as '/', see evil.com. userinfo is not supported, so reject both. --- httplib.h | 6 ++++++ test/test.cc | 13 ++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/httplib.h b/httplib.h index a2ca27f4..c01c852a 100644 --- a/httplib.h +++ b/httplib.h @@ -889,6 +889,12 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) { auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' && url[0] != '?' && url[0] != '#'); if (has_authority) { + // userinfo is not supported, and a backslash that some parsers take for + // '/' would leave them seeing another host than this one. + if (url.find_first_of("@\\", pos) < url.find_first_of("/?#", pos)) { + return false; + } + if (pos < url.size() && url[pos] == '[') { auto close = url.find(']', pos); if (close == std::string::npos) { return false; } diff --git a/test/test.cc b/test/test.cc index 4a55e44d..f2c8f921 100644 --- a/test/test.cc +++ b/test/test.cc @@ -19498,11 +19498,14 @@ TEST(ParseUrlTest, FragmentHandling) { } TEST(ParseUrlTest, UserinfoHandling) { - // Userinfo with @ but no colon — host includes @ - detail::UrlComponents uc; - ASSERT_TRUE(detail::parse_url("http://user@host.com/path", uc)); - EXPECT_EQ("user@host.com", uc.host); - EXPECT_EQ("/path", uc.path); + // userinfo is not supported. Parsers that honour it, or take a backslash for + // '/', would see evil.com where the host used to be the whole authority. + const char *urls[] = {"http://user@evil.com/", "http://user:pass@evil.com/", + "http://evil.com\\@good.com/", "//user@evil.com/x"}; + for (auto url : urls) { + detail::UrlComponents uc; + EXPECT_FALSE(detail::parse_url(url, uc)) << url; + } } TEST(ParseUrlTest, IPv6EdgeCases) {