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