1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/base/proxy_string_util.h"
6
7 #include <string>
8 #include <string_view>
9
10 #include "base/notreached.h"
11 #include "base/strings/strcat.h"
12 #include "base/strings/string_util.h"
13 #include "net/base/proxy_server.h"
14 #include "net/base/url_util.h"
15 #include "net/http/http_util.h"
16 #include "url/third_party/mozilla/url_parse.h"
17
18 namespace net {
19
20 namespace {
21
22 // Parses the proxy type from a PAC string, to a ProxyServer::Scheme.
23 // This mapping is case-insensitive. If no type could be matched
24 // returns SCHEME_INVALID.
GetSchemeFromPacTypeInternal(std::string_view type)25 ProxyServer::Scheme GetSchemeFromPacTypeInternal(std::string_view type) {
26 if (base::EqualsCaseInsensitiveASCII(type, "proxy"))
27 return ProxyServer::SCHEME_HTTP;
28 if (base::EqualsCaseInsensitiveASCII(type, "socks")) {
29 // Default to v4 for compatibility. This is because the SOCKS4 vs SOCKS5
30 // notation didn't originally exist, so if a client returns SOCKS they
31 // really meant SOCKS4.
32 return ProxyServer::SCHEME_SOCKS4;
33 }
34 if (base::EqualsCaseInsensitiveASCII(type, "socks4"))
35 return ProxyServer::SCHEME_SOCKS4;
36 if (base::EqualsCaseInsensitiveASCII(type, "socks5"))
37 return ProxyServer::SCHEME_SOCKS5;
38 if (base::EqualsCaseInsensitiveASCII(type, "direct"))
39 return ProxyServer::SCHEME_DIRECT;
40 if (base::EqualsCaseInsensitiveASCII(type, "https"))
41 return ProxyServer::SCHEME_HTTPS;
42 if (base::EqualsCaseInsensitiveASCII(type, "quic"))
43 return ProxyServer::SCHEME_QUIC;
44
45 return ProxyServer::SCHEME_INVALID;
46 }
47
FromSchemeHostAndPort(ProxyServer::Scheme scheme,std::string_view host_and_port)48 ProxyServer FromSchemeHostAndPort(ProxyServer::Scheme scheme,
49 std::string_view host_and_port) {
50 // Trim leading/trailing space.
51 host_and_port = HttpUtil::TrimLWS(host_and_port);
52
53 if (scheme == ProxyServer::SCHEME_INVALID)
54 return ProxyServer();
55
56 if (scheme == ProxyServer::SCHEME_DIRECT) {
57 if (!host_and_port.empty())
58 return ProxyServer(); // Invalid -- DIRECT cannot have a host/port.
59 return ProxyServer::Direct();
60 }
61
62 url::Component username_component;
63 url::Component password_component;
64 url::Component hostname_component;
65 url::Component port_component;
66 url::ParseAuthority(host_and_port.data(),
67 url::Component(0, host_and_port.size()),
68 &username_component, &password_component,
69 &hostname_component, &port_component);
70 if (username_component.is_valid() || password_component.is_valid() ||
71 hostname_component.is_empty()) {
72 return ProxyServer();
73 }
74
75 std::string_view hostname =
76 host_and_port.substr(hostname_component.begin, hostname_component.len);
77
78 // Reject inputs like "foo:". /url parsing and canonicalization code generally
79 // allows it and treats it the same as a URL without a specified port, but
80 // Chrome has traditionally disallowed it in proxy specifications.
81 if (port_component.is_valid() && port_component.is_empty())
82 return ProxyServer();
83 std::string_view port =
84 port_component.is_nonempty()
85 ? host_and_port.substr(port_component.begin, port_component.len)
86 : "";
87
88 return ProxyServer::FromSchemeHostAndPort(scheme, hostname, port);
89 }
90
ConstructHostPortString(std::string_view hostname,uint16_t port)91 std::string ConstructHostPortString(std::string_view hostname, uint16_t port) {
92 DCHECK(!hostname.empty());
93 DCHECK((hostname.front() == '[' && hostname.back() == ']') ||
94 hostname.find(":") == std::string_view::npos);
95
96 return base::StrCat({hostname, ":", base::NumberToString(port)});
97 }
98
99 } // namespace
100
PacResultElementToProxyChain(std::string_view pac_result_element)101 ProxyChain PacResultElementToProxyChain(std::string_view pac_result_element) {
102 // Proxy chains are not supported in PAC strings, so this is just parsed
103 // as a single server.
104 return ProxyChain(PacResultElementToProxyServer(pac_result_element));
105 }
106
PacResultElementToProxyServer(std::string_view pac_result_element)107 ProxyServer PacResultElementToProxyServer(std::string_view pac_result_element) {
108 // Trim the leading/trailing whitespace.
109 pac_result_element = HttpUtil::TrimLWS(pac_result_element);
110
111 // Input should match:
112 // "DIRECT" | ( <type> 1*(LWS) <host-and-port> )
113
114 // Start by finding the first space (if any).
115 size_t space = 0;
116 for (; space < pac_result_element.size(); space++) {
117 if (HttpUtil::IsLWS(pac_result_element[space])) {
118 break;
119 }
120 }
121
122 // Everything to the left of the space is the scheme.
123 ProxyServer::Scheme scheme =
124 GetSchemeFromPacTypeInternal(pac_result_element.substr(0, space));
125
126 // And everything to the right of the space is the
127 // <host>[":" <port>].
128 return FromSchemeHostAndPort(scheme, pac_result_element.substr(space));
129 }
130
ProxyServerToPacResultElement(const ProxyServer & proxy_server)131 std::string ProxyServerToPacResultElement(const ProxyServer& proxy_server) {
132 switch (proxy_server.scheme()) {
133 case ProxyServer::SCHEME_DIRECT:
134 return "DIRECT";
135 case ProxyServer::SCHEME_HTTP:
136 return std::string("PROXY ") +
137 ConstructHostPortString(proxy_server.GetHost(),
138 proxy_server.GetPort());
139 case ProxyServer::SCHEME_SOCKS4:
140 // For compatibility send SOCKS instead of SOCKS4.
141 return std::string("SOCKS ") +
142 ConstructHostPortString(proxy_server.GetHost(),
143 proxy_server.GetPort());
144 case ProxyServer::SCHEME_SOCKS5:
145 return std::string("SOCKS5 ") +
146 ConstructHostPortString(proxy_server.GetHost(),
147 proxy_server.GetPort());
148 case ProxyServer::SCHEME_HTTPS:
149 return std::string("HTTPS ") +
150 ConstructHostPortString(proxy_server.GetHost(),
151 proxy_server.GetPort());
152 case ProxyServer::SCHEME_QUIC:
153 return std::string("QUIC ") +
154 ConstructHostPortString(proxy_server.GetHost(),
155 proxy_server.GetPort());
156 default:
157 // Got called with an invalid scheme.
158 NOTREACHED();
159 return std::string();
160 }
161 }
162
ProxyUriToProxyChain(std::string_view uri,ProxyServer::Scheme default_scheme)163 ProxyChain ProxyUriToProxyChain(std::string_view uri,
164 ProxyServer::Scheme default_scheme) {
165 return ProxyChain(ProxyUriToProxyServer(uri, default_scheme));
166 }
167
ProxyUriToProxyServer(std::string_view uri,ProxyServer::Scheme default_scheme)168 ProxyServer ProxyUriToProxyServer(std::string_view uri,
169 ProxyServer::Scheme default_scheme) {
170 // We will default to |default_scheme| if no scheme specifier was given.
171 ProxyServer::Scheme scheme = default_scheme;
172
173 // Trim the leading/trailing whitespace.
174 uri = HttpUtil::TrimLWS(uri);
175
176 // Check for [<scheme> "://"]
177 size_t colon = uri.find(':');
178 if (colon != std::string_view::npos && uri.size() - colon >= 3 &&
179 uri[colon + 1] == '/' && uri[colon + 2] == '/') {
180 scheme = GetSchemeFromUriScheme(uri.substr(0, colon));
181 uri = uri.substr(colon + 3); // Skip past the "://"
182 }
183
184 // Now parse the <host>[":"<port>].
185 return FromSchemeHostAndPort(scheme, uri);
186 }
187
ProxyServerToProxyUri(const ProxyServer & proxy_server)188 std::string ProxyServerToProxyUri(const ProxyServer& proxy_server) {
189 switch (proxy_server.scheme()) {
190 case ProxyServer::SCHEME_DIRECT:
191 return "direct://";
192 case ProxyServer::SCHEME_HTTP:
193 // Leave off "http://" since it is our default scheme.
194 return ConstructHostPortString(proxy_server.GetHost(),
195 proxy_server.GetPort());
196 case ProxyServer::SCHEME_SOCKS4:
197 return std::string("socks4://") +
198 ConstructHostPortString(proxy_server.GetHost(),
199 proxy_server.GetPort());
200 case ProxyServer::SCHEME_SOCKS5:
201 return std::string("socks5://") +
202 ConstructHostPortString(proxy_server.GetHost(),
203 proxy_server.GetPort());
204 case ProxyServer::SCHEME_HTTPS:
205 return std::string("https://") +
206 ConstructHostPortString(proxy_server.GetHost(),
207 proxy_server.GetPort());
208 case ProxyServer::SCHEME_QUIC:
209 return std::string("quic://") +
210 ConstructHostPortString(proxy_server.GetHost(),
211 proxy_server.GetPort());
212 default:
213 // Got called with an invalid scheme.
214 NOTREACHED();
215 return std::string();
216 }
217 }
218
GetSchemeFromUriScheme(std::string_view scheme)219 ProxyServer::Scheme GetSchemeFromUriScheme(std::string_view scheme) {
220 if (base::EqualsCaseInsensitiveASCII(scheme, "http"))
221 return ProxyServer::SCHEME_HTTP;
222 if (base::EqualsCaseInsensitiveASCII(scheme, "socks4"))
223 return ProxyServer::SCHEME_SOCKS4;
224 if (base::EqualsCaseInsensitiveASCII(scheme, "socks"))
225 return ProxyServer::SCHEME_SOCKS5;
226 if (base::EqualsCaseInsensitiveASCII(scheme, "socks5"))
227 return ProxyServer::SCHEME_SOCKS5;
228 if (base::EqualsCaseInsensitiveASCII(scheme, "direct"))
229 return ProxyServer::SCHEME_DIRECT;
230 if (base::EqualsCaseInsensitiveASCII(scheme, "https"))
231 return ProxyServer::SCHEME_HTTPS;
232 if (base::EqualsCaseInsensitiveASCII(scheme, "quic"))
233 return ProxyServer::SCHEME_QUIC;
234 return ProxyServer::SCHEME_INVALID;
235 }
236
237 } // namespace net
238