diary

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

caldav.c (21520B)


      1 #include "caldav.h"
      2 
      3 CURL* curl;
      4 char access_token[sizeof(char)*2048] = "";
      5 
      6 // Local bind address for receiving OAuth callbacks.
      7 // Reserve 2 chars for the ipv6 square brackets.
      8 char ip[INET6_ADDRSTRLEN], ipstr[INET6_ADDRSTRLEN + 2];
      9 
     10 // Static XML data for CalDav post requests:
     11 // https://write.in0rdr.ch/caldav-calendar-discovery
     12 const char* principal_request = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/'>"
     13                                 "<d:prop><d:current-user-principal/></d:prop>"
     14                                 "</d:propfind>";
     15 const char* homeset_request = "<d:propfind xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>"
     16                               " <d:prop>"
     17                               "  <c:calendar-home-set />"
     18                               " </d:prop>"
     19                               "</d:propfind>";
     20 const char* calendar_request = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/' xmlns:c='urn:ietf:params:xml:ns:caldav'>"
     21                                " <d:prop>"
     22                                "  <d:resourcetype />"
     23                                "  <d:displayname />"
     24                                "  <cs:getctag />"
     25                                "  <c:supported-calendar-component-set />"
     26                                " </d:prop>"
     27                                "</d:propfind>";
     28 char* events_request = "<c:calendar-query xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>"
     29                        "<d:prop><d:getetag/><c:calendar-data/></d:prop>"
     30                        "<c:filter><c:comp-filter name='VCALENDAR'>"
     31                        "<c:comp-filter name='VEVENT'>"
     32                        "<c:time-range start='%s' end='%s'/></c:comp-filter>"
     33                        "</c:comp-filter></c:filter></c:calendar-query>";
     34 
     35 static size_t curl_write_mem_callback(void* contents, size_t size, size_t nmemb, void* userp) {
     36     size_t realsize = size * nmemb;
     37     struct curl_mem_chunk* mem = (struct curl_mem_chunk*)userp;
     38 
     39     char* ptr = realloc(mem->memory, mem->size + realsize + 1);
     40     if (!ptr) {
     41         tracepoint(diary, error, "Not enough memory (realloc in CURLOPT_WRITEFUNCTION returned NULL)");
     42         return 0;
     43     }
     44 
     45     mem->memory = ptr;
     46     memcpy(&(mem->memory[mem->size]), contents, realsize);
     47     mem->size += realsize;
     48     mem->memory[mem->size] = 0;
     49 
     50     return realsize;
     51 }
     52 
     53 // todo
     54 // https://beej.us/guide/bgnet/html
     55 void* get_in_addr(struct sockaddr* sa) {
     56     if (sa->sa_family == AF_INET) {
     57         return &(((struct sockaddr_in*)sa)->sin_addr);
     58     }
     59 
     60     return &(((struct sockaddr_in6*)sa)->sin6_addr);
     61 }
     62 
     63 /* Get access token from "oauth_eval_cmd" and refresh global var */
     64 void get_access_token() {
     65     FILE *fp;
     66     int status;
     67 
     68     fp = popen(CONFIG.oauth_eval_cmd, "r");
     69     if (fp == NULL)
     70         perror("Failure running oauth_eval_cmd in get_access_token()");
     71 
     72     while (fgets(access_token, sizeof(char)*2048, fp) != NULL)
     73         tracepoint(diary, debug_string, "New access token in get_access_token()", access_token);
     74 
     75     // strip any newline characters
     76     access_token[strcspn(access_token, "\n")] = '\0';
     77 
     78     status = pclose(fp);
     79     if (status == -1) {
     80         perror("Failure during pclose in get_access_token()");
     81     }
     82 }
     83 
     84 /* Make a CalDAV request */
     85 char* caldav_req(struct tm* date, char* url, char* http_method, const char* postfields, int depth, bool basicauth, char* content_type, long num_redirects) {
     86     // only support depths 0 or 1
     87     if (depth < 0 || depth > 1) {
     88         return NULL;
     89     }
     90 
     91     CURLcode res;
     92 
     93     curl = curl_easy_init();
     94 
     95     // https://curl.se/libcurl/c/getinmemory.html
     96     struct curl_mem_chunk caldav_resp;
     97     caldav_resp.memory = malloc(1);
     98     if (caldav_resp.memory == NULL) {
     99         perror("malloc failed");
    100         return NULL;
    101     }
    102     caldav_resp.size = 0;
    103 
    104     if (curl) {
    105         // fail if not authenticated, !CURLE_OK
    106         curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
    107         //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    108         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method);
    109         tracepoint(diary, debug_string, "curl_easy_perform() request url", url);
    110         curl_easy_setopt(curl, CURLOPT_URL, url);
    111         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_mem_callback);
    112         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&caldav_resp);
    113 
    114         // construct header fields
    115         struct curl_slist *header = NULL;
    116 
    117         // prefer basicauth credentials when set
    118         if (basicauth) {
    119                 char basicauth [strlen(CONFIG.caldav_username) + strlen(CONFIG.caldav_password) + 2];
    120                 sprintf(basicauth, "%s:%s", CONFIG.caldav_username, CONFIG.caldav_password);
    121                 curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth);
    122         } else {
    123                 // use OAuth credentials
    124                 char bearer_token[strlen("Authorization: Bearer ") + strlen(access_token) + 1];
    125                 sprintf(bearer_token, "Authorization: Bearer %s", access_token);
    126                 header = curl_slist_append(header, bearer_token);
    127         }
    128 
    129         char depth_header[strlen("Depth: 0") + 1];
    130         sprintf(depth_header, "Depth: %i", depth);
    131         header = curl_slist_append(header, depth_header);
    132         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
    133 
    134         if (strcmp(content_type, "") != 0 && content_type != NULL) {
    135                 char contentType[strlen("Content-Type: ") + strlen(content_type) + 1];
    136                 sprintf(contentType, "Content-Type: %s", content_type);
    137                 header = curl_slist_append(header, contentType);
    138         }
    139 
    140         // set postfields, if any
    141         if (postfields != NULL) {
    142             curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
    143         }
    144 
    145         // enable redirect following, some providers have certain caldav
    146         // endpoinds on sub-paths
    147         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    148         curl_easy_setopt(curl, CURLOPT_MAXREDIRS, num_redirects);
    149 
    150         res = curl_easy_perform(curl);
    151 
    152         long response_code = 0;
    153         curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    154 
    155         // free the memory used for the curl slist
    156         curl_slist_free_all(header);
    157 
    158         curl_easy_cleanup(curl);
    159 
    160         if (res != CURLE_OK) {
    161             tracepoint(diary, error_string, "curl_easy_perform() failed in caldav_req()", curl_easy_strerror(res));
    162             tracepoint(diary, debug_long, "curl_easy_perform() response code", response_code);
    163             free(caldav_resp.memory);
    164             return NULL;
    165         }
    166     }
    167 
    168     return caldav_resp.memory;
    169 }
    170 
    171 /* Upload event to CalDAV server */
    172 void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar_uri, bool basicauth) {
    173     // get entry path
    174     char path[100];
    175     char* ppath = path;
    176     char* descr;
    177     long descr_bytes;
    178 
    179     fpath(dir, dir_size, date, &ppath, sizeof path);
    180     if (ppath == NULL) {
    181         tracepoint(diary, error_date, "Cannot get file path for entry", date);
    182         return;
    183     }
    184 
    185     FILE* fp = fopen(path, "r");
    186     if (fp == NULL) perror("Error opening file");
    187 
    188     fseek(fp, 0, SEEK_END);
    189     descr_bytes = ftell(fp);
    190     rewind(fp);
    191 
    192     size_t descr_label_size = strlen("DESCRIPTION:");
    193     size_t descr_size = descr_bytes + descr_label_size;
    194     descr = calloc(descr_size + 1, sizeof(char));
    195     if (descr == NULL) {
    196         perror("malloc failed");
    197         fclose(fp);
    198         return;
    199     }
    200 
    201     strcat(descr, "DESCRIPTION:");
    202 
    203     size_t items_read = fread(descr + descr_label_size, sizeof(char), descr_bytes, fp);
    204     if (items_read != descr_bytes) {
    205         perror("Error while reading in put_event()");
    206         fclose(fp);
    207         return;
    208     }
    209 
    210     descr[descr_size] = '\0';
    211     char* folded_descr = fold(descr);
    212 
    213     char uid[9];
    214     strftime(uid, sizeof uid, "%Y%m%d", date);
    215 
    216     char* ics = "BEGIN:VCALENDAR\n"
    217                 "PRODID:-//diary\n" // todo: add version
    218                 "VERSION:2.0\n"
    219                 "CALSCALE:GREGORIAN\n"
    220                 "BEGIN:VEVENT\n"
    221                 "UID:%s\n"
    222                 "DTSTART;VALUE=DATE:%s\n"
    223                 "SUMMARY:%s\n"
    224                 "%s\n"
    225                 "END:VEVENT\n"
    226                 "END:VCALENDAR";
    227     char postfields[strlen(ics) + strlen(folded_descr) + 100];
    228     sprintf(postfields, ics,
    229             uid,
    230             uid,
    231             uid, // todo: display first few chars of DESCRIPTION as SUMMARY
    232             folded_descr);
    233 
    234     tracepoint(diary, debug_string, "PUT text/calendar", postfields);
    235 
    236     strcat(calendar_uri, uid);
    237     strcat(calendar_uri, ".ics");
    238     char* response = caldav_req(date, calendar_uri, "PUT", postfields, 0, basicauth, "text/calendar", 0);
    239     fclose(fp);
    240     free(folded_descr);
    241     free(descr);
    242 
    243     if (response == NULL) {
    244         tracepoint(diary, error, "PUT request for event failed in put_event()");
    245     }
    246 
    247     // free memory allocated to store curl response
    248     free(response);
    249 }
    250 
    251 /*
    252 * Sync with CalDAV server.
    253 * Returns the answer char of the confirmation dialogue
    254 * Returns 0 if neither local nor remote file exists.
    255 * Otherwise, returns -1 on error.
    256 */
    257 int caldav_sync(struct tm* date,
    258                  WINDOW* header,
    259                  WINDOW* cal,
    260                  int pad_pos,
    261                  const char* dir,
    262                  size_t dir_size,
    263                  bool confirm) {
    264     pthread_t progress_tid;
    265 
    266     bool oauth_enabled = !(strcmp(CONFIG.oauth_eval_cmd, "") == 0);
    267     bool basicauth_enabled = !(strcmp(CONFIG.caldav_username, "") == 0 || strcmp(CONFIG.caldav_password, "") == 0);
    268     tracepoint(diary, debug_int, "oauth_enabled: ", oauth_enabled);
    269     tracepoint(diary, debug_int, "basicauth_enabled", basicauth_enabled);
    270 
    271     // check remote server and calendar are defined
    272     if (strcmp(CONFIG.caldav_server, "") == 0 || strcmp(CONFIG.caldav_calendar, "") == 0) {
    273         tracepoint(diary, debug_string, "CONFIG.caldav_server", CONFIG.caldav_server);
    274         tracepoint(diary, debug_string, "CONFIG.caldav_calendar", CONFIG.caldav_calendar);
    275         tracepoint(diary, debug_string, "CONFIG.caldav_username", CONFIG.caldav_username);
    276         tracepoint(diary, debug_string, "CONFIG.caldav_password", CONFIG.caldav_password);
    277         show_info(header, "CalDAV config incomplete, press any key to continue.", NULL);
    278         return -1;
    279     }
    280 
    281     if (oauth_enabled) {
    282         if (strcmp(access_token, "") == 0) {
    283             // get acess token with oauth_eval_cmd
    284             tracepoint(diary, debug_string, "Fetching access token using oauth_eval_cmd", CONFIG.oauth_eval_cmd);
    285             get_access_token();
    286             tracepoint(diary, debug_string, "OAuth access token", access_token);
    287         }
    288     }
    289 
    290     pthread_create(&progress_tid, NULL, show_progress, (void*)header);
    291     pthread_detach(progress_tid);
    292 
    293     char* user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_request, 0, basicauth_enabled, "application/xml", 1);
    294     tracepoint(diary, debug_string, "User principal return XML", user_principal);
    295 
    296     if (user_principal == NULL) {
    297         // The principal could not be fetched
    298         tracepoint(diary, debug, "Unable to fetch user principal");
    299         show_info(header, "Unable to fetch user principal, press any key to continue.", &progress_tid);
    300         return -1;
    301     }
    302 
    303     char* current_user_principal = extract_xml_content(
    304             user_principal,
    305             "//*[local-name()='current-user-principal']/*[local-name()='href']",
    306             header,
    307             &progress_tid);
    308 
    309     // free memory allocated by curl request
    310     free(user_principal);
    311 
    312     if (current_user_principal == NULL) {
    313         tracepoint(diary, debug, "Unable to parse current user principal");
    314         show_info(header, "Unable to parse current user principal, press any key to continue.", &progress_tid);
    315         return -1;
    316     }
    317 
    318     // extract host without path as basis for further api paths
    319     char *caldav_host; char* caldav_host_scheme;
    320     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);
    321     uc = curl_url_get(h, CURLUPART_SCHEME, &caldav_host_scheme, 0);
    322     tracepoint(diary, debug_string, "Caldav server host name", caldav_host);
    323     tracepoint(diary, debug_string, "Caldav server scheme/protocol", caldav_host_scheme);
    324     tracepoint(diary, debug_string, "Caldav server host name", caldav_host);
    325     if (!uc) {
    326         tracepoint(diary, error, "cur_url_get() failed in caldav_sync()");
    327     }
    328     curl_url_cleanup(h);
    329 
    330     // get the home-set of the user
    331     char uri[300];
    332     sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, current_user_principal);
    333 
    334     char* home_set = caldav_req(date, uri, "PROPFIND", homeset_request, 0, basicauth_enabled, "application/xml", 0);
    335     tracepoint(diary, debug_string, "Home set xml", home_set);
    336 
    337     // parse home set from xml response
    338     char* home_set_parsed = extract_xml_content(
    339             home_set,
    340             "//*[local-name()='calendar-home-set']/*[local-name()='href']",
    341             header,
    342             &progress_tid);
    343 
    344     if (home_set_parsed == NULL) {
    345         tracepoint(diary, debug, "Unable to parse home set");
    346         show_info(header, "Unable to parse home set, press any key to continue.", &progress_tid);
    347         return -1;
    348     }
    349 
    350     sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, home_set_parsed);
    351 
    352     free(home_set);
    353     free(home_set_parsed);
    354 
    355     // get calendar from home-set
    356     char* calendar_xml = caldav_req(date, uri, "PROPFIND", calendar_request, 1, basicauth_enabled, "application/xml", 0);
    357     tracepoint(diary, debug_string, "Calendar set xml", calendar_xml);
    358 
    359     // get calendar URI from the home-set
    360     char calendar_xpath_query[300];
    361     sprintf(calendar_xpath_query, "//*[local-name()='displayname'][text()='%s']/../../../*[local-name()='href']", CONFIG.caldav_calendar);
    362     tracepoint(diary, debug_string, "Calendar xpath query", calendar_xpath_query);
    363     char* calendar_href = extract_xml_content(
    364             calendar_xml,
    365             calendar_xpath_query,
    366             header,
    367             &progress_tid);
    368 
    369     if (calendar_href == NULL) {
    370         tracepoint(diary, debug_string, "Could not find CalDAV calendar", CONFIG.caldav_calendar);
    371         char* msg_fmtstr = "Could not find CalDAV calendar '%s', press any key to continue.";
    372         char msg[strlen(msg_fmtstr) + strlen(CONFIG.caldav_calendar)];
    373         sprintf(msg, msg_fmtstr, CONFIG.caldav_calendar);
    374         show_info(header, msg, &progress_tid);
    375         return -1;
    376     }
    377 
    378     free(calendar_xml);
    379 
    380     char dstr_start[30];
    381     char dstr_end[30];
    382     char* format_start = "%Y%m%dT000000Z"; // start of day
    383     char* format_end = "%Y%m%dT235900Z"; // end of day
    384     strftime(dstr_start, sizeof dstr_start, format_start, date);
    385     strftime(dstr_end, sizeof dstr_end, format_end, date);
    386 
    387     char caldata_postfields[strlen(events_request) + strlen(dstr_start) + strlen(dstr_end)];
    388     sprintf(caldata_postfields, events_request,
    389             dstr_start,
    390             dstr_end);
    391 
    392     // fetch event for the cursor date
    393     sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, calendar_href);
    394 
    395     char* events_xml = caldav_req(date, uri, "REPORT", caldata_postfields, 1, basicauth_enabled, "application/xml", 0);
    396 
    397     if (events_xml == NULL) {
    398         tracepoint(diary, debug, "Events could not be fetched");
    399         show_info(header, "Events could not be fetched", &progress_tid);
    400         return -1;
    401     }
    402 
    403     // only fetch PRODID diary events
    404     char* event = extract_xml_content(
    405             events_xml,
    406             "//*[local-name()='calendar-data' and contains(text(), 'PRODID:-//diary')]",
    407             header,
    408             &progress_tid);
    409 
    410     free(events_xml);
    411 
    412     // no remote event found
    413     if (event == NULL) {
    414         tracepoint(diary, debug, "No remote events found, continuing.");
    415         event = "";
    416     }
    417 
    418     // get path of entry
    419     char path[100];
    420     char* ppath = path;
    421     fpath(CONFIG.dir, strlen(CONFIG.dir), date, &ppath, sizeof path);
    422 
    423     time_t epoch_zero = 0;
    424 
    425     // assume local file does not exist
    426     struct tm* localfile_time = gmtime(&epoch_zero);
    427 
    428     // check last modification time of local time
    429     struct stat attr;
    430     if (stat(path, &attr) == 0) {
    431         // if local file does exists, remember utc time of file on disk
    432         localfile_time = gmtime(&attr.st_mtime);
    433     }
    434 
    435     time_t localfile_date = mktime(localfile_time);
    436 
    437     struct tm* remote_datetime;
    438     time_t remote_date = 0;
    439     long search_pos = 0;
    440 
    441     // check remote DTSTAMP of remote event
    442     char* remote_dtstamp = extract_ical_field(event, "DTSTAMP", &search_pos, false);
    443     char* remote_uid = extract_ical_field(event, "UID", &search_pos, false);
    444     tracepoint(diary, debug_string, "event", event);
    445     tracepoint(diary, debug_string, "DTSTAMP field", remote_dtstamp);
    446 
    447     // init remote date to 1970, assume remote file does not exist
    448     remote_datetime = gmtime(&epoch_zero);
    449 
    450     if (remote_dtstamp != NULL) {
    451         // if remote date does exist, set date to the correct timestamp
    452         strptime(remote_dtstamp, "%Y%m%dT%H%M%SZ", remote_datetime);
    453         free(remote_dtstamp);
    454     }
    455 
    456     remote_date = mktime(remote_datetime);
    457 
    458     tracepoint(diary, debug_string, "Remote last modified", ctime(&remote_date));
    459     tracepoint(diary, debug_string, "Local last modified", ctime(&localfile_date));
    460     double timediff = difftime(localfile_date, remote_date);
    461     tracepoint(diary, debug_double, "Time diff between local and remote mod time", timediff);
    462 
    463     char* rmt_desc;
    464     char dstr[16];
    465     int conf_ch = 0;
    466 
    467     if (timediff == 0) {
    468         tracepoint(diary, debug, "Local and remote files have equal timestamp or don't exist, giving up.");
    469         pthread_cancel(progress_tid);
    470         wclear(header);
    471         // free memory allocated to store curl response
    472         curl_free(caldav_host_scheme);
    473         curl_free(caldav_host);
    474         free(remote_uid);
    475         return 0;
    476     } else if (timediff > 0) {
    477         tracepoint(diary, debug, "Local file is newer. Uploading to remote.");
    478         if (remote_uid) {
    479             // purge any existing daily calendar entries on the remote side
    480             char event_uri[300];
    481             sprintf(event_uri, "%s://%s%s%s.ics", caldav_host_scheme, caldav_host, calendar_href, remote_uid);
    482             //sprintf(event_uri, "%s%s%s.ics", CONFIG.caldav_server, calendar_href, remote_uid);
    483             tracepoint(diary, debug_string, "Event URI for DELETE request", event_uri);
    484             char* response = caldav_req(date, event_uri, "DELETE", NULL, 0, basicauth_enabled, "", 0);
    485             free(response);
    486         }
    487 
    488         put_event(date, dir, dir_size, uri, basicauth_enabled);
    489 
    490         pthread_cancel(progress_tid);
    491         wclear(header);
    492 
    493     } else if (timediff < 0) {
    494         rmt_desc = extract_ical_field(event, "DESCRIPTION", &search_pos, true);
    495 
    496         if (rmt_desc == NULL) {
    497             tracepoint(diary, error, "Could not fetch description of remote event. Aborting sync.");
    498             pthread_cancel(progress_tid);
    499             wclear(header);
    500             curl_free(caldav_host_scheme);
    501             curl_free(caldav_host);
    502             free(remote_uid);
    503             return -1;
    504         }
    505 
    506         if (confirm) {
    507             // prepare header for confirmation dialogue
    508             curs_set(2);
    509             noecho();
    510             pthread_cancel(progress_tid);
    511             wclear(header);
    512 
    513             // ask for confirmation
    514             strftime(dstr, sizeof dstr, CONFIG.fmt, date);
    515             mvwprintw(header, 0, 0, "Remote event is more recent. Sync entry '%s' and overwrite local file? [(Y)es/(a)ll/(n)o/(c)ancel] ", dstr);
    516             conf_ch = wgetch(header);
    517         }
    518 
    519         if (conf_ch == 'y' || conf_ch == 'Y' || conf_ch == 'a' || conf_ch == '\n' || !confirm) {
    520             tracepoint(diary, debug, "Remote file is newer. Extracting description from remote.");
    521             char* i;
    522 
    523             // persist downloaded buffer to local file
    524             FILE* cursordate_file = fopen(path, "wb");
    525             if (cursordate_file == NULL) {
    526                 perror("Failed to open cursor date file");
    527             } else {
    528                 for (i = rmt_desc; *i != '\0'; i++) {
    529                     if (rmt_desc[i-rmt_desc] == 0x5C) { // backslash
    530                         switch (*(i+1)) {
    531                             case 'n':
    532                                 fputc('\n', cursordate_file);
    533                                 i++;
    534                                 break;
    535                             case 0x5c: // preserve real backslash
    536                                 fputc(0x5c, cursordate_file);
    537                                 i++;
    538                                 break;
    539                         }
    540                     } else {
    541                         fputc(*i, cursordate_file);
    542                     }
    543                 }
    544             }
    545             fclose(cursordate_file);
    546 
    547             // add new entry highlight
    548             chtype atrs = winch(cal) & A_ATTRIBUTES;
    549             wchgat(cal, 2, atrs | A_BOLD, 0, NULL);
    550             prefresh(cal, pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH);
    551         }
    552 
    553         echo();
    554         curs_set(0);
    555         free(rmt_desc);
    556     }
    557 
    558     // free memory allocated to store curl response
    559     curl_free(caldav_host_scheme);
    560     curl_free(caldav_host);
    561     free(remote_uid);
    562     free(calendar_href);
    563     pthread_join(progress_tid, NULL);
    564     wclear(header);
    565     return conf_ch;
    566 }