1 //
2 // Copyright (C) 2019 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 #include "host/libs/web/curl_wrapper.h"
17
18 #include <stdio.h>
19
20 #include <fstream>
21 #include <mutex>
22 #include <sstream>
23 #include <string>
24 #include <thread>
25
26 #include <android-base/logging.h>
27 #include <curl/curl.h>
28 #include <json/json.h>
29
30 namespace cuttlefish {
31 namespace {
32
curl_to_function_cb(char * ptr,size_t,size_t nmemb,void * userdata)33 size_t curl_to_function_cb(char* ptr, size_t, size_t nmemb, void* userdata) {
34 CurlWrapper::DataCallback* callback = (CurlWrapper::DataCallback*)userdata;
35 if (!(*callback)(ptr, nmemb)) {
36 return 0; // Signals error to curl
37 }
38 return nmemb;
39 }
40
file_write_callback(char * ptr,size_t,size_t nmemb,void * userdata)41 size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) {
42 std::stringstream* stream = (std::stringstream*) userdata;
43 stream->write(ptr, nmemb);
44 return nmemb;
45 }
46
build_slist(const std::vector<std::string> & strings)47 curl_slist* build_slist(const std::vector<std::string>& strings) {
48 curl_slist* curl_headers = nullptr;
49 for (const auto& str : strings) {
50 curl_slist* temp = curl_slist_append(curl_headers, str.c_str());
51 if (temp == nullptr) {
52 LOG(ERROR) << "curl_slist_append failed to add " << str;
53 if (curl_headers) {
54 curl_slist_free_all(curl_headers);
55 return nullptr;
56 }
57 }
58 curl_headers = temp;
59 }
60 return curl_headers;
61 }
62
63 class CurlWrapperImpl : public CurlWrapper {
64 public:
CurlWrapperImpl()65 CurlWrapperImpl() {
66 curl_ = curl_easy_init();
67 if (!curl_) {
68 LOG(ERROR) << "failed to initialize curl";
69 return;
70 }
71 }
~CurlWrapperImpl()72 ~CurlWrapperImpl() { curl_easy_cleanup(curl_); }
73
PostToString(const std::string & url,const std::string & data_to_write,const std::vector<std::string> & headers)74 CurlResponse<std::string> PostToString(
75 const std::string& url, const std::string& data_to_write,
76 const std::vector<std::string>& headers) override {
77 std::lock_guard<std::mutex> lock(mutex_);
78 LOG(INFO) << "Attempting to download \"" << url << "\"";
79 if (!curl_) {
80 LOG(ERROR) << "curl was not initialized\n";
81 return {"", -1};
82 }
83 curl_slist* curl_headers = build_slist(headers);
84 curl_easy_reset(curl_);
85 curl_easy_setopt(curl_, CURLOPT_CAINFO,
86 "/etc/ssl/certs/ca-certificates.crt");
87 curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
88 curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
89 curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data_to_write.size());
90 curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data_to_write.c_str());
91 std::stringstream data_to_read;
92 curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
93 curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
94 char error_buf[CURL_ERROR_SIZE];
95 curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
96 curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
97 CURLcode res = curl_easy_perform(curl_);
98 if (curl_headers) {
99 curl_slist_free_all(curl_headers);
100 }
101 if (res != CURLE_OK) {
102 LOG(ERROR) << "curl_easy_perform() failed. "
103 << "Code was \"" << res << "\". "
104 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
105 << "Error buffer was \"" << error_buf << "\".";
106 return {"", -1};
107 }
108 long http_code = 0;
109 curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
110 return {data_to_read.str(), http_code};
111 }
112
PostToJson(const std::string & url,const std::string & data_to_write,const std::vector<std::string> & headers)113 CurlResponse<Json::Value> PostToJson(
114 const std::string& url, const std::string& data_to_write,
115 const std::vector<std::string>& headers) override {
116 CurlResponse<std::string> response =
117 PostToString(url, data_to_write, headers);
118 const std::string& contents = response.data;
119 Json::CharReaderBuilder builder;
120 std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
121 Json::Value json;
122 std::string errorMessage;
123 if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
124 &errorMessage)) {
125 LOG(ERROR) << "Could not parse json: " << errorMessage;
126 json["error"] = "Failed to parse json.";
127 json["response"] = contents;
128 }
129 return {json, response.http_code};
130 }
131
PostToJson(const std::string & url,const Json::Value & data_to_write,const std::vector<std::string> & headers)132 CurlResponse<Json::Value> PostToJson(
133 const std::string& url, const Json::Value& data_to_write,
134 const std::vector<std::string>& headers) override {
135 std::stringstream json_str;
136 json_str << data_to_write;
137 return PostToJson(url, json_str.str(), headers);
138 }
139
DownloadToCallback(DataCallback callback,const std::string & url,const std::vector<std::string> & headers)140 CurlResponse<bool> DownloadToCallback(
141 DataCallback callback, const std::string& url,
142 const std::vector<std::string>& headers) {
143 std::lock_guard<std::mutex> lock(mutex_);
144 LOG(INFO) << "Attempting to download \"" << url << "\"";
145 if (!curl_) {
146 LOG(ERROR) << "curl was not initialized\n";
147 return {false, -1};
148 }
149 if (!callback(nullptr, 0)) { // Signal start of data
150 LOG(ERROR) << "Callback failure\n";
151 return {false, -1};
152 }
153 curl_slist* curl_headers = build_slist(headers);
154 curl_easy_reset(curl_);
155 curl_easy_setopt(curl_, CURLOPT_CAINFO,
156 "/etc/ssl/certs/ca-certificates.crt");
157 curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
158 curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
159 curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_to_function_cb);
160 curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &callback);
161 char error_buf[CURL_ERROR_SIZE];
162 curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
163 curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
164 CURLcode res = curl_easy_perform(curl_);
165 if (curl_headers) {
166 curl_slist_free_all(curl_headers);
167 }
168 if (res != CURLE_OK) {
169 LOG(ERROR) << "curl_easy_perform() failed. "
170 << "Code was \"" << res << "\". "
171 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
172 << "Error buffer was \"" << error_buf << "\".";
173 return {false, -1};
174 }
175 long http_code = 0;
176 curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
177 return {true, http_code};
178 }
179
DownloadToFile(const std::string & url,const std::string & path,const std::vector<std::string> & headers)180 CurlResponse<std::string> DownloadToFile(
181 const std::string& url, const std::string& path,
182 const std::vector<std::string>& headers) {
183 LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\"";
184 std::fstream stream;
185 auto callback = [&stream, path](char* data, size_t size) -> bool {
186 if (data == nullptr) {
187 stream.open(path, std::ios::out | std::ios::binary | std::ios::trunc);
188 return !stream.fail();
189 }
190 stream.write(data, size);
191 return !stream.fail();
192 };
193 auto callback_res = DownloadToCallback(callback, url, headers);
194 if (!callback_res.data) {
195 return {"", callback_res.http_code};
196 }
197 return {path, callback_res.http_code};
198 std::lock_guard<std::mutex> lock(mutex_);
199 if (!curl_) {
200 LOG(ERROR) << "curl was not initialized\n";
201 return {"", -1};
202 }
203 }
204
DownloadToString(const std::string & url,const std::vector<std::string> & headers)205 CurlResponse<std::string> DownloadToString(
206 const std::string& url, const std::vector<std::string>& headers) {
207 std::stringstream stream;
208 auto callback = [&stream](char* data, size_t size) -> bool {
209 if (data == nullptr) {
210 stream = std::stringstream();
211 return true;
212 }
213 stream.write(data, size);
214 return true;
215 };
216 auto callback_res = DownloadToCallback(callback, url, headers);
217 if (!callback_res.data) {
218 return {"", callback_res.http_code};
219 }
220 return {stream.str(), callback_res.http_code};
221 }
222
DownloadToJson(const std::string & url,const std::vector<std::string> & headers)223 CurlResponse<Json::Value> DownloadToJson(
224 const std::string& url, const std::vector<std::string>& headers) {
225 CurlResponse<std::string> response = DownloadToString(url, headers);
226 const std::string& contents = response.data;
227 Json::CharReaderBuilder builder;
228 std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
229 Json::Value json;
230 std::string errorMessage;
231 if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
232 &errorMessage)) {
233 LOG(ERROR) << "Could not parse json: " << errorMessage;
234 json["error"] = "Failed to parse json.";
235 json["response"] = contents;
236 }
237 return {json, response.http_code};
238 }
239
DeleteToJson(const std::string & url,const std::vector<std::string> & headers)240 CurlResponse<Json::Value> DeleteToJson(
241 const std::string& url,
242 const std::vector<std::string>& headers) override {
243 std::lock_guard<std::mutex> lock(mutex_);
244 LOG(INFO) << "Attempting to download \"" << url << "\"";
245 if (!curl_) {
246 LOG(ERROR) << "curl was not initialized\n";
247 return {"", -1};
248 }
249 curl_slist* curl_headers = build_slist(headers);
250 curl_easy_reset(curl_);
251 curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
252 curl_easy_setopt(curl_, CURLOPT_CAINFO,
253 "/etc/ssl/certs/ca-certificates.crt");
254 curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers);
255 curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
256 std::stringstream data_to_read;
257 curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback);
258 curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read);
259 char error_buf[CURL_ERROR_SIZE];
260 curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf);
261 curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
262 CURLcode res = curl_easy_perform(curl_);
263 if (curl_headers) {
264 curl_slist_free_all(curl_headers);
265 }
266 if (res != CURLE_OK) {
267 LOG(ERROR) << "curl_easy_perform() failed. "
268 << "Code was \"" << res << "\". "
269 << "Strerror was \"" << curl_easy_strerror(res) << "\". "
270 << "Error buffer was \"" << error_buf << "\".";
271 return {"", -1};
272 }
273 long http_code = 0;
274 curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
275
276 auto contents = data_to_read.str();
277 Json::CharReaderBuilder builder;
278 std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
279 Json::Value json;
280 std::string errorMessage;
281 if (!reader->parse(&*contents.begin(), &*contents.end(), &json,
282 &errorMessage)) {
283 LOG(ERROR) << "Could not parse json: " << errorMessage;
284 json["error"] = "Failed to parse json.";
285 json["response"] = contents;
286 }
287 return {json, http_code};
288 }
289
UrlEscape(const std::string & text)290 std::string UrlEscape(const std::string& text) override {
291 char* escaped_str = curl_easy_escape(curl_, text.c_str(), text.size());
292 std::string ret{escaped_str};
293 curl_free(escaped_str);
294 return ret;
295 }
296
297 private:
298 CURL* curl_;
299 std::mutex mutex_;
300 };
301
302 class CurlServerErrorRetryingWrapper : public CurlWrapper {
303 public:
CurlServerErrorRetryingWrapper(CurlWrapper & inner,int retry_attempts,std::chrono::milliseconds retry_delay)304 CurlServerErrorRetryingWrapper(CurlWrapper& inner, int retry_attempts,
305 std::chrono::milliseconds retry_delay)
306 : inner_curl_(inner),
307 retry_attempts_(retry_attempts),
308 retry_delay_(retry_delay) {}
309
PostToString(const std::string & url,const std::string & data,const std::vector<std::string> & headers)310 CurlResponse<std::string> PostToString(
311 const std::string& url, const std::string& data,
312 const std::vector<std::string>& headers) override {
313 return RetryImpl<std::string>(
314 [&, this]() { return inner_curl_.PostToString(url, data, headers); });
315 }
316
PostToJson(const std::string & url,const Json::Value & data,const std::vector<std::string> & headers)317 CurlResponse<Json::Value> PostToJson(
318 const std::string& url, const Json::Value& data,
319 const std::vector<std::string>& headers) override {
320 return RetryImpl<Json::Value>(
321 [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
322 }
323
PostToJson(const std::string & url,const std::string & data,const std::vector<std::string> & headers)324 CurlResponse<Json::Value> PostToJson(
325 const std::string& url, const std::string& data,
326 const std::vector<std::string>& headers) override {
327 return RetryImpl<Json::Value>(
328 [&, this]() { return inner_curl_.PostToJson(url, data, headers); });
329 }
330
DownloadToFile(const std::string & url,const std::string & path,const std::vector<std::string> & headers)331 CurlResponse<std::string> DownloadToFile(
332 const std::string& url, const std::string& path,
333 const std::vector<std::string>& headers) {
334 return RetryImpl<std::string>(
335 [&, this]() { return inner_curl_.DownloadToFile(url, path, headers); });
336 }
337
DownloadToString(const std::string & url,const std::vector<std::string> & headers)338 CurlResponse<std::string> DownloadToString(
339 const std::string& url, const std::vector<std::string>& headers) {
340 return RetryImpl<std::string>(
341 [&, this]() { return inner_curl_.DownloadToString(url, headers); });
342 }
343
DownloadToJson(const std::string & url,const std::vector<std::string> & headers)344 CurlResponse<Json::Value> DownloadToJson(
345 const std::string& url, const std::vector<std::string>& headers) {
346 return RetryImpl<Json::Value>(
347 [&, this]() { return inner_curl_.DownloadToJson(url, headers); });
348 }
349
DownloadToCallback(DataCallback cb,const std::string & url,const std::vector<std::string> & hdrs)350 CurlResponse<bool> DownloadToCallback(
351 DataCallback cb, const std::string& url,
352 const std::vector<std::string>& hdrs) override {
353 return RetryImpl<bool>(
354 [&, this]() { return inner_curl_.DownloadToCallback(cb, url, hdrs); });
355 }
DeleteToJson(const std::string & url,const std::vector<std::string> & headers)356 CurlResponse<Json::Value> DeleteToJson(
357 const std::string& url,
358 const std::vector<std::string>& headers) override {
359 return RetryImpl<Json::Value>(
360 [&, this]() { return inner_curl_.DeleteToJson(url, headers); });
361 }
362
UrlEscape(const std::string & text)363 std::string UrlEscape(const std::string& text) override {
364 return inner_curl_.UrlEscape(text);
365 }
366
367 private:
368 template <typename T>
RetryImpl(std::function<CurlResponse<T> ()> attempt_fn)369 CurlResponse<T> RetryImpl(std::function<CurlResponse<T>()> attempt_fn) {
370 CurlResponse<T> response;
371 for (int attempt = 0; attempt != retry_attempts_; ++attempt) {
372 if (attempt != 0) {
373 std::this_thread::sleep_for(retry_delay_);
374 }
375 response = attempt_fn();
376 if (!response.HttpServerError()) {
377 return response;
378 }
379 }
380 return response;
381 }
382
383 private:
384 CurlWrapper& inner_curl_;
385 int retry_attempts_;
386 std::chrono::milliseconds retry_delay_;
387 };
388
389 } // namespace
390
Create()391 /* static */ std::unique_ptr<CurlWrapper> CurlWrapper::Create() {
392 return std::unique_ptr<CurlWrapper>(new CurlWrapperImpl());
393 }
394
WithServerErrorRetry(CurlWrapper & inner,int retry_attempts,std::chrono::milliseconds retry_delay)395 /* static */ std::unique_ptr<CurlWrapper> CurlWrapper::WithServerErrorRetry(
396 CurlWrapper& inner, int retry_attempts,
397 std::chrono::milliseconds retry_delay) {
398 return std::unique_ptr<CurlWrapper>(
399 new CurlServerErrorRetryingWrapper(inner, retry_attempts, retry_delay));
400 }
401
402 CurlWrapper::~CurlWrapper() = default;
403 }
404