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