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