xref: /aosp_15_r20/external/cronet/net/base/proxy_string_util.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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, "https"))
39     return ProxyServer::SCHEME_HTTPS;
40 
41   return ProxyServer::SCHEME_INVALID;
42 }
43 
ConstructHostPortString(std::string_view hostname,uint16_t port)44 std::string ConstructHostPortString(std::string_view hostname, uint16_t port) {
45   DCHECK(!hostname.empty());
46   DCHECK((hostname.front() == '[' && hostname.back() == ']') ||
47          hostname.find(":") == std::string_view::npos);
48 
49   return base::StrCat({hostname, ":", base::NumberToString(port)});
50 }
51 
52 std::tuple<std::string_view, std::string_view>
PacResultElementToSchemeAndHostPort(std::string_view pac_result_element)53 PacResultElementToSchemeAndHostPort(std::string_view pac_result_element) {
54   // Trim the leading/trailing whitespace.
55   pac_result_element = HttpUtil::TrimLWS(pac_result_element);
56 
57   // Input should match:
58   // ( <type> 1*(LWS) <host-and-port> )
59 
60   // Start by finding the first space (if any).
61   size_t space = 0;
62   for (; space < pac_result_element.size(); space++) {
63     if (HttpUtil::IsLWS(pac_result_element[space])) {
64       break;
65     }
66   }
67   // Everything to the left of the space is the scheme.
68   std::string_view scheme = pac_result_element.substr(0, space);
69 
70   // And everything to the right of the space is the
71   // <host>[":" <port>].
72   std::string_view host_and_port = pac_result_element.substr(space);
73   return std::make_tuple(std::move(scheme), std::move(host_and_port));
74 }
75 
76 }  // namespace
77 
PacResultElementToProxyChain(std::string_view pac_result_element)78 ProxyChain PacResultElementToProxyChain(std::string_view pac_result_element) {
79   // Proxy chains are not supported in PAC strings, so this is just parsed
80   // as a single server.
81   auto [type, host_and_port] =
82       PacResultElementToSchemeAndHostPort(pac_result_element);
83   if (base::EqualsCaseInsensitiveASCII(type, "direct") &&
84       host_and_port.empty()) {
85     return ProxyChain::Direct();
86   }
87   return ProxyChain(PacResultElementToProxyServer(pac_result_element));
88 }
89 
PacResultElementToProxyServer(std::string_view pac_result_element)90 ProxyServer PacResultElementToProxyServer(std::string_view pac_result_element) {
91   auto [type, host_and_port] =
92       PacResultElementToSchemeAndHostPort(pac_result_element);
93   ProxyServer::Scheme scheme = GetSchemeFromPacTypeInternal(type);
94   return ProxySchemeHostAndPortToProxyServer(scheme, host_and_port);
95 }
96 
ProxyServerToPacResultElement(const ProxyServer & proxy_server)97 std::string ProxyServerToPacResultElement(const ProxyServer& proxy_server) {
98   switch (proxy_server.scheme()) {
99     case ProxyServer::SCHEME_HTTP:
100       return std::string("PROXY ") +
101              ConstructHostPortString(proxy_server.GetHost(),
102                                      proxy_server.GetPort());
103     case ProxyServer::SCHEME_SOCKS4:
104       // For compatibility send SOCKS instead of SOCKS4.
105       return std::string("SOCKS ") +
106              ConstructHostPortString(proxy_server.GetHost(),
107                                      proxy_server.GetPort());
108     case ProxyServer::SCHEME_SOCKS5:
109       return std::string("SOCKS5 ") +
110              ConstructHostPortString(proxy_server.GetHost(),
111                                      proxy_server.GetPort());
112     case ProxyServer::SCHEME_HTTPS:
113       return std::string("HTTPS ") +
114              ConstructHostPortString(proxy_server.GetHost(),
115                                      proxy_server.GetPort());
116     case ProxyServer::SCHEME_QUIC:
117       return std::string("QUIC ") +
118              ConstructHostPortString(proxy_server.GetHost(),
119                                      proxy_server.GetPort());
120     default:
121       // Got called with an invalid scheme.
122       NOTREACHED();
123       return std::string();
124   }
125 }
126 
ProxyUriToProxyChain(std::string_view uri,ProxyServer::Scheme default_scheme)127 ProxyChain ProxyUriToProxyChain(std::string_view uri,
128                                 ProxyServer::Scheme default_scheme) {
129   // If uri is direct, return direct proxy chain.
130   uri = HttpUtil::TrimLWS(uri);
131   size_t colon = uri.find("://");
132   if (colon != std::string_view::npos &&
133       base::EqualsCaseInsensitiveASCII(uri.substr(0, colon), "direct")) {
134     if (!uri.substr(colon + 3).empty()) {
135       return ProxyChain();  // Invalid -- Direct chain cannot have a host/port.
136     }
137     return ProxyChain::Direct();
138   }
139   return ProxyChain(ProxyUriToProxyServer(uri, default_scheme));
140 }
141 
ProxyUriToProxyServer(std::string_view uri,ProxyServer::Scheme default_scheme)142 ProxyServer ProxyUriToProxyServer(std::string_view uri,
143                                   ProxyServer::Scheme default_scheme) {
144   // We will default to |default_scheme| if no scheme specifier was given.
145   ProxyServer::Scheme scheme = default_scheme;
146 
147   // Trim the leading/trailing whitespace.
148   uri = HttpUtil::TrimLWS(uri);
149 
150   // Check for [<scheme> "://"]
151   size_t colon = uri.find(':');
152   if (colon != std::string_view::npos && uri.size() - colon >= 3 &&
153       uri[colon + 1] == '/' && uri[colon + 2] == '/') {
154     scheme = GetSchemeFromUriScheme(uri.substr(0, colon));
155     uri = uri.substr(colon + 3);  // Skip past the "://"
156   }
157 
158   // Now parse the <host>[":"<port>].
159   return ProxySchemeHostAndPortToProxyServer(scheme, uri);
160 }
161 
ProxyServerToProxyUri(const ProxyServer & proxy_server)162 std::string ProxyServerToProxyUri(const ProxyServer& proxy_server) {
163   switch (proxy_server.scheme()) {
164     case ProxyServer::SCHEME_HTTP:
165       // Leave off "http://" since it is our default scheme.
166       return ConstructHostPortString(proxy_server.GetHost(),
167                                      proxy_server.GetPort());
168     case ProxyServer::SCHEME_SOCKS4:
169       return std::string("socks4://") +
170              ConstructHostPortString(proxy_server.GetHost(),
171                                      proxy_server.GetPort());
172     case ProxyServer::SCHEME_SOCKS5:
173       return std::string("socks5://") +
174              ConstructHostPortString(proxy_server.GetHost(),
175                                      proxy_server.GetPort());
176     case ProxyServer::SCHEME_HTTPS:
177       return std::string("https://") +
178              ConstructHostPortString(proxy_server.GetHost(),
179                                      proxy_server.GetPort());
180     case ProxyServer::SCHEME_QUIC:
181       return std::string("quic://") +
182              ConstructHostPortString(proxy_server.GetHost(),
183                                      proxy_server.GetPort());
184     default:
185       // Got called with an invalid scheme.
186       NOTREACHED();
187       return std::string();
188   }
189 }
190 
ProxySchemeHostAndPortToProxyServer(ProxyServer::Scheme scheme,std::string_view host_and_port)191 ProxyServer ProxySchemeHostAndPortToProxyServer(
192     ProxyServer::Scheme scheme,
193     std::string_view host_and_port) {
194   // Trim leading/trailing space.
195   host_and_port = HttpUtil::TrimLWS(host_and_port);
196 
197   if (scheme == ProxyServer::SCHEME_INVALID) {
198     return ProxyServer();
199   }
200 
201   url::Component username_component;
202   url::Component password_component;
203   url::Component hostname_component;
204   url::Component port_component;
205   url::ParseAuthority(host_and_port.data(),
206                       url::Component(0, host_and_port.size()),
207                       &username_component, &password_component,
208                       &hostname_component, &port_component);
209   if (username_component.is_valid() || password_component.is_valid() ||
210       hostname_component.is_empty()) {
211     return ProxyServer();
212   }
213 
214   std::string_view hostname =
215       host_and_port.substr(hostname_component.begin, hostname_component.len);
216 
217   // Reject inputs like "foo:". /url parsing and canonicalization code generally
218   // allows it and treats it the same as a URL without a specified port, but
219   // Chrome has traditionally disallowed it in proxy specifications.
220   if (port_component.is_valid() && port_component.is_empty()) {
221     return ProxyServer();
222   }
223   std::string_view port =
224       port_component.is_nonempty()
225           ? host_and_port.substr(port_component.begin, port_component.len)
226           : "";
227 
228   return ProxyServer::FromSchemeHostAndPort(scheme, hostname, port);
229 }
230 
GetSchemeFromUriScheme(std::string_view scheme)231 ProxyServer::Scheme GetSchemeFromUriScheme(std::string_view scheme) {
232   if (base::EqualsCaseInsensitiveASCII(scheme, "http"))
233     return ProxyServer::SCHEME_HTTP;
234   if (base::EqualsCaseInsensitiveASCII(scheme, "socks4"))
235     return ProxyServer::SCHEME_SOCKS4;
236   if (base::EqualsCaseInsensitiveASCII(scheme, "socks"))
237     return ProxyServer::SCHEME_SOCKS5;
238   if (base::EqualsCaseInsensitiveASCII(scheme, "socks5"))
239     return ProxyServer::SCHEME_SOCKS5;
240   if (base::EqualsCaseInsensitiveASCII(scheme, "https"))
241     return ProxyServer::SCHEME_HTTPS;
242   return ProxyServer::SCHEME_INVALID;
243 }
244 
245 }  // namespace net
246