| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmCTestCurl.h" |
| |
| #include <cstdio> |
| #include <ostream> |
| |
| #include <cmext/algorithm> |
| |
| #include "cmCTest.h" |
| #include "cmCurl.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| |
| cmCTestCurl::cmCTestCurl(cmCTest* ctest) |
| { |
| this->CTest = ctest; |
| this->SetProxyType(); |
| this->UseHttp10 = false; |
| // In windows, this will init the winsock stuff |
| ::curl_global_init(CURL_GLOBAL_ALL); |
| // default is to verify https |
| this->VerifyPeerOff = false; |
| this->VerifyHostOff = false; |
| this->Quiet = false; |
| this->TimeOutSeconds = 0; |
| this->Curl = curl_easy_init(); |
| } |
| |
| cmCTestCurl::~cmCTestCurl() |
| { |
| ::curl_easy_cleanup(this->Curl); |
| ::curl_global_cleanup(); |
| } |
| |
| std::string cmCTestCurl::Escape(std::string const& source) |
| { |
| char* data1 = curl_easy_escape(this->Curl, source.c_str(), 0); |
| std::string ret = data1; |
| curl_free(data1); |
| return ret; |
| } |
| |
| namespace { |
| size_t curlWriteMemoryCallback(void* ptr, size_t size, size_t nmemb, |
| void* data) |
| { |
| int realsize = static_cast<int>(size * nmemb); |
| const char* chPtr = static_cast<char*>(ptr); |
| cm::append(*static_cast<std::vector<char>*>(data), chPtr, chPtr + realsize); |
| return realsize; |
| } |
| |
| size_t curlDebugCallback(CURL* /*unused*/, curl_infotype /*unused*/, |
| char* chPtr, size_t size, void* data) |
| { |
| cm::append(*static_cast<std::vector<char>*>(data), chPtr, chPtr + size); |
| return 0; |
| } |
| } |
| |
| void cmCTestCurl::SetCurlOptions(std::vector<std::string> const& args) |
| { |
| for (std::string const& arg : args) { |
| if (arg == "CURLOPT_SSL_VERIFYPEER_OFF") { |
| this->VerifyPeerOff = true; |
| } |
| if (arg == "CURLOPT_SSL_VERIFYHOST_OFF") { |
| this->VerifyHostOff = true; |
| } |
| } |
| } |
| |
| bool cmCTestCurl::InitCurl() |
| { |
| if (!this->Curl) { |
| return false; |
| } |
| cmCurlSetCAInfo(this->Curl); |
| if (this->VerifyPeerOff) { |
| curl_easy_setopt(this->Curl, CURLOPT_SSL_VERIFYPEER, 0); |
| } |
| if (this->VerifyHostOff) { |
| curl_easy_setopt(this->Curl, CURLOPT_SSL_VERIFYHOST, 0); |
| } |
| if (!this->HTTPProxy.empty()) { |
| curl_easy_setopt(this->Curl, CURLOPT_PROXY, this->HTTPProxy.c_str()); |
| curl_easy_setopt(this->Curl, CURLOPT_PROXYTYPE, this->HTTPProxyType); |
| if (!this->HTTPProxyAuth.empty()) { |
| curl_easy_setopt(this->Curl, CURLOPT_PROXYUSERPWD, |
| this->HTTPProxyAuth.c_str()); |
| } |
| } |
| if (this->UseHttp10) { |
| curl_easy_setopt(this->Curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); |
| } |
| // enable HTTP ERROR parsing |
| curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); |
| |
| // if there is little to no activity for too long stop submitting |
| if (this->TimeOutSeconds) { |
| curl_easy_setopt(this->Curl, CURLOPT_LOW_SPEED_LIMIT, 1); |
| curl_easy_setopt(this->Curl, CURLOPT_LOW_SPEED_TIME, this->TimeOutSeconds); |
| } |
| |
| return true; |
| } |
| |
| bool cmCTestCurl::UploadFile(std::string const& local_file, |
| std::string const& url, std::string const& fields, |
| std::string& response) |
| { |
| response.clear(); |
| if (!this->InitCurl()) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, "Initialization of curl failed\n"); |
| return false; |
| } |
| /* enable uploading */ |
| curl_easy_setopt(this->Curl, CURLOPT_UPLOAD, 1); |
| |
| /* HTTP PUT please */ |
| ::curl_easy_setopt(this->Curl, CURLOPT_PUT, 1); |
| ::curl_easy_setopt(this->Curl, CURLOPT_VERBOSE, 1); |
| |
| FILE* ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); |
| if (!ftpfile) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, |
| "Could not open file for upload: " << local_file << "\n"); |
| return false; |
| } |
| // set the url |
| std::string upload_url = cmStrCat(url, '?', fields); |
| ::curl_easy_setopt(this->Curl, CURLOPT_URL, upload_url.c_str()); |
| // now specify which file to upload |
| ::curl_easy_setopt(this->Curl, CURLOPT_INFILE, ftpfile); |
| unsigned long filelen = cmSystemTools::FileLength(local_file); |
| // and give the size of the upload (optional) |
| ::curl_easy_setopt(this->Curl, CURLOPT_INFILESIZE, |
| static_cast<long>(filelen)); |
| ::curl_easy_setopt(this->Curl, CURLOPT_WRITEFUNCTION, |
| curlWriteMemoryCallback); |
| ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGFUNCTION, curlDebugCallback); |
| // Set Content-Type to satisfy fussy modsecurity rules. |
| struct curl_slist* headers = |
| ::curl_slist_append(nullptr, "Content-Type: text/xml"); |
| // Add any additional headers that the user specified. |
| for (std::string const& h : this->HttpHeaders) { |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| " Add HTTP Header: \"" << h << "\"" << std::endl, |
| this->Quiet); |
| headers = ::curl_slist_append(headers, h.c_str()); |
| } |
| ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, headers); |
| std::vector<char> responseData; |
| std::vector<char> debugData; |
| ::curl_easy_setopt(this->Curl, CURLOPT_FILE, &responseData); |
| ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGDATA, &debugData); |
| ::curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); |
| // Now run off and do what you've been told! |
| ::curl_easy_perform(this->Curl); |
| ::fclose(ftpfile); |
| ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, NULL); |
| ::curl_slist_free_all(headers); |
| |
| if (!responseData.empty()) { |
| response = std::string(responseData.begin(), responseData.end()); |
| cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, |
| "Curl response: [" << response << "]\n", this->Quiet); |
| } |
| std::string curlDebug; |
| if (!debugData.empty()) { |
| curlDebug = std::string(debugData.begin(), debugData.end()); |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Curl debug: [" << curlDebug << "]\n", this->Quiet); |
| } |
| if (response.empty()) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, |
| "No response from server.\n" |
| << curlDebug << std::endl); |
| return false; |
| } |
| return true; |
| } |
| |
| bool cmCTestCurl::HttpRequest(std::string const& url, |
| std::string const& fields, std::string& response) |
| { |
| response.clear(); |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "HttpRequest\n" |
| << "url: " << url << "\n" |
| << "fields " << fields << "\n", |
| this->Quiet); |
| if (!this->InitCurl()) { |
| cmCTestLog(this->CTest, ERROR_MESSAGE, "Initialization of curl failed\n"); |
| return false; |
| } |
| curl_easy_setopt(this->Curl, CURLOPT_POST, 1); |
| curl_easy_setopt(this->Curl, CURLOPT_POSTFIELDS, fields.c_str()); |
| ::curl_easy_setopt(this->Curl, CURLOPT_URL, url.c_str()); |
| ::curl_easy_setopt(this->Curl, CURLOPT_FOLLOWLOCATION, 1); |
| // set response options |
| ::curl_easy_setopt(this->Curl, CURLOPT_WRITEFUNCTION, |
| curlWriteMemoryCallback); |
| ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGFUNCTION, curlDebugCallback); |
| std::vector<char> responseData; |
| std::vector<char> debugData; |
| ::curl_easy_setopt(this->Curl, CURLOPT_FILE, &responseData); |
| ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGDATA, &debugData); |
| ::curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); |
| |
| // Add headers if any were specified. |
| struct curl_slist* headers = nullptr; |
| if (!this->HttpHeaders.empty()) { |
| for (std::string const& h : this->HttpHeaders) { |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| " Add HTTP Header: \"" << h << "\"" << std::endl, |
| this->Quiet); |
| headers = ::curl_slist_append(headers, h.c_str()); |
| } |
| } |
| |
| ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, headers); |
| CURLcode res = ::curl_easy_perform(this->Curl); |
| ::curl_slist_free_all(headers); |
| |
| if (!responseData.empty()) { |
| response = std::string(responseData.begin(), responseData.end()); |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Curl response: [" << response << "]\n", this->Quiet); |
| } |
| if (!debugData.empty()) { |
| std::string curlDebug = std::string(debugData.begin(), debugData.end()); |
| cmCTestOptionalLog(this->CTest, DEBUG, |
| "Curl debug: [" << curlDebug << "]\n", this->Quiet); |
| } |
| cmCTestOptionalLog(this->CTest, DEBUG, "Curl res: " << res << "\n", |
| this->Quiet); |
| return (res == 0); |
| } |
| |
| void cmCTestCurl::SetProxyType() |
| { |
| this->HTTPProxy.clear(); |
| // this is the default |
| this->HTTPProxyType = CURLPROXY_HTTP; |
| this->HTTPProxyAuth.clear(); |
| if (cmSystemTools::GetEnv("HTTP_PROXY", this->HTTPProxy)) { |
| std::string port; |
| if (cmSystemTools::GetEnv("HTTP_PROXY_PORT", port)) { |
| this->HTTPProxy += ":"; |
| this->HTTPProxy += port; |
| } |
| std::string type; |
| if (cmSystemTools::GetEnv("HTTP_PROXY_TYPE", type)) { |
| // HTTP/SOCKS4/SOCKS5 |
| if (type == "HTTP") { |
| this->HTTPProxyType = CURLPROXY_HTTP; |
| } else if (type == "SOCKS4") { |
| this->HTTPProxyType = CURLPROXY_SOCKS4; |
| } else if (type == "SOCKS5") { |
| this->HTTPProxyType = CURLPROXY_SOCKS5; |
| } |
| } |
| cmSystemTools::GetEnv("HTTP_PROXY_USER", this->HTTPProxyAuth); |
| std::string passwd; |
| if (cmSystemTools::GetEnv("HTTP_PROXY_PASSWD", passwd)) { |
| this->HTTPProxyAuth += ":"; |
| this->HTTPProxyAuth += passwd; |
| } |
| } |
| } |