• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2009 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include "update_engine/libcurl_http_fetcher.h"
18 
19 #include <algorithm>
20 #include <string>
21 
22 #include <base/bind.h>
23 #include <base/format_macros.h>
24 #include <base/location.h>
25 #include <base/logging.h>
26 #include <base/strings/string_util.h>
27 #include <base/strings/stringprintf.h>
28 
29 #include "update_engine/certificate_checker.h"
30 #include "update_engine/common/hardware_interface.h"
31 #include "update_engine/common/platform_constants.h"
32 
33 using base::TimeDelta;
34 using brillo::MessageLoop;
35 using std::max;
36 using std::string;
37 
38 // This is a concrete implementation of HttpFetcher that uses libcurl to do the
39 // http work.
40 
41 namespace chromeos_update_engine {
42 
43 namespace {
44 const int kNoNetworkRetrySeconds = 10;
45 }  // namespace
46 
LibcurlHttpFetcher(ProxyResolver * proxy_resolver,HardwareInterface * hardware)47 LibcurlHttpFetcher::LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
48                                        HardwareInterface* hardware)
49     : HttpFetcher(proxy_resolver), hardware_(hardware) {
50   // Dev users want a longer timeout (180 seconds) because they may
51   // be waiting on the dev server to build an image.
52   if (!hardware_->IsOfficialBuild())
53     low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
54   if (!hardware_->IsOOBEComplete(nullptr))
55     max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
56 }
57 
~LibcurlHttpFetcher()58 LibcurlHttpFetcher::~LibcurlHttpFetcher() {
59   LOG_IF(ERROR, transfer_in_progress_)
60       << "Destroying the fetcher while a transfer is in progress.";
61   CleanUp();
62 }
63 
GetProxyType(const string & proxy,curl_proxytype * out_type)64 bool LibcurlHttpFetcher::GetProxyType(const string& proxy,
65                                       curl_proxytype* out_type) {
66   if (base::StartsWith(
67           proxy, "socks5://", base::CompareCase::INSENSITIVE_ASCII) ||
68       base::StartsWith(
69           proxy, "socks://", base::CompareCase::INSENSITIVE_ASCII)) {
70     *out_type = CURLPROXY_SOCKS5_HOSTNAME;
71     return true;
72   }
73   if (base::StartsWith(
74           proxy, "socks4://", base::CompareCase::INSENSITIVE_ASCII)) {
75     *out_type = CURLPROXY_SOCKS4A;
76     return true;
77   }
78   if (base::StartsWith(
79           proxy, "http://", base::CompareCase::INSENSITIVE_ASCII) ||
80       base::StartsWith(
81           proxy, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
82     *out_type = CURLPROXY_HTTP;
83     return true;
84   }
85   if (base::StartsWith(proxy, kNoProxy, base::CompareCase::INSENSITIVE_ASCII)) {
86     // known failure case. don't log.
87     return false;
88   }
89   LOG(INFO) << "Unknown proxy type: " << proxy;
90   return false;
91 }
92 
ResumeTransfer(const string & url)93 void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
94   LOG(INFO) << "Starting/Resuming transfer";
95   CHECK(!transfer_in_progress_);
96   url_ = url;
97   curl_multi_handle_ = curl_multi_init();
98   CHECK(curl_multi_handle_);
99 
100   curl_handle_ = curl_easy_init();
101   CHECK(curl_handle_);
102   ignore_failure_ = false;
103 
104   CHECK(HasProxy());
105   bool is_direct = (GetCurrentProxy() == kNoProxy);
106   LOG(INFO) << "Using proxy: " << (is_direct ? "no" : "yes");
107   if (is_direct) {
108     CHECK_EQ(curl_easy_setopt(curl_handle_,
109                               CURLOPT_PROXY,
110                               ""), CURLE_OK);
111   } else {
112     CHECK_EQ(curl_easy_setopt(curl_handle_,
113                               CURLOPT_PROXY,
114                               GetCurrentProxy().c_str()), CURLE_OK);
115     // Curl seems to require us to set the protocol
116     curl_proxytype type;
117     if (GetProxyType(GetCurrentProxy(), &type)) {
118       CHECK_EQ(curl_easy_setopt(curl_handle_,
119                                 CURLOPT_PROXYTYPE,
120                                 type), CURLE_OK);
121     }
122   }
123 
124   if (post_data_set_) {
125     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
126     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
127                               post_data_.data()),
128              CURLE_OK);
129     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
130                               post_data_.size()),
131              CURLE_OK);
132   }
133 
134   // Setup extra HTTP headers.
135   if (curl_http_headers_) {
136     curl_slist_free_all(curl_http_headers_);
137     curl_http_headers_ = nullptr;
138   }
139   for (const auto& header : extra_headers_) {
140     // curl_slist_append() copies the string.
141     curl_http_headers_ =
142         curl_slist_append(curl_http_headers_, header.second.c_str());
143   }
144   if (post_data_set_) {
145     // Set the Content-Type HTTP header, if one was specifically set.
146     if (post_content_type_ != kHttpContentTypeUnspecified) {
147       const string content_type_attr = base::StringPrintf(
148           "Content-Type: %s", GetHttpContentTypeString(post_content_type_));
149       curl_http_headers_ =
150           curl_slist_append(curl_http_headers_, content_type_attr.c_str());
151     } else {
152       LOG(WARNING) << "no content type set, using libcurl default";
153     }
154   }
155   CHECK_EQ(
156       curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, curl_http_headers_),
157       CURLE_OK);
158 
159   if (bytes_downloaded_ > 0 || download_length_) {
160     // Resume from where we left off.
161     resume_offset_ = bytes_downloaded_;
162     CHECK_GE(resume_offset_, 0);
163 
164     // Compute end offset, if one is specified. As per HTTP specification, this
165     // is an inclusive boundary. Make sure it doesn't overflow.
166     size_t end_offset = 0;
167     if (download_length_) {
168       end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
169       CHECK_LE((size_t) resume_offset_, end_offset);
170     }
171 
172     // Create a string representation of the desired range.
173     string range_str = base::StringPrintf(
174         "%" PRIu64 "-", static_cast<uint64_t>(resume_offset_));
175     if (end_offset)
176       range_str += std::to_string(end_offset);
177     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
178              CURLE_OK);
179   }
180 
181   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
182   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
183                             StaticLibcurlWrite), CURLE_OK);
184   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()),
185            CURLE_OK);
186 
187   // If the connection drops under |low_speed_limit_bps_| (10
188   // bytes/sec by default) for |low_speed_time_seconds_| (90 seconds,
189   // 180 on non-official builds), reconnect.
190   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT,
191                             low_speed_limit_bps_),
192            CURLE_OK);
193   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME,
194                             low_speed_time_seconds_),
195            CURLE_OK);
196   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CONNECTTIMEOUT,
197                             connect_timeout_seconds_),
198            CURLE_OK);
199 
200   // By default, libcurl doesn't follow redirections. Allow up to
201   // |kDownloadMaxRedirects| redirections.
202   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_FOLLOWLOCATION, 1), CURLE_OK);
203   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_MAXREDIRS,
204                             kDownloadMaxRedirects),
205            CURLE_OK);
206 
207   // Lock down the appropriate curl options for HTTP or HTTPS depending on
208   // the url.
209   if (hardware_->IsOfficialBuild()) {
210     if (base::StartsWith(
211             url_, "http://", base::CompareCase::INSENSITIVE_ASCII)) {
212       SetCurlOptionsForHttp();
213     } else if (base::StartsWith(
214                    url_, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
215       SetCurlOptionsForHttps();
216 #if !defined(__CHROMEOS__) && !defined(__BRILLO__)
217     } else if (base::StartsWith(
218                    url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) {
219       SetCurlOptionsForFile();
220 #endif
221     } else {
222       LOG(ERROR) << "Received invalid URI: " << url_;
223       // Lock down to no protocol supported for the transfer.
224       CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, 0), CURLE_OK);
225     }
226   } else {
227     LOG(INFO) << "Not setting http(s) curl options because we are "
228               << "running a dev/test image";
229   }
230 
231   CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
232   transfer_in_progress_ = true;
233 }
234 
235 // Lock down only the protocol in case of HTTP.
SetCurlOptionsForHttp()236 void LibcurlHttpFetcher::SetCurlOptionsForHttp() {
237   LOG(INFO) << "Setting up curl options for HTTP";
238   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTP),
239            CURLE_OK);
240   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
241                             CURLPROTO_HTTP),
242            CURLE_OK);
243 }
244 
245 // Security lock-down in official builds: makes sure that peer certificate
246 // verification is enabled, restricts the set of trusted certificates,
247 // restricts protocols to HTTPS, restricts ciphers to HIGH.
SetCurlOptionsForHttps()248 void LibcurlHttpFetcher::SetCurlOptionsForHttps() {
249   LOG(INFO) << "Setting up curl options for HTTPS";
250   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYPEER, 1),
251            CURLE_OK);
252   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAPATH,
253                             constants::kCACertificatesPath),
254            CURLE_OK);
255   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS),
256            CURLE_OK);
257   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
258                             CURLPROTO_HTTPS),
259            CURLE_OK);
260   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST, "HIGH:!ADH"),
261            CURLE_OK);
262   if (server_to_check_ != ServerToCheck::kNone) {
263     CHECK_EQ(
264         curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA, &server_to_check_),
265         CURLE_OK);
266     CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
267                               CertificateChecker::ProcessSSLContext),
268              CURLE_OK);
269   }
270 }
271 
272 // Lock down only the protocol in case of a local file.
SetCurlOptionsForFile()273 void LibcurlHttpFetcher::SetCurlOptionsForFile() {
274   LOG(INFO) << "Setting up curl options for FILE";
275   CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_FILE),
276            CURLE_OK);
277   CHECK_EQ(
278       curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_FILE),
279       CURLE_OK);
280 }
281 
282 // Begins the transfer, which must not have already been started.
BeginTransfer(const string & url)283 void LibcurlHttpFetcher::BeginTransfer(const string& url) {
284   CHECK(!transfer_in_progress_);
285   url_ = url;
286   auto closure = base::Bind(&LibcurlHttpFetcher::ProxiesResolved,
287                             base::Unretained(this));
288   if (!ResolveProxiesForUrl(url_, closure)) {
289     LOG(ERROR) << "Couldn't resolve proxies";
290     if (delegate_)
291       delegate_->TransferComplete(this, false);
292   }
293 }
294 
ProxiesResolved()295 void LibcurlHttpFetcher::ProxiesResolved() {
296   transfer_size_ = -1;
297   resume_offset_ = 0;
298   retry_count_ = 0;
299   no_network_retry_count_ = 0;
300   http_response_code_ = 0;
301   terminate_requested_ = false;
302   sent_byte_ = false;
303 
304   // If we are paused, we delay these two operations until Unpause is called.
305   if (transfer_paused_) {
306     restart_transfer_on_unpause_ = true;
307     return;
308   }
309   ResumeTransfer(url_);
310   CurlPerformOnce();
311 }
312 
ForceTransferTermination()313 void LibcurlHttpFetcher::ForceTransferTermination() {
314   CleanUp();
315   if (delegate_) {
316     // Note that after the callback returns this object may be destroyed.
317     delegate_->TransferTerminated(this);
318   }
319 }
320 
TerminateTransfer()321 void LibcurlHttpFetcher::TerminateTransfer() {
322   if (in_write_callback_) {
323     terminate_requested_ = true;
324   } else {
325     ForceTransferTermination();
326   }
327 }
328 
SetHeader(const string & header_name,const string & header_value)329 void LibcurlHttpFetcher::SetHeader(const string& header_name,
330                                    const string& header_value) {
331   string header_line = header_name + ": " + header_value;
332   // Avoid the space if no data on the right side of the semicolon.
333   if (header_value.empty())
334     header_line = header_name + ":";
335   TEST_AND_RETURN(header_line.find('\n') == string::npos);
336   TEST_AND_RETURN(header_name.find(':') == string::npos);
337   extra_headers_[base::ToLowerASCII(header_name)] = header_line;
338 }
339 
CurlPerformOnce()340 void LibcurlHttpFetcher::CurlPerformOnce() {
341   CHECK(transfer_in_progress_);
342   int running_handles = 0;
343   CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
344 
345   // libcurl may request that we immediately call curl_multi_perform after it
346   // returns, so we do. libcurl promises that curl_multi_perform will not block.
347   while (CURLM_CALL_MULTI_PERFORM == retcode) {
348     retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
349     if (terminate_requested_) {
350       ForceTransferTermination();
351       return;
352     }
353   }
354 
355   // If the transfer completes while paused, we should ignore the failure once
356   // the fetcher is unpaused.
357   if (running_handles == 0 && transfer_paused_ && !ignore_failure_) {
358     LOG(INFO) << "Connection closed while paused, ignoring failure.";
359     ignore_failure_ = true;
360   }
361 
362   if (running_handles != 0 || transfer_paused_) {
363     // There's either more work to do or we are paused, so we just keep the
364     // file descriptors to watch up to date and exit, until we are done with the
365     // work and we are not paused.
366     SetupMessageLoopSources();
367     return;
368   }
369 
370   // At this point, the transfer was completed in some way (error, connection
371   // closed or download finished).
372 
373   GetHttpResponseCode();
374   if (http_response_code_) {
375     LOG(INFO) << "HTTP response code: " << http_response_code_;
376     no_network_retry_count_ = 0;
377   } else {
378     LOG(ERROR) << "Unable to get http response code.";
379   }
380 
381   // we're done!
382   CleanUp();
383 
384   // TODO(petkov): This temporary code tries to deal with the case where the
385   // update engine performs an update check while the network is not ready
386   // (e.g., right after resume). Longer term, we should check if the network
387   // is online/offline and return an appropriate error code.
388   if (!sent_byte_ &&
389       http_response_code_ == 0 &&
390       no_network_retry_count_ < no_network_max_retries_) {
391     no_network_retry_count_++;
392     MessageLoop::current()->PostDelayedTask(
393         FROM_HERE,
394         base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
395                    base::Unretained(this)),
396         TimeDelta::FromSeconds(kNoNetworkRetrySeconds));
397     LOG(INFO) << "No HTTP response, retry " << no_network_retry_count_;
398   } else if ((!sent_byte_ && !IsHttpResponseSuccess()) ||
399              IsHttpResponseError()) {
400     // The transfer completed w/ error and we didn't get any bytes.
401     // If we have another proxy to try, try that.
402     //
403     // TODO(garnold) in fact there are two separate cases here: one case is an
404     // other-than-success return code (including no return code) and no
405     // received bytes, which is necessary due to the way callbacks are
406     // currently processing error conditions;  the second is an explicit HTTP
407     // error code, where some data may have been received (as in the case of a
408     // semi-successful multi-chunk fetch).  This is a confusing behavior and
409     // should be unified into a complete, coherent interface.
410     LOG(INFO) << "Transfer resulted in an error (" << http_response_code_
411               << "), " << bytes_downloaded_ << " bytes downloaded";
412 
413     PopProxy();  // Delete the proxy we just gave up on.
414 
415     if (HasProxy()) {
416       // We have another proxy. Retry immediately.
417       LOG(INFO) << "Retrying with next proxy setting";
418       MessageLoop::current()->PostTask(
419           FROM_HERE,
420           base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
421                      base::Unretained(this)));
422     } else {
423       // Out of proxies. Give up.
424       LOG(INFO) << "No further proxies, indicating transfer complete";
425       if (delegate_)
426         delegate_->TransferComplete(this, false);  // signal fail
427     }
428   } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
429     if (!ignore_failure_)
430       retry_count_++;
431     LOG(INFO) << "Transfer interrupted after downloading "
432               << bytes_downloaded_ << " of " << transfer_size_ << " bytes. "
433               << transfer_size_ - bytes_downloaded_ << " bytes remaining "
434               << "after " << retry_count_ << " attempt(s)";
435 
436     if (retry_count_ > max_retry_count_) {
437       LOG(INFO) << "Reached max attempts (" << retry_count_ << ")";
438       if (delegate_)
439         delegate_->TransferComplete(this, false);  // signal fail
440     } else {
441       // Need to restart transfer
442       LOG(INFO) << "Restarting transfer to download the remaining bytes";
443       MessageLoop::current()->PostDelayedTask(
444           FROM_HERE,
445           base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
446                      base::Unretained(this)),
447           TimeDelta::FromSeconds(retry_seconds_));
448     }
449   } else {
450     LOG(INFO) << "Transfer completed (" << http_response_code_
451               << "), " << bytes_downloaded_ << " bytes downloaded";
452     if (delegate_) {
453       bool success = IsHttpResponseSuccess();
454       delegate_->TransferComplete(this, success);
455     }
456   }
457   ignore_failure_ = false;
458 }
459 
LibcurlWrite(void * ptr,size_t size,size_t nmemb)460 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
461   // Update HTTP response first.
462   GetHttpResponseCode();
463   const size_t payload_size = size * nmemb;
464 
465   // Do nothing if no payload or HTTP response is an error.
466   if (payload_size == 0 || !IsHttpResponseSuccess()) {
467     LOG(INFO) << "HTTP response unsuccessful (" << http_response_code_
468               << ") or no payload (" << payload_size << "), nothing to do";
469     return 0;
470   }
471 
472   sent_byte_ = true;
473   {
474     double transfer_size_double;
475     CHECK_EQ(curl_easy_getinfo(curl_handle_,
476                                CURLINFO_CONTENT_LENGTH_DOWNLOAD,
477                                &transfer_size_double), CURLE_OK);
478     off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
479     if (new_transfer_size > 0) {
480       transfer_size_ = resume_offset_ + new_transfer_size;
481     }
482   }
483   bytes_downloaded_ += payload_size;
484   in_write_callback_ = true;
485   if (delegate_)
486     delegate_->ReceivedBytes(this, ptr, payload_size);
487   in_write_callback_ = false;
488   return payload_size;
489 }
490 
Pause()491 void LibcurlHttpFetcher::Pause() {
492   if (transfer_paused_) {
493     LOG(ERROR) << "Fetcher already paused.";
494     return;
495   }
496   transfer_paused_ = true;
497   if (!transfer_in_progress_) {
498     // If pause before we started a connection, we don't need to notify curl
499     // about that, we will simply not start the connection later.
500     return;
501   }
502   CHECK(curl_handle_);
503   CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
504 }
505 
Unpause()506 void LibcurlHttpFetcher::Unpause() {
507   if (!transfer_paused_) {
508     LOG(ERROR) << "Resume attempted when fetcher not paused.";
509     return;
510   }
511   transfer_paused_ = false;
512   if (restart_transfer_on_unpause_) {
513     restart_transfer_on_unpause_ = false;
514     ResumeTransfer(url_);
515     CurlPerformOnce();
516     return;
517   }
518   if (!transfer_in_progress_) {
519     // If resumed before starting the connection, there's no need to notify
520     // anybody. We will simply start the connection once it is time.
521     return;
522   }
523   CHECK(curl_handle_);
524   CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
525   // Since the transfer is in progress, we need to dispatch a CurlPerformOnce()
526   // now to let the connection continue, otherwise it would be called by the
527   // TimeoutCallback but with a delay.
528   CurlPerformOnce();
529 }
530 
531 // This method sets up callbacks with the MessageLoop.
SetupMessageLoopSources()532 void LibcurlHttpFetcher::SetupMessageLoopSources() {
533   fd_set fd_read;
534   fd_set fd_write;
535   fd_set fd_exc;
536 
537   FD_ZERO(&fd_read);
538   FD_ZERO(&fd_write);
539   FD_ZERO(&fd_exc);
540 
541   int fd_max = 0;
542 
543   // Ask libcurl for the set of file descriptors we should track on its
544   // behalf.
545   CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
546                             &fd_exc, &fd_max), CURLM_OK);
547 
548   // We should iterate through all file descriptors up to libcurl's fd_max or
549   // the highest one we're tracking, whichever is larger.
550   for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
551     if (!fd_task_maps_[t].empty())
552       fd_max = max(fd_max, fd_task_maps_[t].rbegin()->first);
553   }
554 
555   // For each fd, if we're not tracking it, track it. If we are tracking it, but
556   // libcurl doesn't care about it anymore, stop tracking it. After this loop,
557   // there should be exactly as many tasks scheduled in fd_task_maps_[0|1] as
558   // there are read/write fds that we're tracking.
559   for (int fd = 0; fd <= fd_max; ++fd) {
560     // Note that fd_exc is unused in the current version of libcurl so is_exc
561     // should always be false.
562     bool is_exc = FD_ISSET(fd, &fd_exc) != 0;
563     bool must_track[2] = {
564       is_exc || (FD_ISSET(fd, &fd_read) != 0),  // track 0 -- read
565       is_exc || (FD_ISSET(fd, &fd_write) != 0)  // track 1 -- write
566     };
567     MessageLoop::WatchMode watch_modes[2] = {
568       MessageLoop::WatchMode::kWatchRead,
569       MessageLoop::WatchMode::kWatchWrite,
570     };
571 
572     for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
573       auto fd_task_it = fd_task_maps_[t].find(fd);
574       bool tracked = fd_task_it != fd_task_maps_[t].end();
575 
576       if (!must_track[t]) {
577         // If we have an outstanding io_channel, remove it.
578         if (tracked) {
579           MessageLoop::current()->CancelTask(fd_task_it->second);
580           fd_task_maps_[t].erase(fd_task_it);
581         }
582         continue;
583       }
584 
585       // If we are already tracking this fd, continue -- nothing to do.
586       if (tracked)
587         continue;
588 
589       // Track a new fd.
590       fd_task_maps_[t][fd] = MessageLoop::current()->WatchFileDescriptor(
591           FROM_HERE,
592           fd,
593           watch_modes[t],
594           true,  // persistent
595           base::Bind(&LibcurlHttpFetcher::CurlPerformOnce,
596                      base::Unretained(this)));
597 
598       static int io_counter = 0;
599       io_counter++;
600       if (io_counter % 50 == 0) {
601         LOG(INFO) << "io_counter = " << io_counter;
602       }
603     }
604   }
605 
606   // Set up a timeout callback for libcurl.
607   if (timeout_id_ == MessageLoop::kTaskIdNull) {
608     VLOG(1) << "Setting up timeout source: " << idle_seconds_ << " seconds.";
609     timeout_id_ = MessageLoop::current()->PostDelayedTask(
610         FROM_HERE,
611         base::Bind(&LibcurlHttpFetcher::TimeoutCallback,
612                    base::Unretained(this)),
613         TimeDelta::FromSeconds(idle_seconds_));
614   }
615 }
616 
RetryTimeoutCallback()617 void LibcurlHttpFetcher::RetryTimeoutCallback() {
618   if (transfer_paused_) {
619     restart_transfer_on_unpause_ = true;
620     return;
621   }
622   ResumeTransfer(url_);
623   CurlPerformOnce();
624 }
625 
TimeoutCallback()626 void LibcurlHttpFetcher::TimeoutCallback() {
627   // We always re-schedule the callback, even if we don't want to be called
628   // anymore. We will remove the event source separately if we don't want to
629   // be called back.
630   timeout_id_ = MessageLoop::current()->PostDelayedTask(
631       FROM_HERE,
632       base::Bind(&LibcurlHttpFetcher::TimeoutCallback, base::Unretained(this)),
633       TimeDelta::FromSeconds(idle_seconds_));
634 
635   // CurlPerformOnce() may call CleanUp(), so we need to schedule our callback
636   // first, since it could be canceled by this call.
637   if (transfer_in_progress_)
638     CurlPerformOnce();
639 }
640 
CleanUp()641 void LibcurlHttpFetcher::CleanUp() {
642   MessageLoop::current()->CancelTask(timeout_id_);
643   timeout_id_ = MessageLoop::kTaskIdNull;
644 
645   for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
646     for (const auto& fd_taks_pair : fd_task_maps_[t]) {
647       if (!MessageLoop::current()->CancelTask(fd_taks_pair.second)) {
648         LOG(WARNING) << "Error canceling the watch task "
649                      << fd_taks_pair.second << " for "
650                      << (t ? "writing" : "reading") << " the fd "
651                      << fd_taks_pair.first;
652       }
653     }
654     fd_task_maps_[t].clear();
655   }
656 
657   if (curl_http_headers_) {
658     curl_slist_free_all(curl_http_headers_);
659     curl_http_headers_ = nullptr;
660   }
661   if (curl_handle_) {
662     if (curl_multi_handle_) {
663       CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
664                CURLM_OK);
665     }
666     curl_easy_cleanup(curl_handle_);
667     curl_handle_ = nullptr;
668   }
669   if (curl_multi_handle_) {
670     CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
671     curl_multi_handle_ = nullptr;
672   }
673   transfer_in_progress_ = false;
674   transfer_paused_ = false;
675   restart_transfer_on_unpause_ = false;
676 }
677 
GetHttpResponseCode()678 void LibcurlHttpFetcher::GetHttpResponseCode() {
679   long http_response_code = 0;  // NOLINT(runtime/int) - curl needs long.
680   if (base::StartsWith(url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) {
681     // Fake out a valid response code for file:// URLs.
682     http_response_code_ = 299;
683   } else if (curl_easy_getinfo(curl_handle_,
684                                CURLINFO_RESPONSE_CODE,
685                                &http_response_code) == CURLE_OK) {
686     http_response_code_ = static_cast<int>(http_response_code);
687   }
688 }
689 
690 }  // namespace chromeos_update_engine
691