commit 49549e3ca1b9a96820e06d63f6f7932cf51e920b
parent 099b3614353dc95326e9637409dc08e5bf1878d7
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Sat, 14 Sep 2024 12:37:21 +0200
feat(libxml): extract_xml_content
Adds the extract_xml_content utility function.The function extracts XML
content using XPath expressions.
Replaces previous custom parsing logic (string token manipulation).
Diffstat:
M | src/caldav.c | | | 185 | ++++++++++++++++++++++++------------------------------------------------------- |
M | src/utils.c | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
M | src/utils.h | | | 4 | ++++ |
3 files changed, 103 insertions(+), 129 deletions(-)
diff --git a/src/caldav.c b/src/caldav.c
@@ -7,6 +7,25 @@ char access_token[sizeof(char)*2048] = "";
// Reserve 2 chars for the ipv6 square brackets.
char ip[INET6_ADDRSTRLEN], ipstr[INET6_ADDRSTRLEN + 2];
+// Static XML data for CalDav post requests:
+// https://write.in0rdr.ch/caldav-calendar-discovery
+const char* principal_postfields = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/'>"
+ "<d:prop><d:current-user-principal/></d:prop>"
+ "</d:propfind>";
+const 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>";
+const 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>";
+
static size_t curl_write_mem_callback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t realsize = size * nmemb;
struct curl_mem_chunk* mem = (struct curl_mem_chunk*)userp;
@@ -57,7 +76,7 @@ void get_access_token() {
}
/* Make a CalDAV request */
-char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields, int depth, bool basicauth, char* content_type, long num_redirects) {
+char* caldav_req(struct tm* date, char* url, char* http_method, const 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;
@@ -144,102 +163,6 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields
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, "current-user-principal");
- // this XML does not contain a user principal at all
- if (xml_key_pos == NULL) {
- return NULL;
- }
-
- //<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
- tok = strstr(tok, ">"); // >/caldav/v2/test%40gmail.com/user
- tok++; // cut >
- //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(const 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 a copy of the href
- char* res = (char*) malloc(strlen(href) * sizeof(char) + 1);
- if (res == NULL) {
- perror("malloc failed in parse_home_set()");
- return NULL;
- }
- strcpy(res, href);
-
- return res;
- }
- 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("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_calendar() displayname_pos was null");
- return NULL;
- }
- tracepoint(diary, debug_string, "parse_caldav_calendar() displayname_pos", displayname_pos);
-
- // shorten multistatus response and find last hyperlink
- *displayname_pos = '\0';
-
- tracepoint(diary, debug_string, "parse_caldav_calendar() shortened multistatus response", xml);
- char* href = strrstr(xml, "href>/");
- if (href != NULL) {
- tracepoint(diary, debug_string, "parse_caldav_calendar() last href", href);
- href = strtok(href, "<"); // :href>/caldav/v2/aaa%40group.calendar.google.com/events/
- tracepoint(diary, debug_string, "parse_caldav_calendar() last href strtok", href);
- if (href != NULL) {
- href = strchr(href, '>');
- href++; // cut >
- }
-
- // return a copy of the href
- char* res = (char*) malloc(strlen(href) * sizeof(char) + 1);
- if (res == NULL) {
- perror("malloc failed in parse_caldav_calendar");
- return NULL;
- }
- strcpy(res, href);
-
- return res;
- }
- return NULL;
-}
-
/* Upload event to CalDAV server */
void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar_uri, bool basicauth) {
// get entry path
@@ -362,13 +285,8 @@ int caldav_sync(struct tm* date,
pthread_create(&progress_tid, NULL, show_progress, (void*)header);
pthread_detach(progress_tid);
- char* principal_postfields = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/'>"
- "<d:prop><d:current-user-principal/></d:prop>"
- "</d:propfind>";
-
-
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);
+ tracepoint(diary, debug_string, "User principal return XML", user_principal);
if (user_principal == NULL) {
// The principal could not be fetched
@@ -377,8 +295,20 @@ int caldav_sync(struct tm* date,
return -1;
}
- char* current_user_principal = parse_caldav_current_user_principal(user_principal);
- tracepoint(diary, debug_string, "Current user principal", current_user_principal);
+ char* current_user_principal = extract_xml_content(
+ user_principal,
+ "//*[local-name()='current-user-principal']/*[local-name()='href']",
+ header,
+ &progress_tid);
+
+ // free memory allocated by curl request
+ free(user_principal);
+
+ if (current_user_principal == NULL) {
+ tracepoint(diary, debug, "Unable to parse current user principal");
+ show_info(header, "Unable to parse current user principal, press any key to continue.", &progress_tid);
+ return -1;
+ }
// extract host without path as basis for further api paths
char *caldav_host; char* caldav_host_scheme;
@@ -395,46 +325,41 @@ int caldav_sync(struct tm* date,
// get the home-set of the user
char uri[300];
sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, current_user_principal);
- // free memory allocated by curl request
- free(user_principal);
-
- 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);
// parse home set from xml response
- char* home_set_parsed = parse_home_set(home_set);
- tracepoint(diary, debug_string, "home_set_parsed", home_set_parsed);
- free(home_set);
+ char* home_set_parsed = extract_xml_content(
+ home_set,
+ "//*[local-name()='calendar-home-set']/*[local-name()='href']",
+ header,
+ &progress_tid);
+
if (home_set_parsed == NULL) {
- show_info(header, "Error while fetching home set in caldav_sync, press any key to continue", &progress_tid);
+ tracepoint(diary, debug, "Unable to parse home set");
+ show_info(header, "Unable to parse home set, press any key to continue.", &progress_tid);
return -1;
}
+
sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, home_set_parsed);
- free(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>";
+ free(home_set);
+ free(home_set_parsed);
// 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(calendar_xml, CONFIG.caldav_calendar);
- free(calendar_xml);
- tracepoint(diary, debug_string, "calendar_href", calendar_href);
+ char calendar_xpath_query[300];
+ sprintf(calendar_xpath_query, "//*[local-name()='displayname'][text()='%s']/../../../*[local-name()='href']", CONFIG.caldav_calendar);
+ tracepoint(diary, debug_string, "Calendar xpath query", calendar_xpath_query);
+ char* calendar_href = extract_xml_content(
+ calendar_xml,
+ calendar_xpath_query,
+ header,
+ &progress_tid);
if (calendar_href == NULL) {
tracepoint(diary, debug_string, "Could not find CalDAV calendar", CONFIG.caldav_calendar);
@@ -445,6 +370,8 @@ int caldav_sync(struct tm* date,
return -1;
}
+ free(calendar_xml);
+
char* xml_filter = "<c:calendar-query xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>"
"<d:prop><d:getetag/><c:calendar-data/></d:prop>"
"<c:filter><c:comp-filter name='VCALENDAR'>"
diff --git a/src/utils.c b/src/utils.c
@@ -284,6 +284,49 @@ char* extract_ical_field(const char* ics, char* key, long* start_pos, bool multi
return buf;
}
+/* Extract xml node content using XPath expression, xmlsoft.org/examples */
+char* extract_xml_content(const char* xml, const char* xpathExpression, WINDOW* w, pthread_t* p) {
+ xmlDocPtr doc = NULL;
+ xmlXPathContextPtr xpathCtx = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+
+ // Macro to check that the libxml version in use is compatible with the
+ // version the software has been compiled against
+ LIBXML_TEST_VERSION;
+
+ doc = xmlReadMemory(xml, strlen(xml), NULL, NULL, 0);
+ if (doc == NULL) {
+ tracepoint(diary, debug, "Failed to parse xml");
+ show_info(w, "Failed to parse xml", p);
+ return NULL;
+ }
+
+ xpathCtx = xmlXPathNewContext(doc);
+ if (xpathCtx == NULL) {
+ tracepoint(diary, debug, "Failed to create xpath context");
+ show_info(w, "Failed to create xpath context", p);
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ xmlChar* xpath = (unsigned char *) xpathExpression;
+ xpathObj = xmlXPathEvalExpression(xpath, xpathCtx);
+ if(xpathObj == NULL) {
+ tracepoint(diary, debug, "Failed to evaluate xpath expression");
+ show_info(w, "Failed to evaluate xpath expression", p);
+ xmlXPathFreeContext(xpathCtx);
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ char* nodeContent = (char *) xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]);
+ tracepoint(diary, debug_string, "XML node contents", nodeContent);
+
+ xmlFreeDoc(doc);
+
+ return nodeContent;
+}
+
// Return expanded file path
char* expand_path(const char* str) {
char* res = "";
diff --git a/src/utils.h b/src/utils.h
@@ -11,6 +11,9 @@
#include <wordexp.h>
#include <stdbool.h>
#include <ncurses.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/tree.h>
#include "diary-tp.h"
@@ -24,6 +27,7 @@ char* extract_json_value(const char* json, char* key, bool quoted);
char* fold(const char* str);
char* unfold(const char* str);
char* extract_ical_field(const char* ical, char* key, long* start_pos, bool multline);
+char* extract_xml_content(const char* xml, const char* xpathExpression, WINDOW* w, pthread_t* p);
char* expand_path(const char* str);
char* strrstr(char *haystack, char *needle);
void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath, size_t rpath_size);