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