| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "cmCTestSubmitHandler.h"#include "cm_curl.h"#include "cm_jsoncpp_reader.h"#include "cm_jsoncpp_value.h"#include <chrono>#include <sstream>#include <stdio.h>#include <stdlib.h>#include "cmAlgorithms.h"#include "cmCTest.h"#include "cmCTestCurl.h"#include "cmCTestScriptHandler.h"#include "cmCryptoHash.h"#include "cmCurl.h"#include "cmDuration.h"#include "cmGeneratedFileStream.h"#include "cmState.h"#include "cmSystemTools.h"#include "cmXMLParser.h"#include "cmake.h"#define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120typedef std::vector<char> cmCTestSubmitHandlerVectorOfChar;class cmCTestSubmitHandler::ResponseParser : public cmXMLParser{public:  ResponseParser() { this->Status = STATUS_OK; }  ~ResponseParser() override {}public:  enum StatusType  {    STATUS_OK,    STATUS_WARNING,    STATUS_ERROR  };  StatusType Status;  std::string Filename;  std::string MD5;  std::string Message;  std::string BuildID;private:  std::vector<char> CurrentValue;  std::string GetCurrentValue()  {    std::string val;    if (!this->CurrentValue.empty()) {      val.assign(&this->CurrentValue[0], this->CurrentValue.size());    }    return val;  }  void StartElement(const std::string& /*name*/,                    const char** /*atts*/) override  {    this->CurrentValue.clear();  }  void CharacterDataHandler(const char* data, int length) override  {    this->CurrentValue.insert(this->CurrentValue.end(), data, data + length);  }  void EndElement(const std::string& name) override  {    if (name == "status") {      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 (name == "filename") {      this->Filename = this->GetCurrentValue();    } else if (name == "md5") {      this->MD5 = this->GetCurrentValue();    } else if (name == "message") {      this->Message = this->GetCurrentValue();    } else if (name == "buildId") {      this->BuildID = this->GetCurrentValue();    }  }};static size_t cmCTestSubmitHandlerWriteMemoryCallback(void* ptr, size_t size,                                                      size_t nmemb, void* data){  int realsize = static_cast<int>(size * nmemb);  cmCTestSubmitHandlerVectorOfChar* vec =    static_cast<cmCTestSubmitHandlerVectorOfChar*>(data);  const char* chPtr = static_cast<char*>(ptr);  vec->insert(vec->end(), chPtr, chPtr + realsize);  return realsize;}static size_t cmCTestSubmitHandlerCurlDebugCallback(CURL* /*unused*/,                                                    curl_infotype /*unused*/,                                                    char* chPtr, size_t size,                                                    void* data){  cmCTestSubmitHandlerVectorOfChar* vec =    static_cast<cmCTestSubmitHandlerVectorOfChar*>(data);  vec->insert(vec->end(), chPtr, chPtr + size);  return size;}cmCTestSubmitHandler::cmCTestSubmitHandler(){  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->HasWarnings = false;  this->HasErrors = false;  this->Superclass::Initialize();  this->HTTPProxy.clear();  this->HTTPProxyType = 0;  this->HTTPProxyAuth.clear();  this->LogFile = nullptr;  this->Files.clear();}bool cmCTestSubmitHandler::SubmitUsingHTTP(  const std::string& localprefix, const std::vector<std::string>& files,  const std::string& remoteprefix, const std::string& url){  CURL* curl;  CURLcode res;  FILE* ftpfile;  char error_buffer[1024];  // 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());  }  /* In windows, this will init the winsock stuff */  ::curl_global_init(CURL_GLOBAL_ALL);  std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions"));  std::vector<std::string> args;  cmSystemTools::ExpandListArgument(curlopt, args);  bool verifyPeerOff = false;  bool verifyHostOff = false;  for (std::string const& arg : args) {    if (arg == "CURLOPT_SSL_VERIFYPEER_OFF") {      verifyPeerOff = true;    }    if (arg == "CURLOPT_SSL_VERIFYHOST_OFF") {      verifyHostOff = true;    }  }  for (std::string const& file : files) {    /* get a curl handle */    curl = curl_easy_init();    if (curl) {      cmCurlSetCAInfo(curl);      if (verifyPeerOff) {        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                           "  Set CURLOPT_SSL_VERIFYPEER to off\n",                           this->Quiet);        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);      }      if (verifyHostOff) {        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                           "  Set CURLOPT_SSL_VERIFYHOST to off\n",                           this->Quiet);        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.empty()) {              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);      ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);      std::string local_file = file;      bool initialize_cdash_buildid = false;      if (!cmSystemTools::FileExists(local_file)) {        local_file = localprefix + "/" + file;        // If this file exists within the local Testing directory we assume        // that it will be associated with the current build in CDash.        initialize_cdash_buildid = true;      }      std::string remote_file =        remoteprefix + cmSystemTools::GetFilenameName(file);      *this->LogFile << "\tUpload file: " << local_file << " to "                     << remote_file << std::endl;      std::string ofile = cmSystemTools::EncodeURL(remote_file);      std::string upload_as = url +        ((url.find('?') == std::string::npos) ? '?' : '&') +        "FileName=" + ofile;      if (initialize_cdash_buildid) {        // Provide extra arguments to CDash so that it can initialize and        // return a buildid.        cmCTestCurl ctest_curl(this->CTest);        upload_as += "&build=";        upload_as +=          ctest_curl.Escape(this->CTest->GetCTestConfiguration("BuildName"));        upload_as += "&site=";        upload_as +=          ctest_curl.Escape(this->CTest->GetCTestConfiguration("Site"));        upload_as += "&stamp=";        upload_as += ctest_curl.Escape(this->CTest->GetCurrentTag());        upload_as += "-";        upload_as += ctest_curl.Escape(this->CTest->GetTestModelString());        cmCTestScriptHandler* ch = static_cast<cmCTestScriptHandler*>(          this->CTest->GetHandler("script"));        cmake* cm = ch->GetCMake();        if (cm) {          const char* subproject =            cm->GetState()->GetGlobalProperty("SubProject");          if (subproject) {            upload_as += "&subproject=";            upload_as += ctest_curl.Escape(subproject);          }        }      }      upload_as += "&MD5=";      if (cmSystemTools::IsOn(this->GetOption("InternalTest"))) {        upload_as += "bad_md5sum";      } else {        upload_as +=          cmSystemTools::ComputeFileHash(local_file, cmCryptoHash::AlgoMD5);      }      // Generate Done.xml right before it is submitted.      // The reason for this is two-fold:      // 1) It must be generated after some other part has been submitted      //    so we have a buildId to refer to in its contents.      // 2) By generating Done.xml here its timestamp will be as late as      //    possible. This gives us a more accurate record of how long the      //    entire build took to complete.      if (file == "Done.xml") {        this->CTest->GenerateDoneFile();      }      if (!cmSystemTools::FileExists(local_file)) {        cmCTestLog(this->CTest, ERROR_MESSAGE,                   "   Cannot find file: " << local_file << std::endl);        ::curl_easy_cleanup(curl);        ::curl_slist_free_all(headers);        ::curl_global_cleanup();        return false;      }      unsigned long filelen = cmSystemTools::FileLength(local_file);      ftpfile = cmsys::SystemTools::Fopen(local_file, "rb");      cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                         "   Upload file: " << local_file << " to "                                            << upload_as << " Size: "                                            << filelen << std::endl,                         this->Quiet);      // specify target      ::curl_easy_setopt(curl, CURLOPT_URL, upload_as.c_str());      // CURLAUTH_BASIC is default, and here we allow additional methods,      // including more secure ones      ::curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);      // 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<long>(filelen));      // 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, &chunk);      ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);      // Now run off and do what you've been told!      res = ::curl_easy_perform(curl);      if (!chunk.empty()) {        cmCTestOptionalLog(this->CTest, DEBUG,                           "CURL output: ["                             << cmCTestLogWrite(&*chunk.begin(), chunk.size())                             << "]" << std::endl,                           this->Quiet);        this->ParseResponse(chunk);      }      if (!chunkDebug.empty()) {        cmCTestOptionalLog(          this->CTest, DEBUG,          "CURL debug output: ["            << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]"            << std::endl,          this->Quiet);      }      // If curl failed for any reason, or checksum fails, wait and retry      //      if (res != CURLE_OK || this->HasErrors) {        std::string retryDelay = this->GetOption("RetryDelay") == nullptr          ? ""          : this->GetOption("RetryDelay");        std::string retryCount = this->GetOption("RetryCount") == nullptr          ? ""          : this->GetOption("RetryCount");        auto delay = cmDuration(          retryDelay.empty()            ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryDelay")                     .c_str())            : atoi(retryDelay.c_str()));        int count = retryCount.empty()          ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryCount")                   .c_str())          : atoi(retryCount.c_str());        for (int i = 0; i < count; i++) {          cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                             "   Submit failed, waiting " << delay.count()                                                          << " seconds...\n",                             this->Quiet);          auto stop = std::chrono::steady_clock::now() + delay;          while (std::chrono::steady_clock::now() < stop) {            cmSystemTools::Delay(100);          }          cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                             "   Retry submission: Attempt "                               << (i + 1) << " of " << count << std::endl,                             this->Quiet);          ::fclose(ftpfile);          ftpfile = cmsys::SystemTools::Fopen(local_file, "rb");          ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile);          chunk.clear();          chunkDebug.clear();          this->HasErrors = false;          res = ::curl_easy_perform(curl);          if (!chunk.empty()) {            cmCTestOptionalLog(              this->CTest, DEBUG,              "CURL output: ["                << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]"                << std::endl,              this->Quiet);            this->ParseResponse(chunk);          }          if (res == CURLE_OK && !this->HasErrors) {            break;          }        }      }      fclose(ftpfile);      if (res) {        cmCTestLog(this->CTest, ERROR_MESSAGE,                   "   Error when uploading file: " << local_file                                                    << std::endl);        cmCTestLog(this->CTest, ERROR_MESSAGE,                   "   Error message was: " << error_buffer << std::endl);        *this->LogFile << "   Error when uploading file: " << local_file                       << std::endl                       << "   Error message was: " << error_buffer                       << std::endl;        // avoid deref of begin for zero size array        if (!chunk.empty()) {          *this->LogFile << "   Curl output was: "                         << cmCTestLogWrite(&*chunk.begin(), chunk.size())                         << std::endl;          cmCTestLog(this->CTest, ERROR_MESSAGE,                     "CURL output: ["                       << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]"                       << std::endl);        }        ::curl_easy_cleanup(curl);        ::curl_slist_free_all(headers);        ::curl_global_cleanup();        return false;      }      // always cleanup      ::curl_easy_cleanup(curl);      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                         "   Uploaded: " + local_file << std::endl,                         this->Quiet);    }  }  ::curl_slist_free_all(headers);  ::curl_global_cleanup();  return true;}void cmCTestSubmitHandler::ParseResponse(  cmCTestSubmitHandlerVectorOfChar chunk){  std::string output;  output.append(chunk.begin(), chunk.end());  if (output.find("<cdash") != std::string::npos) {    ResponseParser parser;    parser.Parse(output.c_str());    if (parser.Status != ResponseParser::STATUS_OK) {      this->HasErrors = true;      cmCTestLog(this->CTest, HANDLER_OUTPUT,                 "   Submission failed: " << parser.Message << std::endl);      return;    }    this->CTest->SetBuildID(parser.BuildID);  }  output = cmSystemTools::UpperCase(output);  if (output.find("WARNING") != std::string::npos) {    this->HasWarnings = true;  }  if (output.find("ERROR") != std::string::npos) {    this->HasErrors = true;  }  if (this->HasWarnings || this->HasErrors) {    cmCTestLog(this->CTest, HANDLER_OUTPUT,               "   Server Response:\n"                 << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "\n");  }}int cmCTestSubmitHandler::HandleCDashUploadFile(std::string const& file,                                                std::string const& typeString){  if (file.empty()) {    cmCTestLog(this->CTest, ERROR_MESSAGE, "Upload file not specified\n");    return -1;  }  if (!cmSystemTools::FileExists(file)) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "Upload file not found: '" << file << "'\n");    return -1;  }  cmCTestCurl curl(this->CTest);  curl.SetQuiet(this->Quiet);  std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions"));  std::vector<std::string> args;  cmSystemTools::ExpandListArgument(curlopt, args);  curl.SetCurlOptions(args);  curl.SetTimeOutSeconds(SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT);  curl.SetHttpHeaders(this->HttpHeaders);  std::string url = this->CTest->GetSubmitURL();  std::string fields;  std::string::size_type pos = url.find('?');  if (pos != std::string::npos) {    fields = url.substr(pos + 1);    url = url.substr(0, pos);  }  if (!cmHasLiteralPrefix(url, "http://") &&      !cmHasLiteralPrefix(url, "https://")) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "Only http and https are supported for CDASH_UPLOAD\n");    return -1;  }  bool internalTest = cmSystemTools::IsOn(this->GetOption("InternalTest"));  // Get RETRY_COUNT and RETRY_DELAY values if they were set.  std::string retryDelayString = this->GetOption("RetryDelay") == nullptr    ? ""    : this->GetOption("RetryDelay");  std::string retryCountString = this->GetOption("RetryCount") == nullptr    ? ""    : this->GetOption("RetryCount");  auto retryDelay = std::chrono::seconds(0);  if (!retryDelayString.empty()) {    unsigned long retryDelayValue = 0;    if (!cmSystemTools::StringToULong(retryDelayString.c_str(),                                      &retryDelayValue)) {      cmCTestLog(this->CTest, WARNING,                 "Invalid value for 'RETRY_DELAY' : " << retryDelayString                                                      << std::endl);    } else {      retryDelay = std::chrono::seconds(retryDelayValue);    }  }  unsigned long retryCount = 0;  if (!retryCountString.empty()) {    if (!cmSystemTools::StringToULong(retryCountString.c_str(), &retryCount)) {      cmCTestLog(this->CTest, WARNING,                 "Invalid value for 'RETRY_DELAY' : " << retryCountString                                                      << std::endl);    }  }  std::string md5sum =    cmSystemTools::ComputeFileHash(file, cmCryptoHash::AlgoMD5);  // 1. request the buildid and check to see if the file  //    has already been uploaded  // TODO I added support for subproject. You would need to add  // a "&subproject=subprojectname" to the first POST.  cmCTestScriptHandler* ch =    static_cast<cmCTestScriptHandler*>(this->CTest->GetHandler("script"));  cmake* cm = ch->GetCMake();  const char* subproject = cm->GetState()->GetGlobalProperty("SubProject");  // TODO: Encode values for a URL instead of trusting caller.  std::ostringstream str;  if (subproject) {    str << "subproject=" << curl.Escape(subproject) << "&";  }  auto timeNow =    std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());  str << "stamp=" << curl.Escape(this->CTest->GetCurrentTag()) << "-"      << curl.Escape(this->CTest->GetTestModelString()) << "&"      << "model=" << curl.Escape(this->CTest->GetTestModelString()) << "&"      << "build="      << curl.Escape(this->CTest->GetCTestConfiguration("BuildName")) << "&"      << "site=" << curl.Escape(this->CTest->GetCTestConfiguration("Site"))      << "&"      << "track=" << curl.Escape(this->CTest->GetTestModelString()) << "&"      << "starttime=" << timeNow << "&"      << "endtime=" << timeNow << "&"      << "datafilesmd5[0]=" << md5sum << "&"      << "type=" << curl.Escape(typeString);  if (!fields.empty()) {    fields += '&';  }  fields += str.str();  cmCTestOptionalLog(this->CTest, DEBUG,                     "fields: " << fields << "\nurl:" << url                                << "\nfile: " << file << "\n",                     this->Quiet);  std::string response;  bool requestSucceeded = curl.HttpRequest(url, fields, response);  if (!internalTest && !requestSucceeded) {    // If request failed, wait and retry.    for (unsigned long i = 0; i < retryCount; i++) {      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                         "   Request failed, waiting " << retryDelay.count()                                                       << " seconds...\n",                         this->Quiet);      auto stop = std::chrono::steady_clock::now() + retryDelay;      while (std::chrono::steady_clock::now() < stop) {        cmSystemTools::Delay(100);      }      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                         "   Retry request: Attempt "                           << (i + 1) << " of " << retryCount << std::endl,                         this->Quiet);      requestSucceeded = curl.HttpRequest(url, fields, response);      if (requestSucceeded) {        break;      }    }  }  if (!internalTest && !requestSucceeded) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "Error in HttpRequest\n"                 << response);    return -1;  }  cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                     "Request upload response: [" << response << "]\n",                     this->Quiet);  Json::Value json;  Json::Reader reader;  if (!internalTest && !reader.parse(response, json)) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "error parsing json string ["                 << response << "]\n"                 << reader.getFormattedErrorMessages() << "\n");    return -1;  }  if (!internalTest && json["status"].asInt() != 0) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "Bad status returned from CDash: " << json["status"].asInt());    return -1;  }  if (!internalTest) {    if (json["datafilesmd5"].isArray()) {      int datares = json["datafilesmd5"][0].asInt();      if (datares == 1) {        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                           "File already exists on CDash, skip upload "                             << file << "\n",                           this->Quiet);        return 0;      }    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE,                 "bad datafilesmd5 value in response " << response << "\n");      return -1;    }  }  std::string upload_as = cmSystemTools::GetFilenameName(file);  std::ostringstream fstr;  fstr << "type=" << curl.Escape(typeString) << "&"       << "md5=" << md5sum << "&"       << "filename=" << curl.Escape(upload_as) << "&"       << "buildid=" << json["buildid"].asString();  bool uploadSucceeded = false;  if (!internalTest) {    uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response);  }  if (!uploadSucceeded) {    // If upload failed, wait and retry.    for (unsigned long i = 0; i < retryCount; i++) {      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                         "   Upload failed, waiting " << retryDelay.count()                                                      << " seconds...\n",                         this->Quiet);      auto stop = std::chrono::steady_clock::now() + retryDelay;      while (std::chrono::steady_clock::now() < stop) {        cmSystemTools::Delay(100);      }      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                         "   Retry upload: Attempt "                           << (i + 1) << " of " << retryCount << std::endl,                         this->Quiet);      if (!internalTest) {        uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response);      }      if (uploadSucceeded) {        break;      }    }  }  if (!uploadSucceeded) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "error uploading to CDash. " << file << " " << url << " "                                            << fstr.str());    return -1;  }  if (!reader.parse(response, json)) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "error parsing json string ["                 << response << "]\n"                 << reader.getFormattedErrorMessages() << "\n");    return -1;  }  cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,                     "Upload file response: [" << response << "]\n",                     this->Quiet);  return 0;}int cmCTestSubmitHandler::ProcessHandler(){  const char* cdashUploadFile = this->GetOption("CDashUploadFile");  const char* cdashUploadType = this->GetOption("CDashUploadType");  if (cdashUploadFile && cdashUploadType) {    return this->HandleCDashUploadFile(cdashUploadFile, cdashUploadType);  }  const std::string& buildDirectory =    this->CTest->GetCTestConfiguration("BuildDirectory");  if (buildDirectory.empty()) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "Cannot find BuildDirectory  key in the DartConfiguration.tcl"                 << std::endl);    return -1;  }  if (getenv("HTTP_PROXY")) {    this->HTTPProxyType = 1;    this->HTTPProxy = getenv("HTTP_PROXY");    if (getenv("HTTP_PROXY_PORT")) {      this->HTTPProxy += ":";      this->HTTPProxy += getenv("HTTP_PROXY_PORT");    }    if (getenv("HTTP_PROXY_TYPE")) {      std::string type = getenv("HTTP_PROXY_TYPE");      // HTTP/SOCKS4/SOCKS5      if (type == "HTTP") {        this->HTTPProxyType = 1;      } else if (type == "SOCKS4") {        this->HTTPProxyType = 2;      } else if (type == "SOCKS5") {        this->HTTPProxyType = 3;      }    }    if (getenv("HTTP_PROXY_USER")) {      this->HTTPProxyAuth = getenv("HTTP_PROXY_USER");    }    if (getenv("HTTP_PROXY_PASSWD")) {      this->HTTPProxyAuth += ":";      this->HTTPProxyAuth += getenv("HTTP_PROXY_PASSWD");    }  }  if (!this->HTTPProxy.empty()) {    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                       "   Use HTTP Proxy: " << this->HTTPProxy << std::endl,                       this->Quiet);  }  cmGeneratedFileStream ofs;  this->StartLogFile("Submit", ofs);  std::vector<std::string> files;  std::string prefix = this->GetSubmitResultsPrefix();  if (!this->Files.empty()) {    // Submit the explicitly selected files:    //    files.insert(files.end(), this->Files.begin(), this->Files.end());  }  // Add to the list of files to submit from any selected, existing parts:  //  // TODO:  // Check if test is enabled  this->CTest->AddIfExists(cmCTest::PartUpdate, "Update.xml");  this->CTest->AddIfExists(cmCTest::PartConfigure, "Configure.xml");  this->CTest->AddIfExists(cmCTest::PartBuild, "Build.xml");  this->CTest->AddIfExists(cmCTest::PartTest, "Test.xml");  if (this->CTest->AddIfExists(cmCTest::PartCoverage, "Coverage.xml")) {    std::vector<std::string> gfiles;    std::string gpath =      buildDirectory + "/Testing/" + this->CTest->GetCurrentTag();    std::string::size_type glen = gpath.size() + 1;    gpath = gpath + "/CoverageLog*";    cmCTestOptionalLog(this->CTest, DEBUG,                       "Globbing for: " << gpath << std::endl, this->Quiet);    if (cmSystemTools::SimpleGlob(gpath, gfiles, 1)) {      for (std::string& gfile : gfiles) {        gfile = gfile.substr(glen);        cmCTestOptionalLog(this->CTest, DEBUG,                           "Glob file: " << gfile << std::endl, this->Quiet);        this->CTest->AddSubmitFile(cmCTest::PartCoverage, gfile.c_str());      }    } else {      cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem globbing" << std::endl);    }  }  this->CTest->AddIfExists(cmCTest::PartMemCheck, "DynamicAnalysis.xml");  this->CTest->AddIfExists(cmCTest::PartMemCheck, "Purify.xml");  this->CTest->AddIfExists(cmCTest::PartNotes, "Notes.xml");  this->CTest->AddIfExists(cmCTest::PartUpload, "Upload.xml");  // Query parts for files to submit.  for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;       p = cmCTest::Part(p + 1)) {    // Skip parts we are not submitting.    if (!this->SubmitPart[p]) {      continue;    }    // Submit files from this part.    std::vector<std::string> const& pfiles = this->CTest->GetSubmitFiles(p);    files.insert(files.end(), pfiles.begin(), pfiles.end());  }  // Make sure files are unique, but preserve order.  {    // This endPos intermediate is needed to work around non-conformant C++11    // standard libraries that have erase(iterator,iterator) instead of    // erase(const_iterator,const_iterator).    size_t endPos = cmRemoveDuplicates(files) - files.cbegin();    files.erase(files.begin() + endPos, files.end());  }  // Submit Done.xml last  if (this->SubmitPart[cmCTest::PartDone]) {    files.push_back("Done.xml");  }  if (ofs) {    ofs << "Upload files:" << std::endl;    int cnt = 0;    for (std::string const& file : files) {      ofs << cnt << "\t" << file << std::endl;      cnt++;    }  }  cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Submit files\n",                     this->Quiet);  const char* specificTrack = this->CTest->GetSpecificTrack();  if (specificTrack) {    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                       "   Send to track: " << specificTrack << std::endl,                       this->Quiet);  }  this->SetLogFile(&ofs);  std::string url = this->CTest->GetSubmitURL();  cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                     "   SubmitURL: " << url << '\n', this->Quiet);  if (!this->SubmitUsingHTTP(buildDirectory + "/Testing/" +                               this->CTest->GetCurrentTag(),                             files, prefix, url)) {    cmCTestLog(this->CTest, ERROR_MESSAGE,               "   Problems when submitting via HTTP\n");    ofs << "   Problems when submitting via HTTP\n";    return -1;  }  if (this->HasErrors) {    cmCTestLog(this->CTest, HANDLER_OUTPUT,               "   Errors occurred during submission.\n");    ofs << "   Errors occurred during submission.\n";  } else {    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,                       "   Submission successful"                         << (this->HasWarnings ? ", with warnings." : "")                         << std::endl,                       this->Quiet);    ofs << "   Submission successful"        << (this->HasWarnings ? ", with warnings." : "") << std::endl;  }  return 0;}std::string cmCTestSubmitHandler::GetSubmitResultsPrefix(){  std::string buildname =    cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName"));  std::string name = this->CTest->GetCTestConfiguration("Site") + "___" +    buildname + "___" + this->CTest->GetCurrentTag() + "-" +    this->CTest->GetTestModelString() + "___XML___";  return name;}void cmCTestSubmitHandler::SelectParts(std::set<cmCTest::Part> const& parts){  // Check whether each part is selected.  for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;       p = cmCTest::Part(p + 1)) {    this->SubmitPart[p] =      (std::set<cmCTest::Part>::const_iterator(parts.find(p)) != parts.end());  }}void cmCTestSubmitHandler::SelectFiles(cmCTest::SetOfStrings const& files){  this->Files.insert(files.begin(), files.end());}
 |