Skip to content

Commit 6e11686

Browse files
committed
refactor format_host_for_connect to comply with RFC 9110 and improve handling of IPv6 and port suffixes
1 parent 74223e9 commit 6e11686

File tree

1 file changed

+28
-61
lines changed

1 file changed

+28
-61
lines changed

include/libtorrent/http_stream.hpp

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -99,78 +99,39 @@ class http_stream : public proxy_base
9999

100100
private:
101101

102-
// format a hostname (not a numeric IP) with a port for an HTTP CONNECT request.
103-
// rules:
102+
// Format a hostname with port for HTTP CONNECT request per RFC 9110 Section 9.3.6.
103+
// The authority component must be in the form "host:port" where IPv6 literals
104+
// are enclosed in square brackets as defined in RFC 3986 Section 3.2.2.
105+
// Assumes host parameter contains no port suffix.
106+
// Rules:
104107
// - if port == 0, return host unchanged
105-
// - if host is bracketed IPv6: [addr] or [addr]:port, append port only if missing
106-
// - if host contains no ':', append ":port"
107-
// - if host contains colon(s):
108-
// * if suffix after last ':' is all digits, assume it's already a host:port -> leave unchanged
109-
// * otherwise treat it as an (unbracketed) IPv6 literal or hostname with colons and wrap
110-
// it in brackets and append :port -> [host]:port
108+
// - if host is already bracketed IPv6: [addr], append ":port"
109+
// - if host contains colons (IPv6), bracket and append ":port" -> [host]:port
110+
// - otherwise append ":port" for regular hostnames/IPv4
111111
static std::string format_host_for_connect(std::string host, unsigned short const port)
112112
{
113-
if (port == 0) return host;
113+
// Assert that host doesn't already contain a port suffix
114+
TORRENT_ASSERT(host.empty() ||
115+
((host.back() != ']' || host.find("]:") == std::string::npos) && // no [IPv6]:port
116+
(host.find(':') == std::string::npos || host.find_last_of(':') == host.find(':')))); // no host:port (unless IPv6)
114117

115-
if (!host.empty() && host.front() == '[')
116-
{
117-
auto const rb = host.find(']');
118-
bool const has_port = (rb != std::string::npos && rb + 1 < host.size() && host[rb + 1] == ':');
119-
if (!has_port) host += ":" + std::to_string(port);
120-
return host;
121-
}
118+
// Handle edge case: if no port specified, return host as-is
119+
if (port == 0) return host; // ← ไม่ใช่ ASSERT
122120

123-
auto const last_colon = host.rfind(':');
124-
if (last_colon == std::string::npos)
121+
// Already bracketed IPv6 literal
122+
if (!host.empty() && host.front() == '[' && host.back() == ']')
125123
{
126-
host += ":" + std::to_string(port);
127-
return host;
128-
}
129-
130-
// Check whether the suffix after the last colon is all digits
131-
bool suffix_digits = last_colon + 1 < host.size();
132-
if (suffix_digits)
133-
{
134-
for (std::size_t i = last_colon + 1; i < host.size(); ++i)
135-
{
136-
if (!std::isdigit(static_cast<unsigned char>(host[i]))) { suffix_digits = false; break; }
137-
}
124+
return host + ":" + std::to_string(port);
138125
}
139126

140-
// If the suffix is digits, the string may be either "host:port" or an
141-
// unbracketed IPv6 literal (e.g. "2001:db8::1") where the last
142-
// segment happens to be numeric. Use inet_pton to detect IPv6
143-
// literals:
144-
// - if the whole host parses as IPv6 -> bracket and append port
145-
// - else if the head (before the last colon) parses as IPv6 -> it's
146-
// IPv6-with-port (leave unchanged)
147-
// - otherwise keep current behavior (leave as host:port)
148-
if (suffix_digits)
127+
// Contains colons (unbracketed IPv6) - need to bracket
128+
if (host.find(':') != std::string::npos)
149129
{
150-
in6_addr addr;
151-
// whole host might be an IPv6 literal (no port)
152-
if (inet_pton(AF_INET6, host.c_str(), &addr) == 1)
153-
{
154-
host = "[" + host + "]:" + std::to_string(port);
155-
return host;
156-
}
157-
158-
// check head (before last colon) for IPv6 literal with an explicit port
159-
std::string head = host.substr(0, last_colon);
160-
if (inet_pton(AF_INET6, head.c_str(), &addr) == 1)
161-
{
162-
// Treat as already host:port (leave unchanged)
163-
return host;
164-
}
165-
166-
// not IPv6; treat as host:port (leave unchanged)
167-
return host;
130+
return "[" + host + "]:" + std::to_string(port);
168131
}
169132

170-
// suffix not all digits -> treat as unbracketed IPv6 or hostname with
171-
// colons: bracket and append port
172-
host = "[" + host + "]:" + std::to_string(port);
173-
return host;
133+
// Regular hostname or IPv4
134+
return host + ":" + std::to_string(port);
174135
}
175136

176137
template <typename Handler>
@@ -208,10 +169,16 @@ class http_stream : public proxy_base
208169
{
209170
std::string const remote_host = format_host_for_connect(m_host, m_remote_endpoint.port());
210171
write_string("CONNECT " + remote_host + " HTTP/1.0\r\n", p);
172+
// Host header is required per RFC 9110 Section 7.2 and RFC 9112 Section 3.2
173+
// for HTTP/1.1 compliance, virtual host support, and proper proxy routing
174+
write_string("Host: " + remote_host + "\r\n", p);
211175
}
212176
else
213177
{
214178
write_string("CONNECT " + endpoint + " HTTP/1.0\r\n", p);
179+
// Host header is required per RFC 9110 Section 7.2 and RFC 9112 Section 3.2
180+
// for HTTP/1.1 compliance, virtual host support, and proper proxy routing
181+
write_string("Host: " + endpoint + "\r\n", p);
215182
}
216183
if (!m_user.empty())
217184
{

0 commit comments

Comments
 (0)