X-Git-Url: http://git.datanom.net/caldav.git/blobdiff_plain/7f587903cb1680dc6d9a70603a9db396dc645627..e1b22e2b5b944589477889b759029f8fe104a731:/src/caldav.c diff --git a/src/caldav.c b/src/caldav.c new file mode 100644 index 0000000..c3328e1 --- /dev/null +++ b/src/caldav.c @@ -0,0 +1,1022 @@ +/* + * caldav.c + * + * Copyright 2016 Michael Rasmussen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "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( + "", + 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( + "", + "", + "", + 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("", h->href, "", NULL); + href = g_string_append(href, elem); + g_free(elem); + } + } + rq->data = g_strconcat( + "", + "", + "", + g_string_free(href, FALSE), "", + 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( + "", + "", + "", + "", + "",timerange ? timerange : "", + "", + "", + 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( + "", + "", + "", + "",timerange ? timerange : "", + "", + "", + 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( + "", + "", + " ", + 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( + "", + "", + timerange ? timerange : "", "", + 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( + "", + "", + "", + 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( + "", + "", + "", + 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( + "", + "", + "", + "", + 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; +}