diary

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

commit bb1b7be5044bd7aee34f59765332cc1b07ea2f7b
parent d8874bf14187809b65fe94733fbf6ba5e9a38199
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Wed, 19 May 2021 00:51:12 +0200

get home-set and calendar uri

Diffstat:
Mcaldav.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcaldav.h | 3++-
Mutils.c | 12++++++++++++
Mutils.h | 1+
4 files changed, 142 insertions(+), 33 deletions(-)

diff --git a/caldav.c b/caldav.c @@ -177,6 +177,7 @@ void get_access_token(char* code, char* verifier, bool refresh) { curl_easy_setopt(curl, CURLOPT_URL, GOOGLE_OAUTH_TOKEN_URL); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); tokenfile_path = expand_path(CONFIG.google_tokenfile); tokenfile = fopen(tokenfile_path, "wb"); @@ -361,51 +362,124 @@ char* get_oauth_code(const char* verifier, WINDOW* header) { return code; } -char* get_event(struct tm* date) { +char* caldav_req(struct tm* date, char* url, char* postfields, int depth) { + + // only support depths 0 or 1 + if (depth < 0 || depth > 1) { + return NULL; + } + CURLcode res; curl = curl_easy_init(); // https://curl.se/libcurl/c/getinmemory.html - struct curl_mem_chunk event_result; - event_result.memory = malloc(1); - event_result.size = 0; + struct curl_mem_chunk caldav_resp; + caldav_resp.memory = malloc(1); + caldav_resp.size = 0; 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_CUSTOMREQUEST, "PROPFIND"); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); + 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)]; - sprintf(bearer_token, "Authorization: Bearer %s", access_token); - header = curl_slist_append(header, "Depth: 0"); + char depth_header[strlen("Depth: 0")]; + sprintf(depth_header, "Depth: %i", depth); + header = curl_slist_append(header, depth_header); header = curl_slist_append(header, bearer_token); - - char* postfields = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/'>" - "<d:prop><d:current-user-principal/></d:prop>" - "</d:propfind>"; - - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PROPFIND"); - curl_easy_setopt(curl, CURLOPT_URL, GOOGLE_CALDAV_URI); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_mem_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&event_result); - // fail if not authenticated, !CURLE_OK - curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + + // set postfields, if any + if (postfields != NULL) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); + } res = curl_easy_perform(curl); curl_easy_cleanup(curl); - //fprintf(stderr, "Curl retrieved %lu bytes\n", (unsigned long)event_result.size); - //fprintf(stderr, "Curl content: %s\n", event_result.memory); + //fprintf(stderr, "Curl retrieved %lu bytes\n", (unsigned long)caldav_resp.size); + //fprintf(stderr, "Curl content: %s\n", caldav_resp.memory); if (res != CURLE_OK) { + //fprintf(stderr, "Curl response: %s\n", caldav_resp.memory); fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); - event_result.memory = NULL; + return NULL; + } + } + + return caldav_resp.memory; +} + +// 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>"); + // this XML does not contain a user principal at all + if (xml_key_pos == NULL) { + return NULL; + } + + //fprintf(stderr, "Found current-user-principal at position: %i\n", *xml_key_pos); + + //<D:current-user-principal> + //<D:href>/caldav/v2/diary.in0rdr%40gmail.com/user</D:href> + char* tok = strtok(xml_key_pos, "<"); // D:current-user-principal> + if (tok != NULL) { + tok = strtok(NULL, "<"); // D:href>/caldav/v2/test%40gmail.com/user + fprintf(stderr, "First token: %s\n", tok); + tok = strstr(tok, ">"); // >/caldav/v2/test%40gmail.com/user + tok++; // cut > + char* tok_end = strrchr(tok, '/'); + *tok_end = '\0'; // cut /user + } + + return tok; +} + +// 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); + fprintf(stderr, "Displayname needle: %s\n", displayname_needle); + char* displayname_pos = strstr(xml, displayname_needle); + // this XML multistatus response does not contain the users calendar + if (displayname_pos == 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> + + // shorten multistatus response and find last hyperlink + *displayname_pos= '\0'; + char* href = strrstr(xml, "<D:href>"); + if (href != NULL) { + //fprintf(stderr, "Found calendar href: %s\n", href); + href = strtok(href, "<"); // :href>/caldav/v2/aaa%40group.calendar.google.com/events/ + if (href != NULL) { + href = strchr(href, '>'); + href++; // cut > + //fprintf(stderr, "Found calendar href: %s\n", href); } + return href; } - return event_result.memory; + return NULL; } void put_event(struct tm* date) { @@ -433,27 +507,48 @@ void caldav_sync(struct tm* date, WINDOW* header) { get_access_token(code, challenge, false); } + char* principal_postfields = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/'>" + "<d:prop><d:current-user-principal/></d:prop>" + "</d:propfind>"; + + // check if we can use the token from the tokenfile - char* event = get_event(date); + char* user_principal = caldav_req(date, GOOGLE_CALDAV_URI, principal_postfields, 0); - if (event == NULL) { - // The event could not be fetched, + if (user_principal == NULL) { + fprintf(stderr, "Unable to fetch principal, refreshing API token.\n"); + // The principal could not be fetched, // get new acess token with refresh token get_access_token(NULL, NULL, true); // Retry request for event with new token - event = get_event(date); + user_principal = caldav_req(date, GOOGLE_CALDAV_URI, principal_postfields, 0); } - // check LAST-MODIFIED - fprintf(stderr, "\nEvent: %s\n\n", event); + //fprintf(stderr, "\nUser Principal: %s\n\n", user_principal); + user_principal = parse_caldav_current_user_principal(user_principal); + fprintf(stderr, "\nUser Principal: %s\n\n", user_principal); - // if local file mod time more recent than LAST-MODIFIED - put_event(date); + // get the home-set of the user + char uri[300]; + sprintf(uri, "%s%s", GOOGLE_API_URI, user_principal); + fprintf(stderr, "\nHome Set URI: %s\n\n", uri); + char* home_set = caldav_req(date, uri, "", 1); + fprintf(stderr, "\nHome Set: %s\n\n", home_set); - // else persist downloaded buffer to local file + // get calendar URI from the home-set + char* calendar = parse_caldav_calendar(home_set, CONFIG.google_calendar); + fprintf(stderr, "\nCalendar href: %s\n\n", calendar); + // get cursor date char dstr[16]; mktime(date); get_date_str(date, dstr, sizeof dstr, CONFIG.fmt); fprintf(stderr, "\nCursor date: %s\n\n", dstr); + + // fetch event for the cursor date + + // check LAST-MODIFIED + // if local file mod time more recent than LAST-MODIFIED + //put_event(date); + // else persist downloaded buffer to local file } diff --git a/caldav.h b/caldav.h @@ -31,7 +31,8 @@ #define GOOGLE_OAUTH_REDIRECT_PORT 9004 #define GOOGLE_OAUTH_REDIRECT_URI "http://" GOOGLE_OAUTH_REDIRECT_HOST ":" MKSTR(GOOGLE_OAUTH_REDIRECT_PORT) #define GOOGLE_OAUTH_REDIRECT_SOCKET_BACKLOG 10 -#define GOOGLE_CALDAV_URI "https://apidata.googleusercontent.com/caldav/v2" +#define GOOGLE_API_URI "https://apidata.googleusercontent.com" +#define GOOGLE_CALDAV_URI GOOGLE_API_URI "/caldav/v2" void caldav_sync(struct tm* date, WINDOW* header); diff --git a/utils.c b/utils.c @@ -36,6 +36,18 @@ char* expand_path(char* str) { return res; } +// Get last occurence of string in string +// https://stackoverflow.com/questions/20213799/finding-last-occurence-of-string +char* strrstr(char *haystack, char *needle) { + int nlen = strlen(needle); + for (char* i = haystack + strlen(haystack) - nlen; i >= haystack; i--) { + if (strncmp(i, needle, nlen) == 0) { + return i; + } + } + return NULL; +} + config CONFIG = { .range = 1, .weekday = 1, diff --git a/utils.h b/utils.h @@ -17,6 +17,7 @@ char* extract_json_value(char* json, char* key, bool quoted); char* expand_path(char* str); +char* strrstr(char *haystack, char *needle); typedef struct {