From 8bb5261cef1a4079aad422aa8cf1c9aa9b895a1a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 28 May 2010 15:41:19 +0200 Subject: take file upload POST code from gavin's lib. Adapt rhfastcheck to use changed API Signed-off-by: Denys Vlasenko --- lib/Plugins/rhticket.cpp | 14 +-- lib/Utils/abrt_curl.cpp | 248 +++++++++++++++++++++++++++++------------- lib/Utils/abrt_curl.h | 73 ++++++++++--- lib/Utils/abrt_rh_support.cpp | 99 ++++++++++++----- 4 files changed, 308 insertions(+), 126 deletions(-) (limited to 'lib') diff --git a/lib/Plugins/rhticket.cpp b/lib/Plugins/rhticket.cpp index ab364c26..d0a72735 100644 --- a/lib/Plugins/rhticket.cpp +++ b/lib/Plugins/rhticket.cpp @@ -136,22 +136,22 @@ string CReporterRHticket::Report(const map_crash_data_t& pCrashData, free(xml_description); string url = concat_path_file(m_sStrataURL.c_str(), "cases"); - curl_post_state *state = new_curl_post_state(0 - + ABRT_CURL_POST_WANT_HEADERS - + ABRT_CURL_POST_WANT_ERROR_MSG); - int http_resp_code = curl_post(state, url.c_str(), postdata.c_str()); + abrt_post_state *state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_ERROR_MSG); + int http_resp_code = abrt_post_string(state, url.c_str(), "application/xml", postdata.c_str()); if (http_resp_code / 100 != 2) { /* not 2xx */ string errmsg = state->curl_error_msg ? state->curl_error_msg : "(none)"; - free_curl_post_state(state); + free_abrt_post_state(state); throw CABRTException(EXCEP_PLUGIN, _("server returned HTTP code %u, error message: %s"), http_resp_code, errmsg.c_str()); } - string result = find_header_in_curl_post_state(state, "Location:") ? : ""; - free_curl_post_state(state); + string result = find_header_in_abrt_post_state(state, "Location:") ? : ""; + free_abrt_post_state(state); return result; } diff --git a/lib/Utils/abrt_curl.cpp b/lib/Utils/abrt_curl.cpp index edc27124..23ce8e4a 100644 --- a/lib/Utils/abrt_curl.cpp +++ b/lib/Utils/abrt_curl.cpp @@ -23,7 +23,7 @@ using namespace std; /* - * Utility function + * Utility functions */ CURL* xcurl_easy_init() { @@ -35,10 +35,6 @@ CURL* xcurl_easy_init() return curl; } - -/* - * curl_post: perform HTTP POST transaction - */ static char* check_curl_error(CURLcode err, const char* msg) { @@ -50,16 +46,77 @@ check_curl_error(CURLcode err, const char* msg) static void die_if_curl_error(CURLcode err) { - char *msg = check_curl_error(err, "curl"); - if (msg) + if (err) { + char *msg = check_curl_error(err, "curl"); error_msg_and_die("%s", msg); + } +} + +static void +xcurl_easy_setopt_ptr(CURL *handle, CURLoption option, const void *parameter) +{ + CURLcode err = curl_easy_setopt(handle, option, parameter); + if (err) { + char *msg = check_curl_error(err, "curl"); + error_msg_and_die("%s", msg); + } +} +static inline void +xcurl_easy_setopt_long(CURL *handle, CURLoption option, long parameter) +{ + xcurl_easy_setopt_ptr(handle, option, (void*)parameter); +} + +/* + * post_state utility functions + */ + +abrt_post_state_t *new_abrt_post_state(int flags) +{ + abrt_post_state_t *state = (abrt_post_state_t *)xzalloc(sizeof(*state)); + state->flags = flags; + return state; +} + +void free_abrt_post_state(abrt_post_state_t *state) +{ + char **headers = state->headers; + if (headers) + { + while (*headers) + free(*headers++); + free(state->headers); + } + free(state->curl_error_msg); + free(state->body); + free(state); +} + +char *find_header_in_abrt_post_state(abrt_post_state_t *state, const char *str) +{ + char **headers = state->headers; + if (headers) + { + unsigned len = strlen(str); + while (*headers) + { + if (strncmp(*headers, str, len) == 0) + return skip_whitespace(*headers + len); + headers++; + } + } + return NULL; } +/* + * abrt_post: perform HTTP POST transaction + */ + /* "save headers" callback */ static size_t save_headers(void *buffer_pv, size_t count, size_t nmemb, void *ptr) { - curl_post_state_t* state = (curl_post_state_t*)ptr; + abrt_post_state_t* state = (abrt_post_state_t*)ptr; size_t size = count * nmemb; @@ -78,15 +135,17 @@ save_headers(void *buffer_pv, size_t count, size_t nmemb, void *ptr) } int -curl_post(curl_post_state_t* state, const char* url, const char* data) +abrt_post(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *data, + off_t data_size) { CURLcode curl_err; long response_code; - struct curl_slist *httpheader_list = NULL; - FILE* body_stream = NULL; - curl_post_state_t localstate; + abrt_post_state_t localstate; - VERB3 log("curl_post('%s','%s')", url, data); + VERB3 log("abrt_post('%s','%s')", url, data); if (!state) { @@ -98,45 +157,111 @@ curl_post(curl_post_state_t* state, const char* url, const char* data) CURL *handle = xcurl_easy_init(); - curl_err = curl_easy_setopt(handle, CURLOPT_VERBOSE, 0); - die_if_curl_error(curl_err); - curl_err = curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); - die_if_curl_error(curl_err); - curl_err = curl_easy_setopt(handle, CURLOPT_URL, url); - die_if_curl_error(curl_err); - curl_err = curl_easy_setopt(handle, CURLOPT_POST, 1); - die_if_curl_error(curl_err); - curl_err = curl_easy_setopt(handle, CURLOPT_POSTFIELDS, data); - die_if_curl_error(curl_err); + // Buffer[CURL_ERROR_SIZE] curl stores human readable error messages in. + // This may be more helpful than just return code from curl_easy_perform. + // curl will need it until curl_easy_cleanup. + state->errmsg[0] = '\0'; + xcurl_easy_setopt_ptr(handle, CURLOPT_ERRORBUFFER, state->errmsg); + // "Display a lot of verbose information about its operations. + // Very useful for libcurl and/or protocol debugging and understanding. + // The verbose information will be sent to stderr, or the stream set + // with CURLOPT_STDERR" + //xcurl_easy_setopt_long(handle, CURLOPT_VERBOSE, 1); + // Shut off the built-in progress meter completely + xcurl_easy_setopt_long(handle, CURLOPT_NOPROGRESS, 1); - httpheader_list = curl_slist_append(httpheader_list, "Content-Type: application/xml"); - curl_err = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, httpheader_list); - die_if_curl_error(curl_err); + // TODO: do we need to check for CURLE_URL_MALFORMAT error *here*, + // not in curl_easy_perform? + xcurl_easy_setopt_ptr(handle, CURLOPT_URL, url); + + // Auth if configured + if (state->username) { + // bitmask of allowed auth methods + xcurl_easy_setopt_long(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + xcurl_easy_setopt_ptr(handle, CURLOPT_USERNAME, state->username); + xcurl_easy_setopt_ptr(handle, CURLOPT_PASSWORD, (state->password ? state->password : "")); + } + + // Do a regular HTTP post. This also makes curl use + // a "Content-Type: application/x-www-form-urlencoded" header. + // (This is by far the most commonly used POST method). + xcurl_easy_setopt_long(handle, CURLOPT_POST, 1); + // Supply POST data... + struct curl_httppost* post = NULL; + struct curl_httppost* last = NULL; + FILE* data_file = NULL; + if (data_size == ABRT_POST_DATA_FROMFILE) { + // ...from a file + data_file = fopen(data, "r"); + if (!data_file) +//FIXME: + perror_msg_and_die("can't open '%s'", data); + xcurl_easy_setopt_ptr(handle, CURLOPT_READDATA, data_file); + } else if (data_size == ABRT_POST_DATA_FROMFILE_AS_FORM_DATA) { + // ...from a file, in multipart/formdata format + CURLFORMcode curlform_err = curl_formadd(&post, &last, + CURLFORM_PTRNAME, "file", + CURLFORM_FILE, data, // filename to read from + CURLFORM_CONTENTTYPE, content_type, + CURLFORM_FILENAME, data, // filename to put in the form + CURLFORM_END); + if (curlform_err != 0) +//FIXME: + error_msg_and_die("out of memory or read error"); + xcurl_easy_setopt_ptr(handle, CURLOPT_HTTPPOST, post); + } else { + // .. from a blob in memory. If data_size == -1, curl will use strlen(data) + xcurl_easy_setopt_ptr(handle, CURLOPT_POSTFIELDS, data); + xcurl_easy_setopt_long(handle, CURLOPT_POSTFIELDSIZE_LARGE, data_size); + } + // Override "Content-Type:" + struct curl_slist *httpheader_list = NULL; + if (data_size != ABRT_POST_DATA_FROMFILE_AS_FORM_DATA) { + char *content_type_header = xasprintf("Content-Type: %s", content_type); + // Note: curl_slist_append() copies content_type_header + httpheader_list = curl_slist_append(httpheader_list, content_type_header); + if (!httpheader_list) + error_msg_and_die("out of memory"); + free(content_type_header); + xcurl_easy_setopt_ptr(handle, CURLOPT_HTTPHEADER, httpheader_list); + } + + // Please handle 301/302 redirects for me + xcurl_easy_setopt_long(handle, CURLOPT_FOLLOWLOCATION, 1); + xcurl_easy_setopt_long(handle, CURLOPT_MAXREDIRS, 10); + // Bitmask to control how libcurl acts on redirects after POSTs. + // Bit 0 set (value CURL_REDIR_POST_301) makes libcurl + // not convert POST requests into GET requests when following + // a 301 redirection. Bit 1 (value CURL_REDIR_POST_302) makes libcurl + // maintain the request method after a 302 redirect. + // CURL_REDIR_POST_ALL is a convenience define that sets both bits. + // The non-RFC behaviour is ubiquitous in web browsers, so the library + // does the conversion by default to maintain consistency. + // However, a server may require a POST to remain a POST. + //xcurl_easy_setopt_long(CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); - if (state->flags & ABRT_CURL_POST_WANT_HEADERS) + // Prepare for saving information + if (state->flags & ABRT_POST_WANT_HEADERS) { - curl_err = curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, save_headers); - die_if_curl_error(curl_err); - curl_err = curl_easy_setopt(handle, CURLOPT_WRITEHEADER, state); - die_if_curl_error(curl_err); + xcurl_easy_setopt_ptr(handle, CURLOPT_HEADERFUNCTION, (void*)save_headers); + xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEHEADER, state); } - if (state->flags & ABRT_CURL_POST_WANT_BODY) + FILE* body_stream = NULL; + if (state->flags & ABRT_POST_WANT_BODY) { body_stream = open_memstream(&state->body, &state->body_size); if (!body_stream) error_msg_and_die("out of memory"); - curl_err = curl_easy_setopt(handle, CURLOPT_WRITEDATA, body_stream); - die_if_curl_error(curl_err); + xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEDATA, body_stream); } - /* This is the place where everything happens. Here errors - * are not limited to "out of memory", can't just die. - */ + // This is the place where everything happens. + // Here errors are not limited to "out of memory", can't just die. curl_err = curl_easy_perform(handle); if (curl_err) { VERB2 log("curl_easy_perform: error %d", (int)curl_err); - if (state->flags & ABRT_CURL_POST_WANT_ERROR_MSG) + if (state->flags & ABRT_POST_WANT_ERROR_MSG) { state->curl_error_msg = check_curl_error(curl_err, "curl_easy_perform"); VERB3 log("curl_easy_perform: error_msg: %s", state->curl_error_msg); @@ -144,55 +269,22 @@ curl_post(curl_post_state_t* state, const char* url, const char* data) goto ret; } + // Headers/body are already saved (if requested), extract more info curl_err = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code); die_if_curl_error(curl_err); state->http_resp_code = response_code; - VERB3 log("after curl_easy_perform: http code %ld body:'%s'", response_code, state->body); ret: curl_easy_cleanup(handle); - curl_slist_free_all(httpheader_list); + if (httpheader_list) + curl_slist_free_all(httpheader_list); if (body_stream) fclose(body_stream); + if (data_file) + fclose(data_file); + if (post) + curl_formfree(post); return response_code; } - -curl_post_state_t *new_curl_post_state(int flags) -{ - curl_post_state_t *state = (curl_post_state_t *)xzalloc(sizeof(*state)); - state->flags = flags; - return state; -} - -void free_curl_post_state(curl_post_state_t *state) -{ - char **headers = state->headers; - if (headers) - { - while (*headers) - free(*headers++); - free(state->headers); - } - free(state->curl_error_msg); - free(state->body); - free(state); - -} - -char *find_header_in_curl_post_state(curl_post_state_t *state, const char *str) -{ - char **headers = state->headers; - if (headers) - { - unsigned len = strlen(str); - while (*headers) - { - if (strncmp(*headers, str, len) == 0) - return skip_whitespace(*headers + len); - headers++; - } - } - return NULL; -} diff --git a/lib/Utils/abrt_curl.h b/lib/Utils/abrt_curl.h index 3c302453..97642bd4 100644 --- a/lib/Utils/abrt_curl.h +++ b/lib/Utils/abrt_curl.h @@ -23,23 +23,64 @@ CURL* xcurl_easy_init(); -typedef struct curl_post_state { - int flags; - int http_resp_code; - unsigned header_cnt; - char **headers; - char *curl_error_msg; - char *body; - size_t body_size; -} curl_post_state_t; +typedef struct abrt_post_state { + /* Supplied by caller: */ + int flags; + const char *username; + const char *password; + /* Results of POST transaction: */ + int http_resp_code; + unsigned header_cnt; + char **headers; + char *curl_error_msg; + char *body; + size_t body_size; + char errmsg[CURL_ERROR_SIZE]; +} abrt_post_state_t; + +abrt_post_state_t *new_abrt_post_state(int flags); +void free_abrt_post_state(abrt_post_state_t *state); +char *find_header_in_abrt_post_state(abrt_post_state_t *state, const char *str); + +enum { + ABRT_POST_WANT_HEADERS = (1 << 0), + ABRT_POST_WANT_ERROR_MSG = (1 << 1), + ABRT_POST_WANT_BODY = (1 << 2), +}; enum { - ABRT_CURL_POST_WANT_HEADERS = (1 << 0), - ABRT_CURL_POST_WANT_ERROR_MSG = (1 << 1), - ABRT_CURL_POST_WANT_BODY = (1 << 2), + ABRT_POST_DATA_STRING = -1, + ABRT_POST_DATA_FROMFILE = -2, + ABRT_POST_DATA_FROMFILE_AS_FORM_DATA = -3, }; -curl_post_state_t *new_curl_post_state(int flags); -void free_curl_post_state(curl_post_state_t *state); -int curl_post(curl_post_state_t* state, const char* url, const char* data); -char *find_header_in_curl_post_state(curl_post_state_t *state, const char *str); +int +abrt_post(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *data, + off_t data_size); +static inline int +abrt_post_string(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *str) +{ + return abrt_post(state, url, content_type, str, ABRT_POST_DATA_STRING); +} +static inline int +abrt_post_file(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *filename) +{ + return abrt_post(state, url, content_type, filename, ABRT_POST_DATA_FROMFILE); +} +static inline int +abrt_post_file_as_form(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *filename) +{ + return abrt_post(state, url, content_type, filename, ABRT_POST_DATA_FROMFILE_AS_FORM_DATA); +} #endif diff --git a/lib/Utils/abrt_rh_support.cpp b/lib/Utils/abrt_rh_support.cpp index be2d3961..61341c0e 100644 --- a/lib/Utils/abrt_rh_support.cpp +++ b/lib/Utils/abrt_rh_support.cpp @@ -34,19 +34,51 @@ struct reportfile { xmlBufferPtr buf; }; -#define die_xml_oom() error_msg_and_die("can't create XML attribute (out of memory?)") +static void __attribute__((__noreturn__)) +die_xml_oom(void) +{ + error_msg_and_die("can't create XML attribute (out of memory?)"); +} + +static xmlBufferPtr +xxmlBufferCreate(void) +{ + xmlBufferPtr r = xmlBufferCreate(); + if (!r) + die_xml_oom(); + return r; +} + +static xmlTextWriterPtr +xxmlNewTextWriterMemory(xmlBufferPtr buf /*, int compression*/) +{ + xmlTextWriterPtr r = xmlNewTextWriterMemory(buf, /*compression:*/ 0); + if (!r) + die_xml_oom(); + return r; +} static void -xxmlTextWriterWriteAttribute(xmlTextWriterPtr writer, const char *name, const char *content) +xxmlTextWriterStartDocument(xmlTextWriterPtr writer, + const char * version, + const char * encoding, + const char * standalone) { - // these bright guys REDEFINED CHAR (!) to unsigned char... - if (xmlTextWriterWriteAttribute(writer, (unsigned char*)name, (unsigned char*)content) < 0) + if (xmlTextWriterStartDocument(writer, version, encoding, standalone) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterEndDocument(xmlTextWriterPtr writer) +{ + if (xmlTextWriterEndDocument(writer) < 0) die_xml_oom(); } static void xxmlTextWriterStartElement(xmlTextWriterPtr writer, const char *name) { + // these bright guys REDEFINED CHAR (!) to unsigned char... if (xmlTextWriterStartElement(writer, (unsigned char*)name) < 0) die_xml_oom(); } @@ -58,6 +90,31 @@ xxmlTextWriterEndElement(xmlTextWriterPtr writer) die_xml_oom(); } +#if 0 //unused +static void +xxmlTextWriterWriteElement(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteElement(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} +#endif + +static void +xxmlTextWriterWriteAttribute(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteAttribute(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} + +#if 0 //unused +static void +xxmlTextWriterWriteString(xmlTextWriterPtr writer, const char *content) +{ + if (xmlTextWriterWriteString(writer, (unsigned char*)content) < 0) + die_xml_oom(); +} +#endif + // // End the reportfile, and prepare it for delivery. // No more bindings can be added after this. @@ -69,9 +126,7 @@ close_writer(reportfile_t* file) return; // close off the end of the xml file - int rc = xmlTextWriterEndDocument(file->writer); - if (rc < 0) - die_xml_oom(); + xxmlTextWriterEndDocument(file->writer); xmlFreeTextWriter(file->writer); file->writer = NULL; } @@ -86,18 +141,12 @@ new_reportfile(void) reportfile_t* file = (reportfile_t*)xmalloc(sizeof(*file)); // set up a libxml 'buffer' and 'writer' to that buffer - file->buf = xmlBufferCreate(); - if (file->buf == NULL) - die_xml_oom(); - file->writer = xmlNewTextWriterMemory(file->buf, /*compression:*/ 0); - if (file->writer == NULL) - die_xml_oom(); + file->buf = xxmlBufferCreate(); + file->writer = xxmlNewTextWriterMemory(file->buf); // start a new xml document: // ... - int rc = xmlTextWriterStartDocument(file->writer, /*version:*/ NULL, /*encoding:*/ NULL, /*standalone:*/ NULL); - if (rc < 0) - die_xml_oom(); + xxmlTextWriterStartDocument(file->writer, /*version:*/ NULL, /*encoding:*/ NULL, /*standalone:*/ NULL); xxmlTextWriterStartElement(file->writer, "report"); xxmlTextWriterWriteAttribute(file->writer, "xmlns", "http://www.redhat.com/gss/strata"); @@ -174,11 +223,11 @@ post_signature(const char* baseURL, const char* signature) { string URL = concat_path_file(baseURL, "/signatures"); - curl_post_state *state = new_curl_post_state(0 - + ABRT_CURL_POST_WANT_HEADERS - + ABRT_CURL_POST_WANT_BODY - + ABRT_CURL_POST_WANT_ERROR_MSG); - int http_resp_code = curl_post(state, URL.c_str(), signature); + abrt_post_state *state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG); + int http_resp_code = abrt_post_string(state, URL.c_str(), "application/xml", signature); char *retval; const char *strata_msg; @@ -192,7 +241,7 @@ post_signature(const char* baseURL, const char* signature) state->body = NULL; break; } - strata_msg = find_header_in_curl_post_state(state, "Strata-Message:"); + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); if (strata_msg && strcmp(strata_msg, "CREATED") != 0) { retval = xstrdup(strata_msg); break; @@ -201,7 +250,7 @@ post_signature(const char* baseURL, const char* signature) break; default: - strata_msg = find_header_in_curl_post_state(state, "Strata-Message:"); + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); if (strata_msg) { retval = xasprintf("Error (HTTP response %d): %s", @@ -221,7 +270,7 @@ post_signature(const char* baseURL, const char* signature) break; } - free_curl_post_state(state); + free_abrt_post_state(state); return retval; } @@ -254,7 +303,7 @@ create_case(const char* baseURL, const char* description) else retval = ssprintf("Error: Response Code: %ld\nBody:\n%s", response_data->code, response_data->body); } - free_curl_post_state(state); + free_abrt_post_state(state); free((void*)response_data->strata_message); free((void*)response_data->body); -- cgit