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
SetProxyServerFromGURL(const GURL & gurl,net::ProxyServer * proxy_server)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
MaybeBypassProxyAndPrepareToRetry(const DataReductionProxyParams * data_reduction_proxy_params,net::URLRequest * request,const net::HttpResponseHeaders * original_response_headers,scoped_refptr<net::HttpResponseHeaders> * override_response_headers,DataReductionProxyBypassType * proxy_bypass_type)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
OnResolveProxyHandler(const GURL & url,int load_flags,const net::ProxyConfig & data_reduction_proxy_config,const net::ProxyRetryInfoMap & proxy_retry_info,const DataReductionProxyParams * params,net::ProxyInfo * result)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
IsRequestIdempotent(const net::URLRequest * request)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
OverrideResponseAsRedirect(net::URLRequest * request,const net::HttpResponseHeaders * original_response_headers,scoped_refptr<net::HttpResponseHeaders> * override_response_headers)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
MarkProxiesAsBadUntil(net::URLRequest * request,base::TimeDelta & bypass_duration,bool bypass_all,const std::pair<GURL,GURL> & data_reduction_proxies)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