1 // Copyright 2014 The Chromium OS 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 <brillo/http/http_transport_curl.h>
6
7 #include <limits>
8
9 #include <base/bind.h>
10 #include <base/logging.h>
11 #include <base/message_loop/message_loop.h>
12 #include <brillo/http/http_connection_curl.h>
13 #include <brillo/http/http_request.h>
14 #include <brillo/strings/string_utils.h>
15
16 namespace {
17
18 const char kCACertificatePath[] =
19 #ifdef __ANDROID__
20 "/system/etc/security/cacerts_google";
21 #else
22 "/usr/share/brillo-ca-certificates";
23 #endif
24
25 } // namespace
26
27 namespace brillo {
28 namespace http {
29 namespace curl {
30
31 // This is a class that stores connection data on particular CURL socket
32 // and provides file descriptor watcher to monitor read and/or write operations
33 // on the socket's file descriptor.
34 class Transport::SocketPollData : public base::MessagePumpForIO::FdWatcher {
35 public:
SocketPollData(const std::shared_ptr<CurlInterface> & curl_interface,CURLM * curl_multi_handle,Transport * transport,curl_socket_t socket_fd)36 SocketPollData(const std::shared_ptr<CurlInterface>& curl_interface,
37 CURLM* curl_multi_handle,
38 Transport* transport,
39 curl_socket_t socket_fd)
40 : curl_interface_(curl_interface),
41 curl_multi_handle_(curl_multi_handle),
42 transport_(transport),
43 socket_fd_(socket_fd),
44 file_descriptor_watcher_(FROM_HERE) {}
45
46 // Returns the pointer for the socket-specific file descriptor watcher.
GetWatcher()47 base::MessagePumpForIO::FdWatchController* GetWatcher() {
48 return &file_descriptor_watcher_;
49 }
50
51 private:
52 // Overrides from base::MessagePumpForIO::Watcher.
OnFileCanReadWithoutBlocking(int fd)53 void OnFileCanReadWithoutBlocking(int fd) override {
54 OnSocketReady(fd, CURL_CSELECT_IN);
55 }
OnFileCanWriteWithoutBlocking(int fd)56 void OnFileCanWriteWithoutBlocking(int fd) override {
57 OnSocketReady(fd, CURL_CSELECT_OUT);
58 }
59
60 // Data on the socket is available to be read from or written to.
61 // Notify CURL of the action it needs to take on the socket file descriptor.
OnSocketReady(int fd,int action)62 void OnSocketReady(int fd, int action) {
63 CHECK_EQ(socket_fd_, fd) << "Unexpected socket file descriptor";
64 int still_running_count = 0;
65 CURLMcode code = curl_interface_->MultiSocketAction(
66 curl_multi_handle_, socket_fd_, action, &still_running_count);
67 CHECK_NE(CURLM_CALL_MULTI_PERFORM, code)
68 << "CURL should no longer return CURLM_CALL_MULTI_PERFORM here";
69
70 if (code == CURLM_OK)
71 transport_->ProcessAsyncCurlMessages();
72 }
73
74 // The CURL interface to use.
75 std::shared_ptr<CurlInterface> curl_interface_;
76 // CURL multi-handle associated with the transport.
77 CURLM* curl_multi_handle_;
78 // Transport object itself.
79 Transport* transport_;
80 // The socket file descriptor for the connection.
81 curl_socket_t socket_fd_;
82 // File descriptor watcher to notify us of asynchronous I/O on the FD.
83 base::MessagePumpForIO::FdWatchController file_descriptor_watcher_;
84
85 DISALLOW_COPY_AND_ASSIGN(SocketPollData);
86 };
87
88 // The request data associated with an asynchronous operation on a particular
89 // connection.
90 struct Transport::AsyncRequestData {
91 // Success/error callbacks to be invoked at the end of the request.
92 SuccessCallback success_callback;
93 ErrorCallback error_callback;
94 // We store a connection here to make sure the object is alive for
95 // as long as asynchronous operation is running.
96 std::shared_ptr<Connection> connection;
97 // The ID of this request.
98 RequestID request_id;
99 };
100
Transport(const std::shared_ptr<CurlInterface> & curl_interface)101 Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface)
102 : curl_interface_{curl_interface} {
103 VLOG(2) << "curl::Transport created";
104 }
105
Transport(const std::shared_ptr<CurlInterface> & curl_interface,const std::string & proxy)106 Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface,
107 const std::string& proxy)
108 : curl_interface_{curl_interface}, proxy_{proxy} {
109 VLOG(2) << "curl::Transport created with proxy " << proxy;
110 }
111
~Transport()112 Transport::~Transport() {
113 ShutDownAsyncCurl();
114 VLOG(2) << "curl::Transport destroyed";
115 }
116
CreateConnection(const std::string & url,const std::string & method,const HeaderList & headers,const std::string & user_agent,const std::string & referer,brillo::ErrorPtr * error)117 std::shared_ptr<http::Connection> Transport::CreateConnection(
118 const std::string& url,
119 const std::string& method,
120 const HeaderList& headers,
121 const std::string& user_agent,
122 const std::string& referer,
123 brillo::ErrorPtr* error) {
124 std::shared_ptr<http::Connection> connection;
125 CURL* curl_handle = curl_interface_->EasyInit();
126 if (!curl_handle) {
127 LOG(ERROR) << "Failed to initialize CURL";
128 brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain,
129 "curl_init_failed", "Failed to initialize CURL");
130 return connection;
131 }
132
133 VLOG(1) << "Sending a " << method << " request to " << url;
134 CURLcode code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_URL, url);
135
136 if (code == CURLE_OK) {
137 code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_CAPATH,
138 kCACertificatePath);
139 }
140 if (code == CURLE_OK) {
141 code =
142 curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1);
143 }
144 if (code == CURLE_OK) {
145 code =
146 curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
147 }
148 if (code == CURLE_OK && !user_agent.empty()) {
149 code = curl_interface_->EasySetOptStr(
150 curl_handle, CURLOPT_USERAGENT, user_agent);
151 }
152 if (code == CURLE_OK && !referer.empty()) {
153 code =
154 curl_interface_->EasySetOptStr(curl_handle, CURLOPT_REFERER, referer);
155 }
156 if (code == CURLE_OK && !proxy_.empty()) {
157 code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_PROXY, proxy_);
158 }
159 if (code == CURLE_OK) {
160 int64_t timeout_ms = connection_timeout_.InMillisecondsRoundedUp();
161
162 if (timeout_ms > 0 && timeout_ms <= std::numeric_limits<int>::max()) {
163 code = curl_interface_->EasySetOptInt(
164 curl_handle, CURLOPT_TIMEOUT_MS,
165 static_cast<int>(timeout_ms));
166 }
167 }
168 if (code == CURLE_OK && !ip_address_.empty()) {
169 code = curl_interface_->EasySetOptStr(
170 curl_handle, CURLOPT_INTERFACE, ip_address_.c_str());
171 }
172
173 // Setup HTTP request method and optional request body.
174 if (code == CURLE_OK) {
175 if (method == request_type::kGet) {
176 code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_HTTPGET, 1);
177 } else if (method == request_type::kHead) {
178 code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_NOBODY, 1);
179 } else if (method == request_type::kPut) {
180 code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_UPLOAD, 1);
181 } else {
182 // POST and custom request methods
183 code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_POST, 1);
184 if (code == CURLE_OK) {
185 code = curl_interface_->EasySetOptPtr(
186 curl_handle, CURLOPT_POSTFIELDS, nullptr);
187 }
188 if (code == CURLE_OK && method != request_type::kPost) {
189 code = curl_interface_->EasySetOptStr(
190 curl_handle, CURLOPT_CUSTOMREQUEST, method);
191 }
192 }
193 }
194
195 if (code != CURLE_OK) {
196 AddEasyCurlError(error, FROM_HERE, code, curl_interface_.get());
197 curl_interface_->EasyCleanup(curl_handle);
198 return connection;
199 }
200
201 connection = std::make_shared<http::curl::Connection>(
202 curl_handle, method, curl_interface_, shared_from_this());
203 if (!connection->SendHeaders(headers, error)) {
204 connection.reset();
205 }
206 return connection;
207 }
208
RunCallbackAsync(const base::Location & from_here,const base::Closure & callback)209 void Transport::RunCallbackAsync(const base::Location& from_here,
210 const base::Closure& callback) {
211 base::MessageLoopForIO::current()->task_runner()->PostTask(
212 from_here, callback);
213 }
214
StartAsyncTransfer(http::Connection * connection,const SuccessCallback & success_callback,const ErrorCallback & error_callback)215 RequestID Transport::StartAsyncTransfer(http::Connection* connection,
216 const SuccessCallback& success_callback,
217 const ErrorCallback& error_callback) {
218 brillo::ErrorPtr error;
219 if (!SetupAsyncCurl(&error)) {
220 RunCallbackAsync(
221 FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
222 return 0;
223 }
224
225 RequestID request_id = ++last_request_id_;
226
227 auto curl_connection = static_cast<http::curl::Connection*>(connection);
228 std::unique_ptr<AsyncRequestData> request_data{new AsyncRequestData};
229 // Add the request data to |async_requests_| before adding the CURL handle
230 // in case CURL feels like calling the socket callback synchronously which
231 // will need the data to be in |async_requests_| map already.
232 request_data->success_callback = success_callback;
233 request_data->error_callback = error_callback;
234 request_data->connection =
235 std::static_pointer_cast<Connection>(curl_connection->shared_from_this());
236 request_data->request_id = request_id;
237 async_requests_.emplace(curl_connection, std::move(request_data));
238 request_id_map_.emplace(request_id, curl_connection);
239
240 // Add the connection's CURL handle to the multi-handle.
241 CURLMcode code = curl_interface_->MultiAddHandle(
242 curl_multi_handle_, curl_connection->curl_handle_);
243 if (code != CURLM_OK) {
244 brillo::ErrorPtr error;
245 AddMultiCurlError(&error, FROM_HERE, code, curl_interface_.get());
246 RunCallbackAsync(
247 FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
248 async_requests_.erase(curl_connection);
249 request_id_map_.erase(request_id);
250 return 0;
251 }
252 VLOG(1) << "Started asynchronous HTTP request with ID " << request_id;
253 return request_id;
254 }
255
CancelRequest(RequestID request_id)256 bool Transport::CancelRequest(RequestID request_id) {
257 auto p = request_id_map_.find(request_id);
258 if (p == request_id_map_.end()) {
259 // The request must have been completed already...
260 // This is not necessarily an error condition, so fail gracefully.
261 LOG(WARNING) << "HTTP request #" << request_id << " not found";
262 return false;
263 }
264 LOG(INFO) << "Canceling HTTP request #" << request_id;
265 CleanAsyncConnection(p->second);
266 return true;
267 }
268
SetDefaultTimeout(base::TimeDelta timeout)269 void Transport::SetDefaultTimeout(base::TimeDelta timeout) {
270 connection_timeout_ = timeout;
271 }
272
SetLocalIpAddress(const std::string & ip_address)273 void Transport::SetLocalIpAddress(const std::string& ip_address) {
274 ip_address_ = "host!" + ip_address;
275 }
276
AddEasyCurlError(brillo::ErrorPtr * error,const base::Location & location,CURLcode code,CurlInterface * curl_interface)277 void Transport::AddEasyCurlError(brillo::ErrorPtr* error,
278 const base::Location& location,
279 CURLcode code,
280 CurlInterface* curl_interface) {
281 brillo::Error::AddTo(error, location, "curl_easy_error",
282 brillo::string_utils::ToString(code),
283 curl_interface->EasyStrError(code));
284 }
285
AddMultiCurlError(brillo::ErrorPtr * error,const base::Location & location,CURLMcode code,CurlInterface * curl_interface)286 void Transport::AddMultiCurlError(brillo::ErrorPtr* error,
287 const base::Location& location,
288 CURLMcode code,
289 CurlInterface* curl_interface) {
290 brillo::Error::AddTo(error, location, "curl_multi_error",
291 brillo::string_utils::ToString(code),
292 curl_interface->MultiStrError(code));
293 }
294
SetupAsyncCurl(brillo::ErrorPtr * error)295 bool Transport::SetupAsyncCurl(brillo::ErrorPtr* error) {
296 if (curl_multi_handle_)
297 return true;
298
299 curl_multi_handle_ = curl_interface_->MultiInit();
300 if (!curl_multi_handle_) {
301 LOG(ERROR) << "Failed to initialize CURL";
302 brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain,
303 "curl_init_failed", "Failed to initialize CURL");
304 return false;
305 }
306
307 CURLMcode code = curl_interface_->MultiSetSocketCallback(
308 curl_multi_handle_, &Transport::MultiSocketCallback, this);
309 if (code == CURLM_OK) {
310 code = curl_interface_->MultiSetTimerCallback(
311 curl_multi_handle_, &Transport::MultiTimerCallback, this);
312 }
313 if (code != CURLM_OK) {
314 AddMultiCurlError(error, FROM_HERE, code, curl_interface_.get());
315 return false;
316 }
317 return true;
318 }
319
ShutDownAsyncCurl()320 void Transport::ShutDownAsyncCurl() {
321 if (!curl_multi_handle_)
322 return;
323 LOG_IF(WARNING, !poll_data_map_.empty())
324 << "There are pending requests at the time of transport's shutdown";
325 // Make sure we are not leaking any memory here.
326 for (const auto& pair : poll_data_map_)
327 delete pair.second;
328 poll_data_map_.clear();
329 curl_interface_->MultiCleanup(curl_multi_handle_);
330 curl_multi_handle_ = nullptr;
331 }
332
MultiSocketCallback(CURL * easy,curl_socket_t s,int what,void * userp,void * socketp)333 int Transport::MultiSocketCallback(CURL* easy,
334 curl_socket_t s,
335 int what,
336 void* userp,
337 void* socketp) {
338 auto transport = static_cast<Transport*>(userp);
339 CHECK(transport) << "Transport must be set for this callback";
340 auto poll_data = static_cast<SocketPollData*>(socketp);
341 if (!poll_data) {
342 // We haven't attached polling data to this socket yet. Let's do this now.
343 poll_data = new SocketPollData{transport->curl_interface_,
344 transport->curl_multi_handle_,
345 transport,
346 s};
347 transport->poll_data_map_.emplace(std::make_pair(easy, s), poll_data);
348 transport->curl_interface_->MultiAssign(
349 transport->curl_multi_handle_, s, poll_data);
350 }
351
352 if (what == CURL_POLL_NONE) {
353 return 0;
354 } else if (what == CURL_POLL_REMOVE) {
355 // Remove the attached data from the socket.
356 transport->curl_interface_->MultiAssign(
357 transport->curl_multi_handle_, s, nullptr);
358 transport->poll_data_map_.erase(std::make_pair(easy, s));
359
360 // Make sure we stop watching the socket file descriptor now, before
361 // we schedule the SocketPollData for deletion.
362 poll_data->GetWatcher()->StopWatchingFileDescriptor();
363 // This method can be called indirectly from SocketPollData::OnSocketReady,
364 // so delay destruction of SocketPollData object till the next loop cycle.
365 base::MessageLoopForIO::current()->task_runner()->DeleteSoon(FROM_HERE,
366 poll_data);
367 return 0;
368 }
369
370 base::MessagePumpForIO::Mode watch_mode = base::MessagePumpForIO::WATCH_READ;
371 switch (what) {
372 case CURL_POLL_IN:
373 watch_mode = base::MessagePumpForIO::WATCH_READ;
374 break;
375 case CURL_POLL_OUT:
376 watch_mode = base::MessagePumpForIO::WATCH_WRITE;
377 break;
378 case CURL_POLL_INOUT:
379 watch_mode = base::MessagePumpForIO::WATCH_READ_WRITE;
380 break;
381 default:
382 LOG(FATAL) << "Unknown CURL socket action: " << what;
383 break;
384 }
385
386 // WatchFileDescriptor() can be called with the same controller object
387 // (watcher) to amend the watch mode, however this has cumulative effect.
388 // For example, if we were watching a file descriptor for READ operations
389 // and now call it to watch for WRITE, it will end up watching for both
390 // READ and WRITE. This is not what we want here, so stop watching the
391 // file descriptor on previous controller before starting with a different
392 // mode.
393 if (!poll_data->GetWatcher()->StopWatchingFileDescriptor())
394 LOG(WARNING) << "Failed to stop watching the previous socket descriptor";
395 CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor(
396 s, true, watch_mode, poll_data->GetWatcher(), poll_data))
397 << "Failed to watch the CURL socket.";
398 return 0;
399 }
400
401 // CURL actually uses "long" types in callback signatures, so we must comply.
MultiTimerCallback(CURLM *,long timeout_ms,void * userp)402 int Transport::MultiTimerCallback(CURLM* /* multi */,
403 long timeout_ms, // NOLINT(runtime/int)
404 void* userp) {
405 auto transport = static_cast<Transport*>(userp);
406 // Cancel any previous timer callbacks.
407 transport->weak_ptr_factory_for_timer_.InvalidateWeakPtrs();
408 if (timeout_ms >= 0) {
409 base::MessageLoopForIO::current()->task_runner()->PostDelayedTask(
410 FROM_HERE,
411 base::Bind(&Transport::OnTimer,
412 transport->weak_ptr_factory_for_timer_.GetWeakPtr()),
413 base::TimeDelta::FromMilliseconds(timeout_ms));
414 }
415 return 0;
416 }
417
OnTimer()418 void Transport::OnTimer() {
419 if (curl_multi_handle_) {
420 int still_running_count = 0;
421 curl_interface_->MultiSocketAction(
422 curl_multi_handle_, CURL_SOCKET_TIMEOUT, 0, &still_running_count);
423 ProcessAsyncCurlMessages();
424 }
425 }
426
ProcessAsyncCurlMessages()427 void Transport::ProcessAsyncCurlMessages() {
428 CURLMsg* msg = nullptr;
429 int msgs_left = 0;
430 while ((msg = curl_interface_->MultiInfoRead(curl_multi_handle_,
431 &msgs_left))) {
432 if (msg->msg == CURLMSG_DONE) {
433 // Async I/O complete for a connection. Invoke the user callbacks.
434 Connection* connection = nullptr;
435 CHECK_EQ(CURLE_OK,
436 curl_interface_->EasyGetInfoPtr(
437 msg->easy_handle,
438 CURLINFO_PRIVATE,
439 reinterpret_cast<void**>(&connection)));
440 CHECK(connection != nullptr);
441 OnTransferComplete(connection, msg->data.result);
442 }
443 }
444 }
445
OnTransferComplete(Connection * connection,CURLcode code)446 void Transport::OnTransferComplete(Connection* connection, CURLcode code) {
447 auto p = async_requests_.find(connection);
448 CHECK(p != async_requests_.end()) << "Unknown connection";
449 AsyncRequestData* request_data = p->second.get();
450 VLOG(1) << "HTTP request # " << request_data->request_id
451 << " has completed "
452 << (code == CURLE_OK ? "successfully" : "with an error");
453 if (code != CURLE_OK) {
454 brillo::ErrorPtr error;
455 AddEasyCurlError(&error, FROM_HERE, code, curl_interface_.get());
456 RunCallbackAsync(FROM_HERE,
457 base::Bind(request_data->error_callback,
458 p->second->request_id,
459 base::Owned(error.release())));
460 } else {
461 if (connection->GetResponseStatusCode() != status_code::Ok) {
462 LOG(INFO) << "Response: " << connection->GetResponseStatusCode() << " ("
463 << connection->GetResponseStatusText() << ")";
464 }
465 brillo::ErrorPtr error;
466 // Rewind the response data stream to the beginning so the clients can
467 // read the data back.
468 const auto& stream = request_data->connection->response_data_stream_;
469 if (stream && stream->CanSeek() && !stream->SetPosition(0, &error)) {
470 RunCallbackAsync(FROM_HERE,
471 base::Bind(request_data->error_callback,
472 p->second->request_id,
473 base::Owned(error.release())));
474 } else {
475 std::unique_ptr<Response> resp{new Response{request_data->connection}};
476 RunCallbackAsync(FROM_HERE,
477 base::Bind(request_data->success_callback,
478 p->second->request_id,
479 base::Passed(&resp)));
480 }
481 }
482 // In case of an error on CURL side, we would have dispatched the error
483 // callback and we need to clean up the current connection, however the
484 // error callback has no reference to the connection itself and
485 // |async_requests_| is the only reference to the shared pointer that
486 // maintains the lifetime of |connection| and possibly even this Transport
487 // object instance. As a result, if we call CleanAsyncConnection() directly,
488 // there is a chance that this object might be deleted.
489 // Instead, schedule an asynchronous task to clean up the connection.
490 RunCallbackAsync(FROM_HERE,
491 base::Bind(&Transport::CleanAsyncConnection,
492 weak_ptr_factory_.GetWeakPtr(),
493 connection));
494 }
495
CleanAsyncConnection(Connection * connection)496 void Transport::CleanAsyncConnection(Connection* connection) {
497 auto p = async_requests_.find(connection);
498 CHECK(p != async_requests_.end()) << "Unknown connection";
499 // Remove the request data from the map first, since this might be the only
500 // reference to the Connection class and even possibly to this Transport.
501 auto request_data = std::move(p->second);
502
503 // Remove associated request ID.
504 request_id_map_.erase(request_data->request_id);
505
506 // Remove the connection's CURL handle from multi-handle.
507 curl_interface_->MultiRemoveHandle(curl_multi_handle_,
508 connection->curl_handle_);
509
510 // Remove all the socket data associated with this connection.
511 auto iter = poll_data_map_.begin();
512 while (iter != poll_data_map_.end()) {
513 if (iter->first.first == connection->curl_handle_)
514 iter = poll_data_map_.erase(iter);
515 else
516 ++iter;
517 }
518 // Remove pending asynchronous request data.
519 // This must be last since there is a chance of this object being
520 // destroyed as the result. See the comment in Transport::OnTransferComplete.
521 async_requests_.erase(p);
522 }
523
524 } // namespace curl
525 } // namespace http
526 } // namespace brillo
527