From 2d8e49dd9b8bba32f45a76eb7fac279aa6bba9c0 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Sat, 15 Aug 2026 06:57:40 +0530 Subject: [PATCH] reject trailing bytes after IPv6 host literal in parse_url (#2536) --- httplib.h | 9 +++++++++ test/test.cc | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/httplib.h b/httplib.h index f4046804..07bcf760 100644 --- a/httplib.h +++ b/httplib.h @@ -851,6 +851,15 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) { } pos = close + 1; + + // The IPv6 literal is the whole host, so ']' must be followed by a port, + // path, query or fragment delimiter (or the end of input). Otherwise the + // trailing bytes would be folded into the path while the connection + // still targets the bracketed address. + if (pos < url.size() && url[pos] != ':' && url[pos] != '/' && + url[pos] != '?' && url[pos] != '#') { + return false; + } } else { auto end = url.find_first_of(":/?#", pos); if (end == std::string::npos) { end = url.size(); } diff --git a/test/test.cc b/test/test.cc index 991de48e..ff8bfb03 100644 --- a/test/test.cc +++ b/test/test.cc @@ -15563,6 +15563,23 @@ TEST(ParseUrlTest, VariousPatterns) { detail::UrlComponents uc; ASSERT_FALSE(detail::parse_url("http://[::1/path", uc)); } + { + // Bytes after the IPv6 literal must be a delimiter, not folded into + // the path while the connection still targets the bracketed host. + detail::UrlComponents uc; + ASSERT_FALSE(detail::parse_url("http://[::1]evil.com/", uc)); + } + { + detail::UrlComponents uc; + ASSERT_FALSE(detail::parse_url("http://[::1]evil", uc)); + } + { + detail::UrlComponents uc; + ASSERT_TRUE(detail::parse_url("http://[::1]/path", uc)); + EXPECT_EQ("::1", uc.host); + EXPECT_TRUE(uc.port.empty()); + EXPECT_EQ("/path", uc.path); + } { detail::UrlComponents uc; ASSERT_TRUE(detail::parse_url("//example.com/path?q=1", uc));