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 
ChromeBrowserProxyResolver(LibCrosProxy * libcros_proxy)47 ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
48     LibCrosProxy* libcros_proxy)
49     : libcros_proxy_(libcros_proxy), timeout_(kTimeout) {}
50 
Init()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 
~ChromeBrowserProxyResolver()61 ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
62   // Kill outstanding timers.
63   for (const auto& it : callbacks_) {
64     MessageLoop::current()->CancelTask(it.second->timeout_id);
65   }
66 }
67 
GetProxiesForUrl(const string & url,const ProxiesResolvedFn & callback)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 
CancelProxyRequest(ProxyRequestId request)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 
ProcessUrlResponse(const string & source_url,const deque<string> & proxies)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 
OnSignalConnected(const string & interface_name,const string & signal_name,bool successful)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 
OnProxyResolvedSignal(const string & source_url,const string & proxy_info,const string & error_message)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 
HandleTimeout(string source_url,ProxyRequestData * request)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 
ParseProxyString(const string & input)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