• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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