| // 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 |