1// Copyright 2013 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 "chrome/browser/net/spdyproxy/proxy_advisor.h"
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/prefs/pref_service.h"
11#include "base/stl_util.h"
12#include "chrome/common/chrome_switches.h"
13#include "chrome/common/pref_names.h"
14#include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h"
15#include "components/data_reduction_proxy/browser/data_reduction_proxy_settings.h"
16#include "content/public/browser/browser_thread.h"
17#include "net/base/load_flags.h"
18#include "net/base/request_priority.h"
19#include "net/http/http_status_code.h"
20#include "net/proxy/proxy_info.h"
21#include "net/proxy/proxy_service.h"
22#include "net/url_request/url_request_context.h"
23#include "net/url_request/url_request_context_getter.h"
24
25// TODO(marq): Remove this class because it is not being used.
26
27// Ensure data reduction features are available.
28#if !defined(OS_ANDROID) && !defined(OS_IOS)
29#error proxy_advisor should only be included in Android or iOS builds.
30#endif
31
32using content::BrowserThread;
33using data_reduction_proxy::DataReductionProxySettings;
34
35namespace {
36const char kOmniboxMotivation[] = "omnibox";
37const char kLowThresholdOmniboxMotivation[] = "low_threshold_omnibox";
38const char kStartupDNSMotivation[] = "startup_dns";
39const char kEarlyLoadMotivation[] = "early_load";
40const char kLearnedReferralMotivation[] = "learned_referral";
41const char kLowThresholdLearnedReferralMotivation[] =
42    "low_threshold_learned_referral";
43const char kSelfReferralMotivation[] = "self_referral";
44const char kPageScanMotivation[] = "page_scan";
45
46// Maps a ResolutionMotivation to a string for use in the advisory HEAD
47// request.
48const char* MotivationName(
49    chrome_browser_net::UrlInfo::ResolutionMotivation motivation,
50    bool is_preconnect) {
51  switch (motivation) {
52    case chrome_browser_net::UrlInfo::OMNIBOX_MOTIVATED:
53      return
54          is_preconnect ? kOmniboxMotivation : kLowThresholdOmniboxMotivation;
55    case chrome_browser_net::UrlInfo::STARTUP_LIST_MOTIVATED:
56      return kStartupDNSMotivation;
57    case chrome_browser_net::UrlInfo::EARLY_LOAD_MOTIVATED:
58      return kEarlyLoadMotivation;
59    case chrome_browser_net::UrlInfo::LEARNED_REFERAL_MOTIVATED:
60      return is_preconnect ?
61          kLearnedReferralMotivation : kLowThresholdLearnedReferralMotivation;
62    case chrome_browser_net::UrlInfo::SELF_REFERAL_MOTIVATED:
63      return kSelfReferralMotivation;
64    case chrome_browser_net::UrlInfo::PAGE_SCAN_MOTIVATED:
65      return kPageScanMotivation;
66    default:
67      // Other motivations should never be passed to here.
68      NOTREACHED();
69      break;
70  }
71  NOTREACHED();
72  return "";
73}
74
75}  // namespace
76
77ProxyAdvisor::ProxyAdvisor(PrefService* pref_service,
78                           net::URLRequestContextGetter* context_getter)
79    : context_getter_(context_getter) {
80  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
81
82  // pref_service may be null in mock test subclasses.
83  if (pref_service) {
84    proxy_pref_member_.Init(
85        data_reduction_proxy::prefs::kDataReductionProxyEnabled,
86        pref_service,
87        base::Bind(&ProxyAdvisor::UpdateProxyState, base::Unretained(this)));
88    proxy_pref_member_.MoveToThread(
89        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO));
90  }
91}
92
93ProxyAdvisor::~ProxyAdvisor() {
94  STLDeleteElements(&inflight_requests_);
95}
96
97void ProxyAdvisor::OnResponseStarted(net::URLRequest* request) {
98  const net::URLRequestStatus& status(request->status());
99  if (!status.is_success()) {
100    DLOG(WARNING) << "Proxy advisory failed "
101        << "status:" << status.status()
102        << " error:" << status.error();
103  } else if (request->GetResponseCode() != net::HTTP_OK) {
104    DLOG(WARNING) << "Proxy advisory status: " << request->GetResponseCode();
105  }
106  RequestComplete(request);
107}
108
109void ProxyAdvisor::OnReadCompleted(net::URLRequest* request, int bytes_read) {
110  // No-op for now, as we don't care yet.
111}
112
113void ProxyAdvisor::Advise(
114    const GURL& url,
115    chrome_browser_net::UrlInfo::ResolutionMotivation motivation,
116    bool is_preconnect) {
117  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
118
119  if (!WouldProxyURL(url))
120    return;
121
122  std::string motivation_name(MotivationName(motivation, is_preconnect));
123  std::string header_value = motivation_name + " " + url.spec();
124  net::URLRequestContext* context = context_getter_->GetURLRequestContext();
125  data_reduction_proxy::DataReductionProxyParams params(
126      data_reduction_proxy::DataReductionProxyParams::kAllowed |
127      data_reduction_proxy::DataReductionProxyParams::kFallbackAllowed |
128      data_reduction_proxy::DataReductionProxyParams::kPromoAllowed);
129  std::string endpoint =
130      params.origin().spec() + "preconnect";
131  scoped_ptr<net::URLRequest> request = context->CreateRequest(
132      GURL(endpoint), net::DEFAULT_PRIORITY, this, NULL);
133  request->set_method("HEAD");
134  request->SetExtraRequestHeaderByName(
135      "Proxy-Host-Advisory", header_value, false);
136  request->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
137                        net::LOAD_DO_NOT_SAVE_COOKIES |
138                        net::LOAD_BYPASS_PROXY |
139                        net::LOAD_DISABLE_CACHE);
140  net::URLRequest* raw_request = request.get();
141  inflight_requests_.insert(request.release());
142  raw_request->Start();
143}
144
145// TODO(marq): Move this into DataReductionProxySettings, and add support for
146// inspecting the current proxy configs -- if ResolveProxy on |url| can be
147// done synchronously, then this is no longer an approximation.
148bool ProxyAdvisor::WouldProxyURL(const GURL& url)  {
149  if (!proxy_pref_member_.GetValue())
150    return false;
151
152  if (url.SchemeIsSecure())
153    return false;
154
155  return true;
156}
157
158void ProxyAdvisor::RequestComplete(net::URLRequest* request) {
159  DCHECK_EQ(1u, inflight_requests_.count(request));
160  scoped_ptr<net::URLRequest> scoped_request_for_deletion(request);
161  inflight_requests_.erase(request);
162  // |scoped_request_for_deletion| will delete |request|
163}
164
165void ProxyAdvisor::UpdateProxyState() {
166  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
167  // Delete all inflight requests. Each request's destructor will call Cancel().
168  STLDeleteElements(&inflight_requests_);
169}
170