aboutsummaryrefslogtreecommitdiff
path: root/libcurl_http_fetcher.h
blob: 0e34f9d2cf17c56b7f1725755afc7730605f1798 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_

#include <map>
#include <memory>
#include <string>
#include <utility>

#include <curl/curl.h>

#include <base/files/file_descriptor_watcher_posix.h>
#include <base/logging.h>
#include <base/macros.h>
#include <brillo/message_loops/message_loop.h>

#include "update_engine/certificate_checker.h"
#include "update_engine/common/hardware_interface.h"
#include "update_engine/common/http_fetcher.h"

// This is a concrete implementation of HttpFetcher that uses libcurl to do the
// http work.

namespace chromeos_update_engine {

// |UnresolvedHostStateMachine| is a representation of internal state machine of
// |LibcurlHttpFetcher|.
class UnresolvedHostStateMachine {
 public:
  UnresolvedHostStateMachine() = default;
  enum class State {
    kInit = 0,
    kRetry = 1,
    kRetriedSuccess = 2,
    kNotRetry = 3,
  };

  State GetState() { return state_; }

  // Updates the following internal state machine:
  //
  // |kInit|
  //   |
  //   |
  //   \/
  // (Try, host Unresolved)
  //   |
  //   |
  //   \/
  // |kRetry| --> (Retry, host resolved)
  //   |                                  |
  //   |                                  |
  //   \/                                 \/
  // (Retry, host Unresolved)    |kRetriedSuccess|
  //   |
  //   |
  //   \/
  // |kNotRetry|
  //
  void UpdateState(bool failed_to_resolve_host);

 private:
  State state_ = {State::kInit};

  DISALLOW_COPY_AND_ASSIGN(UnresolvedHostStateMachine);
};

class LibcurlHttpFetcher : public HttpFetcher {
 public:
  explicit LibcurlHttpFetcher(HardwareInterface* hardware);

  // Cleans up all internal state. Does not notify delegate
  ~LibcurlHttpFetcher() override;

  void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }

  void SetLength(size_t length) override { download_length_ = length; }
  void UnsetLength() override { SetLength(0); }

  // Begins the transfer if it hasn't already begun.
  void BeginTransfer(const std::string& url) override;

  // If the transfer is in progress, aborts the transfer early. The transfer
  // cannot be resumed.
  void TerminateTransfer() override;

  // Pass the headers to libcurl.
  void SetHeader(const std::string& header_name,
                 const std::string& header_value) override;

  bool GetHeader(const std::string& header_name,
                 std::string* header_value) const override;

  // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
  void Pause() override;

  // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
  void Unpause() override;

