/* Copyright (C) 2010 ABRT team Copyright (C) 2010 RedHat Inc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "internal_libreport.h" #include "libreport_curl.h" #include "abrt_rh_support.h" struct reportfile { xmlTextWriterPtr writer; xmlBufferPtr buf; }; 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 xxmlTextWriterStartDocument(xmlTextWriterPtr writer, const char * version, const char * encoding, const char * standalone) { 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(); } static void xxmlTextWriterEndElement(xmlTextWriterPtr writer) { if (xmlTextWriterEndElement(writer) < 0) die_xml_oom(); } static void xxmlTextWriterWriteElement(xmlTextWriterPtr writer, const char *name, const char *content) { if (xmlTextWriterWriteElement(writer, (unsigned char*)name, (unsigned char*)content) < 0) die_xml_oom(); } 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 // // Reportfile helpers // // End the reportfile, and prepare it for delivery. // No more bindings can be added after this. static void close_writer(reportfile_t* file) { if (!file->writer) return; // close off the end of the xml file xxmlTextWriterEndDocument(file->writer); xmlFreeTextWriter(file->writer); file->writer = NULL; } // This allocates a reportfile_t structure and initializes it. reportfile_t* new_reportfile(void) { // create a new reportfile_t reportfile_t* file = (reportfile_t*)xmalloc(sizeof(*file)); // set up a libxml 'buffer' and 'writer' to that buffer file->buf = xxmlBufferCreate(); file->writer = xxmlNewTextWriterMemory(file->buf); // start a new xml document: // ... xxmlTextWriterStartDocument(file->writer, /*version:*/ NULL, /*encoding:*/ NULL, /*standalone:*/ NULL); xxmlTextWriterStartElement(file->writer, "report"); xxmlTextWriterWriteAttribute(file->writer, "xmlns", "http://www.redhat.com/gss/strata"); return file; } static void internal_reportfile_start_binding(reportfile_t* file, const char* name, int isbinary, const char* filename) { // writer, "binding"); xxmlTextWriterWriteAttribute(file->writer, "name", name); if (filename) xxmlTextWriterWriteAttribute(file->writer, "fileName", filename); if (isbinary) xxmlTextWriterWriteAttribute(file->writer, "type", "binary"); else xxmlTextWriterWriteAttribute(file->writer, "type", "text"); } // Add a new text binding void reportfile_add_binding_from_string(reportfile_t* file, const char* name, const char* value) { // internal_reportfile_start_binding(file, name, /*isbinary:*/ 0, /*filename:*/ NULL); xxmlTextWriterWriteAttribute(file->writer, "value", value); xxmlTextWriterEndElement(file->writer); } // Add a new binding to a report whose value is represented as a file. void reportfile_add_binding_from_namedfile(reportfile_t* file, const char* on_disk_filename, /* unused so far */ const char* binding_name, const char* recorded_filename, int isbinary) { // char *href_name = concat_path_file("content", binding_name); xxmlTextWriterWriteAttribute(file->writer, "href", href_name); free(href_name); } // Return the contents of the reportfile as a string. const char* reportfile_as_string(reportfile_t* file) { close_writer(file); // unsigned char -> char return (char*)file->buf->content; } void free_reportfile(reportfile_t* file) { if (!file) return; close_writer(file); xmlBufferFree(file->buf); free(file); } void free_rhts_result(rhts_result_t *p) { if (!p) return; free(p->url); free(p->msg); free(p->body); free(p); } // // Common // static const char *const text_plain_header[] = { "Accept: text/plain", NULL }; // // Creating new case // See // https://access.redhat.com/knowledge/docs/Red_Hat_Customer_Portal/integration_guide.html // // $ curl -X POST -H 'Content-Type: application/xml' --data // ' // // Example Case // Example created with cURL // Red Hat Enterprise Linux6.0 // ' // https://api.access.redhat.com/rs/cases // static char* make_case_data(const char* summary, const char* description, const char* product, const char* version, const char* component) { char* retval; xmlTextWriterPtr writer; xmlBufferPtr buf; buf = xxmlBufferCreate(); writer = xxmlNewTextWriterMemory(buf); xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); xxmlTextWriterStartElement(writer, "case"); xxmlTextWriterWriteAttribute(writer, "xmlns", "http://www.redhat.com/gss/strata"); xxmlTextWriterWriteElement(writer, "summary", summary); xxmlTextWriterWriteElement(writer, "description", description); if (product) { xxmlTextWriterWriteElement(writer, "product", product); } if (version) { xxmlTextWriterWriteElement(writer, "version", version); } if (component) { xxmlTextWriterWriteElement(writer, "component", component); } xxmlTextWriterEndDocument(writer); retval = xstrdup((const char*)buf->content); xmlFreeTextWriter(writer); xmlBufferFree(buf); return retval; } static rhts_result_t* post_case_to_url(const char* url, const char* username, const char* password, bool ssl_verify, const char **additional_headers, const char* product, const char* version, const char* summary, const char* description, const char* component) { rhts_result_t *result = xzalloc(sizeof(*result)); char *url_copy = NULL; char *case_data = make_case_data(summary, description, product, version, component); int redirect_count = 0; char *errmsg; post_state_t *post_state; redirect: post_state = new_post_state(0 + POST_WANT_HEADERS + POST_WANT_BODY + POST_WANT_ERROR_MSG + (ssl_verify ? POST_WANT_SSL_VERIFY : 0) ); post_state->username = username; post_state->password = password; post_string(post_state, url, "application/xml", additional_headers, case_data); char *location = find_header_in_post_state(post_state, "Location:"); switch (post_state->http_resp_code) { case 404: /* Not strictly necessary (default branch would deal with it too), * but makes this typical error less cryptic: * instead of returning html-encoded body, we show short concise message, * and show offending URL (typos in which is a typical cause) */ result->error = -1; result->msg = xasprintf("Error in HTTP POST, " "HTTP code: 404 (Not found), URL:'%s'", url); break; case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ case 302: /* "302 Found" (just in case) */ case 305: /* "305 Use Proxy" */ if (++redirect_count < 10 && location) { free(url_copy); url = url_copy = xstrdup(location); free_post_state(post_state); goto redirect; } /* fall through */ default: // TODO: error messages in headers // are observed to be more informative than the body: // // 'HTTP/1.1 400 Bad Request' // 'Date: Mon, 10 Oct 2011 13:31:56 GMT^M' // 'Server: Apache^M' // 'Strata-Message: The supplied parameter Fedora value can not be processed^M' // ^^^^^^^^^^^^^^^^^^^^^^^^^ useful message // 'Strata-Code: BAD_PARAMETER^M' // 'Content-Length: 1^M' // 'Content-Type: text/plain; charset=UTF-8^M' // 'Connection: close^M' // '^M' // ' ' <------ body is useless result->error = -1; errmsg = post_state->curl_error_msg; if (errmsg && errmsg[0]) { result->msg = xasprintf(_("Error in case creation at '%s': %s"), url, errmsg); } else { errmsg = find_header_in_post_state(post_state, "Strata-Message:"); if (!errmsg) errmsg = post_state->body; if (errmsg && errmsg[0]) result->msg = xasprintf(_("Error in case creation at '%s'," " HTTP code: %d, server says: '%s'"), url, post_state->http_resp_code, errmsg); else result->msg = xasprintf(_("Error in case creation at '%s'," " HTTP code: %d"), url, post_state->http_resp_code); } break; case 200: case 201: /* Created successfully */ result->url = xstrdup(location); /* note: xstrdup(NULL) returns NULL */ } /* switch (HTTP code) */ result->http_resp_code = post_state->http_resp_code; result->body = post_state->body; post_state->body = NULL; free_post_state(post_state); free(case_data); free(url_copy); return result; } rhts_result_t* create_new_case(const char* base_url, const char* username, const char* password, bool ssl_verify, const char* product, const char* version, const char* summary, const char* description, const char* component) { char *url = concat_path_file(base_url, "cases"); rhts_result_t *result = post_case_to_url(url, username, password, ssl_verify, (const char **)text_plain_header, product, version, summary, description, component ); if (!result->error && !result->url) { /* Case Creation returned valid code, but no location */ result->error = -1; free(result->msg); result->msg = xasprintf(_("Error in case creation at '%s':" " no Location URL, HTTP code: %d"), url, result->http_resp_code ); } free(url); return result; } // // Add case comment // // $ curl -X POST -H 'Content-Type: application/xml' --data // ' // // Test comment! This can contain lots of information, etc. // ' // https://api.access.redhat.com/rs/cases/NNNNNNN/comments // static char* make_comment_data(const char *comment_text) { char *retval; xmlTextWriterPtr writer; xmlBufferPtr buf; buf = xxmlBufferCreate(); writer = xxmlNewTextWriterMemory(buf); xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); xxmlTextWriterStartElement(writer, "comment"); xxmlTextWriterWriteAttribute(writer, "xmlns", "http://www.redhat.com/gss/strata"); xxmlTextWriterWriteElement(writer, "text", comment_text); xxmlTextWriterEndDocument(writer); retval = xstrdup((const char*)buf->content); xmlFreeTextWriter(writer); xmlBufferFree(buf); return retval; } static rhts_result_t* post_comment_to_url(const char *url, const char *username, const char *password, bool ssl_verify, const char **additional_headers, const char *comment_text) { rhts_result_t *result = xzalloc(sizeof(*result)); char *url_copy = NULL; char *xml = make_comment_data(comment_text); int redirect_count = 0; char *errmsg; post_state_t *post_state; redirect: post_state = new_post_state(0 + POST_WANT_HEADERS + POST_WANT_BODY + POST_WANT_ERROR_MSG + (ssl_verify ? POST_WANT_SSL_VERIFY : 0) ); post_state->username = username; post_state->password = password; post_string(post_state, url, "application/xml", additional_headers, xml); char *location = find_header_in_post_state(post_state, "Location:"); switch (post_state->http_resp_code) { case 404: /* Not strictly necessary (default branch would deal with it too), * but makes this typical error less cryptic: * instead of returning html-encoded body, we show short concise message, * and show offending URL (typos in which is a typical cause) */ result->error = -1; result->msg = xasprintf("Error in HTTP POST, " "HTTP code: 404 (Not found), URL:'%s'", url); break; case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ case 302: /* "302 Found" (just in case) */ case 305: /* "305 Use Proxy" */ if (++redirect_count < 10 && location) { free(url_copy); url = url_copy = xstrdup(location); free_post_state(post_state); goto redirect; } /* fall through */ default: result->error = -1; errmsg = post_state->curl_error_msg; if (errmsg && errmsg[0]) { result->msg = xasprintf(_("Error in comment creation at '%s': %s"), url, errmsg); } else { errmsg = find_header_in_post_state(post_state, "Strata-Message:"); if (!errmsg) errmsg = post_state->body; if (errmsg && errmsg[0]) result->msg = xasprintf(_("Error in comment creation at '%s'," " HTTP code: %d, server says: '%s'"), url, post_state->http_resp_code, errmsg); else result->msg = xasprintf(_("Error in comment creation at '%s'," " HTTP code: %d"), url, post_state->http_resp_code); } break; case 200: case 201: /* Created successfully */ result->url = xstrdup(location); /* note: xstrdup(NULL) returns NULL */ } /* switch (HTTP code) */ result->http_resp_code = post_state->http_resp_code; result->body = post_state->body; post_state->body = NULL; free_post_state(post_state); free(xml); free(url_copy); return result; } rhts_result_t* add_comment_to_case(const char* base_url, const char* username, const char* password, bool ssl_verify, const char* comment_text) { char *url = concat_path_file(base_url, "comments"); rhts_result_t *result = post_comment_to_url(url, username, password, ssl_verify, // NB! text_plain_header here was causing error 404 instead of 201 (Created)! // NULL makes curl use "Accept: */*" instead and creation works. // Likely a bug on the server! (const char **) NULL, //text_plain_header, comment_text ); if (!result->error && !result->url) { /* Creation returned valid code, but no location */ result->error = -1; free(result->msg); result->msg = xasprintf(_("Error in comment creation at '%s':" " no Location URL, HTTP code: %d"), url, result->http_resp_code ); } free(url); return result; } // // Attach file to case // static rhts_result_t* post_file_to_url(const char* url, const char* username, const char* password, bool ssl_verify, bool post_as_form, const char **additional_headers, const char *file_name) { rhts_result_t *result = xzalloc(sizeof(*result)); char *url_copy = NULL; int redirect_count = 0; char *errmsg; post_state_t *atch_state; redirect_attach: atch_state = new_post_state(0 + POST_WANT_HEADERS + POST_WANT_BODY + POST_WANT_ERROR_MSG + (ssl_verify ? POST_WANT_SSL_VERIFY : 0) ); atch_state->username = username; atch_state->password = password; if (post_as_form) { /* Sends data in multipart/mixed document. One detail is that * file *name* is also sent to the server. */ post_file_as_form(atch_state, url, "application/octet-stream", additional_headers, file_name ); } else { /* Sends file's raw contents */ post_file(atch_state, url, "application/octet-stream", additional_headers, file_name ); } char *atch_location = find_header_in_post_state(atch_state, "Location:"); switch (atch_state->http_resp_code) { case 305: /* "305 Use Proxy" */ if (++redirect_count < 10 && atch_location) { free(url_copy); url = url_copy = xstrdup(atch_location); free_post_state(atch_state); goto redirect_attach; } /* fall through */ default: /* Error */ result->error = -1; errmsg = atch_state->curl_error_msg; if (errmsg && errmsg[0]) { result->msg = xasprintf("Error in file upload at '%s': %s", url, errmsg); } else { errmsg = atch_state->body; if (errmsg && errmsg[0]) result->msg = xasprintf("Error in file upload at '%s'," " HTTP code: %d, server says: '%s'", url, atch_state->http_resp_code, errmsg); else result->msg = xasprintf("Error in file upload at '%s'," " HTTP code: %d", url, atch_state->http_resp_code); } break; case 200: case 201: result->url = xstrdup(atch_location); /* note: xstrdup(NULL) returns NULL */ //result->msg = xstrdup("File uploaded successfully"); } /* switch (HTTP code) */ result->http_resp_code = atch_state->http_resp_code; result->body = atch_state->body; atch_state->body = NULL; free_post_state(atch_state); free(url_copy); return result; } rhts_result_t* attach_file_to_case(const char* base_url, const char* username, const char* password, bool ssl_verify, const char *file_name) { char *url = concat_path_file(base_url, "attachments"); rhts_result_t *result = post_file_to_url(url, username, password, ssl_verify, /*post_as_form:*/ true, (const char **) text_plain_header, file_name ); free(url); return result; } // // Get hint // rhts_result_t* get_rhts_hints(const char* base_url, const char* username, const char* password, bool ssl_verify, const char* file_name) { char *url = concat_path_file(base_url, "problems"); // rhts_result_t *result = post_case_to_url(url, // username, // password, // ssl_verify, // NULL, // release, // summary, // description, // component // ); rhts_result_t *result = post_file_to_url(url, username, password, ssl_verify, /*post_as_form:*/ false, /*headers:*/ NULL, file_name ); free(url); return result; }