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