diff --git a/lib/Plugins/Catcut.cpp b/lib/Plugins/Catcut.cpp index 5f0583f..f683bdd 100644 --- a/lib/Plugins/Catcut.cpp +++ b/lib/Plugins/Catcut.cpp @@ -11,6 +11,8 @@ #include "config.h" #endif +#include + using namespace std; static xmlrpc_env env; @@ -19,7 +21,6 @@ static struct xmlrpc_clientparms clientParms; static struct xmlrpc_curl_xportparms curlParms; static xmlrpc_server_info* server_info = NULL; - static string login(const char* login, const char* passwd); //static void logout(); static void new_xmlrpc_client(const char* url, bool no_ssl_verify); @@ -251,68 +252,349 @@ static string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashR return bug_id_str; } -//static -//void add_attachments(const string& pBugId, const map_crash_report_t& pCrashReport) -//{ -// xmlrpc_value* result = NULL; + +static int +put_stream(const std::string& pURL, FILE* f, size_t content_length) +{ + CURL * curl = curl_easy_init(); + if(!curl) + { + throw CABRTException(EXCEP_PLUGIN, "put_stream: Curl library error."); + } + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, pURL.c_str()); + /*file handle: passed to the default callback, it will fread() it*/ + curl_easy_setopt(curl, CURLOPT_READDATA, f); + /*get file size*/ + curl_easy_setopt(curl, CURLOPT_INFILESIZE, content_length); + /*everything is done here; result 0 means success*/ + int result = curl_easy_perform(curl); + /*goodbye*/ + curl_easy_cleanup(curl); + return result; +} + + + +static void +send_string(const std::string& pURL, + const std::string& pContent, + int retryCount, + int retryDelaySeconds) +{ + if (pURL == "") + { + warn_client(_("send_string: URL not specified")); + return; + } + + do + { + int content_length = pContent.length(); + FILE* f = fmemopen((void*)pContent.c_str(), content_length, "r"); + if(!f) + { + throw CABRTException(EXCEP_PLUGIN, "send_string: could not open string stream"); + } + int result = put_stream(pURL, f, content_length); + fclose(f); + if (!result) + return; + update_client(_("Sending failed, try it again: ") + std::string(curl_easy_strerror((CURLcode)result))); + } + /*retry the upload if not succesful, wait a bit before next try*/ + while( --retryCount != 0 && (sleep(retryDelaySeconds),1) ); + + throw CABRTException(EXCEP_PLUGIN, "send_string: could not send string"); +} + + +static void +send_file(const std::string& pURL, + const std::string& pFilename, + int retryCount, + int retryDelaySeconds) +{ + if (pURL == "") + { + warn_client(_("send_file: URL not specified")); + return; + } + + update_client(_("Sending file ") + pFilename + _(" to ") + pURL); + + do + { + FILE* f = fopen(pFilename.c_str(),"r"); + if(!f) + { + throw CABRTException(EXCEP_PLUGIN, "send_file: could not open string stream"); + } + struct stat buf; + if (stat(pFilename.c_str(), &buf) == -1) + { + throw CABRTException(EXCEP_PLUGIN, "send_file: cannot stat archive file " + pFilename); + } + int content_length = buf.st_size; + int result = put_stream(pURL, f, content_length); + fclose(f); + if (!result) + return; + update_client(_("Sending failed, try it again: ") + std::string(curl_easy_strerror((CURLcode)result))); + } + /*retry the upload if not succesful, wait a bit before next try*/ + while( --retryCount != 0 && (sleep(retryDelaySeconds),1) ); + + throw CABRTException(EXCEP_PLUGIN, "send_file: could not send file"); +} + +static string +resolve_relative_url( const string& url, const string& base ) +{ + // if 'url' is relative (not absolute) combine it with 'base' + // (which must be absolute) + // Only works in limited cases: + // 0) url is already absolute + // 1) url starts with two slashes + // 2) url starts with one slash + + size_t colon_pos = url.find_first_of(':'); + size_t slash_pos = url.find_first_of('/'); + + if (colon_pos != string::npos + && (slash_pos == string::npos || colon_pos < slash_pos)) + { + return url; + } + + size_t end_of_protocol = base.find_first_of(':'); + size_t end_of_host = base.find_first_of('/', end_of_protocol + 3); + + string protocol = base.substr(0, end_of_protocol - 0); + string host = base.substr(end_of_protocol + 3, end_of_host - (end_of_protocol + 3)); + + if ( url[0] == '/' ) + { + if (url[1] == '/') + { + return protocol + ':' + url; + } + else + { + return protocol + "://" + host + url; + } + } + throw CABRTException(EXCEP_PLUGIN, "resolve_relative_url: unhandled relative url"); +} + + // -// map_crash_report_t::const_iterator it = pCrashReport.begin(); -// for (; it != pCrashReport.end(); it++) -// { -// if (it->second[CD_TYPE] == CD_ATT) -// { -// string description = "File: " + it->first; -// const string& to_encode = it->second[CD_CONTENT]; -// char *encoded64 = encode_base64(to_encode.c_str(), to_encode.length()); -// xmlrpc_value* param = xmlrpc_build_value(&env,"(s{s:s,s:s,s:s,s:s})", -// pBugId.c_str(), -// "description", description.c_str(), -// "filename", it->first.c_str(), -// "contenttype", "text/plain", -// "data", encoded64 -// ); -// free(encoded64); -// throw_if_xml_fault_occurred(); +// xmlrpc_struct_find_XXXX +// abstract all the busy work of getting a field's value from +// a struct. XXXX is a type. +// Return true/false = the field is in the struct +// If true, return the field's value in 'value'. +// +// This function currently just assumes that the value in the +// field can be read into the type of 'value'. This should probably +// be fixed to either convert the fields value to the type of 'value' +// or error specifically/usefully. +// +// This function probably should be converted to an overloaded function +// (overloaded on the type of 'value'). It could also be a function +// template. // -//// catcut has this API: -//// struct response requestUpload(string cookie, string ticket, string filename, string description) -////response MUST include "errno", "errmsg" members; if an upload is approved, -////a "URL" MUST be returned in the response. The description string -////should include a brief description of the file. -//// -////The client should upload the file via HTTP PUT to the provided -////URL. The provided URL may be absolute or relative, if relative it must -////be combined with the base URL of the XML-RPC server using the usual -////rules for relative URL's (RFC 3986). -// xmlrpc_client_call2(&env, client, server_info, "catcut.addAttachment", param, &result); -// throw_if_xml_fault_occurred(); -// } -// } -//} + +static bool +xmlrpc_struct_find_int(xmlrpc_env* env, xmlrpc_value* result, + const char* fieldName, int& value) +{ + xmlrpc_value* an_xmlrpc_value; + xmlrpc_struct_find_value(env, result, fieldName, &an_xmlrpc_value); + throw_if_xml_fault_occurred(); + if (an_xmlrpc_value) + { + xmlrpc_read_int(env, an_xmlrpc_value, &value); + throw_if_xml_fault_occurred(); + xmlrpc_DECREF(an_xmlrpc_value); + return true; + } + else + { + return false; + } +} + +static bool +xmlrpc_struct_find_string(xmlrpc_env* env, xmlrpc_value* result, + const char* fieldName, string& value) +{ + xmlrpc_value* an_xmlrpc_value; + xmlrpc_struct_find_value(env, result, fieldName, &an_xmlrpc_value); + throw_if_xml_fault_occurred(); + if (an_xmlrpc_value) + { + const char* value_s; + xmlrpc_read_string(env, an_xmlrpc_value, &value_s); + throw_if_xml_fault_occurred(); + value = value_s; + xmlrpc_DECREF(an_xmlrpc_value); + free((void*)value_s); + return true; + } + else + { + return false; + } +} + + +static string +request_upload(const char* auth_cookie, const string& pTicketName, + const string& fileName, const string& description) +{ + + xmlrpc_value* param = xmlrpc_build_value(&env,"(ssss)", + auth_cookie, + pTicketName.c_str(), + fileName.c_str(), + description.c_str()); + throw_if_xml_fault_occurred(); + + xmlrpc_value* result = NULL; + + xmlrpc_client_call2(&env, client, server_info, "Catcut.requestUpload", param, &result); + throw_if_xml_fault_occurred(); + xmlrpc_DECREF(param); + + string URL; + bool has_URL = xmlrpc_struct_find_string(&env, result, "uri", URL); + if (!has_URL || URL == "") + { + int errno; + bool has_errno = xmlrpc_struct_find_int(&env, result, "errno", errno); + if (has_errno && errno) + { + string errmsg; + bool has_errmsg = xmlrpc_struct_find_string(&env, result, "errmsg", errmsg); + if (has_errmsg) + { + log("error returned by requestUpload: %s", errmsg.c_str()); + update_client(_("error returned by requestUpload: ") + errmsg); + } + else + { + log("error returned by requestUpload: %d", errno); + update_client(_("error returned by requestUpload: ") + errno); + } + } + else + { + log("no URL returned by requestUpload, and no errno"); + update_client(_("no URL returned by requestUpload, and no errno")); + } + } + + log("requestUpload returned URL: %s", URL.c_str()); + update_client(_("requestUpload returned URL: ") + URL); + + xmlrpc_DECREF(result); + return URL; +} + +static void +add_attachments(const string& xmlrpc_URL, + const char* auth_cookie, const string& pTicketName, + const map_crash_report_t& pCrashReport, + int retryCount, + int retryDelaySeconds) +{ + + map_crash_report_t::const_iterator it = pCrashReport.begin(); + for (; it != pCrashReport.end(); it++) + { + if (it->second[CD_TYPE] == CD_ATT) + { + update_client(_("Attaching (CD_ATT): ") + it->first); + + string description = "File: " + it->first; + string URL = request_upload( auth_cookie, + pTicketName, + it->first, + description); + + URL = resolve_relative_url(URL, xmlrpc_URL); + + log("rebased URL: %s", URL.c_str()); + update_client(_("rebased URL: ") + URL); + + send_string(URL, it->second[CD_CONTENT], + retryCount, retryDelaySeconds); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + update_client(_("Attaching (CD_ATT): ") + it->first); + + string description = "File: " + it->first; + string URL = request_upload(auth_cookie, + pTicketName, + it->first, + description); + + URL = resolve_relative_url(URL, xmlrpc_URL); + + log("rebased URL: %s", URL.c_str()); + update_client(_("rebased URL: ") + URL); + + send_file(URL, it->second[CD_CONTENT], + retryCount, retryDelaySeconds); + } + } +} CReporterCatcut::CReporterCatcut() : m_sCatcutURL("http://127.0.0.1:8080/catcut/xmlrpc"), - m_bNoSSLVerify(false) + m_bNoSSLVerify(false), + m_nRetryCount(3), + m_nRetryDelay(20) {} CReporterCatcut::~CReporterCatcut() {} string CReporterCatcut::Report(const map_crash_report_t& pCrashReport, - const map_plugin_settings_t& pSettings, const string& pArgs) + const map_plugin_settings_t& pSettings, + const string& pArgs) { update_client(_("Creating new bug...")); try { - new_xmlrpc_client(m_sCatcutURL.c_str(), m_bNoSSLVerify); + string message; + new_xmlrpc_client(m_sCatcutURL.c_str(), m_bNoSSLVerify); string auth_cookie = login(m_sLogin.c_str(), m_sPassword.c_str()); - string bug_id = (auth_cookie != "") ? new_bug(auth_cookie.c_str(), pCrashReport) : ""; -// add_attachments(to_string(bug_id), pCrashReport); -// update_client(_("Logging out...")); -// logout(); - destroy_xmlrpc_client(); - return "New catcut bug ID: " + bug_id; - + if (auth_cookie != "") + { + string ticket_name = new_bug(auth_cookie.c_str(), pCrashReport); + if (ticket_name != "") + { + add_attachments(m_sCatcutURL, + auth_cookie.c_str(), ticket_name, pCrashReport, + m_nRetryCount, m_nRetryDelay); + message = "New catcut bug ID: " + ticket_name; + } + else + { + message = "Error could not create ticket"; + } + } + else + { + message = "Error could not create ticket"; + } + destroy_xmlrpc_client(); + return message; } catch (CABRTException& e) { @@ -346,6 +628,14 @@ void CReporterCatcut::SetSettings(const map_plugin_settings_t& pSettings) { m_bNoSSLVerify = it->second == "yes"; } + if (pSettings.find("RetryCount") != pSettings.end()) + { + m_nRetryCount = atoi(pSettings.find("RetryCount")->second.c_str()); + } + if (pSettings.find("RetryDelay") != pSettings.end()) + { + m_nRetryDelay = atoi(pSettings.find("RetryDelay")->second.c_str()); + } } map_plugin_settings_t CReporterCatcut::GetSettings() @@ -357,6 +647,13 @@ map_plugin_settings_t CReporterCatcut::GetSettings() ret["Password"] = m_sPassword; ret["NoSSLVerify"] = m_bNoSSLVerify ? "yes" : "no"; + std::stringstream ss; + ss << m_nRetryCount; + ret["RetryCount"] = ss.str(); + ss.str(""); + ss << m_nRetryDelay; + ret["RetryDelay"] = ss.str(); + return ret; } diff --git a/lib/Plugins/Catcut.h b/lib/Plugins/Catcut.h index 40ef399..dd421d1 100644 --- a/lib/Plugins/Catcut.h +++ b/lib/Plugins/Catcut.h @@ -12,6 +12,9 @@ class CReporterCatcut : public CReporter std::string m_sPassword; bool m_bNoSSLVerify; + int m_nRetryCount; + int m_nRetryDelay; + public: CReporterCatcut(); virtual ~CReporterCatcut();