summaryrefslogtreecommitdiffstats
path: root/src/caldav-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/caldav-utils.c')
-rw-r--r--src/caldav-utils.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/src/caldav-utils.c b/src/caldav-utils.c
new file mode 100644
index 0000000..aa3f02e
--- /dev/null
+++ b/src/caldav-utils.c
@@ -0,0 +1,729 @@
+/* vim: set textwidth=80 tabstop=4: */
+
+/* Copyright (c) 2008 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "caldav-utils.h"
+#include "md5.h"
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <curl/curl.h>
+#include <ctype.h>
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param text
+ * @param stream
+ * @param ptr
+ * @param size
+ * @param nohex
+ */
+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
+ */
+int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
+ struct config_data* config = (struct config_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
+ */
+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;
+}
+
+/**
+ * This function is burrowed from the libcurl documentation
+ * @param ptr
+ * @param size
+ * @param nmemb
+ * @param data
+ * @return number of written bytes
+ */
+size_t WriteHeaderCallback(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;
+}
+
+/*
+size_t ReadMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data){
+ struct MemoryStruct* mem = (struct MemoryStruct *)data;
+
+ memcpy(ptr, mem->memory, mem->size);
+ return mem->size;
+}
+*/
+
+/**
+ * Initialize caldav settings structure.
+ * @param settings @see caldav_settings
+ */
+void init_caldav_settings(caldav_settings* settings) {
+ settings->username = NULL;
+ settings->password = NULL;
+ settings->url = NULL;
+ settings->file = NULL;
+ settings->usehttps = FALSE;
+ settings->custom_cacert = NULL;
+ settings->verify_ssl_certificate = TRUE;
+ settings->debug = FALSE;
+ settings->trace_ascii = TRUE;
+ settings->ACTION = UNKNOWN;
+ settings->start = 0;
+ settings->end = 0;
+}
+
+/**
+ * Free memory assigned to caldav settings structure.
+ * @param settings @see caldav_settings
+ */
+void free_caldav_settings(caldav_settings* settings) {
+ if (settings->username) {
+ g_free(settings->username);
+ settings->username = NULL;
+ }
+ if (settings->password) {
+ g_free(settings->password);
+ settings->password = NULL;
+ }
+ if (settings->url) {
+ g_free(settings->url);
+ settings->url = NULL;
+ }
+ if (settings->file) {
+ g_free(settings->file);
+ settings->file = NULL;
+ }
+ if (settings->custom_cacert) {
+ g_free(settings->custom_cacert);
+ settings->custom_cacert = NULL;
+ }
+ settings->verify_ssl_certificate = TRUE;
+ settings->usehttps = FALSE;
+ settings->debug = FALSE;
+ settings->trace_ascii = TRUE;
+ settings->ACTION = UNKNOWN;
+ settings->start = 0;
+ settings->end = 0;
+}
+
+static gchar* place_after_hostname(const gchar* start, const gchar* stop) {
+ gchar* newpos = NULL;
+ gchar* pos = (gchar *) stop;
+ gboolean digit = TRUE;
+
+ if (pos && stop && strcmp(start, pos) != 0) {
+ while (*pos != ':' && strcmp(start, pos) != 0)
+ --pos;
+ if (pos > start) {
+ gchar* tmp = (gchar *) pos + 1;
+ /* is pos++ a port number */
+ while (*tmp != '/' && digit) {
+ if (isdigit(*tmp) != 0) {
+ digit = TRUE;
+ tmp++;
+ }
+ else
+ digit = FALSE;
+ }
+ if (digit) {
+ /* pos was a port number */
+ while (*pos != '@' && strcmp(start, pos) != 0)
+ --pos;
+ if (strcmp(start, pos) != 0)
+ newpos = pos;
+ }
+ else {
+ while (*pos != '@' && pos != stop)
+ pos++;
+ if (pos != stop)
+ newpos = pos;
+ }
+ }
+ else {
+ /* is a username present */
+ gchar* tmp = NULL;
+ while (*pos != '/' && pos != stop) {
+ if (*pos == '@')
+ tmp = pos;
+ pos++;
+ }
+ if (tmp && pos != stop)
+ newpos = tmp;
+ }
+ }
+ return newpos;
+}
+
+/**
+ * Parse URL
+ * @param settings @see caldav_settings
+ * @param url String containing URL to collection
+ */
+void parse_url(caldav_settings* settings, const char* url) {
+ char* start;
+ char* pos;
+ char* end;
+ char* login;
+
+ login = pos = end = start = NULL;
+ if (!url)
+ return;
+ if ((pos = strstr(url, "//")) != NULL) {
+ /* Does the URL use https ?*/
+ if (!g_ascii_strncasecmp(url,"https",5) && settings->usehttps == FALSE) {
+ settings->usehttps=TRUE;
+ }
+ start = g_strdup(&(*(pos + 2)));
+ if ((pos = place_after_hostname(start, strrchr(start, '\0') - 1)) != NULL) {
+ /* username and/or password present */
+ login = g_strndup(start, pos - start);
+ end = pos;
+ if ((pos = strrchr(login, ':')) != NULL) {
+ /* both username and password is present */
+ settings->username = g_strndup(login, pos - login);
+ settings->password = g_strdup(++pos);
+ }
+ else {
+ /* only username present */
+ settings->username = g_strdup(login);
+ settings->password = NULL;
+ }
+ g_free(login);
+ settings->url = g_strdup(++end);
+ }
+ else {
+ /* no username or password present */
+ settings->url = g_strdup(start);
+ settings->username = NULL;
+ settings->password = NULL;
+ }
+ g_free(start);
+ }
+}
+
+/**
+ * Find a specific HTTP header from last request
+ * @param header HTTP header to search for
+ * @param headers String of HTTP headers from last request
+ * @param lowcase Should string be returned in all lower case.
+ * @return The header found or NULL
+ */
+#define MAX_TOKENS 2
+gchar* get_response_header(
+ const char* header, gchar* headers, gboolean lowcase) {
+ gchar* line;
+ gchar* head = NULL;
+ gchar* oldhead = NULL;
+ gchar** buf;
+ gchar* header_list;
+ gchar* saveptr;
+
+ header_list = g_strdup(headers);
+ line = strtok_r(header_list, "\r\n", &saveptr);
+ if (line != NULL) {
+ do {
+ buf = g_strsplit(line, ":", MAX_TOKENS);
+ if (buf[1] != NULL) {
+ if (g_ascii_strcasecmp(buf[0], header) == 0) {
+ if (head) {
+ if (strcmp(head, buf[1]) != 0) {
+ oldhead = head;
+ head = g_strconcat(head, ", ", buf[1], NULL);
+ g_free(oldhead);
+ }
+ }
+ else
+ head = g_strdup(buf[1]);
+ if (head)
+ g_strstrip(head);
+ }
+ }
+ g_strfreev(buf);
+ } while ((line = strtok_r(NULL, "\r\n", &saveptr)) != NULL);
+ }
+ g_free(header_list);
+ if (head)
+ return (lowcase) ? g_ascii_strdown(head, -1) : head;
+ else
+ return NULL;
+}
+
+static const char* VCAL_HEAD =
+"BEGIN:VCALENDAR\r\n"
+"PRODID:-//CalDAV Calendar//NONSGML libcaldav//EN\r\n"
+"VERSION:2.0\r\n";
+static const char* VCAL_FOOT = "END:VCALENDAR";
+
+/**
+ * Parse response from CalDAV server. Internal function.
+ * @param report Response from server
+ * @param element XML element to find
+ * @param type VCalendar element to find
+ * @param wrap Is this the final parsing or just a part
+ * @param recursive Stop after first match or not
+ * @return the parsed result
+ */
+static gchar* parse_caldav_report_wrap(
+ char* report, const char* element, const char* type,
+ gboolean wrap, gboolean recursive) {
+ char* pos;
+ char* start;
+ char* object;
+ char* tmp_report;
+ char* tmp;
+ gchar* response;
+ gchar* begin_type;
+ gchar* end_type;
+ gboolean keep_going = TRUE;
+
+ begin_type = g_strdup_printf("BEGIN:%s", type);
+ end_type = g_strdup_printf("END:%s", type);
+ pos = start = object = response = NULL;
+ tmp_report = g_strdup(report);
+ while ((pos = strstr(tmp_report, element)) != NULL && keep_going) {
+ pos = strchr(pos, '>');
+ if (!pos) {
+ break;
+ }
+ pos = &(*(pos + 1));
+ pos = strstr(pos, begin_type);
+ if (!pos) {
+ break;
+ }
+ object = &(*(pos + strlen(begin_type)));
+ object = g_strchug(object);
+ start = g_strdup(object);
+ if ((pos = strstr(start, end_type)) == NULL) {
+ g_free(start);
+ break;
+ }
+ char end_not_found = 1;
+ while (end_not_found == 1) {
+ if (strstr(pos+1, end_type) < strstr(pos+1, element)) {
+ if (strstr(pos+1, end_type) != NULL) {
+ pos = strstr(pos+1, end_type);
+ }
+ else {
+ end_not_found = 0;
+ }
+ }
+ else {
+ end_not_found = 0;
+ }
+ }
+ object = g_strndup(start, strlen(start) - strlen(pos));
+ if (response) {
+ tmp = g_strdup(response);
+ g_free(response);
+ response = g_strdup_printf("%s%s\r\n%s%s\r\n",
+ tmp, begin_type, object, end_type);
+ g_free(tmp);
+ }
+ else {
+ if (wrap)
+ response = g_strdup_printf("%s%s\r\n%s%s\r\n",
+ VCAL_HEAD, begin_type, object, end_type);
+ else
+ response = g_strdup_printf("%s\r\n%s%s\r\n",
+ begin_type, object, end_type);
+ }
+ if (recursive) {
+ pos = strchr(pos, '>');
+ g_free(tmp_report);
+ tmp_report = g_strdup(&(*(pos + 1)));
+ }
+ else {
+ keep_going = FALSE;
+ }
+ g_free(start);
+ g_free(object);
+ }
+ g_free(tmp_report);
+ g_free(begin_type);
+ g_free(end_type);
+ if (wrap)
+ if (response) {
+ object = g_strdup(response);
+ g_free(response);
+ response = g_strdup_printf("%s%s", object, VCAL_FOOT);
+ g_free(object);
+ }
+ return response;
+}
+
+/**
+ * Parse response from CalDAV server
+ * @param report Response from server
+ * @param element XML element to find
+ * @param type VCalendar element to find
+ * @return the parsed result
+ */
+gchar* parse_caldav_report(char* report, const char* element, const char* type) {
+ gchar* response = NULL;
+ gchar* timezone = NULL;
+ gchar* temp = NULL;
+
+ if (!report || !element || !type)
+ return NULL;
+ /* test for VTIMEZONE.
+ * Only the first found will be used and this will then
+ * be the time zone for the entire report
+ */
+ timezone = parse_caldav_report_wrap(
+ report, element, "VTIMEZONE", FALSE, FALSE);
+ if (timezone) {
+ response = g_strdup_printf("%s%s", VCAL_HEAD, timezone);
+ g_free(timezone);
+ temp = parse_caldav_report_wrap(report, element, type, FALSE, TRUE);
+ if (temp) {
+ gchar* tmp = g_strdup(response);
+ g_free(response);
+ response = g_strdup_printf("%s%s%s", tmp, temp, VCAL_FOOT);
+ g_free(tmp);
+ g_free(temp);
+ }
+ else {
+ g_free(response);
+ return NULL;
+ }
+ }
+ else
+ response = parse_caldav_report_wrap(report, element, type, TRUE, TRUE);
+ return response;
+}
+
+/**
+ * Convert a time_t variable to CalDAV DateTime
+ * @param time a specific date and time
+ * @return the CalDAV DateTime
+ */
+gchar* get_caldav_datetime(time_t* time) {
+ struct tm *current;
+ gchar* datetime;
+
+ current = localtime(time);
+ datetime = g_strdup_printf("%d%.2d%.2dT%.2d%.2d%.2dZ",
+ current->tm_year + 1900, current->tm_mon + 1, current->tm_mday,
+ current->tm_hour, current->tm_min, current->tm_sec);
+ return datetime;
+}
+
+/**
+ * Create a random text string, using MD5. @see caldav_md5_hex_digest()
+ * @param text some text to randomize
+ * @return MD5 hash of text
+ */
+gchar* random_file_name(gchar* text) {
+ unsigned char* name;
+ gchar md5sum[33];
+
+ name = (unsigned char *) g_strdup(text);
+ caldav_md5_hex_digest(md5sum, name);
+ g_free(name);
+ return g_strdup(md5sum);
+}
+
+/**
+ * Does the event contain a UID element or not. If not add it.
+ * @param object A specific event
+ * @return event, eventually added UID
+ */
+gchar* verify_uid(gchar* object) {
+ gchar* uid;
+ gchar* newobj;
+ gchar* pos;
+
+ newobj = g_strdup(object);
+ uid = get_response_header("uid", object, TRUE);
+ if (!uid) {
+ object = g_strdup(newobj);
+ g_free(newobj);
+ pos = strstr(object, "END:VEVENT");
+ newobj = g_strndup(object, strlen(object) - strlen(pos));
+ newobj = g_strchomp(newobj);
+ uid = random_file_name(object);
+ gchar*tmp = g_strdup(newobj);
+ g_free(newobj);
+ newobj = g_strdup_printf("%s\r\nUID:libcaldav-%s@tempuri.org\r\n%s",
+ tmp, uid, pos);
+ g_free(uid);
+ g_free(tmp);
+ g_free(object);
+ }
+ else
+ g_free(uid);
+ /*uid = g_strdup(newobj);
+ g_free(newobj);*/
+ g_strchomp(newobj);
+ /*g_free(uid);*/
+ return newobj;
+}
+
+/**
+ * Fetch a URL from a XML element
+ * @param text String
+ * @return URL
+ */
+#define ELEM_HREF "href>"
+gchar* get_url(gchar* text) {
+ gchar* pos;
+ gchar* url = NULL;
+
+ if ((pos = strstr(text, ELEM_HREF)) == NULL)
+ return url;
+ pos = &(*(pos + strlen(ELEM_HREF)));
+ url = g_strndup(pos, strlen(pos) - strlen(strchr(pos, '<')));
+ return url;
+}
+
+/**
+ * Fetch any element from XML
+ * @param text String
+ * @param tag The element to look for
+ * @return element
+ */
+gchar* get_tag(const gchar* tag, gchar* text) {
+ gchar *pos;
+ gchar* res = NULL;
+ gchar* the_tag = NULL;
+
+ /*printf("%s\n", text);*/
+ the_tag = g_strdup_printf("<%s>", tag);
+ if ((pos = strstr(text, the_tag)) == NULL) {
+ g_free(the_tag);
+ return res;
+ }
+ pos = &(*(pos + strlen(the_tag)));
+ res = g_strndup(pos, strlen(pos) - strlen(strchr(pos, '<')));
+ g_free(the_tag);
+ return res;
+}
+
+/**
+ * Fetch the etag element from XML
+ * @param text String
+ * @return etag
+ */
+#define ELEM_ETAG "getetag"
+gchar* get_etag(gchar* text) {
+ gchar* etag = NULL;
+
+ etag = get_tag(ELEM_ETAG, text);
+ /* Maybe namespace prefixed */
+ if (!etag) {
+ etag = get_tag("D:getetag", text);
+ }
+ return etag;
+}
+
+/**
+ * Fetch host from URL
+ * @param url URL
+ * @return host
+ */
+gchar* get_host(gchar* url) {
+ gchar** buf;
+ gchar* result = NULL;
+
+ buf = g_strsplit(url, "/", 2);
+ if (buf[0]) {
+ result = g_strdup(buf[0]);
+ }
+ g_strfreev(buf);
+ return result;
+}
+
+/**
+ * rebuild a raw URL with https if needed from the settings
+ * @param settings caldav_settings
+ * @param uri URI to use instead of base
+ * @return URL
+ */
+
+gchar* rebuild_url(caldav_settings* settings, gchar* uri){
+ gchar* url = NULL;
+ gchar* mystr = NULL;
+ if (settings->usehttps) {
+ mystr = "https://";
+ } else {
+ mystr = "http://";
+ }
+ if (uri)
+ url = g_strdup_printf("%s%s", mystr, uri);
+ else
+ url = g_strdup_printf("%s%s", mystr,settings->url);
+
+ return url;
+}
+
+/**
+ * Prepare a curl connection
+ * @param settings caldav_settings
+ * @return CURL
+ */
+CURL* get_curl(caldav_settings* setting) {
+ CURL* curl;
+ gchar* userpwd = NULL;
+ gchar* url = NULL;
+
+ curl = curl_easy_init();
+ if (curl) {
+ if (setting->username) {
+ if (setting->password)
+ userpwd = g_strdup_printf("%s:%s",
+ setting->username, setting->password);
+ else
+ userpwd = g_strdup_printf("%s", setting->username);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd);
+ g_free(userpwd);
+ }
+ if (setting->verify_ssl_certificate)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
+ else {
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ }
+ if (setting->custom_cacert)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, setting->custom_cacert);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, __CALDAV_USERAGENT);
+ url = rebuild_url(setting, NULL);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ g_free(url);
+ }
+ return (curl) ? curl : NULL;
+}