Home | History | Annotate | Download | only in update_engine
      1 //
      2 // Copyright (C) 2011 The Android Open Source Project
      3 //
      4 // Licensed under the Apache License, Version 2.0 (the "License");
      5 // you may not use this file except in compliance with the License.
      6 // You may obtain a copy of the License at
      7 //
      8 //      http://www.apache.org/licenses/LICENSE-2.0
      9 //
     10 // Unless required by applicable law or agreed to in writing, software
     11 // distributed under the License is distributed on an "AS IS" BASIS,
     12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 // See the License for the specific language governing permissions and
     14 // limitations under the License.
     15 //
     16 
     17 #include "update_engine/chrome_browser_proxy_resolver.h"
     18 
     19 #include <deque>
     20 #include <string>
     21 
     22 #include <base/bind.h>
     23 #include <base/strings/string_tokenizer.h>
     24 #include <base/strings/string_util.h>
     25 
     26 #include "update_engine/common/utils.h"
     27 
     28 namespace chromeos_update_engine {
     29 
     30 using base::StringTokenizer;
     31 using base::TimeDelta;
     32 using brillo::MessageLoop;
     33 using std::deque;
     34 using std::string;
     35 
     36 const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
     37 const char kLibCrosProxyResolveName[] = "ProxyResolved";
     38 const char kLibCrosProxyResolveSignalInterface[] =
     39     "org.chromium.UpdateEngineLibcrosProxyResolvedInterface";
     40 
     41 namespace {
     42 
     43 const int kTimeout = 5;  // seconds
     44 
     45 }  // namespace
     46 
     47 ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
     48     LibCrosProxy* libcros_proxy)
     49     : libcros_proxy_(libcros_proxy), timeout_(kTimeout) {}
     50 
     51 bool ChromeBrowserProxyResolver::Init() {
     52   libcros_proxy_->ue_proxy_resolved_interface()
     53       ->RegisterProxyResolvedSignalHandler(
     54           base::Bind(&ChromeBrowserProxyResolver::OnProxyResolvedSignal,
     55                      base::Unretained(this)),
     56           base::Bind(&ChromeBrowserProxyResolver::OnSignalConnected,
     57                      base::Unretained(this)));
     58   return true;
     59 }
     60 
     61 ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
     62   // Kill outstanding timers.
     63   for (const auto& it : callbacks_) {
     64     MessageLoop::current()->CancelTask(it.second->timeout_id);
     65   }
     66 }
     67 
     68 ProxyRequestId ChromeBrowserProxyResolver::GetProxiesForUrl(
     69     const string& url, const ProxiesResolvedFn& callback) {
     70   int timeout = timeout_;
     71   brillo::ErrorPtr error;
     72   if (!libcros_proxy_->service_interface_proxy()->ResolveNetworkProxy(
     73           url.c_str(),
     74           kLibCrosProxyResolveSignalInterface,
     75           kLibCrosProxyResolveName,
     76           &error)) {
     77     LOG(WARNING) << "Can't resolve the proxy. Continuing with no proxy.";
     78     timeout = 0;
     79   }
     80 
     81   std::unique_ptr<ProxyRequestData> request(new ProxyRequestData());
     82   request->callback = callback;
     83   ProxyRequestId timeout_id = MessageLoop::current()->PostDelayedTask(
     84       FROM_HERE,
     85       base::Bind(&ChromeBrowserProxyResolver::HandleTimeout,
     86                  base::Unretained(this),
     87                  url,
     88                  request.get()),
     89       TimeDelta::FromSeconds(timeout));
     90   request->timeout_id = timeout_id;
     91   callbacks_.emplace(url, std::move(request));
     92 
     93   // We re-use the timeout_id from the MessageLoop as the request id.
     94   return timeout_id;
     95 }
     96 
     97 bool ChromeBrowserProxyResolver::CancelProxyRequest(ProxyRequestId request) {
     98   // Finding the timeout_id in the callbacks_ structure requires a linear search
     99   // but we expect this operation to not be so frequent and to have just a few
    100   // proxy requests, so this should be fast enough.
    101   for (auto it = callbacks_.begin(); it != callbacks_.end(); ++it) {
    102     if (it->second->timeout_id == request) {
    103       MessageLoop::current()->CancelTask(request);
    104       callbacks_.erase(it);
    105       return true;
    106     }
    107   }
    108   return false;
    109 }
    110 
    111 void ChromeBrowserProxyResolver::ProcessUrlResponse(
    112     const string& source_url, const deque<string>& proxies) {
    113   // Call all the occurrences of the |source_url| and erase them.
    114   auto lower_end = callbacks_.lower_bound(source_url);
    115   auto upper_end = callbacks_.upper_bound(source_url);
    116   for (auto it = lower_end; it != upper_end; ++it) {
    117     ProxyRequestData* request = it->second.get();
    118     MessageLoop::current()->CancelTask(request->timeout_id);
    119     request->callback.Run(proxies);
    120   }
    121   callbacks_.erase(lower_end, upper_end);
    122 }
    123 
    124 void ChromeBrowserProxyResolver::OnSignalConnected(const string& interface_name,
    125                                                    const string& signal_name,
    126                                                    bool successful) {
    127   if (!successful) {
    128     LOG(ERROR) << "Couldn't connect to the signal " << interface_name << "."
    129                << signal_name;
    130   }
    131 }
    132 
    133 void ChromeBrowserProxyResolver::OnProxyResolvedSignal(
    134     const string& source_url,
    135     const string& proxy_info,
    136     const string& error_message) {
    137   if (!error_message.empty()) {
    138     LOG(WARNING) << "ProxyResolved error: " << error_message;
    139   }
    140   ProcessUrlResponse(source_url, ParseProxyString(proxy_info));
    141 }
    142 
    143 void ChromeBrowserProxyResolver::HandleTimeout(string source_url,
    144                                                ProxyRequestData* request) {
    145   LOG(INFO) << "Timeout handler called. Seems Chrome isn't responding.";
    146   // Mark the timer_id that produced this callback as invalid to prevent
    147   // canceling the timeout callback that already fired.
    148   request->timeout_id = MessageLoop::kTaskIdNull;
    149 
    150   deque<string> proxies = {kNoProxy};
    151   ProcessUrlResponse(source_url, proxies);
    152 }
    153 
    154 deque<string> ChromeBrowserProxyResolver::ParseProxyString(
    155     const string& input) {
    156   deque<string> ret;
    157   // Some of this code taken from
    158   // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
    159   // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
    160   StringTokenizer entry_tok(input, ";");
    161   while (entry_tok.GetNext()) {
    162     string token = entry_tok.token();
    163     base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
    164 
    165     // Start by finding the first space (if any).
    166     string::iterator space;
    167     for (space = token.begin(); space != token.end(); ++space) {
    168       if (base::IsAsciiWhitespace(*space)) {
    169         break;
    170       }
    171     }
    172 
    173     string scheme = base::ToLowerASCII(string(token.begin(), space));
    174     // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
    175     if (scheme == "socks")
    176       scheme += "4";
    177     else if (scheme == "proxy")
    178       scheme = "http";
    179     else if (scheme != "https" &&
    180              scheme != "socks4" &&
    181              scheme != "socks5" &&
    182              scheme != "direct")
    183       continue;  // Invalid proxy scheme
    184 
    185     string host_and_port = string(space, token.end());
    186     base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
    187     if (scheme != "direct" && host_and_port.empty())
    188       continue;  // Must supply host/port when non-direct proxy used.
    189     ret.push_back(scheme + "://" + host_and_port);
    190   }
    191   if (ret.empty() || *ret.rbegin() != kNoProxy)
    192     ret.push_back(kNoProxy);
    193   return ret;
    194 }
    195 
    196 }  // namespace chromeos_update_engine
    197