  // Libcurl sometimes asks to be called back after some time while
  // leaving that time unspecified. In that case, we pick a reasonable
  // default of one second, but it can be overridden here. This is
  // primarily useful for testing.
  // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
  //     if libcurl returns a -1 timeout here, it just means that libcurl
  //     currently has no stored timeout value. You must not wait too long
  //     (more than a few seconds perhaps) before you call
  //     curl_multi_perform() again.
  void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }

  // Sets the retry timeout. Useful for testing.
  void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }

  void set_no_network_max_retries(int retries) {
    no_network_max_retries_ = retries;
  }

  int get_no_network_max_retries() { return no_network_max_retries_; }

  void set_server_to_check(ServerToCheck server_to_check) {
    server_to_check_ = server_to_check;
  }

  size_t GetBytesDownloaded() override {
    return static_cast<size_t>(bytes_downloaded_);
  }

  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
    low_speed_limit_bps_ = low_speed_bps;
    low_speed_time_seconds_ = low_speed_sec;
  }

  void set_connect_timeout(int connect_timeout_seconds) override {
    connect_timeout_seconds_ = connect_timeout_seconds;
  }

  void set_max_retry_count(int max_retry_count) override {
    max_retry_count_ = max_retry_count;
  }

  void set_is_update_check(bool is_update_check) {
    is_update_check_ = is_update_check;
  }

 private:
  FRIEND_TEST(LibcurlHttpFetcherTest, HostResolvedTest);

  // libcurl's CURLOPT_CLOSESOCKETFUNCTION callback function. Called when
  // closing a socket created with the CURLOPT_OPENSOCKETFUNCTION callback.
  static int LibcurlCloseSocketCallback(void* clientp, curl_socket_t item);

  // Asks libcurl for the http response code and stores it in the object.
  virtual void GetHttpResponseCode();

  // Returns the last |CURLcode|.
  CURLcode GetCurlCode();

  // Checks whether stored HTTP response is within the success range.
  inline bool IsHttpResponseSuccess() {
    return (http_response_code_ >= 200 && http_response_code_ < 300);
  }

  // Checks whether stored HTTP response is within the error range. This
  // includes both errors with the request (4xx) and server errors (5xx).
  inline bool IsHttpResponseError() {
    return (http_response_code_ >= 400 && http_response_code_ < 600);
  }

  // Resumes a transfer where it left off. This will use the
  // HTTP Range: header to make a new connection from where the last
  // left off.
  virtual void ResumeTransfer(const std::string& url);

  void TimeoutCallback();
  void RetryTimeoutCallback();

  // Calls into curl_multi_perform to let libcurl do its work. Returns after
  // curl_multi_perform is finished, which may actually be after more than
  // one call to curl_multi_perform. This method will set up the message
  // loop with sources for future work that libcurl will do, if any, or complete
  // the transfer and finish the action if no work left to do.
  // This method will not block.
  void CurlPerformOnce();

  // Sets up message loop sources as needed by libcurl. This is generally
  // the file descriptor of the socket and a timer in case nothing happens
  // on the fds.
  void SetupMessageLoopSources();

  // Callback called by libcurl when new data has arrived on the transfer
  size_t LibcurlWrite(void* ptr, size_t size, size_t nmemb);
  static size_t StaticLibcurlWrite(void* ptr,
                                   size_t size,
                                   size_t nmemb,
                                   void* stream) {
    return reinterpret_cast<LibcurlHttpFetcher*>(stream)->LibcurlWrite(
        ptr, size, nmemb);
  }

  // Cleans up the following if they are non-null:
  // curl(m) handles, fd_controller_maps_(fd_task_maps_), timeout_id_.
  void CleanUp();

  // Force terminate the transfer. This will invoke the delegate's (if any)
  // TransferTerminated callback so, after returning, this fetcher instance may
  // be destroyed.
  void ForceTransferTermination();

  // Sets the curl options for HTTP URL.
  void SetCurlOptionsForHttp();

  // Sets the curl options for HTTPS URL.
  void SetCurlOptionsForHttps();

  // Sets the curl options for file URI.
  void SetCurlOptionsForFile();

  // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
  // conversion was successful, false otherwise (in which case nothing is
  // written to |out_type|).
  bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);

  // Hardware interface used to query dev-mode and official build settings.
  HardwareInterface* hardware_;

  // Handles for the libcurl library
  CURLM* curl_multi_handle_{nullptr};
  CURL* curl_handle_{nullptr};
  struct curl_slist* curl_http_headers_{nullptr};

  // The extra headers that will be sent on each request.
  std::map<std::string, std::string> extra_headers_;

  // Lists of all read(0)/write(1) file descriptors that we're waiting on from
  // the message loop. libcurl may open/close descriptors and switch their
  // directions so maintain two separate lists so that watch conditions can be
  // set appropriately.
  std::map<int, std::unique_ptr<base::FileDescriptorWatcher::Controller>>
      fd_controller_maps_[2];

  // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
  // on it.
  brillo::MessageLoop::TaskId timeout_id_{brillo::MessageLoop::kTaskIdNull};

  bool transfer_in_progress_{false};
  bool transfer_paused_{false};

  // Whether it should ignore transfer failures for the purpose of retrying the
  // connection.
  bool ignore_failure_{false};

  // Whether we should restart the transfer once Unpause() is called. This can
  // be caused because either the connection dropped while pause or the proxy
  // was resolved and we never started the transfer in the first place.
  bool restart_transfer_on_unpause_{false};

  // The transfer size. -1 if not known.
  off_t transfer_size_{0};

  // How many bytes have been downloaded and sent to the delegate.
  off_t bytes_downloaded_{0};

  // The remaining maximum number of bytes to download. Zero represents an
  // unspecified length.
  size_t download_length_{0};

  // If we resumed an earlier transfer, data offset that we used for the
  // new connection.  0 otherwise.
  // In this class, resume refers to resuming a dropped HTTP connection,
  // not to resuming an interrupted download.
  off_t resume_offset_{0};

  // Number of resumes performed so far and the max allowed.
  int retry_count_{0};
  int max_retry_count_{kDownloadMaxRetryCount};

  // Seconds to wait before retrying a resume.
  int retry_seconds_{20};

  // When waiting for a retry, the task id of the retry callback.
  brillo::MessageLoop::TaskId retry_task_id_{brillo::MessageLoop::kTaskIdNull};

  // Number of resumes due to no network (e.g., HTTP response code 0).
  int no_network_retry_count_{0};
  int no_network_max_retries_{0};

  // Seconds to wait before asking libcurl to "perform".
  int idle_seconds_{1};

  // If true, we are currently performing a write callback on the delegate.
  bool in_write_callback_{false};

  // If true, we have returned at least one byte in the write callback
  // to the delegate.
  bool sent_byte_{false};

  // We can't clean everything up while we're in a write callback, so
  // if we get a terminate request, queue it until we can handle it.
  bool terminate_requested_{false};

  // The ServerToCheck used when checking this connection's certificate. If no
  // certificate check needs to be performed, this should be set to
  // ServerToCheck::kNone.
  ServerToCheck server_to_check_{ServerToCheck::kNone};

  // True if this object is for update check.
  bool is_update_check_{false};

  // Internal state machine.
  UnresolvedHostStateMachine unresolved_host_state_machine_;

  int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
  int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
  int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};

  DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
};

}  // namespace chromeos_update_engine

#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_