/* vim: set textwidth=80 tabstop=4: */ /* Copyright (c) 2008 Michael Rasmussen (mir@datanom.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "caldav-utils.h" #include "md5.h" #include #include #include #include #include #include #include #include /** * This function is burrowed from the libcurl documentation * @param text * @param stream * @param ptr * @param size * @param nohex */ void dump(const char* text, FILE* stream, char* ptr, size_t size, char nohex) { size_t i; size_t c; unsigned int width=0x10; if(nohex) /* without the hex output, we can fit more on screen */ width = 0x40; fprintf(stream, "%s, %zd bytes (0x%zx)\n", text, size, size); for(i=0; i=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.'); /* check again for 0D0A, to avoid an extra \n if it's at width */ if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) { i+=(c+3-width); break; } } fputc('\n', stream); /* newline */ } fflush(stream); } /** * This function is burrowed from the libcurl documentation * @param handle * @param type * @param data * @param size * @param userp * @return */ int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) { struct config_data* config = (struct config_data *)userp; const char* text; (void)handle; /* prevent compiler warning */ switch (type) { case CURLINFO_TEXT: fprintf(stderr, "== Info: %s", data); default: /* in case a new one is introduced to shock us */ return 0; case CURLINFO_HEADER_OUT: text = "=> Send header"; break; case CURLINFO_DATA_OUT: text = "=> Send data"; break; case CURLINFO_SSL_DATA_OUT: text = "=> Send SSL data"; break; case CURLINFO_HEADER_IN: text = "<= Recv header"; break; case CURLINFO_DATA_IN: text = "<= Recv data"; break; case CURLINFO_SSL_DATA_IN: text = "<= Recv SSL data"; break; } dump(text, stderr, data, size, config->trace_ascii); return 0; } /** * This function is burrowed from the libcurl documentation * @param ptr * @param size * @return void* to memory region */ static void* myrealloc(void* ptr, size_t size) { /* There might be a realloc() out there that doesn't like reallocing * NULL pointers, so we take care of it here * */ if(ptr) return realloc(ptr, size); else return malloc(size); } /** * This function is burrowed from the libcurl documentation * @param ptr * @param size * @param nmemb * @param data * @return number of written bytes */ size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) { size_t realsize = size * nmemb; struct MemoryStruct* mem = (struct MemoryStruct *)data; mem->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1); if (mem->memory) { memcpy(&(mem->memory[mem->size]), ptr, realsize); mem->size += realsize; mem->memory[mem->size] = 0; } return realsize; } /** * This function is burrowed from the libcurl documentation * @param ptr * @param size * @param nmemb * @param data * @return number of written bytes */ size_t WriteHeaderCallback(void* ptr, size_t size, size_t nmemb, void* data) { size_t realsize = size * nmemb; struct MemoryStruct* mem = (struct MemoryStruct *)data; mem->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1); if (mem->memory) { memcpy(&(mem->memory[mem->size]), ptr, realsize); mem->size += realsize; mem->memory[mem->size] = 0; } return realsize; } /* size_t ReadMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data){ struct MemoryStruct* mem = (struct MemoryStruct *)data; memcpy(ptr, mem->memory, mem->size); return mem->size; } */ /** * Initialize caldav settings structure. * @param settings @see caldav_settings */ void init_caldav_settings(caldav_settings* settings) { settings->username = NULL; settings->password = NULL; settings->url = NULL; settings->file = NULL; settings->usehttps = FALSE; settings->custom_cacert = NULL; settings->verify_ssl_certificate = TRUE; settings->debug = FALSE; settings->trace_ascii = TRUE; settings->ACTION = UNKNOWN; settings->start = 0; settings->end = 0; } /** * Free memory assigned to caldav settings structure. * @param settings @see caldav_settings */ void free_caldav_settings(caldav_settings* settings) { if (settings->username) { g_free(settings->username); settings->username = NULL; } if (settings->password) { g_free(settings->password); settings->password = NULL; } if (settings->url) { g_free(settings->url); settings->url = NULL; } if (settings->file) { g_free(settings->file); settings->file = NULL; } if (settings->custom_cacert) { g_free(settings->custom_cacert); settings->custom_cacert = NULL; } settings->verify_ssl_certificate = TRUE; settings->usehttps = FALSE; settings->debug = FALSE; settings->trace_ascii = TRUE; settings->ACTION = UNKNOWN; settings->start = 0; settings->end = 0; } static gchar* place_after_hostname(const gchar* start, const gchar* stop) { gchar* newpos = NULL; gchar* pos = (gchar *) stop; gboolean digit = TRUE; if (pos && stop && strcmp(start, pos) != 0) { while (*pos != ':' && strcmp(start, pos) != 0) --pos; if (pos > start) { gchar* tmp = (gchar *) pos + 1; /* is pos++ a port number */ while (*tmp != '/' && digit) { if (isdigit(*tmp) != 0) { digit = TRUE; tmp++; } else digit = FALSE; } if (digit) { /* pos was a port number */ while (*pos != '@' && strcmp(start, pos) != 0) --pos; if (strcmp(start, pos) != 0) newpos = pos; } else { while (*pos != '@' && pos != stop) pos++; if (pos != stop) newpos = pos; } } else { /* is a username present */ gchar* tmp = NULL; while (*pos != '/' && pos != stop) { if (*pos == '@') tmp = pos; pos++; } if (tmp && pos != stop) newpos = tmp; } } return newpos; } /** * Parse URL * @param settings @see caldav_settings * @param url String containing URL to collection */ void parse_url(caldav_settings* settings, const char* url) { char* start; char* pos; char* end; char* login; login = pos = end = start = NULL; if (!url) return; if ((pos = strstr(url, "//")) != NULL) { /* Does the URL use https ?*/ if (!g_ascii_strncasecmp(url,"https",5) && settings->usehttps == FALSE) { settings->usehttps=TRUE; } start = g_strdup(&(*(pos + 2))); if ((pos = place_after_hostname(start, strrchr(start, '\0') - 1)) != NULL) { /* username and/or password present */ login = g_strndup(start, pos - start); end = pos; if ((pos = strrchr(login, ':')) != NULL) { /* both username and password is present */ settings->username = g_strndup(login, pos - login); settings->password = g_strdup(++pos); } else { /* only username present */ settings->username = g_strdup(login); settings->password = NULL; } g_free(login); settings->url = g_strdup(++end); } else { /* no username or password present */ settings->url = g_strdup(start); settings->username = NULL; settings->password = NULL; } g_free(start); } } /** * Find a specific HTTP header from last request * @param header HTTP header to search for * @param headers String of HTTP headers from last request * @param lowcase Should string be returned in all lower case. * @return The header found or NULL */ #define MAX_TOKENS 2 gchar* get_response_header( const char* header, gchar* headers, gboolean lowcase) { gchar* line; gchar* head = NULL; gchar* oldhead = NULL; gchar** buf; gchar* header_list; gchar* saveptr; header_list = g_strdup(headers); line = strtok_r(header_list, "\r\n", &saveptr); if (line != NULL) { do { buf = g_strsplit(line, ":", MAX_TOKENS); if (buf[1] != NULL) { if (g_ascii_strcasecmp(buf[0], header) == 0) { if (head) { oldhead = head; head = g_strconcat(head, ", ", buf[1], NULL); g_free(oldhead); } else head = g_strdup(buf[1]); if (head) g_strstrip(head); } } g_strfreev(buf); } while ((line = strtok_r(NULL, "\r\n", &saveptr)) != NULL); } g_free(header_list); if (head) return (lowcase) ? g_ascii_strdown(head, -1) : head; else return NULL; } static const char* VCAL_HEAD = "BEGIN:VCALENDAR\r\n" "PRODID:-//CalDAV Calendar//NONSGML libcaldav//EN\r\n" "VERSION:2.0\r\n"; static const char* VCAL_FOOT = "END:VCALENDAR"; /** * Parse response from CalDAV server. Internal function. * @param report Response from server * @param element XML element to find * @param type VCalendar element to find * @param wrap Is this the final parsing or just a part * @param recursive Stop after first match or not * @return the parsed result */ static gchar* parse_caldav_report_wrap( char* report, const char* element, const char* type, gboolean wrap, gboolean recursive) { char* pos; char* start; char* object; char* tmp_report; char* tmp; gchar* response; gchar* begin_type; gchar* end_type; gboolean keep_going = TRUE; begin_type = g_strdup_printf("BEGIN:%s", type); end_type = g_strdup_printf("END:%s", type); pos = start = object = response = NULL; tmp_report = g_strdup(report); while ((pos = strstr(tmp_report, element)) != NULL && keep_going) { pos = strchr(pos, '>'); if (!pos) { break; } pos = &(*(pos + 1)); pos = strstr(pos, begin_type); if (!pos) { break; } object = &(*(pos + strlen(begin_type))); object = g_strchug(object); start = g_strdup(object); if ((pos = strstr(start, end_type)) == NULL) { g_free(start); break; } object = g_strndup(start, strlen(start) - strlen(pos)); if (response) { tmp = g_strdup(response); g_free(response); response = g_strdup_printf("%s%s\r\n%s%s\r\n", tmp, begin_type, object, end_type); g_free(tmp); } else { if (wrap) response = g_strdup_printf("%s%s\r\n%s%s\r\n", VCAL_HEAD, begin_type, object, end_type); else response = g_strdup_printf("%s\r\n%s%s\r\n", begin_type, object, end_type); } if (recursive) { pos = strchr(pos, '>'); g_free(tmp_report); tmp_report = g_strdup(&(*(pos + 1))); } else { keep_going = FALSE; } g_free(start); g_free(object); } g_free(tmp_report); g_free(begin_type); g_free(end_type); if (wrap) if (response) { object = g_strdup(response); g_free(response); response = g_strdup_printf("%s%s", object, VCAL_FOOT); g_free(object); } return response; } /** * Parse response from CalDAV server * @param report Response from server * @param element XML element to find * @param type VCalendar element to find * @return the parsed result */ gchar* parse_caldav_report(char* report, const char* element, const char* type) { gchar* response = NULL; gchar* timezone = NULL; gchar* temp = NULL; if (!report || !element || !type) return NULL; /* test for VTIMEZONE. * Only the first found will be used and this will then * be the time zone for the entire report */ timezone = parse_caldav_report_wrap( report, element, "VTIMEZONE", FALSE, FALSE); if (timezone) { response = g_strdup_printf("%s%s", VCAL_HEAD, timezone); g_free(timezone); temp = parse_caldav_report_wrap(report, element, type, FALSE, TRUE); if (temp) { gchar* tmp = g_strdup(response); g_free(response); response = g_strdup_printf("%s%s%s", tmp, temp, VCAL_FOOT); g_free(tmp); g_free(temp); } else { g_free(response); return NULL; } } else response = parse_caldav_report_wrap(report, element, type, TRUE, TRUE); return response; } /** * Convert a time_t variable to CalDAV DateTime * @param time a specific date and time * @return the CalDAV DateTime */ gchar* get_caldav_datetime(time_t* time) { struct tm *current; gchar* datetime; current = localtime(time); datetime = g_strdup_printf("%d%.2d%.2dT%.2d%.2d%.2dZ", current->tm_year + 1900, current->tm_mon + 1, current->tm_mday, current->tm_hour, current->tm_min, current->tm_sec); return datetime; } /** * Create a random text string, using MD5. @see caldav_md5_hex_digest() * @param text some text to randomize * @return MD5 hash of text */ gchar* random_file_name(gchar* text) { unsigned char* name; gchar md5sum[33]; name = (unsigned char *) g_strdup(text); caldav_md5_hex_digest(md5sum, name); g_free(name); return g_strdup(md5sum); } /** * Does the event contain a UID element or not. If not add it. * @param object A specific event * @return event, eventually added UID */ gchar* verify_uid(gchar* object) { gchar* uid; gchar* newobj; gchar* pos; newobj = g_strdup(object); uid = get_response_header("uid", object, TRUE); if (!uid) { object = g_strdup(newobj); g_free(newobj); pos = strstr(object, "END:VEVENT"); newobj = g_strndup(object, strlen(object) - strlen(pos)); newobj = g_strchomp(newobj); uid = random_file_name(object); gchar*tmp = g_strdup(newobj); g_free(newobj); newobj = g_strdup_printf("%s\r\nUID:libcaldav-%s@tempuri.org\r\n%s", tmp, uid, pos); g_free(uid); g_free(tmp); g_free(object); } else g_free(uid); /*uid = g_strdup(newobj); g_free(newobj);*/ g_strchomp(newobj); /*g_free(uid);*/ return newobj; } /** * Fetch a URL from a XML element * @param text String * @return URL */ #define ELEM_HREF "href>" gchar* get_url(gchar* text) { gchar* pos; gchar* url = NULL; if ((pos = strstr(text, ELEM_HREF)) == NULL) return url; pos = &(*(pos + strlen(ELEM_HREF))); url = g_strndup(pos, strlen(pos) - strlen(strchr(pos, '<'))); return url; } /** * Fetch any element from XML * @param text String * @param tag The element to look for * @return element */ gchar* get_tag(const gchar* tag, gchar* text) { gchar *pos; gchar* res = NULL; gchar* the_tag = NULL; /*printf("%s\n", text);*/ the_tag = g_strdup_printf("<%s>", tag); if ((pos = strstr(text, the_tag)) == NULL) { g_free(the_tag); return res; } pos = &(*(pos + strlen(the_tag))); res = g_strndup(pos, strlen(pos) - strlen(strchr(pos, '<'))); g_free(the_tag); return res; } /** * Fetch the etag element from XML * @param text String * @return etag */ #define ELEM_ETAG "getetag" gchar* get_etag(gchar* text) { gchar* etag = NULL; etag = get_tag(ELEM_ETAG, text); /* Maybe namespace prefixed */ if (!etag) { etag = get_tag("D:getetag", text); } return etag; } /** * Fetch host from URL * @param url URL * @return host */ gchar* get_host(gchar* url) { gchar** buf; gchar* result = NULL; buf = g_strsplit(url, "/", 2); if (buf[0]) { result = g_strdup(buf[0]); } g_strfreev(buf); return result; } /** * rebuild a raw URL with https if needed from the settings * @param settings caldav_settings * @param uri URI to use instead of base * @return URL */ gchar* rebuild_url(caldav_settings* settings, gchar* uri){ gchar* url = NULL; gchar* mystr = NULL; if (settings->usehttps) { mystr = "https://"; } else { mystr = "http://"; } if (uri) url = g_strdup_printf("%s%s", mystr, uri); else url = g_strdup_printf("%s%s", mystr,settings->url); return url; } /** * Prepare a curl connection * @param settings caldav_settings * @return CURL */ CURL* get_curl(caldav_settings* setting) { CURL* curl; gchar* userpwd = NULL; gchar* url = NULL; curl = curl_easy_init(); if (curl) { if (setting->username) { if (setting->password) userpwd = g_strdup_printf("%s:%s", setting->username, setting->password); else userpwd = g_strdup_printf("%s", setting->username); curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd); g_free(userpwd); } if (setting->verify_ssl_certificate) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); else { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); } if (setting->custom_cacert) curl_easy_setopt(curl, CURLOPT_CAINFO, setting->custom_cacert); curl_easy_setopt(curl, CURLOPT_USERAGENT, __CALDAV_USERAGENT); url = rebuild_url(setting, NULL); curl_easy_setopt(curl, CURLOPT_URL, url); g_free(url); } return (curl) ? curl : NULL; }