+/*
+ * caldav.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 <stdio.h>
+#include <unistd.h>
+#include "caldav.h"
+#include "curllib.h"
+#include "xml.h"
+
+static gchar* get_timerange(Runtime* runtime) {
+ gchar* timerange = NULL;
+
+ if (runtime->start && runtime->finish) {
+ GDateTime* s_utc = g_date_time_to_utc(runtime->start);
+ GDateTime* f_utc = g_date_time_to_utc(runtime->finish);
+ gchar* start = g_date_time_format(s_utc, "%Y%m%dT%H%M%SZ");
+ gchar* finish = g_date_time_format(f_utc, "%Y%m%dT%H%M%SZ");
+ g_date_time_unref(s_utc);
+ g_date_time_unref(f_utc);
+
+ timerange = g_strconcat(
+ "<c:time-range start=\"", start, "\"",
+ " end=\"", finish, "\"/>",
+ NULL);
+ g_free(start);
+ g_free(finish);
+ }
+
+ return timerange;
+}
+
+/*
+ * Operation Depth RequestType
+ * CALENDARINFO 0 PROPFIND
+ * GETOBJECTS 1 REPORT
+ * CHANGEINFO 1 REPORT
+ * UPDATEOBJECTS - PUT
+ * ADDOBJECTS - PUT
+ * DELETEOBJECTS - DELETE
+ * DISCOVER 0 PROPFIND
+ * OPTIONSINFO 0 OPTIONS
+ * LOCKING 0
+*/
+static Request* request_init(Runtime* r) {
+ Request* rq = request_new();
+ GString* href = g_string_new("");
+ gchar* timerange = NULL;
+
+ switch (*r->operation) {
+ case CALENDARINFO:
+ rq->request = PROPFIND;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\">",
+ "<d:prop><d:displayname /><cs:getctag /></d:prop></d:propfind>",
+ NULL);
+ break;
+ case GETOBJECTS:
+ rq->request = REPORT;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 1"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ if (r->hrefs) {
+ for (GSList* l = r->hrefs; l; l = g_slist_next(l)) {
+ HrefData* h = (HrefData *) l->data;
+ gchar* elem = g_strconcat("<d:href>", h->href, "</d:href>", NULL);
+ href = g_string_append(href, elem);
+ g_free(elem);
+ }
+ }
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<c:calendar-multiget xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ "<d:prop><d:getetag /><c:calendar-data /></d:prop>",
+ g_string_free(href, FALSE), "</c:calendar-multiget>",
+ NULL);
+ break;
+ case GETALLOBJECTS:
+ rq->request = REPORT;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 1"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ timerange = get_timerange(r);
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ "<d:prop><d:getetag /><c:calendar-data /></d:prop>",
+ "<c:filter><c:comp-filter name=\"VCALENDAR\">",
+ "<c:comp-filter name=\"VEVENT\">",timerange ? timerange : "",
+ "</c:comp-filter></c:comp-filter>",
+ "</c:filter></c:calendar-query>",
+ NULL);
+ if (timerange)
+ g_free(timerange);
+ break;
+ case CHANGEINFO:
+ rq->request = REPORT;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 1"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ timerange = get_timerange(r);
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ "<d:prop><d:getetag /></d:prop><c:filter><c:comp-filter name=\"VCALENDAR\">",
+ "<c:comp-filter name=\"VEVENT\">",timerange ? timerange : "",
+ "</c:comp-filter></c:comp-filter>",
+ "</c:filter></c:calendar-query>",
+ NULL);
+ if (timerange)
+ g_free(timerange);
+ break;
+ case UPDATEOBJECTS:
+ rq->request = PUT;
+ rq->headers = g_slist_append(rq->headers, g_strconcat("If-Match: \"", r->etag ? r->etag : "", "\"", NULL));
+ rq->data = g_strdup(r->component ? r->component : "");
+ break;
+ case ADDOBJECTS:
+ rq->request = PUT;
+ rq->headers = g_slist_append(rq->headers, g_strdup("If-None-Match: *"));
+ rq->data = g_strdup(r->component ? r->component : "");
+ break;
+ case DELETEOBJECTS:
+ rq->request = DELETE;
+ rq->headers = g_slist_append(rq->headers, g_strconcat("If-Match: \"", r->etag ? r->etag : "", "\"", NULL));
+ break;
+ case DISCOVER:
+ rq->request = PROPFIND;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ break;
+ case SIMPLEGET:
+ rq->request = GET;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ break;
+ case OPTIONSINFO:
+ rq->request = OPTIONS;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ break;
+ case LOCKING:
+ rq->request = LOCK;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Timeout: Second-60"));
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<D:lockinfo xmlns:D=\"DAV:\"><D:lockscope><D:exclusive/></D:lockscope>",
+ " <D:locktype><D:write/></D:locktype></D:lockinfo>",
+ NULL);
+ break;
+ case UNLOCKING:
+ rq->request = UNLOCK;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 0"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ break;
+ case FREEBUSY:
+ rq->request = REPORT;
+ rq->headers = g_slist_append(rq->headers, g_strdup("Depth: 1"));
+ rq->headers = g_slist_append(rq->headers, g_strdup("Prefer: return-minimal"));
+ timerange = get_timerange(r);
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<c:free-busy-query xmlns:D=\"DAV:\"",
+ " xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ timerange ? timerange : "", "</c:free-busy-query>",
+ NULL);
+ if (timerange)
+ g_free(timerange);
+ break;
+ default:
+ request_free(rq);
+ rq = NULL;
+ }
+
+ if (rq) {
+ rq->headers = g_slist_append(rq->headers, g_strdup("Content-Type: application/xml; charset=utf-8"));
+ rq->username = g_strdup(r->username);
+ rq->password = g_strdup(r->password);
+ rq->url = g_strdup(r->url);
+ }
+
+ return rq;
+}
+
+static gchar* get_host_part(const gchar* url) {
+ gchar* host_part = NULL;
+
+ gchar* s = g_strstr_len(url, -1, "://");
+ if (!s) {
+ s = (gchar *) url;
+ } else {
+ s += 3;
+ }
+
+ gchar* s1 = g_strstr_len(s, -1, "/");
+
+ if (s1) {
+ host_part = g_strndup(url, s1 - url);
+ } else {
+ host_part = g_strdup(url);
+ }
+
+ return host_part;
+}
+
+static GSList* gchar_list_copy(GSList* list) {
+ GSList* headers = NULL;
+ for (GSList* l = list; l; l = g_slist_next(l)) {
+ gchar* h = g_strdup((gchar *) l->data);
+ headers = g_slist_append(headers, h);
+ }
+
+ return headers;
+}
+
+static void debug_request(Request* rq) {
+ printf("HTTP Headers:\n");
+ for (GSList* l = rq->headers; l; l = g_slist_next(l)) {
+ printf("%s\n", (gchar *) l->data);
+ }
+ switch (rq->request) {
+ case DELETE: printf("DELETE "); break;
+ case PROPFIND: printf("PROPFIND "); break;
+ case PUT: printf("PUT "); break;
+ case REPORT: printf("REPORT "); break;
+ case GET: printf("GET "); break;
+ case OPTIONS: printf("OPTIONS "); break;
+ case LOCK: printf("LOCK "); break;
+ case UNLOCK: printf("UNLOCK "); break;
+ }
+ printf("%s\n", rq->url);
+ printf("DATA:\n%s\n", rq->data ? rq->data : "None");
+}
+
+static void debug_response(Response* r) {
+ printf("HTTP Status: %i\nHTTP headers:\n", r->status);
+ for (GSList* l = r->headers; l; l = g_slist_next(l)) {
+ printf("%s\n", (gchar *) l->data);
+ }
+}
+
+static guint execute_options(gpointer ptr);
+static void caldav_response_free(Operation op, CaldavResponse* cr);
+
+static gchar* execute_locking(Runtime* runtime, const gchar* lock_token, guint* status) {
+ gchar* token = NULL;
+ Request* rq = NULL;
+ Response* r = NULL;
+
+ Runtime* tmp = runtime_copy(runtime);
+ if (!tmp->options) {
+ *tmp->operation = OPTIONSINFO;
+ guint status = execute_options((gpointer) tmp);
+ if (status != 200)
+ return NULL;
+ caldav_response_free(*tmp->operation, tmp->output);
+ runtime->options = slist_copy_gchar(tmp->options);
+ }
+
+ if (tmp->options && has_option(tmp, "lock")) {
+ if (lock_token) {
+ *tmp->operation = UNLOCKING;
+ rq = request_init(tmp);
+ rq->headers = g_slist_append(rq->headers,
+ g_strconcat("Lock-Token: ", lock_token, NULL));
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+ r = request_send(rq, runtime->debug);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+ }
+ } else {
+ *tmp->operation = LOCKING;
+ rq = request_init(tmp);
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+ r = request_send(rq, runtime->debug);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+
+ if (r->status != 423)
+ token = find_header(r->headers, "Lock-Token");
+ else
+ token = g_strdup(r->data);
+ }
+ }
+ }
+
+ if (status)
+ *status = r->status;
+
+ response_free(r);
+ request_free(rq);
+ runtime_free(tmp);
+
+ return token;
+}
+
+static guint execute_default(gpointer ptr) {
+ Runtime* runtime = (Runtime *) ptr;
+ Request* rq = request_init(runtime);
+ guint status = 500;
+ gchar* lock_token = NULL;
+
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+
+ if (*runtime->operation == DELETEOBJECTS) {
+ lock_token = execute_locking(runtime, NULL, &status);
+ if (status == 423) {
+ runtime->output = g_new0(CaldavResponse, 1);
+ runtime->output->data = (gpointer) g_strdup(lock_token);
+ runtime->output->status = status;
+ g_free(lock_token);
+ request_free(rq);
+ return status;
+ }
+ }
+ if (lock_token) {
+ rq->headers = g_slist_append(rq->headers,
+ g_strconcat("Lock-Token: ", lock_token, NULL));
+ }
+ Response* r = request_send(rq, runtime->debug);
+ if (lock_token) {
+ execute_locking(runtime, lock_token, &status);
+ g_free(lock_token);
+ lock_token = NULL;
+ }
+ request_free(rq);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+
+ runtime->output = g_new0(CaldavResponse, 1);
+ runtime->output->data = (gpointer) g_strdup(r->data);
+
+ if (runtime->file)
+ fprintf(runtime->file, r->data);
+
+ runtime->output->status = status = r->status;
+
+ runtime->output->headers = gchar_list_copy(r->headers);
+
+ response_free(r);
+ }
+
+ return status;
+}
+
+static guint execute_options(gpointer ptr) {
+ Runtime* runtime = (Runtime *) ptr;
+ Request* rq = request_init(runtime);
+ guint status = 500;
+
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+ Response* r = request_send(rq, runtime->debug);
+ request_free(rq);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+
+ gchar* headers = find_header(r->headers, "Allow");
+ if (headers) {
+ gchar** options = g_strsplit(headers, ",", 0);
+ for (int i = 0; options[i]; i++) {
+ gchar* tmp = g_strdup(options[i]);
+ tmp = g_strstrip(tmp);
+ runtime->options = g_slist_append(runtime->options, g_strdup(tmp));
+ g_free(tmp);
+ }
+ g_strfreev(options);
+ g_free(headers);
+ }
+
+ if (runtime->file) {
+ for (GSList* l = runtime->options; l; l = g_slist_next(l)) {
+ fprintf(runtime->file, "%s\n", (gchar *) l->data);
+ }
+ }
+
+ runtime->output = g_new0(CaldavResponse, 1);
+ runtime->output->status = status = r->status;
+
+ runtime->output->headers = gchar_list_copy(r->headers);
+
+ response_free(r);
+ }
+
+ return status;
+}
+
+static guint execute_freebusy(gpointer ptr) {
+ Runtime* runtime = (Runtime *) ptr;
+
+ Request* rq = request_init(runtime);
+ guint status = 500;
+
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+ Response* r = request_send(rq, runtime->debug);
+ request_free(rq);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+
+ runtime->output = g_new0(CaldavResponse, 1);
+ runtime->output->data = (gpointer) g_strdup(r->data);
+
+ if (runtime->file)
+ fprintf(runtime->file, r->data);
+
+ runtime->output->status = status = r->status;
+
+ runtime->output->headers = gchar_list_copy(r->headers);
+
+ response_free(r);
+ }
+
+ return status;
+}
+
+static guint execute_discover(gpointer ptr) {
+ Runtime* runtime = (Runtime *) ptr;
+ Response* r = NULL;
+ Request* rq = NULL;
+ GSList* list = NULL;
+ guint status = 500;
+
+ rq = request_init(runtime);
+ if (rq) {
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<d:propfind xmlns:d=\"DAV:\">",
+ "<d:prop><d:current-user-principal /></d:prop></d:propfind>",
+ NULL);
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+ r = request_send(rq, runtime->debug);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+ list = find_element("//d:current-user-principal/*", r->data, "d", "DAV:");
+ response_free(r);
+ if (list) {
+ /* Change URL to where "current-user-principal" is located */
+ gchar* s = get_host_part(runtime->url);
+ g_free(runtime->url);
+ runtime->url = g_strconcat(s, list->data, NULL);
+ g_free(s);
+ slist_free_gchar(list);
+ list = NULL;
+ }
+ g_free(rq->data);
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<d:propfind xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ "<d:prop><c:calendar-home-set /></d:prop></d:propfind>",
+ NULL);
+ r = request_send(rq, runtime->debug);
+ list = find_element("//c:calendar-home-set/*", r->data,
+ "c", "urn:ietf:params:xml:ns:caldav");
+ response_free(r);
+ if (list) {
+ /* Change URL to where "calendar-home-set" is located */
+ gchar* s = get_host_part(runtime->url);
+ g_free(runtime->url);
+ runtime->url = g_strconcat(s, list->data, NULL);
+ g_free(s);
+ slist_free_gchar(list);
+ list = NULL;
+ }
+ g_free(rq->data);
+ rq->data = g_strconcat(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
+ "<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\" ",
+ "xmlns:c=\"urn:ietf:params:xml:ns:caldav\">",
+ "<d:prop><d:resourcetype/><d:displayname/><cs:getctag/>",
+ "<c:supported-calendar-component-set/></d:prop></d:propfind>",
+ NULL);
+ for (GSList* l = rq->headers; l; l = g_slist_next(l)) {
+ if (g_strcmp0(l->data, "Depth: 0") == 0) {
+ g_free(l->data);
+ l->data = g_strdup("Depth: 1");
+ }
+
+ }
+ r = request_send(rq, runtime->debug);
+ gchar* s = get_host_part(runtime->url);
+ list = find_calendars(s, r->data);
+ g_free(s);
+
+ runtime->output = g_new0(CaldavResponse, 1);
+ GSList* data = NULL;
+ for (GSList* l = list; l; l = g_slist_next(l)) {
+ Calendar* c = calendar_copy((Calendar *) l->data);
+ data = g_slist_append(data, c);
+ }
+ runtime->output->data = (gpointer) data;
+
+ if (runtime->file) {
+ for (GSList* l = list; l; l = g_slist_next(l)) {
+ calendar_dump(runtime->file, (Calendar *) l->data);
+ }
+ }
+ g_slist_foreach(list, (GFunc)calendar_free, NULL);
+ g_slist_free(list);
+ list = NULL;
+
+ runtime->output->headers = gchar_list_copy(r->headers);
+
+ runtime->output->status = status = r->status;
+
+ response_free(r);
+ request_free(rq);
+ }
+
+ return status;
+}
+
+static guint execute_add_update(gpointer ptr) {
+ Runtime* runtime = (Runtime *) ptr;
+ Request* rq = request_init(runtime);
+ guint status = 500;
+ gchar* lock_token = NULL;
+
+ if (rq) {
+ if (runtime->debug) {
+ debug_request(rq);
+ }
+
+ if (*runtime->operation == UPDATEOBJECTS) {
+ lock_token = execute_locking(runtime, NULL, &status);
+ if (status == 423) {
+ runtime->output = g_new0(CaldavResponse, 1);
+ runtime->output->data = (gpointer) g_strdup(lock_token);
+ runtime->output->status = status;
+ g_free(lock_token);
+ request_free(rq);
+ return status;
+ }
+ }
+ if (lock_token) {
+ rq->headers = g_slist_append(rq->headers,
+ g_strconcat("Lock-Token: ", lock_token, NULL));
+ }
+ Response* r = request_send(rq, runtime->debug);
+ if (lock_token) {
+ execute_locking(runtime, lock_token, &status);
+ g_free(lock_token);
+ lock_token = NULL;
+ }
+ request_free(rq);
+
+ if (runtime->debug) {
+ debug_response(r);
+ }
+
+ runtime->output = g_new0(CaldavResponse, 1);
+ if (r->status == 201 || r->status == 204) {
+ gchar* etag = find_header(r->headers, "ETag");
+ if (etag == NULL) {
+ Runtime* r_new = runtime_copy(runtime);
+ *r_new->operation = SIMPLEGET;
+ guint s = execute(r_new);
+ if (s == 200 && r_new->output && r_new->output->headers) {
+ etag = find_header(r_new->output->headers, "ETag");
+ }
+ runtime_free(r_new);
+ }
+ runtime->output->data = (gpointer) g_strdup(etag);
+ if (runtime->file) {
+ if (etag)
+ fprintf(runtime->file, "ETag: %s\n", etag);
+ else
+ fprintf(runtime->file, "%s\n", "No ETag returned");
+ }
+ g_free(etag);
+ } else {
+ runtime->output->data = (gpointer) g_strdup(r->data);
+ if (runtime->file)
+ fprintf(runtime->file, "%s\n", r->data);
+ }
+
+ runtime->output->status = status = r->status;
+
+ runtime->output->headers = gchar_list_copy(r->headers);
+
+ response_free(r);
+ }
+
+ return status;
+}
+
+static void caldav_response_free(Operation op, CaldavResponse* cr) {
+ GSList* list = NULL;
+
+ if (cr) {
+ if (cr->headers) {
+ slist_free_gchar(cr->headers);
+ cr->headers = NULL;
+ }
+ if (cr->data) {
+ switch (op) {
+ case DISCOVER:
+ list = (GSList *) cr->data;
+ g_slist_foreach(list, (GFunc)calendar_free, NULL);
+ g_slist_free(list);
+ break;
+ case OPTIONSINFO:
+ list = (GSList *) cr->data;
+ slist_free_gchar(list);
+ break;
+ default:
+ g_free((gchar *)cr->data);
+ }
+ cr->data = NULL;
+ }
+ }
+}
+
+gchar* find_header(GSList* list, const gchar* header) {
+ g_return_val_if_fail(header != NULL, NULL);
+
+ GString* hit = NULL;
+
+ for (GSList* l = list; l; l = g_slist_next(l)) {
+ gchar* s = (gchar *) l->data;
+ gchar** t = g_strsplit(s, ":", 2);
+ for (int i = 0; t[i]; i++) {
+ gchar* s1 = g_utf8_casefold(g_strstrip(t[i]), -1);
+ gchar* s2 = g_utf8_casefold(header, -1);
+ if (g_strcmp0(s1, s2) == 0) {
+ if (hit) {
+ hit = g_string_append(hit, ", ");
+ hit = g_string_append(hit, g_strstrip(t[i+1]));
+ } else {
+ hit = g_string_new(g_strstrip(t[i+1]));
+ }
+ }
+ g_free(s1);
+ g_free(s2);
+ }
+ }
+
+ return (hit) ? g_string_free(hit, FALSE) : NULL;
+}
+
+void slist_free_gchar(GSList* list) {
+ if (list) {
+ g_slist_foreach(list, (GFunc)g_free, NULL);
+ g_slist_free(list);
+ list = NULL;
+ }
+}
+
+Runtime* runtime_new() {
+ Runtime* r = g_new0(Runtime, 1);
+ r->default_executor = TRUE;
+
+ return r;
+}
+
+Calendar* calendar_copy(Calendar* cal) {
+ Calendar* new_cal = NULL;
+
+ if (!cal)
+ return NULL;
+
+ new_cal = g_new0(Calendar, 1);
+ new_cal->displayname = g_strdup(cal->displayname);
+ new_cal->url = g_strdup(cal->url);
+ new_cal->ctag = g_strdup(cal->ctag);
+ new_cal->components = g_slist_copy(cal->components);
+
+ return new_cal;
+}
+
+void calendar_dump(FILE* file, Calendar* cal) {
+ g_return_if_fail(file && cal);
+
+ fprintf(file, "------- Display Name: %s -------\n", cal->displayname);
+ fprintf(file, "URL: %s\n", cal->url);
+ fprintf(file, "CTAG: %s\n", cal->ctag);
+ fprintf(file, "Supported components\n");
+ for (GSList* l = cal->components; l; l = g_slist_next(l)) {
+ Component c = GPOINTER_TO_UINT(l->data);
+ gchar* value = component_to_string(c);
+ fprintf(file, "\t%s\n", value);
+ g_free(value);
+ }
+}
+
+void calendar_free(Calendar* cal) {
+ if (cal) {
+ if (cal->url) {
+ g_free(cal->url);
+ cal->url = NULL;
+ }
+ if (cal->ctag) {
+ g_free(cal->ctag);
+ cal->ctag = NULL;
+ }
+ if (cal->displayname) {
+ g_free(cal->displayname);
+ cal->displayname = NULL;
+ }
+ if (cal->components) {
+ g_slist_free(cal->components);
+ cal->components = NULL;
+ }
+ g_free(cal);
+ cal = NULL;
+ }
+}
+
+void runtime_free(Runtime* runtime) {
+ if (runtime) {
+ if (runtime->username) {
+ g_free(runtime->username);
+ runtime->username = NULL;
+ }
+ if (runtime->password) {
+ g_free(runtime->password);
+ runtime->password = NULL;
+ }
+ if (runtime->url) {
+ g_free(runtime->url);
+ runtime->url = NULL;
+ }
+ if (runtime->etag) {
+ g_free(runtime->etag);
+ runtime->etag = NULL;
+ }
+ if (runtime->component) {
+ g_free(runtime->component);
+ runtime->component = NULL;
+ }
+ if (runtime->hrefs) {
+ slist_free_gchar(runtime->hrefs);
+ runtime->hrefs = NULL;
+ }
+ if (runtime->options) {
+ slist_free_gchar(runtime->options);
+ runtime->options = NULL;
+ }
+ if (runtime->output && runtime->default_executor) {
+ caldav_response_free(*runtime->operation, runtime->output);
+ runtime->output = NULL;
+ }
+ if (runtime->operation) {
+ g_free(runtime->operation);
+ runtime->operation = NULL;
+ }
+ if (runtime->executor) {
+ runtime->executor = NULL;
+ }
+ if (runtime->start) {
+ g_date_time_unref(runtime->start);
+ runtime->start = NULL;
+ }
+ if (runtime->finish) {
+ g_date_time_unref(runtime->finish);
+ runtime->finish = NULL;
+ }
+ g_free(runtime);
+ runtime = NULL;
+ }
+}
+
+guint execute(Runtime* runtime) {
+ g_return_val_if_fail(runtime && runtime->operation, 500);
+
+ switch (*runtime->operation) {
+ case DISCOVER:
+ runtime->executor = execute_discover;
+ break;
+ case ADDOBJECTS:
+ case UPDATEOBJECTS:
+ runtime->executor = execute_add_update;
+ break;
+ case OPTIONSINFO:
+ runtime->executor = execute_options;
+ break;
+ case FREEBUSY:
+ runtime->executor = execute_freebusy;
+ break;
+ default:
+ runtime->executor = execute_default;
+ }
+
+ return runtime->executor(runtime);
+}
+
+gboolean is_component(const gchar* value, Component component) {
+ g_return_val_if_fail(value != NULL, FALSE);
+
+ if (g_strcmp0(value, "VEVENT") == 0 && component == VEVENT)
+ return TRUE;
+ else if (g_strcmp0(value, "VTODO") == 0 && component == VTODO)
+ return TRUE;
+ else if (g_strcmp0(value, "VFREEBUSY") == 0 && component == VFREEBUSY)
+ return TRUE;
+ else if (g_strcmp0(value, "VJOURNAL") == 0 && component == VJOURNAL)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+gchar* component_to_string(Component component) {
+ if (component == VEVENT)
+ return g_strdup("VEVENT");
+ else if (component == VTODO)
+ return g_strdup("VTODO");
+ else if (component == VFREEBUSY)
+ return g_strdup("VFREEBUSY");
+ else if (component == VJOURNAL)
+ return g_strdup("VJOURNAL");
+ else
+ return NULL;
+}
+
+Component string_to_component(const gchar* value) {
+ g_return_val_if_fail(value != NULL, VUNKNOWN);
+
+ if (g_strcmp0(value, "VEVENT") == 0)
+ return VEVENT;
+ else if (g_strcmp0(value, "VTODO") == 0)
+ return VTODO;
+ else if (g_strcmp0(value, "VFREEBUSY") == 0)
+ return VFREEBUSY;
+ else if (g_strcmp0(value, "VJOURNAL") == 0)
+ return VJOURNAL;
+ else
+ return VUNKNOWN;
+}
+
+gchar* status_str(Runtime* runtime) {
+ gchar* str = NULL;
+
+ g_return_val_if_fail(runtime && runtime->output, NULL);
+
+ switch (runtime->output->status) {
+ case 200: str = g_strdup("OK"); break;
+ case 201: str = g_strdup("Created"); break;
+ case 204: str = g_strdup("No Content"); break;
+ case 207: str = g_strdup("Multi-Status"); break;
+ case 400: str = g_strdup("Bad Request"); break;
+ case 401: str = g_strdup("Unauthorized"); break;
+ case 403: str = g_strdup("Forbidden"); break;
+ case 404: str = g_strdup("Not Found"); break;
+ case 405: str = g_strdup("Method Not Allowed"); break;
+ case 406: str = g_strdup("Not Acceptable"); break;
+ case 409: str = g_strdup("Conflict"); break;
+ case 410: str = g_strdup("Gone"); break;
+ case 412: str = g_strdup("Precondition Failed"); break;
+ case 415: str = g_strdup("Unsupported Media Type"); break;
+ case 423: str = g_strdup("Locked"); break;
+ case 424: str = g_strdup("Failed Dependency"); break;
+ case 500: str = g_strdup("Internal Server Error"); break;
+ case 501: str = g_strdup("Not Implemented"); break;
+ case 503: str = g_strdup("Service Unavailable"); break;
+ case 507: str = g_strdup("Insufficient Storage"); break;
+ default: str = g_strdup_printf("%d: Unexpected status", runtime->output->status);
+ }
+
+ return str;
+}
+
+HrefData* href_data_new(const gchar* href, const gchar* data) {
+ g_return_val_if_fail(href != NULL, NULL);
+
+ HrefData* h = g_new0(HrefData, 1);
+ h->href = g_strdup(href);
+ h->data = data ? g_strdup(data) : NULL;
+
+ return h;
+}
+
+void href_data_free(HrefData* href_data) {
+ if (href_data) {
+ if (href_data->href) {
+ g_free(href_data->href);
+ href_data->href = NULL;
+ }
+ if (href_data->data) {
+ g_free(href_data->data);
+ href_data->data = NULL;
+ }
+ g_free(href_data);
+ href_data = NULL;
+ }
+}
+
+gboolean has_option(Runtime* runtime, const gchar* option) {
+ g_return_val_if_fail(runtime && option, FALSE);
+ gboolean has = FALSE;
+
+ gchar* s2 = g_utf8_casefold(option, -1);
+ if (runtime->options) {
+ for (GSList* l = runtime->options; l && ! has; l = g_slist_next(l)) {
+ gchar* s1 = g_utf8_casefold((gchar *) l->data, -1);
+ if (g_strcmp0(s1, s2) == 0) {
+ has = TRUE;
+ }
+ g_free(s1);
+ }
+ }
+ g_free(s2);
+
+ return has;
+}
+
+Runtime* runtime_copy(Runtime* runtime) {
+ Runtime* new = NULL;
+
+ g_return_val_if_fail(runtime != NULL, NULL);
+
+ new = runtime_new();
+ new->username = g_strdup(runtime->username);
+ new->password = g_strdup(runtime->password);
+ new->url = g_strdup(runtime->url);
+ new->debug = runtime->debug;
+ new->operation = g_new0(Operation, 1);
+ *new->operation = *runtime->operation;
+ new->executor = runtime->executor;
+ new->default_executor = runtime->default_executor;
+ new->etag = g_strdup(runtime->etag);
+ new->component = g_strdup(runtime->component);
+ new->hrefs = slist_copy_gchar(runtime->hrefs);
+ new->options = slist_copy_gchar(runtime->options);
+
+ return new;
+}
+
+GSList* slist_copy_gchar(GSList* list) {
+ GSList* new = NULL;
+
+ for (GSList* l = list; l; l = g_slist_next(l)) {
+ new = g_slist_append(new, g_strdup(l->data));
+ }
+
+ return new;
+}
+
+GDateTime* get_date_time_from_string(const gchar* datetime) {
+ GDateTime* dt = NULL;
+ g_return_val_if_fail(datetime != NULL, NULL);
+
+ gchar** dt_part = g_strsplit(datetime, "T", 0);
+ if (!dt_part || dt_part[2] != NULL) {
+ g_strfreev(dt_part);
+ return NULL;
+ }
+
+ gchar** d_part = g_strsplit(dt_part[0], "-", 0);
+ if (!d_part || d_part[3] != NULL) {
+ g_strfreev(dt_part);
+ g_strfreev(d_part);
+ return NULL;
+ }
+
+ gchar** t_part = g_strsplit(dt_part[1], ":", 0);
+ if (!t_part || t_part[3] != NULL) {
+ g_strfreev(dt_part);
+ g_strfreev(t_part);
+ return NULL;
+ }
+
+ g_strfreev(dt_part);
+ char* endptr;
+ dt = g_date_time_new_local(
+ atoi(d_part[0]), atoi(d_part[1]), atoi(d_part[2]),
+ atoi(t_part[0]), atoi(t_part[1]), strtod(t_part[2], &endptr));
+
+ g_strfreev(d_part);
+ g_strfreev(t_part);
+
+ return dt;
+}