]> git.datanom.net - caldav.git/blobdiff - src/caldav.c
First complete version
[caldav.git] / src / caldav.c
diff --git a/src/caldav.c b/src/caldav.c
new file mode 100644 (file)
index 0000000..c3328e1
--- /dev/null
@@ -0,0 +1,1022 @@
+/*
+ * 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;
+}
This page took 0.091524 seconds and 5 git commands to generate.