// // Copyright (C) 2019 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. #include "host/libs/web/curl_wrapper.h" #include #include #include #include #include #include #include #include #include namespace cuttlefish { namespace { size_t curl_to_function_cb(char* ptr, size_t, size_t nmemb, void* userdata) { CurlWrapper::DataCallback* callback = (CurlWrapper::DataCallback*)userdata; if (!(*callback)(ptr, nmemb)) { return 0; // Signals error to curl } return nmemb; } size_t file_write_callback(char *ptr, size_t, size_t nmemb, void *userdata) { std::stringstream* stream = (std::stringstream*) userdata; stream->write(ptr, nmemb); return nmemb; } curl_slist* build_slist(const std::vector& strings) { curl_slist* curl_headers = nullptr; for (const auto& str : strings) { curl_slist* temp = curl_slist_append(curl_headers, str.c_str()); if (temp == nullptr) { LOG(ERROR) << "curl_slist_append failed to add " << str; if (curl_headers) { curl_slist_free_all(curl_headers); return nullptr; } } curl_headers = temp; } return curl_headers; } class CurlWrapperImpl : public CurlWrapper { public: CurlWrapperImpl() { curl_ = curl_easy_init(); if (!curl_) { LOG(ERROR) << "failed to initialize curl"; return; } } ~CurlWrapperImpl() { curl_easy_cleanup(curl_); } CurlResponse PostToString( const std::string& url, const std::string& data_to_write, const std::vector& headers) override { std::lock_guard lock(mutex_); LOG(INFO) << "Attempting to download \"" << url << "\""; if (!curl_) { LOG(ERROR) << "curl was not initialized\n"; return {"", -1}; } curl_slist* curl_headers = build_slist(headers); curl_easy_reset(curl_); curl_easy_setopt(curl_, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt"); curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers); curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data_to_write.size()); curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data_to_write.c_str()); std::stringstream data_to_read; curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback); curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read); char error_buf[CURL_ERROR_SIZE]; curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf); curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl_); if (curl_headers) { curl_slist_free_all(curl_headers); } if (res != CURLE_OK) { LOG(ERROR) << "curl_easy_perform() failed. " << "Code was \"" << res << "\". " << "Strerror was \"" << curl_easy_strerror(res) << "\". " << "Error buffer was \"" << error_buf << "\"."; return {"", -1}; } long http_code = 0; curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); return {data_to_read.str(), http_code}; } CurlResponse PostToJson( const std::string& url, const std::string& data_to_write, const std::vector& headers) override { CurlResponse response = PostToString(url, data_to_write, headers); const std::string& contents = response.data; Json::CharReaderBuilder builder; std::unique_ptr reader(builder.newCharReader()); Json::Value json; std::string errorMessage; if (!reader->parse(&*contents.begin(), &*contents.end(), &json, &errorMessage)) { LOG(ERROR) << "Could not parse json: " << errorMessage; json["error"] = "Failed to parse json."; json["response"] = contents; } return {json, response.http_code}; } CurlResponse PostToJson( const std::string& url, const Json::Value& data_to_write, const std::vector& headers) override { std::stringstream json_str; json_str << data_to_write; return PostToJson(url, json_str.str(), headers); } CurlResponse DownloadToCallback( DataCallback callback, const std::string& url, const std::vector& headers) { std::lock_guard lock(mutex_); LOG(INFO) << "Attempting to download \"" << url << "\""; if (!curl_) { LOG(ERROR) << "curl was not initialized\n"; return {false, -1}; } if (!callback(nullptr, 0)) { // Signal start of data LOG(ERROR) << "Callback failure\n"; return {false, -1}; } curl_slist* curl_headers = build_slist(headers); curl_easy_reset(curl_); curl_easy_setopt(curl_, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt"); curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers); curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_to_function_cb); curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &callback); char error_buf[CURL_ERROR_SIZE]; curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf); curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl_); if (curl_headers) { curl_slist_free_all(curl_headers); } if (res != CURLE_OK) { LOG(ERROR) << "curl_easy_perform() failed. " << "Code was \"" << res << "\". " << "Strerror was \"" << curl_easy_strerror(res) << "\". " << "Error buffer was \"" << error_buf << "\"."; return {false, -1}; } long http_code = 0; curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); return {true, http_code}; } CurlResponse DownloadToFile( const std::string& url, const std::string& path, const std::vector& headers) { LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\""; std::fstream stream; auto callback = [&stream, path](char* data, size_t size) -> bool { if (data == nullptr) { stream.open(path, std::ios::out | std::ios::binary | std::ios::trunc); return !stream.fail(); } stream.write(data, size); return !stream.fail(); }; auto callback_res = DownloadToCallback(callback, url, headers); if (!callback_res.data) { return {"", callback_res.http_code}; } return {path, callback_res.http_code}; std::lock_guard lock(mutex_); if (!curl_) { LOG(ERROR) << "curl was not initialized\n"; return {"", -1}; } } CurlResponse DownloadToString( const std::string& url, const std::vector& headers) { std::stringstream stream; auto callback = [&stream](char* data, size_t size) -> bool { if (data == nullptr) { stream = std::stringstream(); return true; } stream.write(data, size); return true; }; auto callback_res = DownloadToCallback(callback, url, headers); if (!callback_res.data) { return {"", callback_res.http_code}; } return {stream.str(), callback_res.http_code}; } CurlResponse DownloadToJson( const std::string& url, const std::vector& headers) { CurlResponse response = DownloadToString(url, headers); const std::string& contents = response.data; Json::CharReaderBuilder builder; std::unique_ptr reader(builder.newCharReader()); Json::Value json; std::string errorMessage; if (!reader->parse(&*contents.begin(), &*contents.end(), &json, &errorMessage)) { LOG(ERROR) << "Could not parse json: " << errorMessage; json["error"] = "Failed to parse json."; json["response"] = contents; } return {json, response.http_code}; } CurlResponse DeleteToJson( const std::string& url, const std::vector& headers) override { std::lock_guard lock(mutex_); LOG(INFO) << "Attempting to download \"" << url << "\""; if (!curl_) { LOG(ERROR) << "curl was not initialized\n"; return {"", -1}; } curl_slist* curl_headers = build_slist(headers); curl_easy_reset(curl_); curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_easy_setopt(curl_, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt"); curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers); curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); std::stringstream data_to_read; curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, file_write_callback); curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &data_to_read); char error_buf[CURL_ERROR_SIZE]; curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf); curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl_); if (curl_headers) { curl_slist_free_all(curl_headers); } if (res != CURLE_OK) { LOG(ERROR) << "curl_easy_perform() failed. " << "Code was \"" << res << "\". " << "Strerror was \"" << curl_easy_strerror(res) << "\". " << "Error buffer was \"" << error_buf << "\"."; return {"", -1}; } long http_code = 0; curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); auto contents = data_to_read.str(); Json::CharReaderBuilder builder; std::unique_ptr reader(builder.newCharReader()); Json::Value json; std::string errorMessage; if (!reader->parse(&*contents.begin(), &*contents.end(), &json, &errorMessage)) { LOG(ERROR) << "Could not parse json: " << errorMessage; json["error"] = "Failed to parse json."; json["response"] = contents; } return {json, http_code}; } std::string UrlEscape(const std::string& text) override { char* escaped_str = curl_easy_escape(curl_, text.c_str(), text.size()); std::string ret{escaped_str}; curl_free(escaped_str); return ret; } private: CURL* curl_; std::mutex mutex_; }; class CurlServerErrorRetryingWrapper : public CurlWrapper { public: CurlServerErrorRetryingWrapper(CurlWrapper& inner, int retry_attempts, std::chrono::milliseconds retry_delay) : inner_curl_(inner), retry_attempts_(retry_attempts), retry_delay_(retry_delay) {} CurlResponse PostToString( const std::string& url, const std::string& data, const std::vector& headers) override { return RetryImpl( [&, this]() { return inner_curl_.PostToString(url, data, headers); }); } CurlResponse PostToJson( const std::string& url, const Json::Value& data, const std::vector& headers) override { return RetryImpl( [&, this]() { return inner_curl_.PostToJson(url, data, headers); }); } CurlResponse PostToJson( const std::string& url, const std::string& data, const std::vector& headers) override { return RetryImpl( [&, this]() { return inner_curl_.PostToJson(url, data, headers); }); } CurlResponse DownloadToFile( const std::string& url, const std::string& path, const std::vector& headers) { return RetryImpl( [&, this]() { return inner_curl_.DownloadToFile(url, path, headers); }); } CurlResponse DownloadToString( const std::string& url, const std::vector& headers) { return RetryImpl( [&, this]() { return inner_curl_.DownloadToString(url, headers); }); } CurlResponse DownloadToJson( const std::string& url, const std::vector& headers) { return RetryImpl( [&, this]() { return inner_curl_.DownloadToJson(url, headers); }); } CurlResponse DownloadToCallback( DataCallback cb, const std::string& url, const std::vector& hdrs) override { return RetryImpl( [&, this]() { return inner_curl_.DownloadToCallback(cb, url, hdrs); }); } CurlResponse DeleteToJson( const std::string& url, const std::vector& headers) override { return RetryImpl( [&, this]() { return inner_curl_.DeleteToJson(url, headers); }); } std::string UrlEscape(const std::string& text) override { return inner_curl_.UrlEscape(text); } private: template CurlResponse RetryImpl(std::function()> attempt_fn) { CurlResponse response; for (int attempt = 0; attempt != retry_attempts_; ++attempt) { if (attempt != 0) { std::this_thread::sleep_for(retry_delay_); } response = attempt_fn(); if (!response.HttpServerError()) { return response; } } return response; } private: CurlWrapper& inner_curl_; int retry_attempts_; std::chrono::milliseconds retry_delay_; }; } // namespace /* static */ std::unique_ptr CurlWrapper::Create() { return std::unique_ptr(new CurlWrapperImpl()); } /* static */ std::unique_ptr CurlWrapper::WithServerErrorRetry( CurlWrapper& inner, int retry_attempts, std::chrono::milliseconds retry_delay) { return std::unique_ptr( new CurlServerErrorRetryingWrapper(inner, retry_attempts, retry_delay)); } CurlWrapper::~CurlWrapper() = default; }