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 \nDTSTART;VALUE=DATE:20240618 \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:
M | src/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);
}