1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 "components/data_reduction_proxy/browser/data_reduction_proxy_protocol.h" 6 7 #include "base/memory/ref_counted.h" 8 #include "base/time/time.h" 9 #include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h" 10 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h" 11 #include "components/data_reduction_proxy/browser/data_reduction_proxy_usage_stats.h" 12 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h" 13 #include "net/base/load_flags.h" 14 #include "net/http/http_response_headers.h" 15 #include "net/proxy/proxy_config.h" 16 #include "net/proxy/proxy_info.h" 17 #include "net/proxy/proxy_list.h" 18 #include "net/proxy/proxy_retry_info.h" 19 #include "net/proxy/proxy_server.h" 20 #include "net/proxy/proxy_service.h" 21 #include "net/url_request/url_request.h" 22 #include "net/url_request/url_request_context.h" 23 #include "url/gurl.h" 24 25 namespace { 26 27 bool SetProxyServerFromGURL(const GURL& gurl, 28 net::ProxyServer* proxy_server) { 29 DCHECK(proxy_server); 30 if (!gurl.SchemeIsHTTPOrHTTPS()) 31 return false; 32 *proxy_server = net::ProxyServer(gurl.SchemeIs("http") ? 33 net::ProxyServer::SCHEME_HTTP : 34 net::ProxyServer::SCHEME_HTTPS, 35 net::HostPortPair::FromURL(gurl)); 36 return true; 37 } 38 39 } // namespace 40 41 namespace data_reduction_proxy { 42 43 bool MaybeBypassProxyAndPrepareToRetry( 44 const DataReductionProxyParams* data_reduction_proxy_params, 45 net::URLRequest* request, 46 const net::HttpResponseHeaders* original_response_headers, 47 scoped_refptr<net::HttpResponseHeaders>* override_response_headers, 48 DataReductionProxyBypassType* proxy_bypass_type) { 49 if (!data_reduction_proxy_params) 50 return false; 51 DataReductionProxyTypeInfo data_reduction_proxy_type_info; 52 if (!data_reduction_proxy_params->WasDataReductionProxyUsed( 53 request, &data_reduction_proxy_type_info)) { 54 return false; 55 } 56 // TODO(bengr): Implement bypass for CONNECT tunnel. 57 if (data_reduction_proxy_type_info.is_ssl) 58 return false; 59 60 // Empty implies either that the request was served from cache or that 61 // request was served directly from the origin. 62 if (request->proxy_server().IsEmpty()) 63 return false; 64 65 if (data_reduction_proxy_type_info.proxy_servers.first.is_empty()) 66 return false; 67 68 // At this point, the response is expected to have the data reduction proxy 69 // via header, so detect and report cases where the via header is missing. 70 DataReductionProxyUsageStats::DetectAndRecordMissingViaHeaderResponseCode( 71 !data_reduction_proxy_type_info.proxy_servers.second.is_empty(), 72 original_response_headers); 73 74 DataReductionProxyTamperDetection::DetectAndReport( 75 original_response_headers, 76 data_reduction_proxy_type_info.proxy_servers.first.SchemeIsSecure()); 77 78 DataReductionProxyInfo data_reduction_proxy_info; 79 DataReductionProxyBypassType bypass_type = 80 GetDataReductionProxyBypassType(original_response_headers, 81 &data_reduction_proxy_info); 82 83 if (bypass_type == BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER && 84 DataReductionProxyParams:: 85 IsIncludedInRemoveMissingViaHeaderOtherBypassFieldTrial()) { 86 // Ignore MISSING_VIA_HEADER_OTHER proxy bypass events if the client is part 87 // of the field trial to remove these kinds of bypasses. 88 bypass_type = BYPASS_EVENT_TYPE_MAX; 89 } 90 91 if (proxy_bypass_type) 92 *proxy_bypass_type = bypass_type; 93 if (bypass_type == BYPASS_EVENT_TYPE_MAX) 94 return false; 95 96 DCHECK(request->context()); 97 DCHECK(request->context()->proxy_service()); 98 net::ProxyServer proxy_server; 99 SetProxyServerFromGURL( 100 data_reduction_proxy_type_info.proxy_servers.first, &proxy_server); 101 102 // Only record UMA if the proxy isn't already on the retry list. 103 const net::ProxyRetryInfoMap& proxy_retry_info = 104 request->context()->proxy_service()->proxy_retry_info(); 105 if (proxy_retry_info.find(proxy_server.ToURI()) == proxy_retry_info.end()) { 106 DataReductionProxyUsageStats::RecordDataReductionProxyBypassInfo( 107 !data_reduction_proxy_type_info.proxy_servers.second.is_empty(), 108 data_reduction_proxy_info.bypass_all, 109 proxy_server, 110 bypass_type); 111 } 112 113 if (data_reduction_proxy_info.mark_proxies_as_bad) { 114 MarkProxiesAsBadUntil(request, 115 data_reduction_proxy_info.bypass_duration, 116 data_reduction_proxy_info.bypass_all, 117 data_reduction_proxy_type_info.proxy_servers); 118 } 119 120 // Only retry idempotent methods. 121 if (!IsRequestIdempotent(request)) 122 return false; 123 124 OverrideResponseAsRedirect(request, 125 original_response_headers, 126 override_response_headers); 127 return true; 128 } 129 130 void OnResolveProxyHandler(const GURL& url, 131 int load_flags, 132 const net::ProxyConfig& data_reduction_proxy_config, 133 const net::ProxyRetryInfoMap& proxy_retry_info, 134 const DataReductionProxyParams* params, 135 net::ProxyInfo* result) { 136 if (data_reduction_proxy_config.is_valid() && 137 result->proxy_server().is_direct()) { 138 net::ProxyInfo data_reduction_proxy_info; 139 data_reduction_proxy_config.proxy_rules().Apply( 140 url, &data_reduction_proxy_info); 141 data_reduction_proxy_info.DeprioritizeBadProxies(proxy_retry_info); 142 if (!data_reduction_proxy_info.proxy_server().is_direct()) 143 result->UseProxyList(data_reduction_proxy_info.proxy_list()); 144 } 145 146 if ((load_flags & net::LOAD_BYPASS_DATA_REDUCTION_PROXY) && 147 DataReductionProxyParams::IsIncludedInCriticalPathBypassFieldTrial() && 148 !result->is_empty() && 149 !result->is_direct() && 150 params && 151 params->IsDataReductionProxy( 152 result->proxy_server().host_port_pair(), NULL)) { 153 result->UseDirect(); 154 } 155 } 156 157 bool IsRequestIdempotent(const net::URLRequest* request) { 158 DCHECK(request); 159 if (request->method() == "GET" || 160 request->method() == "OPTIONS" || 161 request->method() == "HEAD" || 162 request->method() == "PUT" || 163 request->method() == "DELETE" || 164 request->method() == "TRACE") 165 return true; 166 return false; 167 } 168 169 void OverrideResponseAsRedirect( 170 net::URLRequest* request, 171 const net::HttpResponseHeaders* original_response_headers, 172 scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { 173 DCHECK(request); 174 DCHECK(original_response_headers); 175 DCHECK(override_response_headers->get() == NULL); 176 177 request->SetLoadFlags(request->load_flags() | 178 net::LOAD_DISABLE_CACHE | 179 net::LOAD_BYPASS_PROXY); 180 *override_response_headers = new net::HttpResponseHeaders( 181 original_response_headers->raw_headers()); 182 (*override_response_headers)->ReplaceStatusLine("HTTP/1.1 302 Found"); 183 (*override_response_headers)->RemoveHeader("Location"); 184 (*override_response_headers)->AddHeader("Location: " + 185 request->url().spec()); 186 std::string http_origin; 187 const net::HttpRequestHeaders& request_headers = 188 request->extra_request_headers(); 189 if (request_headers.GetHeader("Origin", &http_origin)) { 190 // If this redirect is used in a cross-origin request, add CORS headers to 191 // make sure that the redirect gets through. Note that the destination URL 192 // is still subject to the usual CORS policy, i.e. the resource will only 193 // be available to web pages if the server serves the response with the 194 // required CORS response headers. 195 (*override_response_headers)->AddHeader( 196 "Access-Control-Allow-Origin: " + http_origin); 197 (*override_response_headers)->AddHeader( 198 "Access-Control-Allow-Credentials: true"); 199 } 200 // TODO(bengr): Should we pop_back the request->url_chain? 201 } 202 203 void MarkProxiesAsBadUntil( 204 net::URLRequest* request, 205 base::TimeDelta& bypass_duration, 206 bool bypass_all, 207 const std::pair<GURL, GURL>& data_reduction_proxies) { 208 DCHECK(!data_reduction_proxies.first.is_empty()); 209 // Synthesize a suitable |ProxyInfo| to add the proxies to the 210 // |ProxyRetryInfoMap| of the proxy service. 211 net::ProxyList proxy_list; 212 net::ProxyServer primary; 213 SetProxyServerFromGURL(data_reduction_proxies.first, &primary); 214 if (primary.is_valid()) 215 proxy_list.AddProxyServer(primary); 216 net::ProxyServer fallback; 217 if (bypass_all) { 218 if (!data_reduction_proxies.second.is_empty()) 219 SetProxyServerFromGURL(data_reduction_proxies.second, &fallback); 220 if (fallback.is_valid()) 221 proxy_list.AddProxyServer(fallback); 222 proxy_list.AddProxyServer(net::ProxyServer::Direct()); 223 } 224 net::ProxyInfo proxy_info; 225 proxy_info.UseProxyList(proxy_list); 226 DCHECK(request->context()); 227 net::ProxyService* proxy_service = request->context()->proxy_service(); 228 DCHECK(proxy_service); 229 230 proxy_service->MarkProxiesAsBadUntil(proxy_info, 231 bypass_duration, 232 fallback, 233 request->net_log()); 234 } 235 236 } // namespace data_reduction_proxy 237