+/*
+ * curllib.c
+ *
+ * Copyright 2016 Michael Rasmussen <mir@datanom.net>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <glib.h>
+#include "config.h"
+#include "curllib.h"
+#include "caldav.h"
+#include "xml.h"
+
+static gboolean curl_not_initialized = TRUE;
+static struct Data config;
+
+struct MemoryStruct {
+ char * memory;
+ size_t size;
+};
+
+struct Data {
+ char trace_ascii; /* 1 or 0 */
+};
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param text
+ * @param stream
+ * @param ptr
+ * @param size
+ * @param nohex
+ */
+static void dump(const char* text, FILE* stream, char* ptr, size_t size, char nohex) {
+ size_t i;
+ size_t c;
+
+ unsigned int width=0x10;
+
+ if(nohex)
+ /* without the hex output, we can fit more on screen */
+ width = 0x40;
+ fprintf(stream, "%s, %zd bytes (0x%zx)\n", text, size, size);
+ for(i=0; i<size; i+= width) {
+ fprintf(stream, "%04zx: ", i);
+ if(!nohex) {
+ /* hex not disabled, show it */
+ for(c = 0; c < width; c++) {
+ if(i+c < size)
+ fprintf(stream, "%02x ", ptr[i+c]);
+ else
+ fputs(" ", stream);
+ }
+ }
+ for(c = 0; (c < width) && (i+c < size); c++) {
+ /* check for 0D0A; if found, skip past and start a new line of output */
+ if (nohex && (i+c+1 < size) && ptr[i+c]==0x0D && ptr[i+c+1]==0x0A) {
+ i+=(c+2-width);
+ break;
+ }
+ fprintf(stream, "%c",(ptr[i+c]>=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.');
+ /* check again for 0D0A, to avoid an extra \n if it's at width */
+ if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) {
+ i+=(c+3-width);
+ break;
+ }
+ }
+ fputc('\n', stream); /* newline */
+ }
+ fflush(stream);
+}
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param handle
+ * @param type
+ * @param data
+ * @param size
+ * @param userp
+ * @return
+ */
+static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
+ struct Data* config = (struct Data *)userp;
+ const char* text;
+ (void)handle; /* prevent compiler warning */
+
+ switch (type) {
+ case CURLINFO_TEXT:
+ fprintf(stderr, "== Info: %s", data);
+ default: /* in case a new one is introduced to shock us */
+ return 0;
+ case CURLINFO_HEADER_OUT:
+ text = "=> Send header";
+ break;
+ case CURLINFO_DATA_OUT:
+ text = "=> Send data";
+ break;
+ case CURLINFO_SSL_DATA_OUT:
+ text = "=> Send SSL data";
+ break;
+ case CURLINFO_HEADER_IN:
+ text = "<= Recv header";
+ break;
+ case CURLINFO_DATA_IN:
+ text = "<= Recv data";
+ break;
+ case CURLINFO_SSL_DATA_IN:
+ text = "<= Recv SSL data";
+ break;
+ }
+ dump(text, stderr, data, size, config->trace_ascii);
+ return 0;
+}
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param ptr
+ * @param size
+ * @return void* to memory region
+ */
+static void* myrealloc(void* ptr, size_t size) {
+/* There might be a realloc() out there that doesn't like reallocing
+ * NULL pointers, so we take care of it here
+ * */
+ if(ptr)
+ return realloc(ptr, size);
+ else
+ return malloc(size);
+}
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param ptr
+ * @param size
+ * @param nmemb
+ * @param data
+ * @return number of written bytes
+ */
+static size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) {
+ size_t realsize = size * nmemb;
+ struct MemoryStruct* mem = (struct MemoryStruct *)data;
+ mem->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1);
+
+ if (mem->memory) {
+ memcpy(&(mem->memory[mem->size]), ptr, realsize);
+ mem->size += realsize;
+ mem->memory[mem->size] = 0;
+ }
+ return realsize;
+}
+
+static struct curl_slist* gslist_to_slist(GSList* list) {
+ struct curl_slist* s = NULL;
+
+ for (GSList* elem = list; elem; elem = g_slist_next(elem)) {
+ s = curl_slist_append(s, elem->data);
+ }
+
+ return s;
+}
+
+static void curl_set_request(CURL* curl, RequestType type) {
+ gchar* req = NULL;
+
+ switch (type) {
+ case DELETE: req = g_strdup("DELETE"); break;
+ case PROPFIND: req = g_strdup("PROPFIND"); break;
+ case PUT: req = g_strdup("PUT"); break;
+ case REPORT: req = g_strdup("REPORT"); break;
+ case GET: req = g_strdup("GET"); break;
+ case OPTIONS: req = g_strdup("OPTIONS"); break;
+ case LOCK: req = g_strdup("LOCK"); break;
+ case UNLOCK: req = g_strdup("UNLOCK"); break;
+ }
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req);
+ g_free(req);
+}
+
+static CURL* get_curl(Request* request, gboolean debug) {
+ CURL* curl;
+ gchar* userpwd;
+
+ curl = curl_easy_init();
+ if (curl) {
+ if (request->password) {
+ userpwd = g_strconcat(request->username, ":", request->password, NULL);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd);
+ } else {
+ userpwd = g_strconcat(request->username, NULL);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd);
+ }
+ g_free(userpwd);
+ curl_set_request(curl, request->request);
+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE_STRING);
+ curl_easy_setopt(curl, CURLOPT_URL, request->url);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, WriteMemoryCallback);
+ if (debug) {
+ config.trace_ascii = 1;
+ curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &config);
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ }
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1);
+ curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+ if (request->request != DELETE && request->request != GET && request->request != OPTIONS) {
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data);
+ curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE,
+ (request->data) ? strlen(request->data) : 0);
+ }
+ }
+
+ return curl;
+}
+
+static GSList* memory_struct_to_gslist(struct MemoryStruct buffer) {
+ GSList* list = NULL;
+ gchar **elems, **e;
+
+ if (buffer.size > 0) {
+ elems = g_strsplit(buffer.memory, "\n", 0);
+ for (e = elems; *e != NULL; ++e) {
+ if (g_strcmp0(*e, "\r") != 0 || g_strcmp0(*e, "") != 0)
+ list = g_slist_prepend(list, g_strdup(*e));
+ }
+ g_strfreev(elems);
+ }
+
+ return list;
+}
+
+Request* request_new() {
+ Request* r = g_new0(Request, 1);
+
+ return r;
+}
+
+
+void request_free(Request* request) {
+ if (request) {
+ if (request->headers) {
+ slist_free_gchar(request->headers);
+ request->headers = NULL;
+ }
+ if (request->data) {
+ g_free(request->data);
+ request->data = NULL;
+ }
+ if (request->username) {
+ g_free(request->username);
+ request->username = NULL;
+ }
+ if (request->password) {
+ g_free(request->password);
+ request->password = NULL;
+ }
+ if (request->url) {
+ g_free(request->url);
+ request->url = NULL;
+ }
+ g_free(request);
+ request = NULL;
+ }
+}
+
+Response* request_send(Request* request, gboolean debug) {
+ g_return_val_if_fail(request != NULL, NULL);
+
+ Response* r = g_new0(Response, 1);
+ CURLcode curlres = 0;
+ struct MemoryStruct data;
+ struct MemoryStruct headers;
+ char error_buf[CURL_ERROR_SIZE];
+ struct curl_slist* http_headers;
+
+ data.memory = NULL;
+ data.size = 0;
+ headers.memory = NULL;
+ headers.size = 0;
+
+ if (curl_not_initialized) {
+ curl_global_init(CURL_GLOBAL_ALL);
+ curl_not_initialized = FALSE;
+ }
+
+ CURL* curl = get_curl(request, debug);
+ if (curl) {
+ http_headers = gslist_to_slist(request->headers);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_headers);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&data);
+ curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *)&headers);
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, (char *) &error_buf);
+ curlres = curl_easy_perform(curl);
+ if (curlres != CURLE_OK) {
+ request->data = g_strdup(error_buf);
+ }
+ else {
+ curlres = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r->status);
+ r->headers = memory_struct_to_gslist(headers);
+ r->data = g_strndup(data.memory, data.size);
+ if (r->status == 207 && (request->request == DELETE || request->request == PUT)) {
+ /* Error code is returnded inside XML response */
+ GSList* response = find_element("//d:status", r->data, "d", "DAV:");
+ if (response) {
+ if (g_strstr_len((gchar *) response->data, -1, "423")) {
+ r->status = 423;
+ }
+ }
+ slist_free_gchar(response);
+ }
+ }
+ if (data.memory)
+ free(data.memory);
+ if (headers.memory)
+ free(headers.memory);
+ curl_slist_free_all(http_headers);
+ curl_easy_cleanup(curl);
+ }
+
+ return r;
+}
+
+void response_free(Response* response) {
+ if (response) {
+ if (response->headers) {
+ slist_free_gchar(response->headers);
+ response->headers = NULL;
+ }
+ if (response->data) {
+ g_free(response->data);
+ response->data = NULL;
+ }
+ g_free(response);
+ response = NULL;
+ }
+}