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#ifndef UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
18#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
19
20#include <map>
21#include <memory>
22#include <string>
23#include <utility>
24
25#include <curl/curl.h>
26
27#include <base/files/file_descriptor_watcher_posix.h>
28#include <base/logging.h>
29#include <base/macros.h>
30#include <brillo/message_loops/message_loop.h>
31
32#include "update_engine/certificate_checker.h"
33#include "update_engine/common/hardware_interface.h"
34#include "update_engine/common/http_fetcher.h"
35
36// This is a concrete implementation of HttpFetcher that uses libcurl to do the
37// http work.
38
39namespace chromeos_update_engine {
40
41// |UnresolvedHostStateMachine| is a representation of internal state machine of
42// |LibcurlHttpFetcher|.
43class UnresolvedHostStateMachine {
44 public:
45  UnresolvedHostStateMachine() = default;
46  enum class State {
47    kInit = 0,
48    kRetry = 1,
49    kRetriedSuccess = 2,
50    kNotRetry = 3,
51  };
52
53  State GetState() { return state_; }
54
55  // Updates the following internal state machine:
56  //
57  // |kInit|
58  //   |
59  //   |
60  //   \/
61  // (Try, host Unresolved)
62  //   |
63  //   |
64  //   \/
65  // |kRetry| --> (Retry, host resolved)
66  //   |                                  |
67  //   |                                  |
68  //   \/                                 \/
69  // (Retry, host Unresolved)    |kRetriedSuccess|
70  //   |
71  //   |
72  //   \/
73  // |kNotRetry|
74  //
75  void UpdateState(bool failed_to_resolve_host);
76
77 private:
78  State state_ = {State::kInit};
79
80  DISALLOW_COPY_AND_ASSIGN(UnresolvedHostStateMachine);
81};
82
83class LibcurlHttpFetcher : public HttpFetcher {
84 public:
85  LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
86                     HardwareInterface* hardware);
87
88  // Cleans up all internal state. Does not notify delegate
89  ~LibcurlHttpFetcher() override;
90
91  void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
92
93  void SetLength(size_t length) override { download_length_ = length; }
94  void UnsetLength() override { SetLength(0); }
95
96  // Begins the transfer if it hasn't already begun.
97  void BeginTransfer(const std::string& url) override;
98
99  // If the transfer is in progress, aborts the transfer early. The transfer
100  // cannot be resumed.
101  void TerminateTransfer() override;
102
103  // Pass the headers to libcurl.
104  void SetHeader(const std::string& header_name,
105                 const std::string& header_value) override;
106
107  bool GetHeader(const std::string& header_name,
108                 std::string* header_value) const override;
109
110  // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
111  void Pause() override;
112
113  // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
114  void Unpause() override;
115
116  // Libcurl sometimes asks to be called back after some time while
117  // leaving that time unspecified. In that case, we pick a reasonable
118  // default of one second, but it can be overridden here. This is
119  // primarily useful for testing.
120  // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
121  //     if libcurl returns a -1 timeout here, it just means that libcurl
122  //     currently has no stored timeout value. You must not wait too long
123  //     (more than a few seconds perhaps) before you call
124  //     curl_multi_perform() again.
125  void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }
126
127  // Sets the retry timeout. Useful for testing.
128  void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }
129
130  void set_no_network_max_retries(int retries) {
131    no_network_max_retries_ = retries;
132  }
133
134  int get_no_network_max_retries() { return no_network_max_retries_; }
135
136  void set_server_to_check(ServerToCheck server_to_check) {
137    server_to_check_ = server_to_check;
138  }
139
140  size_t GetBytesDownloaded() override {
141    return static_cast<size_t>(bytes_downloaded_);
142  }
143
144  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
145    low_speed_limit_bps_ = low_speed_bps;
146    low_speed_time_seconds_ = low_speed_sec;
147  }
148
149  void set_connect_timeout(int connect_timeout_seconds) override {
150    connect_timeout_seconds_ = connect_timeout_seconds;
151  }
152
153  void set_max_retry_count(int max_retry_count) override {
154    max_retry_count_ = max_retry_count;
155  }
156
157  void set_is_update_check(bool is_update_check) {
158    is_update_check_ = is_update_check;
159  }
160
161 private:
162  FRIEND_TEST(LibcurlHttpFetcherTest, HostResolvedTest);
163
164  // libcurl's CURLOPT_CLOSESOCKETFUNCTION callback function. Called when
165  // closing a socket created with the CURLOPT_OPENSOCKETFUNCTION callback.
166  static int LibcurlCloseSocketCallback(void* clientp, curl_socket_t item);
167
168  // Callback for when proxy resolution has completed. This begins the
169  // transfer.
170  void ProxiesResolved();
171
172  // Asks libcurl for the http response code and stores it in the object.
173  virtual void GetHttpResponseCode();
174
175  // Returns the last |CURLcode|.
176  CURLcode GetCurlCode();
177
178  // Checks whether stored HTTP response is within the success range.
179  inline bool IsHttpResponseSuccess() {
180    return (http_response_code_ >= 200 && http_response_code_ < 300);
181  }
182
183  // Checks whether stored HTTP response is within the error range. This
184  // includes both errors with the request (4xx) and server errors (5xx).
185  inline bool IsHttpResponseError() {
186    return (http_response_code_ >= 400 && http_response_code_ < 600);
187  }
188
189  // Resumes a transfer where it left off. This will use the
190  // HTTP Range: header to make a new connection from where the last
191  // left off.
192  virtual void ResumeTransfer(const std::string& url);
193
194  void TimeoutCallback();
195  void RetryTimeoutCallback();
196
197  // Calls into curl_multi_perform to let libcurl do its work. Returns after
198  // curl_multi_perform is finished, which may actually be after more than
199  // one call to curl_multi_perform. This method will set up the message
200  // loop with sources for future work that libcurl will do, if any, or complete
201  // the transfer and finish the action if no work left to do.
202  // This method will not block.
203  void CurlPerformOnce();
204
205  // Sets up message loop sources as needed by libcurl. This is generally
206  // the file descriptor of the socket and a timer in case nothing happens
207  // on the fds.
208  void SetupMessageLoopSources();
209
210  // Callback called by libcurl when new data has arrived on the transfer
211  size_t LibcurlWrite(void* ptr, size_t size, size_t nmemb);
212  static size_t StaticLibcurlWrite(void* ptr,
213                                   size_t size,
214                                   size_t nmemb,
215                                   void* stream) {
216    return reinterpret_cast<LibcurlHttpFetcher*>(stream)->LibcurlWrite(
217        ptr, size, nmemb);
218  }
219
220  // Cleans up the following if they are non-null:
221  // curl(m) handles, fd_controller_maps_(fd_task_maps_), timeout_id_.
222  void CleanUp();
223
224  // Force terminate the transfer. This will invoke the delegate's (if any)
225  // TransferTerminated callback so, after returning, this fetcher instance may
226  // be destroyed.
227  void ForceTransferTermination();
228
229  // Sets the curl options for HTTP URL.
230  void SetCurlOptionsForHttp();
231
232  // Sets the curl options for HTTPS URL.
233  void SetCurlOptionsForHttps();
234
235  // Sets the curl options for file URI.
236  void SetCurlOptionsForFile();
237
238  // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
239  // conversion was successful, false otherwise (in which case nothing is
240  // written to |out_type|).
241  bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);
242
243  // Hardware interface used to query dev-mode and official build settings.
244  HardwareInterface* hardware_;
245
246  // Handles for the libcurl library
247  CURLM* curl_multi_handle_{nullptr};
248  CURL* curl_handle_{nullptr};
249  struct curl_slist* curl_http_headers_{nullptr};
250
251  // The extra headers that will be sent on each request.
252  std::map<std::string, std::string> extra_headers_;
253
254  // Lists of all read(0)/write(1) file descriptors that we're waiting on from
255  // the message loop. libcurl may open/close descriptors and switch their
256  // directions so maintain two separate lists so that watch conditions can be
257  // set appropriately.
258  std::map<int, std::unique_ptr<base::FileDescriptorWatcher::Controller>>
259      fd_controller_maps_[2];
260
261  // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
262  // on it.
263  brillo::MessageLoop::TaskId timeout_id_{brillo::MessageLoop::kTaskIdNull};
264
265  bool transfer_in_progress_{false};
266  bool transfer_paused_{false};
267
268  // Whether it should ignore transfer failures for the purpose of retrying the
269  // connection.
270  bool ignore_failure_{false};
271
272  // Whether we should restart the transfer once Unpause() is called. This can
273  // be caused because either the connection dropped while pause or the proxy
274  // was resolved and we never started the transfer in the first place.
275  bool restart_transfer_on_unpause_{false};
276
277  // The transfer size. -1 if not known.
278  off_t transfer_size_{0};
279
280  // How many bytes have been downloaded and sent to the delegate.
281  off_t bytes_downloaded_{0};
282
283  // The remaining maximum number of bytes to download. Zero represents an
284  // unspecified length.
285  size_t download_length_{0};
286
287  // If we resumed an earlier transfer, data offset that we used for the
288  // new connection.  0 otherwise.
289  // In this class, resume refers to resuming a dropped HTTP connection,
290  // not to resuming an interrupted download.
291  off_t resume_offset_{0};
292
293  // Number of resumes performed so far and the max allowed.
294  int retry_count_{0};
295  int max_retry_count_{kDownloadMaxRetryCount};
296
297  // Seconds to wait before retrying a resume.
298  int retry_seconds_{20};
299
300  // When waiting for a retry, the task id of the retry callback.
301  brillo::MessageLoop::TaskId retry_task_id_{brillo::MessageLoop::kTaskIdNull};
302
303  // Number of resumes due to no network (e.g., HTTP response code 0).
304  int no_network_retry_count_{0};
305  int no_network_max_retries_{0};
306
307  // Seconds to wait before asking libcurl to "perform".
308  int idle_seconds_{1};
309
310  // If true, we are currently performing a write callback on the delegate.
311  bool in_write_callback_{false};
312
313  // If true, we have returned at least one byte in the write callback
314  // to the delegate.
315  bool sent_byte_{false};
316
317  // We can't clean everything up while we're in a write callback, so
318  // if we get a terminate request, queue it until we can handle it.
319  bool terminate_requested_{false};
320
321  // The ServerToCheck used when checking this connection's certificate. If no
322  // certificate check needs to be performed, this should be set to
323  // ServerToCheck::kNone.
324  ServerToCheck server_to_check_{ServerToCheck::kNone};
325
326  // True if this object is for update check.
327  bool is_update_check_{false};
328
329  // Internal state machine.
330  UnresolvedHostStateMachine unresolved_host_state_machine_;
331
332  int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
333  int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
334  int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};
335
336  DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
337};
338
339}  // namespace chromeos_update_engine
340
341#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
342