Aktualizr
C++ SOTA Client
httpclient.cc
1 #include "httpclient.h"
2 
3 #include <cassert>
4 #include <sstream>
5 
6 #include "utilities/aktualizr_version.h"
7 #include "utilities/utils.h"
8 
9 struct WriteStringArg {
10  std::string out;
11  int64_t limit{0};
12 };
13 
14 /*****************************************************************************/
15 /**
16  * \par Description:
17  * A writeback handler for the curl library. It handles writing response
18  * data from curl into a string.
19  * https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
20  *
21  */
22 static size_t writeString(void* contents, size_t size, size_t nmemb, void* userp) {
23  assert(contents);
24  assert(userp);
25  // append the writeback data to the provided string
26  auto* arg = static_cast<WriteStringArg*>(userp);
27  if (arg->limit > 0) {
28  if (arg->out.length() + size * nmemb > static_cast<uint64_t>(arg->limit)) {
29  return 0;
30  }
31  }
32  (static_cast<WriteStringArg*>(userp))->out.append(static_cast<char*>(contents), size * nmemb);
33 
34  // return size of written data
35  return size * nmemb;
36 }
37 
38 HttpClient::HttpClient(const std::vector<std::string>* extra_headers) {
39  curl = curl_easy_init();
40  if (curl == nullptr) {
41  throw std::runtime_error("Could not initialize curl");
42  }
43  headers = nullptr;
44 
45  curlEasySetoptWrapper(curl, CURLOPT_NOSIGNAL, 1L);
46  curlEasySetoptWrapper(curl, CURLOPT_TIMEOUT, 60L);
47  curlEasySetoptWrapper(curl, CURLOPT_CONNECTTIMEOUT, 60L);
48  curlEasySetoptWrapper(curl, CURLOPT_CAPATH, Utils::getCaPath());
49 
50  // let curl use our write function
51  curlEasySetoptWrapper(curl, CURLOPT_WRITEFUNCTION, writeString);
52  curlEasySetoptWrapper(curl, CURLOPT_WRITEDATA, NULL);
53 
54  curlEasySetoptWrapper(curl, CURLOPT_VERBOSE, get_curlopt_verbose());
55 
56  headers = curl_slist_append(headers, "Accept: */*");
57 
58  if (extra_headers != nullptr) {
59  for (const auto& header : *extra_headers) {
60  headers = curl_slist_append(headers, header.c_str());
61  }
62  }
63  curlEasySetoptWrapper(curl, CURLOPT_USERAGENT, Utils::getUserAgent());
64 }
65 
66 HttpClient::HttpClient(const HttpClient& curl_in) : pkcs11_key(curl_in.pkcs11_key), pkcs11_cert(curl_in.pkcs11_key) {
67  curl = curl_easy_duphandle(curl_in.curl);
68  headers = curl_slist_dup(curl_in.headers);
69 }
70 
71 CurlGlobalInitWrapper HttpClient::manageCurlGlobalInit_{};
72 
73 HttpClient::~HttpClient() {
74  curl_slist_free_all(headers);
75  curl_easy_cleanup(curl);
76 }
77 
78 void HttpClient::setCerts(const std::string& ca, CryptoSource ca_source, const std::string& cert,
79  CryptoSource cert_source, const std::string& pkey, CryptoSource pkey_source) {
80  curlEasySetoptWrapper(curl, CURLOPT_SSL_VERIFYPEER, 1);
81  curlEasySetoptWrapper(curl, CURLOPT_SSL_VERIFYHOST, 2);
82  curlEasySetoptWrapper(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
83 
84  if (ca_source == CryptoSource::kPkcs11) {
85  throw std::runtime_error("Accessing CA certificate on PKCS11 devices isn't currently supported");
86  }
87  std::unique_ptr<TemporaryFile> tmp_ca_file = std_::make_unique<TemporaryFile>("tls-ca");
88  tmp_ca_file->PutContents(ca);
89  curlEasySetoptWrapper(curl, CURLOPT_CAINFO, tmp_ca_file->Path().c_str());
90  tls_ca_file = std::move_if_noexcept(tmp_ca_file);
91 
92  if (cert_source == CryptoSource::kPkcs11) {
93  curlEasySetoptWrapper(curl, CURLOPT_SSLCERT, cert.c_str());
94  curlEasySetoptWrapper(curl, CURLOPT_SSLCERTTYPE, "ENG");
95  } else { // cert_source == CryptoSource::kFile
96  std::unique_ptr<TemporaryFile> tmp_cert_file = std_::make_unique<TemporaryFile>("tls-cert");
97  tmp_cert_file->PutContents(cert);
98  curlEasySetoptWrapper(curl, CURLOPT_SSLCERT, tmp_cert_file->Path().c_str());
99  curlEasySetoptWrapper(curl, CURLOPT_SSLCERTTYPE, "PEM");
100  tls_cert_file = std::move_if_noexcept(tmp_cert_file);
101  }
102  pkcs11_cert = (cert_source == CryptoSource::kPkcs11);
103 
104  if (pkey_source == CryptoSource::kPkcs11) {
105  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE, "pkcs11");
106  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE_DEFAULT, 1L);
107  curlEasySetoptWrapper(curl, CURLOPT_SSLKEY, pkey.c_str());
108  curlEasySetoptWrapper(curl, CURLOPT_SSLKEYTYPE, "ENG");
109  } else { // pkey_source == CryptoSource::kFile
110  std::unique_ptr<TemporaryFile> tmp_pkey_file = std_::make_unique<TemporaryFile>("tls-pkey");
111  tmp_pkey_file->PutContents(pkey);
112  curlEasySetoptWrapper(curl, CURLOPT_SSLKEY, tmp_pkey_file->Path().c_str());
113  curlEasySetoptWrapper(curl, CURLOPT_SSLKEYTYPE, "PEM");
114  tls_pkey_file = std::move_if_noexcept(tmp_pkey_file);
115  }
116  pkcs11_key = (pkey_source == CryptoSource::kPkcs11);
117 }
118 
119 HttpResponse HttpClient::get(const std::string& url, int64_t maxsize) {
120  CURL* curl_get = Utils::curlDupHandleWrapper(curl, pkcs11_key);
121 
122  curlEasySetoptWrapper(curl_get, CURLOPT_HTTPHEADER, headers);
123 
124  if (pkcs11_cert) {
125  curlEasySetoptWrapper(curl_get, CURLOPT_SSLCERTTYPE, "ENG");
126  }
127 
128  // Clear POSTFIELDS to remove any lingering references to strings that have
129  // probably since been deallocated.
130  curlEasySetoptWrapper(curl_get, CURLOPT_POSTFIELDS, "");
131  curlEasySetoptWrapper(curl_get, CURLOPT_URL, url.c_str());
132  curlEasySetoptWrapper(curl_get, CURLOPT_HTTPGET, 1L);
133  LOG_DEBUG << "GET " << url;
134  HttpResponse response = perform(curl_get, RETRY_TIMES, maxsize);
135  curl_easy_cleanup(curl_get);
136  return response;
137 }
138 
139 HttpResponse HttpClient::post(const std::string& url, const std::string& content_type, const std::string& data) {
140  CURL* curl_post = Utils::curlDupHandleWrapper(curl, pkcs11_key);
141  curl_slist* req_headers = curl_slist_dup(headers);
142  req_headers = curl_slist_append(req_headers, (std::string("Content-Type: ") + content_type).c_str());
143  curlEasySetoptWrapper(curl_post, CURLOPT_HTTPHEADER, req_headers);
144  curlEasySetoptWrapper(curl_post, CURLOPT_URL, url.c_str());
145  curlEasySetoptWrapper(curl_post, CURLOPT_POST, 1);
146  curlEasySetoptWrapper(curl_post, CURLOPT_POSTFIELDS, data.c_str());
147  auto result = perform(curl_post, RETRY_TIMES, HttpInterface::kPostRespLimit);
148  curl_easy_cleanup(curl_post);
149  curl_slist_free_all(req_headers);
150  return result;
151 }
152 
153 HttpResponse HttpClient::post(const std::string& url, const Json::Value& data) {
154  std::string data_str = Utils::jsonToCanonicalStr(data);
155  LOG_TRACE << "post request body:" << data;
156  return post(url, "application/json", data_str);
157 }
158 
159 HttpResponse HttpClient::put(const std::string& url, const std::string& content_type, const std::string& data) {
160  CURL* curl_put = Utils::curlDupHandleWrapper(curl, pkcs11_key);
161  curl_slist* req_headers = curl_slist_dup(headers);
162  req_headers = curl_slist_append(req_headers, (std::string("Content-Type: ") + content_type).c_str());
163  curlEasySetoptWrapper(curl_put, CURLOPT_HTTPHEADER, req_headers);
164  curlEasySetoptWrapper(curl_put, CURLOPT_URL, url.c_str());
165  curlEasySetoptWrapper(curl_put, CURLOPT_POSTFIELDS, data.c_str());
166  curlEasySetoptWrapper(curl_put, CURLOPT_CUSTOMREQUEST, "PUT");
167  HttpResponse result = perform(curl_put, RETRY_TIMES, HttpInterface::kPutRespLimit);
168  curl_easy_cleanup(curl_put);
169  curl_slist_free_all(req_headers);
170  return result;
171 }
172 
173 HttpResponse HttpClient::put(const std::string& url, const Json::Value& data) {
174  std::string data_str = Utils::jsonToCanonicalStr(data);
175  LOG_TRACE << "put request body:" << data;
176  return put(url, "application/json", data_str);
177 }
178 
179 HttpResponse HttpClient::perform(CURL* curl_handler, int retry_times, int64_t size_limit) {
180  if (size_limit >= 0) {
181  // it will only take effect if the server declares the size in advance,
182  // writeString callback takes care of the other case
183  curlEasySetoptWrapper(curl_handler, CURLOPT_MAXFILESIZE_LARGE, size_limit);
184  }
185  curlEasySetoptWrapper(curl_handler, CURLOPT_LOW_SPEED_TIME, speed_limit_time_interval_);
186  curlEasySetoptWrapper(curl_handler, CURLOPT_LOW_SPEED_LIMIT, speed_limit_bytes_per_sec_);
187 
188  WriteStringArg response_arg;
189  response_arg.limit = size_limit;
190  curlEasySetoptWrapper(curl_handler, CURLOPT_WRITEDATA, static_cast<void*>(&response_arg));
191  CURLcode result = curl_easy_perform(curl_handler);
192  long http_code; // NOLINT(google-runtime-int)
193  curl_easy_getinfo(curl_handler, CURLINFO_RESPONSE_CODE, &http_code);
194  HttpResponse response(response_arg.out, http_code, result, (result != CURLE_OK) ? curl_easy_strerror(result) : "");
195  if (response.curl_code != CURLE_OK || response.http_status_code >= 500) {
196  std::ostringstream error_message;
197  error_message << "curl error " << response.curl_code << " (http code " << response.http_status_code
198  << "): " << response.error_message;
199  LOG_ERROR << error_message.str();
200  if (retry_times != 0) {
201  sleep(1);
202  response = perform(curl_handler, --retry_times, size_limit);
203  }
204  }
205  LOG_TRACE << "response http code: " << response.http_status_code;
206  LOG_TRACE << "response: " << response.body;
207  return response;
208 }
209 
210 HttpResponse HttpClient::download(const std::string& url, curl_write_callback write_cb,
211  curl_xferinfo_callback progress_cb, void* userp, curl_off_t from) {
212  return downloadAsync(url, write_cb, progress_cb, userp, from, nullptr).get();
213 }
214 
215 std::future<HttpResponse> HttpClient::downloadAsync(const std::string& url, curl_write_callback write_cb,
216  curl_xferinfo_callback progress_cb, void* userp, curl_off_t from,
217  CurlHandler* easyp) {
218  CURL* curl_download = Utils::curlDupHandleWrapper(curl, pkcs11_key);
219 
220  CurlHandler curlp = CurlHandler(curl_download, curl_easy_cleanup);
221 
222  if (easyp != nullptr) {
223  *easyp = curlp;
224  }
225 
226  curlEasySetoptWrapper(curl_download, CURLOPT_URL, url.c_str());
227  curlEasySetoptWrapper(curl_download, CURLOPT_HTTPGET, 1L);
228  curlEasySetoptWrapper(curl_download, CURLOPT_FOLLOWLOCATION, 1L);
229  curlEasySetoptWrapper(curl_download, CURLOPT_MAXREDIRS, 10L);
230  curlEasySetoptWrapper(curl_download, CURLOPT_WRITEFUNCTION, write_cb);
231  curlEasySetoptWrapper(curl_download, CURLOPT_WRITEDATA, userp);
232  if (progress_cb != nullptr) {
233  curlEasySetoptWrapper(curl_download, CURLOPT_NOPROGRESS, 0);
234  curlEasySetoptWrapper(curl_download, CURLOPT_XFERINFOFUNCTION, progress_cb);
235  curlEasySetoptWrapper(curl_download, CURLOPT_XFERINFODATA, userp);
236  }
237  curlEasySetoptWrapper(curl_download, CURLOPT_TIMEOUT, 0);
238  curlEasySetoptWrapper(curl_download, CURLOPT_LOW_SPEED_TIME, speed_limit_time_interval_);
239  curlEasySetoptWrapper(curl_download, CURLOPT_LOW_SPEED_LIMIT, speed_limit_bytes_per_sec_);
240  curlEasySetoptWrapper(curl_download, CURLOPT_RESUME_FROM_LARGE, from);
241 
242  std::promise<HttpResponse> resp_promise;
243  auto resp_future = resp_promise.get_future();
244  std::thread(
245  [curlp](std::promise<HttpResponse> promise) {
246  CURLcode result = curl_easy_perform(curlp.get());
247  long http_code; // NOLINT(google-runtime-int)
248  curl_easy_getinfo(curlp.get(), CURLINFO_RESPONSE_CODE, &http_code);
249  HttpResponse response("", http_code, result, (result != CURLE_OK) ? curl_easy_strerror(result) : "");
250  promise.set_value(response);
251  },
252  std::move(resp_promise))
253  .detach();
254  return resp_future;
255 }
256 
257 bool HttpClient::updateHeader(const std::string& name, const std::string& value) {
258  curl_slist* item = headers;
259  std::string lookfor(name + ": ");
260 
261  while (item != nullptr) {
262  if (strncmp(lookfor.c_str(), item->data, lookfor.length()) == 0) {
263  free(item->data); // NOLINT(cppcoreguidelines-no-malloc, hicpp-no-malloc)
264  lookfor += value;
265  item->data = strdup(lookfor.c_str());
266  return true;
267  }
268  item = item->next;
269  }
270  return false;
271 }
272 
273 curl_slist* HttpClient::curl_slist_dup(curl_slist* sl) {
274  curl_slist* new_list = nullptr;
275 
276  for (curl_slist* item = sl; item != nullptr; item = item->next) {
277  new_list = curl_slist_append(new_list, item->data);
278  }
279 
280  return new_list;
281 }
282 
283 // vim: set tabstop=2 shiftwidth=2 expandtab:
WriteStringArg
Definition: httpclient.cc:9
data
General data structures.
Definition: types.h:217
HttpResponse
Definition: httpinterface.h:17
CurlGlobalInitWrapper
Helper class to manage curl_global_init/curl_global_cleanup calls.
Definition: httpclient.h:18
HttpClient
Definition: httpclient.h:28
result
Results of libaktualizr API calls.
Definition: results.h:12