First complete version
authorMichael Rasmussen <mir@datanom.net>
Fri, 12 May 2017 11:44:20 +0000 (13:44 +0200)
committerMichael Rasmussen <mir@datanom.net>
Fri, 12 May 2017 11:44:20 +0000 (13:44 +0200)
src/caldav.c [new file with mode: 0644]
src/caldav.h [new file with mode: 0644]
src/curllib.c [new file with mode: 0644]
src/curllib.h [new file with mode: 0644]
src/xml.c [new file with mode: 0644]
src/xml.h [new file with mode: 0644]
test-app/Makefile.am [new file with mode: 0644]
test-app/cmdline-parser.c [new file with mode: 0644]
test-app/cmdline-parser.h [new file with mode: 0644]
test-app/test-app.c [new file with mode: 0644]

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;
+}
diff --git a/src/caldav.h b/src/caldav.h
new file mode 100644 (file)
index 0000000..e6af5cd
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * caldav.h
+ *
+ * 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 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef __CALDAV_H__
+#define __CALDAV_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+       VUNKNOWN,
+       VEVENT,
+       VTODO,
+       VFREEBUSY,
+       VJOURNAL,
+} Component;
+
+typedef struct {
+       guint status;    /* Status code from the request */
+       GSList* headers; /* List of headers in the response */
+       gpointer data;   /* Either gchar* or GSList* (Calendar) or GSList* (options). */
+                                        /* If adding or updating a component a successful */
+                                        /* request will return the ETag in data */
+} CaldavResponse;
+
+typedef enum {
+       UNKNOWN,
+       ADDOBJECTS,
+       CALENDARINFO,
+       CHANGEINFO,
+       DELETEOBJECTS,
+       DISCOVER,
+       GETALLOBJECTS,
+       GETOBJECTS,
+       LOCKING,
+       UNLOCKING,
+       OPTIONSINFO,
+       SIMPLEGET,
+       UPDATEOBJECTS,
+       FREEBUSY,
+} Operation;
+
+typedef struct {
+       gchar* href;
+       gchar* data;
+} HrefData;
+
+typedef guint (*Executor)(gpointer runtime);
+typedef struct {
+       FILE* file; /* Where to write output. If file is NULL only output will be in 'output' */
+       gchar* username; /* Account to use */
+       gchar* password; /* Password for the account */
+       gchar* url; /* URL to caldav server */
+       gboolean debug; /* Show debug */
+       Operation* operation; /* Which operation to execute */
+       CaldavResponse* output; /* Structure containing the response */
+       Executor executor; /* Function which executes the request */
+       gboolean default_executor; /* Using default executors or custom executors */
+       gchar* etag; /* For If-Match */
+       gchar* component; /* The data for UPDATEOBJECTS or ADDOBJECTS */
+       GSList* hrefs; /* For GETOBJECTS: list of HrefData */
+       GSList* options; /* List of supported options by this caldav server */
+       GDateTime* start; /* Start time interval in ISO 8601 local time. */
+                                         /* 2017-01-13T13:13:13. For freebusy or get operations */
+       GDateTime* finish; /* End time interval in ISO 8601 local time. */
+                                          /* 2017-01-13T13:13:13. For freebusy or get operations */
+} Runtime;
+
+typedef struct {
+       gchar* url;
+       gchar* displayname;
+       gchar* ctag;
+       GSList* components; /* list of Components */
+} Calendar;
+
+/**
+ * Return a new Runtime structure all zeroed out.
+ * @return NULL or a new Runtime
+ */
+Runtime* runtime_new();
+
+/**
+ * Free a previously created Runtime structure.
+ * @param runtime
+ */
+void runtime_free(Runtime* runtime);
+
+/**
+ * Make a deep copy of a Runtime.
+ * @param runtime
+ * @return Runtime
+ */
+Runtime* runtime_copy(Runtime* runtime);
+
+/**
+ * Execute the Runtime
+ * @param runtime
+ * @return guint
+ */
+guint execute(Runtime* runtime);
+
+/**
+ * Make a deep copy of a Calendar structure
+ * @param cal
+ * @return a deep copy of the Calendar
+ */
+Calendar* calendar_copy(Calendar* cal);
+
+/**
+ * Free previously created Calendar.
+ * @param cal
+ */
+void calendar_free(Calendar* cal);
+
+/**
+ * Dump Calendar to FILE.
+ * @param file
+ * @param cal
+ */
+void calendar_dump(FILE* file, Calendar* cal);
+
+/**
+ * Test whether Component is equal to Component
+ * @param value
+ * @param component
+ * @return TRUE or FALSE
+ */
+gboolean is_component(const gchar* value, Component component);
+
+/**
+ * Convert Component to its string representation
+ * @param component
+ * @return String
+ */
+gchar* component_to_string(Component component);
+
+/**
+ * Convert a String representation of a component to Component.
+ * @param value
+ * @return Component
+ */
+Component string_to_component(const gchar* value);
+
+/**
+ * Get error as String from Runtime.
+ * @param runtime
+ * @return String
+ */
+gchar* status_str(Runtime* runtime);
+
+/**
+ * Deep copy of a GSList containing Strings to another GSList.
+ * @param list
+ * @return GSList
+ */
+GSList* slist_copy_gchar(GSList* list);
+
+/**
+ * Free a GSList of Strings.
+ * @param list
+ */
+void slist_free_gchar(GSList* list);
+
+/**
+ * Create a new HrefData structure.
+ * @param href
+ * @param data
+ * @return HrefData
+ */
+HrefData* href_data_new(const gchar* href, const gchar* data);
+
+/**
+ * Free a HrefData structure.
+ * @param href_data
+ */
+void href_data_free(HrefData* href_data);
+
+/**
+ * Find HTTP header in GSList of HTTP headers.
+ * @param list
+ * @param header
+ * @return String
+ */
+gchar* find_header(GSList* list, const gchar* header);
+
+/**
+ * Test whether caldav server supports option.
+ * @param runtime
+ * @param option
+ * @return TRUE or FALSE
+ */
+gboolean has_option(Runtime* runtime, const gchar* option);
+
+/**
+ * Convert a string representation of an ISO 8601 date to GDateTime.
+ * @param datetime
+ * @return GDateTime
+ */
+GDateTime* get_date_time_from_string(const gchar* datetime);
+
+G_END_DECLS
+
+#endif
+
diff --git a/src/curllib.c b/src/curllib.c
new file mode 100644 (file)
index 0000000..985bf53
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * 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;
+       }
+}
diff --git a/src/curllib.h b/src/curllib.h
new file mode 100644 (file)
index 0000000..7066187
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * curllib.h
+ *
+ * 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 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef __CURLLIB_H__
+#define __CURLLIB_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+       DELETE,
+       GET,
+       PROPFIND,
+       PUT,
+       REPORT,
+       OPTIONS,
+       LOCK,
+       UNLOCK,
+} RequestType;
+
+typedef struct {
+       RequestType request;
+       GSList* headers;
+       gchar* data;
+       gchar* username;
+       gchar* password;
+       gchar* url;
+} Request;
+
+typedef struct {
+       gint status;
+       GSList* headers;
+       gchar* data;
+} Response;
+
+Request* request_new();
+void request_free(Request* request);
+Response* request_send(Request* request, gboolean debug);
+void response_free(Response* response);
+
+G_END_DECLS
+
+#endif
+
diff --git a/src/xml.c b/src/xml.c
new file mode 100644 (file)
index 0000000..fe7928b
--- /dev/null
+++ b/src/xml.c
@@ -0,0 +1,242 @@
+/*
+ * xml.c
+ *
+ * Copyright 2017 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 <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <string.h>
+#include "xml.h"
+
+static gboolean debug = FALSE;
+
+static xmlDocPtr getdoc(const gchar* xml) {
+       xmlDocPtr doc;
+       doc = xmlParseMemory(xml, strlen(xml));
+
+       if (doc == NULL ) {
+               g_warning("Document not parsed successfully.");
+               return NULL;
+       }
+
+       return doc;
+}
+
+static xmlXPathObjectPtr getnodeset(xmlDocPtr doc, xmlChar *xpath, const gchar* prefix, const gchar* ns_url) {
+
+       xmlXPathContextPtr context;
+       xmlXPathObjectPtr result = NULL;
+
+       context = xmlXPathNewContext(doc);
+       if (context == NULL) {
+               g_warning("Error in xmlXPathNewContext");
+               return NULL;
+       }
+
+       if (prefix && ns_url) {
+               xmlXPathRegisterNs(context, (xmlChar *)prefix, (xmlChar *)ns_url);
+       }
+
+       result = xmlXPathEvalExpression(xpath, context);
+       xmlXPathFreeContext(context);
+       xmlFree(xpath);
+       if (result == NULL) {
+               g_warning("Error in xmlXPathEvalExpression");
+               return NULL;
+       }
+
+       if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
+               xmlXPathFreeObject(result);
+               result = NULL;
+       }
+
+       return result;
+}
+
+xmlNodePtr find_node_by_name(xmlNodePtr rootnode, const xmlChar* nodename, GSList** nodes) {
+       xmlNodePtr node = rootnode;
+
+    if (!node) {
+        return NULL;
+    }
+
+    while (node != NULL) {
+
+        if (!xmlStrcmp(node->name, nodename)) {
+                       if (nodes) {
+                               *nodes = g_slist_append(*nodes, (gpointer) node);
+                       } else {
+                               return node;
+                       }
+        } else if (node->children != NULL) {
+            xmlNodePtr intNode = find_node_by_name(node->children, nodename, nodes);
+            if (intNode != NULL) {
+                               if (nodes) {
+                                       *nodes = g_slist_append(*nodes, (gpointer) node);
+                               } else {
+                                       return intNode;
+                               }
+            }
+        }
+        node = node->next;
+    }
+
+    return NULL;
+}
+
+static GSList* find_all_nodes_by_name(xmlDocPtr doc, const gchar* nodename) {
+       GSList* nodes = NULL;
+
+       find_node_by_name(doc->children, (const xmlChar *) nodename, &nodes);
+
+       return nodes;
+}
+
+void dump_node(FILE* f, xmlDocPtr doc, xmlNodePtr node) {
+       g_return_if_fail(f && doc && node);
+
+       xmlElemDump(f, doc, node);
+       fprintf(f, "\n");
+}
+
+void dump_nodes(gpointer data, gpointer user_data) {
+       g_return_if_fail(data && user_data);
+
+       xmlNodePtr node = (xmlNodePtr) data;
+       xmlDocPtr doc = (xmlDocPtr) user_data;
+
+       dump_node(stderr, doc, node);
+}
+
+void get_props(gpointer data, gpointer user_data) {
+       g_return_if_fail(data && user_data);
+
+       xmlNodePtr node = (xmlNodePtr) data;
+       Calendar* cal = (Calendar *) user_data;
+
+       xmlChar* comp = xmlGetProp(node, (const xmlChar *)"name");
+       if (comp) {
+               Component c = string_to_component((const gchar *) comp);
+               cal->components = g_slist_append(cal->components, GUINT_TO_POINTER(c));
+               xmlFree(comp);
+       }
+}
+
+GSList* find_element(const gchar* element, const gchar* xml, const gchar* prefix, const gchar* ns_url) {
+       GSList* elements = NULL;
+       xmlChar* keyword = NULL;
+
+       g_return_val_if_fail(element && xml, NULL);
+
+       xmlDocPtr doc = getdoc(xml);
+               if (doc) {
+               xmlXPathObjectPtr result = getnodeset(doc, xmlCharStrdup(element), prefix, ns_url);
+
+               if (result) {
+                       xmlNodeSetPtr nodeset = result->nodesetval;
+                       for (int i = 0; i < nodeset->nodeNr; i++) {
+                               keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
+                               elements = g_slist_append(elements, (gpointer) g_strdup((gchar *) keyword));
+                               xmlFree(keyword);
+                       }
+                       xmlXPathFreeObject(result);
+               }
+
+               xmlFreeDoc(doc);
+       }
+       xmlCleanupParser();
+
+       return elements;
+}
+
+GSList* find_node(const gchar* node, const gchar* xml) {
+       GSList* nodes = NULL;
+       xmlDocPtr doc = getdoc(xml);
+
+       if (doc) {
+               GSList* n = find_all_nodes_by_name(doc, node);
+
+               for (GSList* l = n; l; l = g_slist_next(l)) {
+                       xmlNodePtr p = (xmlNodePtr) l->data;
+                       nodes = g_slist_append(nodes, g_strdup((gchar *) p->name));
+               }
+
+               g_slist_free(n);
+               xmlFreeDoc(doc);
+       }
+       xmlCleanupParser();
+
+       return nodes;
+}
+
+GSList* find_calendars(const gchar* host_part, const gchar* xml) {
+       GSList* list = NULL;
+       xmlNodePtr ptr;
+       xmlNodePtr found;
+       xmlDocPtr doc = getdoc(xml);
+
+       if (doc) {
+               GSList* n = find_all_nodes_by_name(doc, "response");
+               for (GSList* l = n; l; l = g_slist_next(l)) {
+                       xmlNodePtr p = (xmlNodePtr) l->data;
+                       if (debug)
+                               dump_node(stderr, doc, p);
+                       found = find_node_by_name(p, (const xmlChar*)"resourcetype", NULL);
+                       if (found) {
+                               found = find_node_by_name(found, (const xmlChar*)"calendar", NULL);
+                               if (found) {
+                                       if (debug)
+                                               dump_node(stderr, doc, p);
+                                       Calendar* cal = g_new0(Calendar, 1);
+                                       ptr = find_node_by_name(p, (const xmlChar*)"href", NULL);
+                                       if (ptr)
+                                               cal->url = g_strconcat(host_part, ptr->children->content, NULL);
+                                       ptr = find_node_by_name(p, (const xmlChar*)"displayname", NULL);
+                                       if (ptr)
+                                               cal->displayname = g_strdup((gchar *)ptr->children->content);
+                                       ptr = find_node_by_name(p, (const xmlChar*)"getctag", NULL);
+                                       if (ptr)
+                                               cal->ctag = g_strdup((gchar *)ptr->children->content);
+                                       ptr = find_node_by_name(p, (const xmlChar*)"supported-calendar-component-set", NULL);
+                                       if (ptr) {
+                                               GSList* comps = NULL;
+                                               find_node_by_name(ptr, (const xmlChar *)"comp", &comps);
+                                               g_slist_foreach(comps, (GFunc)get_props, (gpointer) cal);
+                                               g_slist_free(comps);
+                                       }
+                                       list = g_slist_append(list, (gpointer) cal);
+                                       if (debug)
+                                               calendar_dump(stderr, cal);
+                               }
+                       }
+               }
+               g_slist_free(n);
+       }
+
+       return list;
+}
+
+void set_debug_mode(gboolean mode) {
+       debug = mode;
+}
diff --git a/src/xml.h b/src/xml.h
new file mode 100644 (file)
index 0000000..87e2bbc
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,42 @@
+/*
+ * xml.h
+ *
+ * Copyright 2017 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.
+ */
+
+#ifndef __XML_H__
+#define __XML_H__
+
+#include <glib.h>
+#include <libxml/parser.h>
+#include "caldav.h"
+
+G_BEGIN_DECLS
+
+GSList* find_element(const gchar* element, const gchar* xml, const gchar* prefix, const gchar* ns_url);
+xmlNodePtr find_node_by_name(xmlNodePtr rootnode, const xmlChar* nodename, GSList** nodes);
+GSList* find_node(const gchar* node, const gchar* xml);
+GSList* find_calendars(const gchar* host_part, const gchar* xml);
+void dump_node(FILE* f, xmlDocPtr doc, xmlNodePtr node);
+void dump_nodes(gpointer data, gpointer user_data);
+void get_props(gpointer data, gpointer user_data);
+void set_debug_mode(gboolean mode);
+
+G_END_DECLS
+
+#endif
diff --git a/test-app/Makefile.am b/test-app/Makefile.am
new file mode 100644 (file)
index 0000000..5f6fa96
--- /dev/null
@@ -0,0 +1,23 @@
+AUTOMAKE_OPTIONS = gnu
+
+AM_CPPFLAGS = \
+               @GLIB_CFLAGS@ \
+               @CURL_CFLAGS@ \
+               -I$(top_srcdir) -I$(top_builddir) \
+               -I$(top_srcdir)/src -I$(top_builddir)/src \
+               -I$(top_srcdir)/test-app -I$(top_builddir)/test-app
+
+bin_PROGRAMS = test-app
+
+test_app_SOURCES = \
+               test-app.c \
+               cmdline-parser.h \
+               cmdline-parser.c
+
+test_app_LDFLAGS = \
+               -L$(top_srcdir)/src/
+
+test_app_LDADD = \
+               $(GLIB_LIBS) \
+               @CURL_LIBS@ \
+               -lcaldav
diff --git a/test-app/cmdline-parser.c b/test-app/cmdline-parser.c
new file mode 100644 (file)
index 0000000..747e3d7
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * cmdline-parser.c
+ *
+ * Copyright 2017 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 <stdlib.h>
+#include <getopt.h>
+#include <glib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "cmdline-parser.h"
+#include "caldav.h"
+
+static const gchar* help[] = {
+       "Usage: test-app [Options] -o operation -p password -u username URL",
+       "",
+       "a|data     Use this option to insert calendar data from file.",
+       "           As an alternative use redirect IO with <.",
+       "d|debug    Print a lot of debug information to STDOUT",
+       "e|etag     ETag to use for update and delete",
+       "f|finish   End time interval in ISO 8601 local time. 2017-01-13T13:13:13",
+       "h|help     Show this help info.",
+       "r|href     HREFs to use for getobjects. Each href on a separate line.",
+       "           As an alternative use redirect IO with <.",
+       "s|start    Start time interval in ISO 8601 local time. 2017-01-13T13:13:13",
+       NULL
+};
+
+static void show_help() {
+       for (int i = 0; help[i]; i++) {
+               printf("%s\n", help[i]);
+       }
+}
+
+static void get_operation(const gchar* op, Operation* o) {
+       if (o) {
+               if (g_strcmp0("CALENDARINFO", op) == 0) {
+                       *o = CALENDARINFO;
+               } else if (g_strcmp0("GETOBJECTS", op) == 0) {
+                       *o = GETOBJECTS;
+               } else if (g_strcmp0("GETALLOBJECTS", op) == 0) {
+                       *o = GETALLOBJECTS;
+               } else if (g_strcmp0("SIMPLEGET", op) == 0) {
+                       *o = SIMPLEGET;
+               } else if (g_strcmp0("CHANGEINFO", op) == 0) {
+                       *o = CHANGEINFO;
+               } else if (g_strcmp0("UPDATEOBJECTS", op) == 0) {
+                       *o = UPDATEOBJECTS;
+               } else if (g_strcmp0("ADDOBJECTS", op) == 0) {
+                       *o = ADDOBJECTS;
+               } else if (g_strcmp0("DELETEOBJECTS", op) == 0) {
+                       *o = DELETEOBJECTS;
+               } else if (g_strcmp0("DISCOVER", op) == 0) {
+                       *o = DISCOVER;
+               } else if (g_strcmp0("OPTIONSINFO", op) == 0) {
+                       *o = OPTIONSINFO;
+               } else if (g_strcmp0("FREEBUSY", op) == 0) {
+                       *o = FREEBUSY;
+               } else {
+                       *o = UNKNOWN;
+               }
+       }
+}
+
+static gboolean invalid_test_case(Runtime* test) {
+       gboolean invalid = TRUE;
+       gboolean error = FALSE;
+
+       if (test) {
+               if (!test->username) {
+                       printf("Missing username\n");
+                       error = TRUE;
+               }
+               if (!test->password) {
+                       printf("Missing password\n");
+                       error = TRUE;
+               }
+               if (!test->url) {
+                       printf("Missing URL\n");
+                       error = TRUE;
+               }
+               if (!test->operation) {
+                       printf("Missing operation\n");
+                       error = TRUE;
+               }
+               invalid = (error) ? TRUE : FALSE;
+       }
+
+       return invalid;
+}
+
+#define BUF_SIZE 1024
+static gchar* read_stream(FILE* stream) {
+       gchar buffer[BUF_SIZE];
+       size_t size = 1;
+       gchar* content = g_malloc0(sizeof(gchar) * BUF_SIZE);
+
+       while (fgets(buffer, BUF_SIZE, stream) != NULL) {
+               size += strlen(buffer);
+               content = g_realloc(content, size);
+               g_strlcat(content, buffer, size);
+       }
+
+       return content;
+}
+
+static GSList* parse_input(const gchar* input) {
+       GSList* list = NULL;
+
+       gchar** strings = g_strsplit(input, "\n", 0);
+       for (gchar** h = strings; *h; ++h) {
+               if (g_strcmp0(*h, "") != 0) {
+                       HrefData* hd = href_data_new(*h, NULL);
+                       list = g_slist_append(list, hd);
+               }
+       }
+       g_strfreev(strings);
+
+       return list;
+}
+
+Runtime* parse_cmdline(int argc, char **argv) {
+       int opt = 0;
+       int debug_flag = 0;
+       Runtime* test = runtime_new();
+       Operation* op = g_new(Operation, 1);
+       FILE* stream = NULL;
+
+       struct option long_options[] = {
+               {"data",      required_argument, 0,           'a'},
+               {"etag",      required_argument, 0,           'e'},
+               {"finish",    required_argument, 0,           'f'},
+               {"debug",     no_argument,       &debug_flag,  1 },
+               {"help",      no_argument,       0,           'h'},
+               {"operation", required_argument, 0,           'o'},
+               {"password",  required_argument, 0,           'p'},
+               {"href",      required_argument, 0,           'r'},
+               {"start",     required_argument, 0,           's'},
+               {"username",  required_argument, 0,           'u'},
+               {0,           0,                 0,            0 }
+       };
+
+       int option_index = 0;
+       while ((opt = getopt_long(argc, argv, "a:de:f:h:o:p:rs:u:",
+                                       long_options, &option_index)) != -1) {
+               switch (opt) {
+                       case 'a': {
+                               stream = fopen(optarg, "r");
+                               if (!stream) {
+                                       perror("File");
+                                       runtime_free(test);
+                                       exit(EXIT_FAILURE);
+                               }
+                               break;
+                       }
+
+                       case 'd':
+                               debug_flag = 1;
+                               break;
+
+                       case 'e':
+                               test->etag = g_strdup(optarg);
+                               break;
+
+                       case 'f':
+                               test->finish = get_date_time_from_string(optarg);
+                               break;
+
+                       case 'h':
+                               runtime_free(test);
+                               show_help();
+                               exit(EXIT_SUCCESS);
+
+                       case 'o':
+                               get_operation(optarg, op);
+                               if (op && *op != UNKNOWN) {
+                                       test->operation = op;
+                               } else {
+                                       runtime_free(test);
+                                       fprintf(stderr, "%s: Invalid operation\n", optarg);
+                                       show_help();
+                                       exit(EXIT_FAILURE);
+                               }
+                               break;
+
+                       case 'p':
+                               test->password = g_strdup(optarg);
+                               break;
+
+                       case 'r': {
+                               stream = fopen(optarg, "r");
+                               if (!stream) {
+                                       perror("File");
+                                       runtime_free(test);
+                                       exit(EXIT_FAILURE);
+                               }
+                               break;
+                       }
+
+                       case 's':
+                               test->start = get_date_time_from_string(optarg);
+                               break;
+
+                       case 'u':
+                               test->username = g_strdup(optarg);
+                               break;
+
+                       case ':':
+                               /* missing option argument */
+                               runtime_free(test);
+                               printf("%s: option '-%c' requires an argument\n", argv[0], optopt);
+                               show_help();
+                               exit(EXIT_FAILURE);
+
+                       case '?':
+                       default:
+                               /* invalid option */
+                               runtime_free(test);
+                               fprintf(stderr, "%s: option '-%c' is invalid: ignored\n", argv[0], optopt);
+                               show_help();
+                               exit(EXIT_FAILURE);
+               }
+       }
+
+       if (optind + 1 < argc) {
+               runtime_free(test);
+               printf("Only one URL allowed\n");
+               show_help();
+               exit(EXIT_FAILURE);
+       }
+
+       test->url= g_strdup(argv[optind]);
+
+       if (invalid_test_case(test)) {
+               runtime_free(test);
+               show_help();
+               exit(EXIT_FAILURE);
+       }
+
+       if (*test->operation == ADDOBJECTS || *test->operation == UPDATEOBJECTS) {
+               struct stat sb;
+               if (fstat(fileno(stdin), &sb) == -1) {
+                       if (!stream) {
+                               perror("Stat");
+                               runtime_free(test);
+                               exit(EXIT_FAILURE);
+                       }
+               } else {
+                       if (stream && sb.st_size > 0) {
+                               fprintf(stderr, "Error: Option --data or --href active. Cannot redirect STDIN\n");
+                               runtime_free(test);
+                               exit(EXIT_FAILURE);
+                       } else {
+                               stream = (stream) ? stream : stdin;
+                       }
+               }
+
+               gchar* text = read_stream(stream);
+
+               switch (*test->operation) {
+                       case GETOBJECTS:
+                               test->hrefs = parse_input(text);
+                               g_free(text);
+                               break;
+                       case UPDATEOBJECTS:
+                       case ADDOBJECTS:
+                               test->component = text;
+                               break;
+                       default:
+                               fprintf(stderr, "%i: Input not allowed\n", *test->operation);
+                               runtime_free(test);
+                               g_free(text);
+                               exit(EXIT_FAILURE);
+               }
+       }
+
+       test->debug = (debug_flag) ? TRUE : FALSE;
+
+       return test;
+}
+
diff --git a/test-app/cmdline-parser.h b/test-app/cmdline-parser.h
new file mode 100644 (file)
index 0000000..65c590e
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * cmdline-parser.h
+ *
+ * Copyright 2017 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.
+ */
+
+#ifndef __CMDLINE_PARSER_H__
+#define __CMDLINE_PARSER_H__
+
+#include <glib.h>
+#include "caldav.h"
+
+G_BEGIN_DECLS
+
+Runtime* parse_cmdline(int argc, char **argv);
+
+G_END_DECLS
+
+#endif
diff --git a/test-app/test-app.c b/test-app/test-app.c
new file mode 100644 (file)
index 0000000..aec68cb
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * test-app.c
+ *
+ * Copyright 2017 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 "caldav.h"
+#include "cmdline-parser.h"
+
+int main(int argc, char **argv) {
+       Runtime* test = parse_cmdline(argc, argv);
+       test->file = stderr;
+    guint status = (guint) execute(test);
+
+       gchar* err = status_str(test);
+       fprintf(stdout, "\n%u: %s\n", test->output->status, err);
+       g_free(err);
+    runtime_free(test);
+
+    return (int) status;
+}
+
This page took 0.130436 seconds and 5 git commands to generate.