commit ba4099ce31feb15195d6fe6f5a9dc58865275fde
parent bcb5d288053191612654daf3a3e88b2d72e78697
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Sat, 22 Jun 2024 16:42:44 +0200
Merge branch 'feat/caldav'
Diffstat:
11 files changed, 383 insertions(+), 128 deletions(-)
diff --git a/config/diary.cfg b/config/diary.cfg
@@ -14,10 +14,17 @@ fmt_cmd =
#no_mouse = false
# Editor to open journal files with
editor =
-# Google calendar name for CalDAV sync
-#google_calendar =
+# CalDAV server URI
+# For example, Google calendar URI: "https://apidata.googleusercontent.com/caldav/v2"
+#caldav_server =
+# Calendar name for CalDAV sync
+#caldav_calendar =
# Google OAuth2 clientid and secretid
#google_clientid =
#google_secretid =
# Google OAuth2 tokenfile
#google_tokenfile = ~/.diary-token
+# CalDAV server username
+#caldav_username =
+# CalDAV server password
+#caldav_password =
diff --git a/man1/diary.1 b/man1/diary.1
@@ -294,13 +294,20 @@ fmt_cmd =
#no_mouse = false
# Editor to open journal files with
editor =
-# Google calendar name for CalDAV sync
-#google_calendar =
+# CalDAV server URI
+# For example, Google calendar URI: "https://apidata\&.googleusercontent\&.com/caldav/v2"
+#caldav_server =
+# Calendar name for CalDAV sync
+#caldav_calendar =
# Google OAuth2 clientid and secretid
#google_clientid =
#google_secretid =
# Google OAuth2 tokenfile
#google_tokenfile = ~/\&.diary-token
+# CalDAV server username
+#caldav_username =
+# CalDAV server password
+#caldav_password =
EOF
.fi
.RE
@@ -332,6 +339,8 @@ l lx lx lx lx
l lx lx lx lx
l lx lx lx lx
l lx lx lx lx
+l lx lx lx lx
+l lx lx lx lx
l lx lx lx lx.
T{
\fBCommand Line Option\fR
@@ -435,20 +444,20 @@ T}
T{
n/a
T} T{
-google_calendar
+caldav_calendar
T} T{
diary
T} T{
(empty)
T} T{
-Displayname of Google Calendar for \fBCALDAV SYNC\fR
+Calendar name for \fBCALDAV SYNC\fR
T}
T{
n/a
T} T{
google_clientid
T} T{
-123.\&com
+
T} T{
built-in at build time (see \fBGOOGLE CALENDAR OAUTH2\fR) or empty
T} T{
@@ -459,7 +468,7 @@ n/a
T} T{
google_secretid
T} T{
-321
+
T} T{
built-in at build time (see \fBGOOGLE CALENDAR OAUTH2\fR) or empty
T} T{
@@ -476,6 +485,28 @@ T} T{
T} T{
\fBGOOGLE CALENDAR OAUTH2\fR API token file location
T}
+T{
+n/a
+T} T{
+caldav_username
+T} T{
+
+T} T{
+(empty)
+T} T{
+CalDAV server username for \fBCALDAV SYNC\fR
+T}
+T{
+n/a
+T} T{
+caldav_password
+T} T{
+
+T} T{
+(empty)
+T} T{
+CalDAV server password for \fBCALDAV SYNC\fR
+T}
.TE
.sp 1
.SH CUSTOM FORMATTING
@@ -522,7 +553,7 @@ takes quite some time for large batches.\&
Currently, only the Google Calendar is supported as remote provider.\&
.PP
The calender for synchronization can be defined with the configuration key
-"google_calendar", see \fBCONFIGURATION FILE\fR.\& This key is empty by default.\&
+"caldav_calendar", see \fBCONFIGURATION FILE\fR.\& This key is empty by default.\&
.PP
Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server.\&
.PP
@@ -549,11 +580,11 @@ The application requires two OAuth2 scopes
.IP \(bu 4
"https://www.\&googleapis.\&com/auth/calendar": read/share access to Calendars,
required to discover the unique hyperlink/URI for the calendar specified by
-config key "google_calendar"
+config key "caldav_calendar"
.IP \(bu 4
"https://www.\&googleapis.\&com/auth/calendar.\&events": read/write access to
Events owned by the user, allows diary to create/read/update/delete events in
-"google_calendar"
+"caldav_calendar"
.PD
.PP
The user is asked for consent during the first CALDAV SYNC.\&
diff --git a/man1/diary.1.html b/man1/diary.1.html
@@ -236,13 +236,20 @@ fmt_cmd =
#no_mouse = false
# Editor to open journal files with
editor =
-# Google calendar name for CalDAV sync
-#google_calendar =
+# CalDAV server URI
+# For example, Google calendar URI: "https://apidata.googleusercontent.com/caldav/v2"
+#caldav_server =
+# Calendar name for CalDAV sync
+#caldav_calendar =
# Google OAuth2 clientid and secretid
#google_clientid =
#google_secretid =
# Google OAuth2 tokenfile
#google_tokenfile = ~/.diary-token
+# CalDAV server username
+#caldav_username =
+# CalDAV server password
+#caldav_password =
EOF</pre>
</div>
<p class="Pp">To copy the sample file from the source repository:</p>
@@ -348,22 +355,22 @@ EOF</pre>
</tr>
<tr>
<td>n/a</td>
- <td>google_calendar </td>
+ <td>caldav_calendar</td>
<td>diary </td>
<td>(empty)</td>
- <td>Displayname of Google Calendar for <b>CALDAV SYNC</b></td>
+ <td>Calendar name for <b>CALDAV SYNC</b></td>
</tr>
<tr>
<td>n/a</td>
<td>google_clientid</td>
- <td>123.com</td>
+ <td></td>
<td>built-in at build time (see <b>GOOGLE CALENDAR OAUTH2</b>) or empty</td>
<td>Google Calendar for <b>GOOGLE CALENDAR OAUTH2</b> clientid</td>
</tr>
<tr>
<td>n/a</td>
<td>google_secretid</td>
- <td>321</td>
+ <td></td>
<td>built-in at build time (see <b>GOOGLE CALENDAR OAUTH2</b>) or empty</td>
<td>Google Calendar for <b>GOOGLE CALENDAR OAUTH2</b> secretid</td>
</tr>
@@ -374,6 +381,20 @@ EOF</pre>
<td>~/.diary-token</td>
<td><b>GOOGLE CALENDAR OAUTH2</b> API token file location</td>
</tr>
+ <tr>
+ <td>n/a</td>
+ <td>caldav_username</td>
+ <td></td>
+ <td>(empty)</td>
+ <td>CalDAV server username for <b>CALDAV SYNC</b></td>
+ </tr>
+ <tr>
+ <td>n/a</td>
+ <td>caldav_password</td>
+ <td></td>
+ <td>(empty)</td>
+ <td>CalDAV server password for <b>CALDAV SYNC</b></td>
+ </tr>
</table>
<p class="Pp"></p>
</section>
@@ -429,7 +450,7 @@ EOF</pre>
<p class="Pp">Currently, only the Google Calendar is supported as remote
provider.</p>
<p class="Pp">The calender for synchronization can be defined with the
- configuration key "google_calendar", see <b>CONFIGURATION
+ configuration key "caldav_calendar", see <b>CONFIGURATION
FILE</b>. This key is empty by default.</p>
<p class="Pp">Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google
server.</p>
@@ -457,10 +478,10 @@ EOF</pre>
<ul class="Bl-bullet">
<li>"https://www.googleapis.com/auth/calendar": read/share access to
Calendars, required to discover the unique hyperlink/URI for the calendar
- specified by config key "google_calendar"</li>
+ specified by config key "caldav_calendar"</li>
<li>"https://www.googleapis.com/auth/calendar.events": read/write
access to Events owned by the user, allows diary to
- create/read/update/delete events in "google_calendar"</li>
+ create/read/update/delete events in "caldav_calendar"</li>
</ul>
<p class="Pp">The user is asked for consent during the first CALDAV SYNC.</p>
</section>
diff --git a/man1/diary.1.scd b/man1/diary.1.scd
@@ -176,13 +176,20 @@ fmt_cmd =
#no_mouse = false
# Editor to open journal files with
editor =
-# Google calendar name for CalDAV sync
-#google_calendar =
+# CalDAV server URI
+# For example, Google calendar URI: "https://apidata.googleusercontent.com/caldav/v2"
+#caldav_server =
+# Calendar name for CalDAV sync
+#caldav_calendar =
# Google OAuth2 clientid and secretid
#google_clientid =
#google_secretid =
# Google OAuth2 tokenfile
#google_tokenfile = ~/.diary-token
+# CalDAV server username
+#caldav_username =
+# CalDAV server password
+#caldav_password =
EOF
```
@@ -265,18 +272,18 @@ The following configuration keys are currently supported:
the first weekday defaults to 1 (Monday), unless specified otherwise with this
option.
| n/a
-: google_calendar
+: caldav_calendar
: diary
: (empty)
-: Displayname of Google Calendar for *CALDAV SYNC*
+: Calendar name for *CALDAV SYNC*
| n/a
: google_clientid
-: 123.com
+:
: built-in at build time (see *GOOGLE CALENDAR OAUTH2*) or empty
: Google Calendar for *GOOGLE CALENDAR OAUTH2* clientid
| n/a
: google_secretid
-: 321
+:
: built-in at build time (see *GOOGLE CALENDAR OAUTH2*) or empty
: Google Calendar for *GOOGLE CALENDAR OAUTH2* secretid
| n/a
@@ -284,6 +291,16 @@ The following configuration keys are currently supported:
: ~/.diary-token
: ~/.diary-token
: *GOOGLE CALENDAR OAUTH2* API token file location
+| n/a
+: caldav_username
+:
+: (empty)
+: CalDAV server username for *CALDAV SYNC*
+| n/a
+: caldav_password
+:
+: (empty)
+: CalDAV server password for *CALDAV SYNC*
# CUSTOM FORMATTING
The preview of the journal entries can be processed with a custom *--fmt-cmd*.
@@ -322,6 +339,10 @@ No sync is performed, when both files (local/remote) have the same modification
timestamp or no files exists on the server and on disk at all (i.e., no entry
for that date).
+The diary creates a full-day calendar entry. It's advised to use a separate
+calendar for syncing purposes and not to have multiple events for a day on the
+remote calendar. The diary only considers the first event of a given day.
+
Use the EXPORT/IMPORT functionality for more batch oriented processing. Making
a network request and deciding the most recently modified entry (local/remote)
takes quite some time for large batches.
@@ -329,7 +350,7 @@ takes quite some time for large batches.
Currently, only the Google Calendar is supported as remote provider.
The calender for synchronization can be defined with the configuration key
-"google_calendar", see *CONFIGURATION FILE*. This key is empty by default.
+"caldav_calendar", see *CONFIGURATION FILE*. This key is empty by default.
Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server.
@@ -354,10 +375,10 @@ The application requires two OAuth2 scopes
- "https://www.googleapis.com/auth/calendar": read/share access to Calendars,
required to discover the unique hyperlink/URI for the calendar specified by
- config key "google_calendar"
+ config key "caldav_calendar"
- "https://www.googleapis.com/auth/calendar.events": read/write access to
Events owned by the user, allows diary to create/read/update/delete events in
- "google_calendar"
+ "caldav_calendar"
The user is asked for consent during the first CALDAV SYNC.
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) {
+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,29 +435,54 @@ 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);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_mem_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&caldav_resp);
// construct header fields
struct curl_slist *header = NULL;
- char bearer_token[strlen("Authorization: Bearer ") + strlen(access_token) + 1];
- sprintf(bearer_token, "Authorization: Bearer %s", access_token);
+
+ // default to basic auth when Google credentials are not set
+ if (basicauth) {
+ char basicauth [strlen(CONFIG.caldav_username) + strlen(CONFIG.caldav_password) + 2];
+ sprintf(basicauth, "%s:%s", CONFIG.caldav_username, CONFIG.caldav_password);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth);
+ tracepoint(diary, debug_string, "curl_easy_perform() basicauth", basicauth);
+ } else {
+ char bearer_token[strlen("Authorization: Bearer ") + strlen(access_token) + 1];
+ sprintf(bearer_token, "Authorization: Bearer %s", access_token);
+ header = curl_slist_append(header, bearer_token);
+ }
+
char depth_header[strlen("Depth: 0") + 1];
sprintf(depth_header, "Depth: %i", depth);
header = curl_slist_append(header, depth_header);
- header = curl_slist_append(header, bearer_token);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
+ 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);
+ }
+
// set postfields, if any
if (postfields != NULL) {
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;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+
// free the memory used for the curl slist
curl_slist_free_all(header);
@@ -459,6 +490,7 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields
if (res != CURLE_OK) {
tracepoint(diary, error_string, "curl_easy_perform() failed in caldav_req()", curl_easy_strerror(res));
+ tracepoint(diary, debug_long, "curl_easy_perform() response code", response_code);
free(caldav_resp.memory);
return NULL;
}
@@ -469,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, "<D: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;
@@ -482,39 +514,60 @@ 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;
}
+/* Return home set uri from CalDAV home set XML response */
+char* parse_home_set(char* xml) {
+ char* homeset_needle = "calendar-home-set";
+
+ char* homeset_pos = strstr(xml, homeset_needle);
+ if (homeset_pos == NULL) {
+ tracepoint(diary, debug, "parse_home_set() homeset_pos was null");
+ return NULL;
+ }
+ tracepoint(diary, debug_string, "parse_home_set() homeset_pos", homeset_pos);
+
+ char* href = strrstr(homeset_pos, "href>/");
+ if (href != NULL) {
+ tracepoint(diary, debug_string, "parse_home_set() href", href);
+ href = strtok(href, "<");
+ tracepoint(diary, debug_string, "parse_home_set() href strtok", href);
+ if (href != NULL) {
+ href = strchr(href, '>');
+ href++; // cut >
+ }
+ return href;
+ }
+ return NULL;
+}
+
/* Return calendar uri from CalDAV home set XML response */
char* parse_caldav_calendar(char* xml, char* calendar) {
- char displayname_needle[strlen(calendar) + strlen("<D:displayname></D:displayname>")];
- sprintf(displayname_needle, "<D:displayname>%s</D:displayname>", calendar);
+ char displayname_needle[strlen(calendar) + strlen("displayname></") + 1];
+ sprintf(displayname_needle, "displayname>%s</", calendar);
char* displayname_pos = strstr(xml, displayname_needle);
// this XML multistatus response does not contain the users calendar
if (displayname_pos == NULL) {
+ tracepoint(diary, debug, "parse_caldav_calenar() displayname_pos was null");
return NULL;
}
-
- // <D:response>
- // <D:href>/caldav/v2/2fcv7j5mf38o5u2kg5tar4baao%40group.calendar.google.com/events/</D:href>
- // <D:propstat>
- // <D:status></D:status>
- // <D:prop>
- // <D:displayname>diary</D:displayname>
- // </D:prop>
- // </D:propstat>
- // </D:response>
+ tracepoint(diary, debug_string, "parse_caldav_calenar() displayname_pos", displayname_pos);
// shorten multistatus response and find last hyperlink
*displayname_pos= '\0';
- char* href = strrstr(xml, "<D:href>");
+
+ tracepoint(diary, debug_string, "parse_caldav_calenar() shortened multistatus response", xml);
+ char* href = strrstr(xml, "href>/");
if (href != NULL) {
+ tracepoint(diary, debug_string, "parse_caldav_calenar() last href", href);
href = strtok(href, "<"); // :href>/caldav/v2/aaa%40group.calendar.google.com/events/
+ tracepoint(diary, debug_string, "parse_caldav_calenar() last href strtok", href);
if (href != NULL) {
href = strchr(href, '>');
href++; // cut >
@@ -525,7 +578,7 @@ char* parse_caldav_calendar(char* xml, char* calendar) {
}
/* Upload event to CalDAV server */
-void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar_uri) {
+void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar_uri, bool basicauth) {
// get entry path
char path[100];
char* ppath = path;
@@ -550,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;
}
@@ -568,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"
@@ -582,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);
+ char* response = caldav_req(date, calendar_uri, "PUT", postfields, 0, basicauth, "text/calendar", 0);
fclose(fp);
free(folded_descr);
free(descr);
@@ -614,10 +674,17 @@ int caldav_sync(struct tm* date,
char* info_txt;
- if (strcmp(CONFIG.google_clientid, "") == 0 || strcmp(CONFIG.google_secretid, "") == 0 || strcmp(CONFIG.google_calendar, "") == 0 ) {
+ bool google_oauth_enabled = !(strcmp(CONFIG.google_clientid, "") == 0 || strcmp(CONFIG.google_clientid, "") == 0);
+ bool basicauth_enabled = !(strcmp(CONFIG.caldav_username, "") == 0 || strcmp(CONFIG.caldav_password, "") == 0);
+
+ if (strcmp(CONFIG.caldav_server, "") == 0 || strcmp(CONFIG.caldav_calendar, "") == 0 || !(google_oauth_enabled || basicauth_enabled)) {
+ tracepoint(diary, debug_string, "CONFIG.caldav_server", CONFIG.caldav_server);
+ tracepoint(diary, debug_string, "CONFIG.caldav_calendar", CONFIG.caldav_calendar);
+ tracepoint(diary, debug_string, "CONFIG.caldav_username", CONFIG.caldav_username);
+ tracepoint(diary, debug_string, "CONFIG.caldav_password", CONFIG.caldav_password);
wclear(header);
wresize(header, LINES, getmaxx(header));
- info_txt = "Missing sync parameters. Set Google Client ID, secret and remote calendar name.\n"
+ info_txt = "Missing sync parameters. Configure CalDAV server and credentials.\n"
"Press any key to continue.";
mvwaddstr(header, 0, 0, info_txt);
wrefresh(header);
@@ -629,27 +696,29 @@ int caldav_sync(struct tm* date,
wclear(header);
}
- // fetch existing API tokens from file
- char* tokfile = read_tokenfile();
- free(tokfile);
+ if (google_oauth_enabled) {
+ // fetch existing API tokens from file
+ char* tokfile = read_tokenfile();
+ free(tokfile);
+
+ if (strcmp(access_token, "") == 0) {
+ // no access token exists yet, create new verifier
+ char challenge[GOOGLE_OAUTH_CODE_VERIFIER_LENGTH + 1];
+ random_code_challenge(GOOGLE_OAUTH_CODE_VERIFIER_LENGTH, challenge);
+
+ // fetch new code with verifier
+ char* code = get_oauth_code(challenge, header);
+ if (code == NULL) {
+ tracepoint(diary, error, "Error retrieving oauth code in caldav_sync()");
+ return -1;
+ }
- if (strcmp(access_token, "") == 0) {
- // no access token exists yet, create new verifier
- char challenge[GOOGLE_OAUTH_CODE_VERIFIER_LENGTH + 1];
- random_code_challenge(GOOGLE_OAUTH_CODE_VERIFIER_LENGTH, challenge);
+ // get acess token using code and verifier
+ tracepoint(diary, debug, "Fetching access token with code challenge");
+ get_access_token(code, challenge, false);
- // fetch new code with verifier
- char* code = get_oauth_code(challenge, header);
- if (code == NULL) {
- tracepoint(diary, error, "Error retrieving oauth code in caldav_sync()");
- return -1;
+ free(code);
}
-
- // get acess token using code and verifier
- tracepoint(diary, debug, "Fetching access token with code challenge");
- get_access_token(code, challenge, false);
-
- free(code);
}
pthread_create(&progress_tid, NULL, show_progress, (void*)header);
@@ -661,22 +730,26 @@ int caldav_sync(struct tm* date,
// check if we can use the token from the tokenfile
- char* user_principal = caldav_req(date, GOOGLE_CALDAV_URI, "PROPFIND", principal_postfields, 0);
+ 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) {
- // The principal could not be fetched,
- // get new acess token with refresh token
- tracepoint(diary, debug, "Unable to fetch principal, refreshing API token");
- 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, GOOGLE_CALDAV_URI, "PROPFIND", principal_postfields, 0);
+ // The principal could not be fetched
+ tracepoint(diary, debug, "Unable to fetch principal");
+ if (google_oauth_enabled) {
+ // get new acess token with refresh token
+ 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", 1);
+ }
}
if (user_principal == NULL) {
pthread_cancel(progress_tid);
wclear(header);
wresize(header, LINES, getmaxx(header));
+ // TODO: this could also be wrong basicauth credentials
info_txt = "Offline, corrupted or otherwise invalid OAuth2 credential tokenfile.\n"
"Go online or delete tokenfile '%s' and restart diary to retry.\n"
"Press any key to continue.";
@@ -693,56 +766,107 @@ int caldav_sync(struct tm* date,
}
char* current_user_principal = parse_caldav_current_user_principal(user_principal);
+ tracepoint(diary, debug_string, "Current user principal", current_user_principal);
+
+ // extract host without path as basis for further api paths
+ char *caldav_host; char* caldav_host_scheme;
+ CURLU *h = curl_url();
+ CURLUcode uc = curl_url_set(h, CURLUPART_URL, CONFIG.caldav_server, 0);
+ uc = curl_url_get(h, CURLUPART_HOST, &caldav_host, 0);
+ uc = curl_url_get(h, CURLUPART_SCHEME, &caldav_host_scheme, 0);
+ tracepoint(diary, debug_string, "Caldav server host name", caldav_host);
+ tracepoint(diary, debug_string, "Caldav server scheme/protocol", caldav_host_scheme);
+ tracepoint(diary, debug_string, "Caldav server host name", caldav_host);
+ if (!uc) {
+ tracepoint(diary, error, "cur_url_get() failed in caldav_sync()");
+ }
// get the home-set of the user
char uri[300];
- sprintf(uri, "%s%s", GOOGLE_API_URI, current_user_principal);
+ sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, current_user_principal);
// free memory allocated by curl request
free(user_principal);
- char* home_set = caldav_req(date, uri, "PROPFIND", "", 1);
+
+ char* homeset_request = "<d:propfind xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>"
+ " <d:prop>"
+ " <c:calendar-home-set />"
+ " </d:prop>"
+ "</d:propfind>";
+
+ 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);
+ if (home_set == NULL) {
+ pthread_cancel(progress_tid);
+ wclear(header);
+ wresize(header, LINES, getmaxx(header));
+ info_txt = "Error while fetching home set in caldav_sync()\n"
+ "Press any key to continue.";
+ mvwaddstr(header, 0, 0, info_txt);
+ wrefresh(header);
+
+ // accept any input to proceed
+ noecho();
+ wgetch(header);
+ echo();
+
+ wclear(header);
+ return -1;
+ }
+
+ // parse home set from xml response
+ char* home_set_parsed = parse_home_set(home_set);
+ sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, home_set_parsed);
+
+ char* calendar_request = "<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>";
+
+ // get calendar from home-set
+ 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(home_set, CONFIG.google_calendar);
+ 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>"
"</c:comp-filter></c:filter></c:calendar-query>";
- // construct next day from date+1
- time_t date_time = mktime(date);
- struct tm* next_day = localtime(&date_time);
- next_day->tm_mday++;
- mktime(next_day);
-
- char dstr_cursor[30];
- char dstr_next_day[30];
-
- char* format = "%Y%m%dT000000Z";
- strftime(dstr_cursor, sizeof dstr_cursor, format, date);
- strftime(dstr_next_day, sizeof dstr_next_day, format, next_day);
+ char dstr_start[30];
+ char dstr_end[30];
+ char* format_start = "%Y%m%dT000000Z"; // start of day
+ char* format_end = "%Y%m%dT235900Z"; // end of day
+ strftime(dstr_start, sizeof dstr_start, format_start, date);
+ strftime(dstr_end, sizeof dstr_end, format_end, date);
- char caldata_postfields[strlen(xml_filter)+50];
+ char caldata_postfields[strlen(xml_filter) + strlen(dstr_start) + strlen(dstr_end)];
sprintf(caldata_postfields, xml_filter,
- dstr_cursor,
- dstr_next_day);
+ dstr_start,
+ dstr_end);
// fetch event for the cursor date
- sprintf(uri, "%s%s", GOOGLE_API_URI, calendar_href);
+ sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, calendar_href);
- char* event = caldav_req(date, uri, "REPORT", caldata_postfields, 0);
+ 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
- // LAST-MODIFIED, UID and DESCRIPTION (from the first event)
+ // DTSTAMP, UID and DESCRIPTION (from the first event)
if (event == NULL) {
// Event not found. The curl request probably failed due to one of the missing input parameters:
// - Google Client ID
// - Google Secret ID
// - Google Calendar Name
+ tracepoint(diary, debug, "Event not found");
pthread_cancel(progress_tid);
wclear(header);
free(home_set);
@@ -772,17 +896,19 @@ int caldav_sync(struct tm* date,
time_t remote_date = 0;
long search_pos = 0;
- // check remote LAST-MODIFIED:20210521T212441Z of remote event
- char* remote_last_mod = extract_ical_field(event, "LAST-MODIFIED", &search_pos, false);
+ // check remote DTSTAMP of remote event
+ char* remote_dtstamp = extract_ical_field(event, "DTSTAMP", &search_pos, false);
char* remote_uid = extract_ical_field(event, "UID", &search_pos, false);
+ tracepoint(diary, debug_string, "event", event);
+ tracepoint(diary, debug_string, "DTSTAMP field", remote_dtstamp);
// init remote date to 1970, assume remote file does not exist
remote_datetime = gmtime(&epoch_zero);
- if (remote_last_mod != NULL) {
+ if (remote_dtstamp != NULL) {
// if remote date does exist, set date to the correct timestamp
- strptime(remote_last_mod, "%Y%m%dT%H%M%SZ", remote_datetime);
- free(remote_last_mod);
+ strptime(remote_dtstamp, "%Y%m%dT%H%M%SZ", remote_datetime);
+ free(remote_dtstamp);
}
remote_date = mktime(remote_datetime);
@@ -801,6 +927,8 @@ int caldav_sync(struct tm* date,
pthread_cancel(progress_tid);
wclear(header);
// free memory allocated to store curl response
+ curl_free(caldav_host_scheme);
+ curl_free(caldav_host);
free(event);
free(remote_uid);
free(home_set);
@@ -810,13 +938,14 @@ int caldav_sync(struct tm* date,
if (remote_uid) {
// purge any existing daily calendar entries on the remote side
char event_uri[300];
- sprintf(event_uri, "%s%s%s.ics", GOOGLE_API_URI, calendar_href, remote_uid);
+ sprintf(event_uri, "%s://%s%s%s.ics", caldav_host_scheme, caldav_host, calendar_href, remote_uid);
+ //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);
+ char* response = caldav_req(date, event_uri, "DELETE", NULL, 0, basicauth_enabled, "", 0);
free(response);
}
- put_event(date, dir, dir_size, uri);
+ put_event(date, dir, dir_size, uri, basicauth_enabled);
pthread_cancel(progress_tid);
wclear(header);
@@ -828,6 +957,8 @@ int caldav_sync(struct tm* date,
tracepoint(diary, error, "Could not fetch description of remote event. Aborting sync.");
pthread_cancel(progress_tid);
wclear(header);
+ curl_free(caldav_host_scheme);
+ curl_free(caldav_host);
free(event);
free(remote_uid);
free(home_set);
@@ -887,6 +1018,8 @@ int caldav_sync(struct tm* date,
}
// free memory allocated to store curl response
+ curl_free(caldav_host_scheme);
+ curl_free(caldav_host);
free(event);
free(remote_uid);
free(home_set); // home_set was required for sprintfing calendar_href to event_uri
diff --git a/src/caldav.h b/src/caldav.h
@@ -32,7 +32,6 @@
#define GOOGLE_OAUTH_REDIRECT_PORT 9004
#define GOOGLE_OAUTH_REDIRECT_SOCKET_BACKLOG 10
#define GOOGLE_API_URI "https://apidata.googleusercontent.com"
-#define GOOGLE_CALDAV_URI GOOGLE_API_URI "/caldav/v2"
int caldav_sync(struct tm* date,
WINDOW* header,
diff --git a/src/diary-tp.h b/src/diary-tp.h
@@ -121,6 +121,19 @@ TRACEPOINT_EVENT(
TRACEPOINT_EVENT(
diary,
+ debug_long,
+ TP_ARGS(
+ char*, msg_arg,
+ long, n_arg
+ ),
+ TP_FIELDS(
+ ctf_string(msg, msg_arg)
+ ctf_integer(long, n, n_arg)
+ )
+)
+
+TRACEPOINT_EVENT(
+ diary,
debug_sizet,
TP_ARGS(
char*, msg_arg,
@@ -179,6 +192,7 @@ TRACEPOINT_LOGLEVEL(diary, debug, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, debug_string, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, debug_int, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, debug_double, TRACE_DEBUG)
+TRACEPOINT_LOGLEVEL(diary, debug_long, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, debug_sizet, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, debug_tm, TRACE_DEBUG)
TRACEPOINT_LOGLEVEL(diary, warning_string, TRACE_WARNING)
diff --git a/src/diary.c b/src/diary.c
@@ -363,9 +363,18 @@ bool read_config(const char* file_path) {
} else if (strcmp("google_secretid", key_buf) == 0) {
CONFIG.google_secretid = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
strcpy(CONFIG.google_secretid, value_buf);
- } else if (strcmp("google_calendar", key_buf) == 0) {
- CONFIG.google_calendar = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
- strcpy(CONFIG.google_calendar, value_buf);
+ } else if (strcmp("caldav_server", key_buf) == 0) {
+ CONFIG.caldav_server = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
+ strcpy(CONFIG.caldav_server, value_buf);
+ } else if (strcmp("caldav_calendar", key_buf) == 0) {
+ CONFIG.caldav_calendar = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
+ strcpy(CONFIG.caldav_calendar, value_buf);
+ } else if (strcmp("caldav_username", key_buf) == 0) {
+ CONFIG.caldav_username = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
+ strcpy(CONFIG.caldav_username, value_buf);
+ } else if (strcmp("caldav_password", key_buf) == 0) {
+ CONFIG.caldav_password = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
+ strcpy(CONFIG.caldav_password, value_buf);
}
}
}
@@ -816,7 +825,10 @@ int main(int argc, char** argv) {
mousemask(oldmask, NULL);
}
- system(ecmd);
+ int ret = system(ecmd);
+ if (ret == -1) {
+ perror("Failure while running edit command in main()");
+ }
if (!CONFIG.no_mouse) {
mousemask(ALL_MOUSE_EVENTS, &oldmask);
@@ -930,6 +942,9 @@ int main(int argc, char** argv) {
free(config_file_path);
free(CONFIG.dir);
endwin();
- system("clear");
+ int ret = system("clear");
+ if (ret == -1) {
+ perror("Failure while running clear command in main()");
+ }
return 0;
}
diff --git a/src/import.c b/src/import.c
@@ -15,7 +15,12 @@ void ics_import(const char* ics_input, WINDOW* header, WINDOW* cal, WINDOW* asid
rewind(pfile);
char* ics = malloc(ics_bytes + 1);
- fread(ics, 1, ics_bytes, pfile);
+ size_t read = fread(ics, 1, ics_bytes, pfile);
+ if (read != ics_bytes) {
+ perror("Error while reading in ics_import()");
+ fclose(pfile);
+ return;
+ }
fclose(pfile);
ics[ics_bytes] = '\0';
@@ -118,4 +123,4 @@ void ics_import(const char* ics_input, WINDOW* header, WINDOW* cal, WINDOW* asid
free(vevent_desc);
}
free(ics);
-}
-\ No newline at end of file
+}
diff --git a/src/utils.c b/src/utils.c
@@ -106,7 +106,8 @@ char* fold(const char* str) {
// break lines after 75 chars
// split between any two characters by inserting a CRLF
// immediately followed by a white space character
- buf[bufl-2] = '\n';
+ buf[bufl-2] = '\r';
+ buf[bufl-1] = '\n';
escch = ' ';
esc = true;
continue;
@@ -165,7 +166,7 @@ char* unfold(const char* str) {
}
strcpy(strcp, str);
- char* res = strtok(strcp, "\n");
+ char* res = strtok(strcp, "\r\n");
if (res == NULL) {
tracepoint(diary, debug, "No more lines in multiline string, stop unfolding");
@@ -224,7 +225,7 @@ char* unfold(const char* str) {
char* extract_ical_field(const char* ics, char* key, long* start_pos, bool multiline) {
regex_t re;
regmatch_t pm[1];
- char key_regex[strlen(key) + 1];
+ char key_regex[strlen(key) + 2];
sprintf(key_regex, "^%s", key);
if (regcomp(&re, key_regex, 0) != 0) {
@@ -405,5 +406,8 @@ config CONFIG = {
.google_tokenfile = GOOGLE_OAUTH_TOKEN_FILE,
.google_clientid = GOOGLE_OAUTH_CLIENT_ID,
.google_secretid = GOOGLE_OAUTH_CLIENT_SECRET,
- .google_calendar = ""
+ .caldav_calendar = "",
+ .caldav_server = "",
+ .caldav_username = "",
+ .caldav_password = ""
};
diff --git a/src/utils.h b/src/utils.h
@@ -62,8 +62,14 @@ typedef struct
char* google_clientid;
// Google secret id
char* google_secretid;
- // Google calendar to synchronize
- char* google_calendar;
+ // CalDAV calendar to synchronize
+ char* caldav_calendar;
+ // CalDAV server URI
+ char* caldav_server;
+ // CalDAV username
+ char* caldav_username;
+ // CalDAV password
+ char* caldav_password;
} config;
extern config CONFIG;