blob: b3dfd8a9637df2fb90fc930d92bd8de63a84977e [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/base/proxy_string_util.h"
#include <string>
#include <string_view>
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "net/base/proxy_server.h"
#include "net/base/url_util.h"
#include "net/http/http_util.h"
#include "url/third_party/mozilla/url_parse.h"
namespace net {
namespace {
// Parses the proxy type from a PAC string, to a ProxyServer::Scheme.
// This mapping is case-insensitive. If no type could be matched
// returns SCHEME_INVALID.
ProxyServer::Scheme GetSchemeFromPacTypeInternal(std::string_view type) {
if (base::EqualsCaseInsensitiveASCII(type, "proxy"))
return ProxyServer::SCHEME_HTTP;
if (base::EqualsCaseInsensitiveASCII(type, "socks")) {
// Default to v4 for compatibility. This is because the SOCKS4 vs SOCKS5
// notation didn't originally exist, so if a client returns SOCKS they
// really meant SOCKS4.
return ProxyServer::SCHEME_SOCKS4;
}
if (base::EqualsCaseInsensitiveASCII(type, "socks4"))
return ProxyServer::SCHEME_SOCKS4;
if (base::EqualsCaseInsensitiveASCII(type, "socks5"))
return ProxyServer::SCHEME_SOCKS5;
if (base::EqualsCaseInsensitiveASCII(type, "direct"))
return ProxyServer::SCHEME_DIRECT;
if (base::EqualsCaseInsensitiveASCII(type, "https"))
return ProxyServer::SCHEME_HTTPS;
if (base::EqualsCaseInsensitiveASCII(type, "quic"))
return ProxyServer::SCHEME_QUIC;
return ProxyServer::SCHEME_INVALID;
}
ProxyServer FromSchemeHostAndPort(ProxyServer::Scheme scheme,
std::string_view host_and_port) {
// Trim leading/trailing space.
host_and_port = HttpUtil::TrimLWS(host_and_port);
if (scheme == ProxyServer::SCHEME_INVALID)
return ProxyServer();
if (scheme == ProxyServer::SCHEME_DIRECT) {
if (!host_and_port.empty())
return ProxyServer(); // Invalid -- DIRECT cannot have a host/port.
return ProxyServer::Direct();
}
url::Component username_component;
url::Component password_component;
url::Component hostname_component;
url::Component port_component;
url::ParseAuthority(host_and_port.data(),
url::Component(0, host_and_port.size()),
&username_component, &password_component,
&hostname_component, &port_component);
if (username_component.is_valid() || password_component.is_valid() ||
hostname_component.is_empty()) {
return ProxyServer();
}
std::string_view hostname =
host_and_port.substr(hostname_component.begin, hostname_component.len);
// Reject inputs like "foo:". /url parsing and canonicalization code generally
// allows it and treats it the same as a URL without a specified port, but
// Chrome has traditionally disallowed it in proxy specifications.
if (port_component.is_valid() && port_component.is_empty())
return ProxyServer();
std::string_view port =
port_component.is_nonempty()
? host_and_port.substr(port_component.begin, port_component.len)
: "";
return ProxyServer::FromSchemeHostAndPort(scheme, hostname, port);
}
std::string ConstructHostPortString(std::string_view hostname, uint16_t port) {
DCHECK(!hostname.empty());
DCHECK((hostname.front() == '[' && hostname.back() == ']') ||
hostname.find(":") == std::string_view::npos);
return base::StrCat({hostname, ":", base::NumberToString(port)});
}
} // namespace
ProxyChain PacResultElementToProxyChain(std::string_view pac_result_element) {
// Proxy chains are not supported in PAC strings, so this is just parsed
// as a single server.
return ProxyChain(PacResultElementToProxyServer(pac_result_element));
}
ProxyServer PacResultElementToProxyServer(std::string_view pac_result_element) {
// Trim the leading/trailing whitespace.
pac_result_element = HttpUtil::TrimLWS(pac_result_element);
// Input should match:
// "DIRECT" | ( <type> 1*(LWS) <host-and-port> )
// Start by finding the first space (if any).
size_t space = 0;
for (; space < pac_result_element.size(); space++) {
if (HttpUtil::IsLWS(pac_result_element[space])) {
break;
}
}
// Everything to the left of the space is the scheme.
ProxyServer::Scheme scheme =
GetSchemeFromPacTypeInternal(pac_result_element.substr(0, space));
// And everything to the right of the space is the
// <host>[":" <port>].
return FromSchemeHostAndPort(scheme, pac_result_element.substr(space));
}
std::string ProxyServerToPacResultElement(const ProxyServer& proxy_server) {
switch (proxy_server.scheme()) {
case ProxyServer::SCHEME_DIRECT:
return "DIRECT";
case ProxyServer::SCHEME_HTTP:
return std::string("PROXY ") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_SOCKS4:
// For compatibility send SOCKS instead of SOCKS4.
return std::string("SOCKS ") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_SOCKS5:
return std::string("SOCKS5 ") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_HTTPS:
return std::string("HTTPS ") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_QUIC:
return std::string("QUIC ") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
default:
// Got called with an invalid scheme.
NOTREACHED();
return std::string();
}
}
ProxyChain ProxyUriToProxyChain(std::string_view uri,
ProxyServer::Scheme default_scheme) {
return ProxyChain(ProxyUriToProxyServer(uri, default_scheme));
}
ProxyServer ProxyUriToProxyServer(std::string_view uri,
ProxyServer::Scheme default_scheme) {
// We will default to |default_scheme| if no scheme specifier was given.
ProxyServer::Scheme scheme = default_scheme;
// Trim the leading/trailing whitespace.
uri = HttpUtil::TrimLWS(uri);
// Check for [<scheme> "://"]
size_t colon = uri.find(':');
if (colon != std::string_view::npos && uri.size() - colon >= 3 &&
uri[colon + 1] == '/' && uri[colon + 2] == '/') {
scheme = GetSchemeFromUriScheme(uri.substr(0, colon));
uri = uri.substr(colon + 3); // Skip past the "://"
}
// Now parse the <host>[":"<port>].
return FromSchemeHostAndPort(scheme, uri);
}
std::string ProxyServerToProxyUri(const ProxyServer& proxy_server) {
switch (proxy_server.scheme()) {
case ProxyServer::SCHEME_DIRECT:
return "direct://";
case ProxyServer::SCHEME_HTTP:
// Leave off "http://" since it is our default scheme.
return ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_SOCKS4:
return std::string("socks4://") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_SOCKS5:
return std::string("socks5://") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_HTTPS:
return std::string("https://") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
case ProxyServer::SCHEME_QUIC:
return std::string("quic://") +
ConstructHostPortString(proxy_server.GetHost(),
proxy_server.GetPort());
default:
// Got called with an invalid scheme.
NOTREACHED();
return std::string();
}
}
ProxyServer::Scheme GetSchemeFromUriScheme(std::string_view scheme) {
if (base::EqualsCaseInsensitiveASCII(scheme, "http"))
return ProxyServer::SCHEME_HTTP;
if (base::EqualsCaseInsensitiveASCII(scheme, "socks4"))
return ProxyServer::SCHEME_SOCKS4;
if (base::EqualsCaseInsensitiveASCII(scheme, "socks"))
return ProxyServer::SCHEME_SOCKS5;
if (base::EqualsCaseInsensitiveASCII(scheme, "socks5"))
return ProxyServer::SCHEME_SOCKS5;
if (base::EqualsCaseInsensitiveASCII(scheme, "direct"))
return ProxyServer::SCHEME_DIRECT;
if (base::EqualsCaseInsensitiveASCII(scheme, "https"))
return ProxyServer::SCHEME_HTTPS;
if (base::EqualsCaseInsensitiveASCII(scheme, "quic"))
return ProxyServer::SCHEME_QUIC;
return ProxyServer::SCHEME_INVALID;
}
} // namespace net