• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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