blob: 9bb15f776dcdd938ba0bae8851ae8f6603e04e2d [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/network_service_proxy_delegate.h"
#include "net/base/url_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_util.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "services/network/url_loader.h"
#include "url/url_constants.h"
namespace network {
namespace {
// The maximum size of the cache that contains the GURLs that should use
// alternate proxy list.
constexpr size_t kMaxCacheSize = 15;
// The maximum number of previous configs to keep.
constexpr size_t kMaxPreviousConfigs = 2;
void GetAlternativeProxy(const GURL& url,
const net::ProxyRetryInfoMap& proxy_retry_info,
net::ProxyInfo* result) {
net::ProxyServer resolved_proxy_server = result->proxy_server();
DCHECK(resolved_proxy_server.is_valid());
// Right now, HTTPS proxies are assumed to support quic. If this needs to
// change, add a setting in CustomProxyConfig to control this behavior.
if (!resolved_proxy_server.is_https())
return;
net::ProxyInfo alternative_proxy_info;
alternative_proxy_info.UseProxyServer(net::ProxyServer(
net::ProxyServer::SCHEME_QUIC, resolved_proxy_server.host_port_pair()));
alternative_proxy_info.DeprioritizeBadProxies(proxy_retry_info);
if (alternative_proxy_info.is_empty())
return;
result->SetAlternativeProxy(alternative_proxy_info.proxy_server());
}
bool ApplyProxyConfigToProxyInfo(const net::ProxyConfig::ProxyRules& rules,
const net::ProxyRetryInfoMap& proxy_retry_info,
const GURL& url,
net::ProxyInfo* proxy_info) {
DCHECK(proxy_info);
if (rules.empty())
return false;
rules.Apply(url, proxy_info);
proxy_info->DeprioritizeBadProxies(proxy_retry_info);
return !proxy_info->is_empty() && !proxy_info->proxy_server().is_direct();
}
// Checks if |target_proxy| is in |proxy_list|.
bool CheckProxyList(const net::ProxyList& proxy_list,
const net::ProxyServer& target_proxy) {
for (const auto& proxy : proxy_list.GetAll()) {
if (!proxy.is_direct() &&
proxy.host_port_pair().Equals(target_proxy.host_port_pair())) {
return true;
}
}
return false;
}
// Returns true if there is a possibility that |proxy_rules->Apply()| can
// choose |target_proxy|. This does not consider the bypass rules; it only
// scans the possible set of proxy server.
bool RulesContainsProxy(const net::ProxyConfig::ProxyRules& proxy_rules,
const net::ProxyServer& target_proxy) {
switch (proxy_rules.type) {
case net::ProxyConfig::ProxyRules::Type::EMPTY:
return false;
case net::ProxyConfig::ProxyRules::Type::PROXY_LIST:
return CheckProxyList(proxy_rules.single_proxies, target_proxy);
case net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME:
return CheckProxyList(proxy_rules.proxies_for_http, target_proxy) ||
CheckProxyList(proxy_rules.proxies_for_https, target_proxy);
}
NOTREACHED();
return false;
}
bool IsValidCustomProxyConfig(const mojom::CustomProxyConfig& config) {
switch (config.rules.type) {
case net::ProxyConfig::ProxyRules::Type::EMPTY:
return true;
case net::ProxyConfig::ProxyRules::Type::PROXY_LIST:
return !config.rules.single_proxies.IsEmpty();
case net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME:
return !config.rules.proxies_for_http.IsEmpty() ||
!config.rules.proxies_for_https.IsEmpty();
}
NOTREACHED();
return false;
}
// Merges headers from |in| to |out|. If the header already exists in |out| they
// are combined.
void MergeRequestHeaders(net::HttpRequestHeaders* out,
const net::HttpRequestHeaders& in) {
for (net::HttpRequestHeaders::Iterator it(in); it.GetNext();) {
std::string old_value;
if (out->GetHeader(it.name(), &old_value)) {
out->SetHeader(it.name(), old_value + ", " + it.value());
} else {
out->SetHeader(it.name(), it.value());
}
}
}
} // namespace
NetworkServiceProxyDelegate::NetworkServiceProxyDelegate(
mojom::CustomProxyConfigPtr initial_config,
mojom::CustomProxyConfigClientRequest config_client_request)
: proxy_config_(std::move(initial_config)),
binding_(this, std::move(config_client_request)),
should_use_alternate_proxy_list_cache_(kMaxCacheSize) {
// Make sure there is always a valid proxy config so we don't need to null
// check it.
if (!proxy_config_)
proxy_config_ = mojom::CustomProxyConfig::New();
}
void NetworkServiceProxyDelegate::OnBeforeStartTransaction(
net::URLRequest* request,
net::HttpRequestHeaders* headers) {
if (!MayProxyURL(request->url()))
return;
// For other schemes, the headers can be added to the CONNECT request when
// establishing the secure tunnel instead, see OnBeforeHttp1TunnelRequest().
const bool scheme_is_http = request->url().SchemeIs(url::kHttpScheme);
if (scheme_is_http)
MergeRequestHeaders(headers, proxy_config_->pre_cache_headers);
auto* url_loader = URLLoader::ForRequest(*request);
if (url_loader) {
if (url_loader->custom_proxy_use_alternate_proxy_list()) {
should_use_alternate_proxy_list_cache_.Put(request->url().spec(), true);
}
if (scheme_is_http) {
MergeRequestHeaders(headers,
url_loader->custom_proxy_pre_cache_headers());
}
}
}
void NetworkServiceProxyDelegate::OnBeforeSendHeaders(
net::URLRequest* request,
const net::ProxyInfo& proxy_info,
net::HttpRequestHeaders* headers) {
// For other schemes, the headers can be added to the CONNECT request when
// establishing the secure tunnel instead, see OnBeforeHttp1TunnelRequest().
if (!request->url().SchemeIs(url::kHttpScheme))
return;
auto* url_loader = URLLoader::ForRequest(*request);
if (IsInProxyConfig(proxy_info.proxy_server())) {
MergeRequestHeaders(headers, proxy_config_->post_cache_headers);
if (url_loader) {
MergeRequestHeaders(headers,
url_loader->custom_proxy_post_cache_headers());
}
} else if (MayHaveProxiedURL(request->url())) {
for (const auto& kv : proxy_config_->pre_cache_headers.GetHeaderVector()) {
headers->RemoveHeader(kv.key);
}
if (url_loader) {
for (const auto& kv :
url_loader->custom_proxy_pre_cache_headers().GetHeaderVector()) {
headers->RemoveHeader(kv.key);
}
}
}
}
NetworkServiceProxyDelegate::~NetworkServiceProxyDelegate() {}
void NetworkServiceProxyDelegate::OnResolveProxy(
const GURL& url,
const std::string& method,
const net::ProxyRetryInfoMap& proxy_retry_info,
net::ProxyInfo* result) {
if (!EligibleForProxy(*result, method))
return;
net::ProxyInfo proxy_info;
if (ApplyProxyConfigToProxyInfo(GetProxyRulesForURL(url), proxy_retry_info,
url, &proxy_info)) {
DCHECK(!proxy_info.is_empty() && !proxy_info.is_direct());
result->OverrideProxyList(proxy_info.proxy_list());
GetAlternativeProxy(url, proxy_retry_info, result);
}
}
void NetworkServiceProxyDelegate::OnFallback(const net::ProxyServer& bad_proxy,
int net_error) {}
void NetworkServiceProxyDelegate::OnBeforeHttp1TunnelRequest(
const net::ProxyServer& proxy_server,
net::HttpRequestHeaders* extra_headers) {
if (IsInProxyConfig(proxy_server))
MergeRequestHeaders(extra_headers, proxy_config_->connect_tunnel_headers);
}
net::Error NetworkServiceProxyDelegate::OnHttp1TunnelHeadersReceived(
const net::ProxyServer& proxy_server,
const net::HttpResponseHeaders& response_headers) {
return net::OK;
}
void NetworkServiceProxyDelegate::OnCustomProxyConfigUpdated(
mojom::CustomProxyConfigPtr proxy_config) {
DCHECK(IsValidCustomProxyConfig(*proxy_config));
if (proxy_config_) {
previous_proxy_configs_.push_front(std::move(proxy_config_));
if (previous_proxy_configs_.size() > kMaxPreviousConfigs)
previous_proxy_configs_.pop_back();
}
proxy_config_ = std::move(proxy_config);
}
void NetworkServiceProxyDelegate::MarkProxiesAsBad(
base::TimeDelta bypass_duration,
const net::ProxyList& bad_proxies_list,
MarkProxiesAsBadCallback callback) {
std::vector<net::ProxyServer> bad_proxies = bad_proxies_list.GetAll();
// Synthesize a suitable |ProxyInfo| to add the proxies to the
// |ProxyRetryInfoMap| of the proxy service.
//
// TODO(eroman): Support this more directly on ProxyResolutionService.
net::ProxyList proxy_list;
for (const auto& bad_proxy : bad_proxies)
proxy_list.AddProxyServer(bad_proxy);
proxy_list.AddProxyServer(net::ProxyServer::Direct());
net::ProxyInfo proxy_info;
proxy_info.UseProxyList(proxy_list);
proxy_resolution_service_->MarkProxiesAsBadUntil(
proxy_info, bypass_duration, bad_proxies, net::NetLogWithSource());
std::move(callback).Run();
}
void NetworkServiceProxyDelegate::ClearBadProxiesCache() {
proxy_resolution_service_->ClearBadProxiesCache();
}
bool NetworkServiceProxyDelegate::IsInProxyConfig(
const net::ProxyServer& proxy_server) const {
if (!proxy_server.is_valid() || proxy_server.is_direct())
return false;
if (RulesContainsProxy(proxy_config_->rules, proxy_server))
return true;
for (const auto& config : previous_proxy_configs_) {
if (RulesContainsProxy(config->rules, proxy_server))
return true;
}
return false;
}
bool NetworkServiceProxyDelegate::MayProxyURL(const GURL& url) const {
return !proxy_config_->rules.empty();
}
bool NetworkServiceProxyDelegate::MayHaveProxiedURL(const GURL& url) const {
if (!proxy_config_->rules.empty())
return true;
for (const auto& config : previous_proxy_configs_) {
if (!config->rules.empty())
return true;
}
return false;
}
bool NetworkServiceProxyDelegate::EligibleForProxy(
const net::ProxyInfo& proxy_info,
const std::string& method) const {
bool has_existing_config =
!proxy_info.is_direct() || proxy_info.proxy_list().size() > 1u;
if (!proxy_config_->should_override_existing_config && has_existing_config)
return false;
if (!proxy_config_->allow_non_idempotent_methods &&
!net::HttpUtil::IsMethodIdempotent(method)) {
return false;
}
return true;
}
net::ProxyConfig::ProxyRules NetworkServiceProxyDelegate::GetProxyRulesForURL(
const GURL& url) const {
const auto iter = should_use_alternate_proxy_list_cache_.Peek(url.spec());
return iter != should_use_alternate_proxy_list_cache_.end()
? proxy_config_->alternate_rules
: proxy_config_->rules;
}
} // namespace network