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
25namespace {
26
27bool 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
41namespace data_reduction_proxy {
42
43bool 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
130void 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
157bool 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
169void 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
203void 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