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/common/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/common/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