X-Git-Url: http://git.datanom.net/caldav.git/blobdiff_plain/7f587903cb1680dc6d9a70603a9db396dc645627..e1b22e2b5b944589477889b759029f8fe104a731:/src/curllib.c diff --git a/src/curllib.c b/src/curllib.c new file mode 100644 index 0000000..985bf53 --- /dev/null +++ b/src/curllib.c @@ -0,0 +1,360 @@ +/* + * curllib.c + * + * Copyright 2016 Michael Rasmussen + * + * 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 +#include +#include +#include +#include +#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=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; + } +}