/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc. Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmCTestSubmitHandler.h" #include "cmSystemTools.h" #include "cmVersion.h" #include "cmGeneratedFileStream.h" #include "cmCTest.h" #include "cmXMLParser.h" #include #include // For XML-RPC submission #include "cm_xmlrpc.h" // For curl submission #include "cm_curl.h" #include #define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120 typedef std::vector cmCTestSubmitHandlerVectorOfChar; //---------------------------------------------------------------------------- class cmCTestSubmitHandler::ResponseParser: public cmXMLParser { public: ResponseParser() { this->Status = STATUS_OK; } ~ResponseParser() {} public: enum StatusType { STATUS_OK, STATUS_WARNING, STATUS_ERROR }; StatusType Status; std::string CDashVersion; std::string Filename; std::string MD5; std::string Message; private: std::vector CurrentValue; std::string GetCurrentValue() { std::string val; if(this->CurrentValue.size()) { val.assign(&this->CurrentValue[0], this->CurrentValue.size()); } return val; } virtual void StartElement(const char* name, const char** atts) { this->CurrentValue.clear(); if(strcmp(name, "cdash") == 0) { this->CDashVersion = this->FindAttribute(atts, "version"); } } virtual void CharacterDataHandler(const char* data, int length) { this->CurrentValue.insert(this->CurrentValue.end(), data, data+length); } virtual void EndElement(const char* name) { if(strcmp(name, "status") == 0) { std::string status = cmSystemTools::UpperCase(this->GetCurrentValue()); if(status == "OK" || status == "SUCCESS") { this->Status = STATUS_OK; } else if(status == "WARNING") { this->Status = STATUS_WARNING; } else { this->Status = STATUS_ERROR; } } else if(strcmp(name, "filename") == 0) { this->Filename = this->GetCurrentValue(); } else if(strcmp(name, "md5") == 0) { this->MD5 = this->GetCurrentValue(); } else if(strcmp(name, "message") == 0) { this->Message = this->GetCurrentValue(); } } }; static size_t cmCTestSubmitHandlerWriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) { register int realsize = (int)(size * nmemb); cmCTestSubmitHandlerVectorOfChar *vec = static_cast(data); const char* chPtr = static_cast(ptr); vec->insert(vec->end(), chPtr, chPtr + realsize); return realsize; } static size_t cmCTestSubmitHandlerCurlDebugCallback(CURL *, curl_infotype, char *chPtr, size_t size, void *data) { cmCTestSubmitHandlerVectorOfChar *vec = static_cast(data); vec->insert(vec->end(), chPtr, chPtr + size); return size; } //---------------------------------------------------------------------------- cmCTestSubmitHandler::cmCTestSubmitHandler() : HTTPProxy(), FTPProxy() { this->Initialize(); } //---------------------------------------------------------------------------- void cmCTestSubmitHandler::Initialize() { // We submit all available parts by default. for(cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; p = cmCTest::Part(p+1)) { this->SubmitPart[p] = true; } this->CDash = false; this->HasWarnings = false; this->HasErrors = false; this->Superclass::Initialize(); this->HTTPProxy = ""; this->HTTPProxyType = 0; this->HTTPProxyAuth = ""; this->FTPProxy = ""; this->FTPProxyType = 0; this->LogFile = 0; this->Files.clear(); } //---------------------------------------------------------------------------- bool cmCTestSubmitHandler::SubmitUsingFTP(const cmStdString& localprefix, const std::set& files, const cmStdString& remoteprefix, const cmStdString& url) { CURL *curl; CURLcode res; FILE* ftpfile; char error_buffer[1024]; /* In windows, this will init the winsock stuff */ ::curl_global_init(CURL_GLOBAL_ALL); cmCTest::SetOfStrings::const_iterator file; for ( file = files.begin(); file != files.end(); ++file ) { /* get a curl handle */ curl = curl_easy_init(); if(curl) { // Using proxy if ( this->FTPProxyType > 0 ) { curl_easy_setopt(curl, CURLOPT_PROXY, this->FTPProxy.c_str()); switch (this->FTPProxyType) { case 2: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); break; case 3: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); break; default: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); } } // enable uploading ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // if there is little to no activity for too long stop submitting ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); cmStdString local_file = *file; if ( !cmSystemTools::FileExists(local_file.c_str()) ) { local_file = localprefix + "/" + *file; } cmStdString upload_as = url + "/" + remoteprefix + cmSystemTools::GetFilenameName(*file); struct stat st; if ( ::stat(local_file.c_str(), &st) ) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " << local_file.c_str() << std::endl); ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; } ftpfile = ::fopen(local_file.c_str(), "rb"); *this->LogFile << "\tUpload file: " << local_file.c_str() << " to " << upload_as.c_str() << std::endl; cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Upload file: " << local_file.c_str() << " to " << upload_as.c_str() << std::endl); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); // specify target ::curl_easy_setopt(curl,CURLOPT_URL, upload_as.c_str()); // now specify which file to upload ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile); // and give the size of the upload (optional) ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast(st.st_size)); // and give curl the buffer for errors ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer); // specify handler for output ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; ::curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk); ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)&chunkDebug); // Now run off and do what you've been told! res = ::curl_easy_perform(curl); if ( chunk.size() > 0 ) { cmCTestLog(this->CTest, DEBUG, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl); } if ( chunkDebug.size() > 0 ) { cmCTestLog(this->CTest, DEBUG, "CURL debug output: [" << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" << std::endl); } fclose(ftpfile); if ( res ) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Error when uploading file: " << local_file.c_str() << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, " Error message was: " << error_buffer << std::endl); *this->LogFile << " Error when uploading file: " << local_file.c_str() << std::endl << " Error message was: " << error_buffer << std::endl << " Curl output was: "; // avoid dereference of empty vector if(chunk.size()) { *this->LogFile << cmCTestLogWrite(&*chunk.begin(), chunk.size()); cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl); } *this->LogFile << std::endl; ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; } // always cleanup ::curl_easy_cleanup(curl); cmCTestLog(this->CTest, HANDLER_OUTPUT, " Uploaded: " + local_file << std::endl); } } ::curl_global_cleanup(); return true; } //---------------------------------------------------------------------------- // Uploading files is simpler bool cmCTestSubmitHandler::SubmitUsingHTTP(const cmStdString& localprefix, const std::set& files, const cmStdString& remoteprefix, const cmStdString& url) { CURL *curl; CURLcode res; FILE* ftpfile; char error_buffer[1024]; /* In windows, this will init the winsock stuff */ ::curl_global_init(CURL_GLOBAL_ALL); cmStdString dropMethod(this->CTest->GetCTestConfiguration("DropMethod")); cmStdString curlopt(this->CTest->GetCTestConfiguration("CurlOptions")); std::vector args; cmSystemTools::ExpandListArgument(curlopt.c_str(), args); bool verifyPeerOff = false; bool verifyHostOff = false; for( std::vector::iterator i = args.begin(); i != args.end(); ++i) { if(*i == "CURLOPT_SSL_VERIFYPEER_OFF") { verifyPeerOff = true; } if(*i == "CURLOPT_SSL_VERIFYHOST_OFF") { verifyHostOff = true; } } cmStdString::size_type kk; cmCTest::SetOfStrings::const_iterator file; for ( file = files.begin(); file != files.end(); ++file ) { /* get a curl handle */ curl = curl_easy_init(); if(curl) { if(verifyPeerOff) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Set CURLOPT_SSL_VERIFYPEER to off\n"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); } if(verifyHostOff) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Set CURLOPT_SSL_VERIFYHOST to off\n"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); } // Using proxy if ( this->HTTPProxyType > 0 ) { curl_easy_setopt(curl, CURLOPT_PROXY, this->HTTPProxy.c_str()); switch (this->HTTPProxyType) { case 2: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); break; case 3: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); break; default: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); if (this->HTTPProxyAuth.size() > 0) { curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, this->HTTPProxyAuth.c_str()); } } } if(this->CTest->ShouldUseHTTP10()) { curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } // enable HTTP ERROR parsing curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); /* enable uploading */ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // if there is little to no activity for too long stop submitting ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); /* HTTP PUT please */ ::curl_easy_setopt(curl, CURLOPT_PUT, 1); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); cmStdString local_file = *file; if ( !cmSystemTools::FileExists(local_file.c_str()) ) { local_file = localprefix + "/" + *file; } cmStdString remote_file = remoteprefix + cmSystemTools::GetFilenameName(*file); *this->LogFile << "\tUpload file: " << local_file.c_str() << " to " << remote_file.c_str() << std::endl; cmStdString ofile = ""; for ( kk = 0; kk < remote_file.size(); kk ++ ) { char c = remote_file[kk]; char hexCh[4] = { 0, 0, 0, 0 }; hexCh[0] = c; switch ( c ) { case '+': case '?': case '/': case '\\': case '&': case ' ': case '=': case '%': sprintf(hexCh, "%%%02X", (int)c); ofile.append(hexCh); break; default: ofile.append(hexCh); } } cmStdString upload_as = url + ((url.find("?",0) == cmStdString::npos) ? "?" : "&") + "FileName=" + ofile; char md5[33]; cmSystemTools::ComputeFileMD5(local_file.c_str(), md5); md5[32] = 0; upload_as += "&MD5="; upload_as += md5; struct stat st; if ( ::stat(local_file.c_str(), &st) ) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " << local_file.c_str() << std::endl); ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; } ftpfile = ::fopen(local_file.c_str(), "rb"); cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Upload file: " << local_file.c_str() << " to " << upload_as.c_str() << " Size: " << st.st_size << std::endl); // specify target ::curl_easy_setopt(curl,CURLOPT_URL, upload_as.c_str()); // now specify which file to upload ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile); // and give the size of the upload (optional) ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast(st.st_size)); // and give curl the buffer for errors ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer); // specify handler for output ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; ::curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk); ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)&chunkDebug); // Now run off and do what you've been told! res = ::curl_easy_perform(curl); if ( chunk.size() > 0 ) { cmCTestLog(this->CTest, DEBUG, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl); this->ParseResponse(chunk); } if ( chunkDebug.size() > 0 ) { cmCTestLog(this->CTest, DEBUG, "CURL debug output: [" << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" << std::endl); } // If we time out or checksum fails, wait and retry if(res == CURLE_OPERATION_TIMEDOUT || this->HasErrors) { std::string retryTime = this->GetOption("RetryTime") == NULL ? "" : this->GetOption("RetryTime"); std::string retryCount = this->GetOption("RetryCount") == NULL ? "" : this->GetOption("RetryCount"); int time = retryTime == "" ? atoi(this->CTest->GetCTestConfiguration( "CTestSubmitRetryDelay").c_str()) : atoi(retryTime.c_str()); int count = retryCount == "" ? atoi(this->CTest->GetCTestConfiguration( "CTestSubmitRetryCount").c_str()) : atoi(retryCount.c_str()); for(int i = 0; i < count; i++) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Connection timed out, waiting " << time << " seconds...\n"); double stop = cmSystemTools::GetTime() + time; while(cmSystemTools::GetTime() < stop) {} //wait