diff --git a/libtransmission/web-utils.cc b/libtransmission/web-utils.cc index 294d154e4..d0bb18f33 100644 --- a/libtransmission/web-utils.cc +++ b/libtransmission/web-utils.cc @@ -292,7 +292,6 @@ std::string_view getSiteName(std::string_view host) return host; } - } // namespace std::optional tr_urlParse(std::string_view url) @@ -335,8 +334,31 @@ std::optional tr_urlParse(std::string_view url) parsed.authority = url.substr(0, pos); url = pos == std::string_view::npos ? ""sv : url.substr(pos); + // A host identified by an Internet Protocol literal address, version 6 + // [RFC3513] or later, is distinguished by enclosing the IP literal + // within square brackets ("[" and "]"). This is the only place where + // square bracket characters are allowed in the URI syntax. auto remain = parsed.authority; - parsed.host = tr_strvSep(&remain, ':'); + if (tr_strvStartsWith(remain, '[')) + { + remain.remove_prefix(1); // '[' + parsed.host = tr_strvSep(&remain, ']'); + if (tr_strvStartsWith(remain, ':')) + { + remain.remove_prefix(1); + } + } + // Not legal by RFC3986 standards, but sometimes users omit + // square brackets for an IPv6 address with an implicit port + else if (std::count(std::begin(remain), std::end(remain), ':') > 1U) + { + parsed.host = remain; + remain = ""sv; + } + else + { + parsed.host = tr_strvSep(&remain, ':'); + } parsed.sitename = getSiteName(parsed.host); parsed.port = parsePort(!std::empty(remain) ? remain : getPortForScheme(parsed.scheme)); } diff --git a/tests/libtransmission/web-utils-test.cc b/tests/libtransmission/web-utils-test.cc index 8c3100529..27a5fa028 100644 --- a/tests/libtransmission/web-utils-test.cc +++ b/tests/libtransmission/web-utils-test.cc @@ -111,7 +111,7 @@ TEST_F(WebUtilsTest, urlParse) EXPECT_EQ("/some/other/path"sv, parsed->path); EXPECT_EQ(80, parsed->port); - // test a host with an IP address + // test a host with an IPv4 address url = "https://127.0.0.1:8080/some/path"sv; parsed = tr_urlParse(url); EXPECT_TRUE(parsed); @@ -120,6 +120,33 @@ TEST_F(WebUtilsTest, urlParse) EXPECT_EQ("127.0.0.1"sv, parsed->host); EXPECT_EQ("/some/path"sv, parsed->path); EXPECT_EQ(8080, parsed->port); + + // test a host with a bracketed IPv6 address and explicit port + url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080/announce"sv; + parsed = tr_urlParse(url); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); + EXPECT_EQ("/announce"sv, parsed->path); + EXPECT_EQ(8080, parsed->port); + + // test a host with a bracketed IPv6 address and implicit port + url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]/announce"sv; + parsed = tr_urlParse(url); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); + EXPECT_EQ("/announce"sv, parsed->path); + EXPECT_EQ(80, parsed->port); + + // test a host with an unbracketed IPv6 address and implicit port + url = "http://2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d/announce"sv; + parsed = tr_urlParse(url); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); + EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); + EXPECT_EQ("/announce"sv, parsed->path); + EXPECT_EQ(80, parsed->port); } TEST(WebUtilsTest, urlParseFuzz)