diary

Text-based journaling program
git clone https://git.in0rdr.ch/diary.git
Log | Files | Refs | README | LICENSE

commit fff175f97b8e8f240fc185bccf476171cddc2116
parent af1c53e3ad9d28bdce9efff97036f75bd3363834
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Sat, 22 Jun 2024 09:26:01 +0200

feat(caldav): caldav testing with gmx

These changes are required for sync with GMX caldav servers:
* follow redirects during principal discovery
* add missing ics fields for upload
* add missing etag prop for calendar retrieval
* relax xml token search and parsing

There is still a bug in the download part. All ics fields in the xml
response have the carriage return html escape sequence '#13;'. For
example, this is what is retrieved:

 DESCRIPTION:12345&#13;\nDTSTART;VALUE=DATE:20240618&#13;\n

Unfortunately this escape sequence is not handled correctly on our side
yet and saved to the text file on disk. All other caldav server
implementations only send '\n' at the end of each ics line, so there
it's not a problem.

Diffstat:
Msrc/caldav.c | 56+++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/src/caldav.c b/src/caldav.c @@ -93,12 +93,18 @@ char* read_tokenfile() { token_buf = malloc(token_bytes + 1); if (token_buf != NULL) { - fread(token_buf, sizeof(char), token_bytes, token_file); + size_t items_read = fread(token_buf, sizeof(char), token_bytes, token_file); + if (items_read != token_bytes) { + perror("Error while reading in read_tokenfile()"); + fclose(token_file); + return NULL; + } token_buf[token_bytes] = '\0'; update_global_token_vars(token_buf); } else { perror("malloc failed"); + fclose(token_file); return NULL; } fclose(token_file); @@ -407,7 +413,7 @@ char* get_oauth_code(const char* verifier, WINDOW* header) { } /* Make a CalDAV request */ -char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields, int depth, bool basicauth, char* content_type) { +char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields, int depth, bool basicauth, char* content_type, long num_redirects) { // only support depths 0 or 1 if (depth < 0 || depth > 1) { return NULL; @@ -429,7 +435,7 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields if (curl) { // fail if not authenticated, !CURLE_OK curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); - // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method); tracepoint(diary, debug_string, "curl_easy_perform() request url", url); curl_easy_setopt(curl, CURLOPT_URL, url); @@ -456,7 +462,7 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields header = curl_slist_append(header, depth_header); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header); - if (content_type != "" && content_type != NULL) { + if (strcmp(content_type, "") != 0 && content_type != NULL) { char contentType[strlen("Content-Type: ") + strlen(content_type) + 1]; sprintf(contentType, "Content-Type: %s", content_type); header = curl_slist_append(header, contentType); @@ -467,6 +473,11 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); } + // enable redirect following, some providers have certain caldav + // endpoinds on sub-paths + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, num_redirects); + res = curl_easy_perform(curl); long response_code = 0; @@ -490,7 +501,7 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields /* Return current user principal from CalDAV XML response */ char* parse_caldav_current_user_principal(char* xml) { - char* xml_key_pos = strstr(xml, "current-user-principal>"); + char* xml_key_pos = strstr(xml, "current-user-principal"); // this XML does not contain a user principal at all if (xml_key_pos == NULL) { return NULL; @@ -503,8 +514,8 @@ char* parse_caldav_current_user_principal(char* xml) { tok = strtok(NULL, "<"); // D:href>/caldav/v2/test%40gmail.com/user tok = strstr(tok, ">"); // >/caldav/v2/test%40gmail.com/user tok++; // cut > - char* tok_end = strrchr(tok, '/'); - *tok_end = '\0'; // cut /user + //char* tok_end = strrchr(tok, '/'); + //*tok_end = '\0'; // cut /user } return tok; @@ -512,7 +523,7 @@ char* parse_caldav_current_user_principal(char* xml) { /* Return home set uri from CalDAV home set XML response */ char* parse_home_set(char* xml) { - char* homeset_needle = "<calendar-home-set>"; + char* homeset_needle = "calendar-home-set"; char* homeset_pos = strstr(xml, homeset_needle); if (homeset_pos == NULL) { @@ -592,14 +603,16 @@ void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar descr = calloc(descr_size + 1, sizeof(char)); if (descr == NULL) { perror("malloc failed"); + fclose(fp); return; } strcat(descr, "DESCRIPTION:"); - int items_read = fread(descr + descr_label_size, sizeof(char), descr_bytes, fp); + size_t items_read = fread(descr + descr_label_size, sizeof(char), descr_bytes, fp); if (items_read != descr_bytes) { - tracepoint(diary, error_int_long, "Read n items but expected m items, aborting.", items_read, descr_bytes); + perror("Error while reading in read_tokenfile()"); + fclose(fp); return; } @@ -610,6 +623,9 @@ void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar strftime(uid, sizeof uid, "%Y%m%d", date); char* ics = "BEGIN:VCALENDAR\n" + "PRODID:-//diary\n" // todo: add version + "VERSION:2.0\n" + "CALSCALE:GREGORIAN\n" "BEGIN:VEVENT\n" "UID:%s\n" "DTSTART;VALUE=DATE:%s\n" @@ -624,9 +640,11 @@ void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar uid, // todo: display first few chars of DESCRIPTION as SUMMARY folded_descr); + tracepoint(diary, debug_string, "PUT text/calendar", postfields); + strcat(calendar_uri, uid); strcat(calendar_uri, ".ics"); - char* response = caldav_req(date, calendar_uri, "PUT", postfields, 0, basicauth, "text/calendar"); + char* response = caldav_req(date, calendar_uri, "PUT", postfields, 0, basicauth, "text/calendar", 0); fclose(fp); free(folded_descr); free(descr); @@ -712,7 +730,7 @@ int caldav_sync(struct tm* date, // check if we can use the token from the tokenfile - char* user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml"); + char* user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml", 1); tracepoint(diary, debug_string, "User principal", user_principal); if (user_principal == NULL) { @@ -723,7 +741,7 @@ int caldav_sync(struct tm* date, tracepoint(diary, debug, "Fetching access token with refresh token"); get_access_token(NULL, NULL, true); // Retry request for event with new token - user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml"); + user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml", 1); } } @@ -762,7 +780,7 @@ int caldav_sync(struct tm* date, " </d:prop>" "</d:propfind>"; - char* home_set = caldav_req(date, uri, "PROPFIND", homeset_request, 0, basicauth_enabled, "application/xml"); + char* home_set = caldav_req(date, uri, "PROPFIND", homeset_request, 0, basicauth_enabled, "application/xml", 0); tracepoint(diary, debug_string, "Home set xml", home_set); // parse home set from xml response @@ -779,14 +797,14 @@ int caldav_sync(struct tm* date, "</d:propfind>"; // get calendar from home-set - char* calendar_xml = caldav_req(date, uri, "PROPFIND", calendar_request, 1, basicauth_enabled, "application/xml"); + char* calendar_xml = caldav_req(date, uri, "PROPFIND", calendar_request, 1, basicauth_enabled, "application/xml", 0); tracepoint(diary, debug_string, "Calendar set xml", calendar_xml); // get calendar URI from the home-set char* calendar_href = parse_caldav_calendar(calendar_xml, CONFIG.caldav_calendar); char* xml_filter = "<c:calendar-query xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>" - "<d:prop><c:calendar-data/></d:prop>" + "<d:prop><d:getetag/><c:calendar-data/></d:prop>" "<c:filter><c:comp-filter name='VCALENDAR'>" "<c:comp-filter name='VEVENT'>" "<c:time-range start='%s' end='%s'/></c:comp-filter>" @@ -796,7 +814,7 @@ int caldav_sync(struct tm* date, char* format = "%Y%m%dT000000Z"; strftime(dstr_cursor, sizeof dstr_cursor, format, date); - char caldata_postfields[strlen(xml_filter)+50]; + char caldata_postfields[strlen(xml_filter) + 2*strlen(dstr_cursor)]; sprintf(caldata_postfields, xml_filter, dstr_cursor, dstr_cursor); @@ -804,7 +822,7 @@ int caldav_sync(struct tm* date, // fetch event for the cursor date sprintf(uri, "%s%s", CONFIG.caldav_server, calendar_href); - char* event = caldav_req(date, uri, "REPORT", caldata_postfields, 1, basicauth_enabled, "application/xml"); + char* event = caldav_req(date, uri, "REPORT", caldata_postfields, 1, basicauth_enabled, "application/xml", 0); // todo: warn if multiple events, // multistatus has more than just one caldav:calendar-data elements // currently, the code below will just extract the first occurance of @@ -886,7 +904,7 @@ int caldav_sync(struct tm* date, char event_uri[300]; sprintf(event_uri, "%s%s%s.ics", CONFIG.caldav_server, calendar_href, remote_uid); tracepoint(diary, debug_string, "Event URI for DELETE request", event_uri); - char* response = caldav_req(date, event_uri, "DELETE", NULL, 0, basicauth_enabled, ""); + char* response = caldav_req(date, event_uri, "DELETE", NULL, 0, basicauth_enabled, "", 0); free(response); }