summaryrefslogtreecommitdiffstats
path: root/lib/utils/abrt_curl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/utils/abrt_curl.cpp')
-rw-r--r--lib/utils/abrt_curl.cpp320
1 files changed, 320 insertions, 0 deletions
diff --git a/lib/utils/abrt_curl.cpp b/lib/utils/abrt_curl.cpp
new file mode 100644
index 00000000..af3defca
--- /dev/null
+++ b/lib/utils/abrt_curl.cpp
@@ -0,0 +1,320 @@
+/*
+ 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 "abrtlib.h"
+#include "abrt_curl.h"
+#include "comm_layer_inner.h"
+
+using namespace std;
+
+/*
+ * Utility functions
+ */
+CURL* xcurl_easy_init()
+{
+ CURL* curl = curl_easy_init();
+ if (!curl)
+ {
+ error_msg_and_die("Can't create curl handle");
+ }
+ return curl;
+}
+
+static char*
+check_curl_error(CURLcode err, const char* msg)
+{
+ if (err)
+ return xasprintf("%s: %s", msg, curl_easy_strerror(err));
+ return NULL;
+}
+
+static void
+die_if_curl_error(CURLcode err)
+{
+ 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)
+{
+ abrt_post_state_t* state = (abrt_post_state_t*)ptr;
+ size_t size = count * nmemb;
+
+ char *h = xstrndup((char*)buffer_pv, size);
+ strchrnul(h, '\r')[0] = '\0';
+ strchrnul(h, '\n')[0] = '\0';
+
+ unsigned cnt = state->header_cnt;
+
+ /* Check for the case when curl follows a redirect:
+ * header 0: 'HTTP/1.1 301 Moved Permanently'
+ * header 1: 'Connection: close'
+ * header 2: 'Location: NEW_URL'
+ * header 3: ''
+ * header 0: 'HTTP/1.1 200 OK' <-- we need to forget all hdrs and start anew
+ */
+ if (cnt != 0
+ && strncmp(h, "HTTP/", 5) == 0
+ && state->headers[cnt-1][0] == '\0' /* prev header is an empty string */
+ ) {
+ char **headers = state->headers;
+ if (headers)
+ {
+ while (*headers)
+ free(*headers++);
+ }
+ cnt = 0;
+ }
+
+ VERB3 log("save_headers: header %d: '%s'", cnt, h);
+ state->headers = (char**)xrealloc(state->headers, (cnt+2) * sizeof(state->headers[0]));
+ state->headers[cnt] = h;
+ state->header_cnt = ++cnt;
+ state->headers[cnt] = NULL;
+
+ return size;
+}
+
+int
+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;
+ abrt_post_state_t localstate;
+
+ VERB3 log("abrt_post('%s','%s')", url, data);
+
+ if (!state)
+ {
+ memset(&localstate, 0, sizeof(localstate));
+ state = &localstate;
+ }
+
+ state->http_resp_code = response_code = -1;
+
+ CURL *handle = xcurl_easy_init();
+
+ // 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);
+
+ // 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
+ xcurl_easy_setopt_ptr(handle, CURLOPT_POSTFIELDS, data);
+ // note1: if data_size == ABRT_POST_DATA_STRING == -1, curl will use strlen(data)
+ xcurl_easy_setopt_long(handle, CURLOPT_POSTFIELDSIZE, data_size);
+ // note2: CURLOPT_POSTFIELDSIZE_LARGE can't be used: xcurl_easy_setopt_long()
+ // truncates data_size on 32-bit arch. Need xcurl_easy_setopt_long_long()?
+ // Also, I'm not sure CURLOPT_POSTFIELDSIZE_LARGE special-cases -1.
+ }
+ // 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);
+
+ // Prepare for saving information
+ if (state->flags & ABRT_POST_WANT_HEADERS)
+ {
+ xcurl_easy_setopt_ptr(handle, CURLOPT_HEADERFUNCTION, (void*)save_headers);
+ xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEHEADER, state);
+ }
+ 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");
+ xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEDATA, body_stream);
+ }
+ if (!(state->flags & ABRT_POST_WANT_SSL_VERIFY))
+ {
+ xcurl_easy_setopt_long(handle, CURLOPT_SSL_VERIFYPEER, 0);
+ xcurl_easy_setopt_long(handle, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
+ // 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_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);
+ }
+ 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);
+ 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;
+}