// CalDAV.cpp - CalDAV Connection Object. // // (c) 2016 Xestia Software Development. // // This file is part of Xestia Calendar. // // Xestia Address Book is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by the // Free Software Foundation, version 3 of the license. // // Xestia Address Book is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with Xestia Calendar. If not, see #include "CalDAV.h" using namespace std; size_t CalDAVReceive(char *ReceivedBuffer, size_t Size, size_t NewMemoryBytes, string *StringPointer) { string ReceivedBufferString = ""; ReceivedBufferString.append(ReceivedBuffer, NewMemoryBytes); StringPointer->append(ReceivedBufferString); return Size * NewMemoryBytes; } size_t CalDAVSend(char *SendBuffer, size_t Size, size_t NewMemoryBytes, void *DataStruct){ struct CalDAVSendData *UploadPtr = (struct CalDAVSendData *)DataStruct; if (UploadPtr->sizeleft){ UploadPtr->sizeleft--; char CharSend; CharSend = (*UploadPtr->readptr)[UploadPtr->seek]; *SendBuffer = CharSend; UploadPtr->seek++; return 1; } return 0; } CalDAV::CalDAV(){ // Setup the objects within the CalDAV connection // object. ConnectionHandle = curl_easy_init(); } CalDAV::~CalDAV(){ // Destory the objects within the CalDAV connection // object. curl_easy_cleanup(ConnectionHandle); ConnectionHandle = nullptr; } void CalDAV::SetupConnectionData(CalDAVConnectionData *ConnData){ // Check if ConnData is a nullptr, return if it is. if (ConnData == nullptr){ return; } // Set the connection settings to the values from ConnData. ConnectionData = (*ConnData); } CalDAVStatus CalDAV::GetConnectionData(){ // Get the current connection settings for the CalDAV // connection object and return a CalDAVStatus object. CalDAVStatus ConnectionStatus; ConnectionStatus.Hostname = ConnectionData.Hostname; ConnectionStatus.Port = ConnectionData.Port; ConnectionStatus.Username = ConnectionData.Username; ConnectionStatus.Prefix = ConnectionData.Prefix; ConnectionStatus.UseSSL = ConnectionData.UseSSL; ConnectionStatus.Timeout = ConnectionData.Timeout; return ConnectionStatus; } CalDAVServerResult CalDAV::Connect(){ CalDAVServerResult ServerResult; string ServerAddress = ""; string ServerUserPass = ""; // Setup the server address. ServerAddress = BuildServerAddress(&ConnectionData, "/principals/"); // Setup the server password. ServerUserPass += ConnectionData.Username; ServerUserPass += ":"; ServerUserPass += ConnectionData.Password; curl_easy_setopt(ConnectionHandle, CURLOPT_URL, ServerAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_USERPWD, ServerUserPass.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); curl_easy_setopt(ConnectionHandle, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(ConnectionHandle, CURLOPT_TIMEOUT, ConnectionData.Timeout); curl_easy_setopt(ConnectionHandle, CURLOPT_WRITEFUNCTION, CalDAVReceive); curl_easy_setopt(ConnectionHandle, CURLOPT_WRITEDATA, &ServerData); curl_easy_setopt(ConnectionHandle, CURLOPT_WRITEHEADER, &ServerHeader); // Connect to the CalDAV server. ServerResult.Code = curl_easy_perform(ConnectionHandle); // Process the result received from the server. if (ServerResult.Code != CURLE_OK){ ServerResult.Result = CALDAVQUERYRESULT_SERVERERROR; } else { ServerResult.Result = CALDAVQUERYRESULT_OK; } // Get the HTTP code. curl_easy_getinfo(ConnectionHandle, CURLINFO_RESPONSE_CODE, &ServerResult.HTTPCode); return ServerResult; } CalDAVServerResult CalDAV::GetServerResult(){ return ConnectionServerResult; } CalDAVServerSupport CalDAV::GetServerSupport(){ CalDAVServerSupport ServerStatus; // Setup the server connection. curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, "OPTIONS"); CURLcode ServerResult = curl_easy_perform(ConnectionHandle); // Set the results. if (ServerResult == CURLE_OK){ ConnectionServerResult.Result = CALDAVQUERYRESULT_OK; } else { ConnectionServerResult.Result = CALDAVQUERYRESULT_SERVERERROR; } ConnectionServerResult.Code = ServerResult; curl_easy_getinfo(ConnectionHandle, CURLINFO_RESPONSE_CODE, &ConnectionServerResult.HTTPCode); if (ServerResult != CURLE_OK){ return ServerStatus; } // Check that the server header has data in, // otherwise return an "empty" CalDAVServerSupport. if (ServerHeader.size() == 0){ return ServerStatus; } // Process each line looking for the first DAV header // line. bool NewlineMode = true; string DAVLine; for (int CharSeek = 0; CharSeek < ServerHeader.size(); CharSeek++){ if (NewlineMode == true){ // Check if we have reached the end of the string. if (CharSeek >= ServerHeader.size()){ break; } // Check the first four letters to make sure // they are 'DAV:'. string DAVHeaderCheck = ""; try { DAVHeaderCheck = ServerHeader.substr(CharSeek, 4); } catch (out_of_range &oor){ break; } if (DAVHeaderCheck == "DAV:"){ CharSeek += 5; for (; CharSeek < ServerHeader.size(); CharSeek++){ if (ServerHeader[CharSeek] == '\n'){ break; } DAVLine.push_back(ServerHeader[CharSeek]); } break; } NewlineMode = false; } if (ServerHeader[CharSeek] == '\n'){ NewlineMode = true; } } // Process the DAV line. vector DAVLineData; string DAVSegmentString; for (int CharSeek = 0; CharSeek < DAVLine.size(); CharSeek++){ if (DAVLine[CharSeek] == ' '){ continue; } if (DAVLine[CharSeek] == ','){ DAVLineData.push_back(DAVSegmentString); DAVSegmentString.clear(); continue; } DAVSegmentString += DAVLine[CharSeek]; } // Process the DAV values and set each value // to true as required. for (int DAVItemSeek = 0; DAVItemSeek < DAVLineData.size(); DAVItemSeek++){ if (DAVLineData.at(DAVItemSeek) == "calendar-access"){ ServerStatus.BasicSupport = true; } } // Reset the connection status. curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, NULL); return ServerStatus; } string CalDAV::GetUserPrincipal(){ string CurrentUserPrincipal = ""; string UserPrincipalRequest = ""; CalDAVSendData UserPrincipalSendData; UserPrincipalRequest = "\n" "\n" " \n" " \n" " \n" ""; UserPrincipalSendData.readptr = &UserPrincipalRequest; UserPrincipalSendData.sizeleft = UserPrincipalRequest.size(); // Setup the header. struct curl_slist *UserPrincipalRequestHeader = NULL; UserPrincipalRequestHeader = curl_slist_append(UserPrincipalRequestHeader, "Depth: 0"); UserPrincipalRequestHeader = curl_slist_append(UserPrincipalRequestHeader, "Prefer: return-minimal"); UserPrincipalRequestHeader = curl_slist_append(UserPrincipalRequestHeader, "Content-Type: application/xml; charset=utf-8"); curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPHEADER, UserPrincipalRequestHeader); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, "PROPFIND"); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, &UserPrincipalSendData); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, CalDAVSend); // Process the data. ServerData.clear(); ServerHeader.clear(); CURLcode ServerResult = curl_easy_perform(ConnectionHandle); // Set the results. if (ServerResult == CURLE_OK){ ConnectionServerResult.Result = CALDAVQUERYRESULT_OK; } else { ConnectionServerResult.Result = CALDAVQUERYRESULT_SERVERERROR; } ConnectionServerResult.Code = ServerResult; curl_easy_getinfo(ConnectionHandle, CURLINFO_RESPONSE_CODE, &ConnectionServerResult.HTTPCode); if (ServerResult != CURLE_OK){ return CurrentUserPrincipal; } // Process the User Principal from the ServerData. CurrentUserPrincipal = ProcessXMLUserPrincipal(); // Reset the changed settings. curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, NULL); return CurrentUserPrincipal; } string CalDAV::GetCalendarHome(string UserPrincipalURI){ string CalendarHomeURI = ""; // Build the Calendar Home URL address. string CalendarHomeURL = BuildServerAddress(&ConnectionData, UserPrincipalURI); // Setup the header request. CalDAVSendData CalendarHomeSendData; string CalendarHomeRequest = "\n" "\n" " \n" " \n" " \n" ""; CalendarHomeSendData.readptr = &CalendarHomeRequest; CalendarHomeSendData.sizeleft = CalendarHomeRequest.size(); // Setup the header. struct curl_slist *CalendarRequestHeader = NULL; CalendarRequestHeader = curl_slist_append(CalendarRequestHeader, "Depth: 0"); CalendarRequestHeader = curl_slist_append(CalendarRequestHeader, "Prefer: return-minimal"); CalendarRequestHeader = curl_slist_append(CalendarRequestHeader, "Content-Type: application/xml; charset=utf-8"); curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPHEADER, CalendarRequestHeader); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, CalendarHomeURL.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, "PROPFIND"); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, &CalendarHomeSendData); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, CalDAVSend); // Process the data. ServerData.clear(); ServerHeader.clear(); CURLcode ServerResult = curl_easy_perform(ConnectionHandle); // Set the results. if (ServerResult == CURLE_OK){ ConnectionServerResult.Result = CALDAVQUERYRESULT_OK; } else { ConnectionServerResult.Result = CALDAVQUERYRESULT_SERVERERROR; } ConnectionServerResult.Code = ServerResult; curl_easy_getinfo(ConnectionHandle, CURLINFO_RESPONSE_CODE, &ConnectionServerResult.HTTPCode); if (ServerResult != CURLE_OK){ return CalendarHomeURI; } // Process the User Principal from the ServerData. CalendarHomeURI = ProcessXMLCalendarHome(); // Reset the changed settings. string OriginalServerAddress = BuildServerAddress(&ConnectionData, "/principals/"); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, OriginalServerAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPHEADER, NULL); return CalendarHomeURI; } CalDAVCalendarList CalDAV::GetCalendars(){ CalDAVCalendarList ServerList; CalDAVSendData CalendarListSendData; // Build the server address. string UserPrincipalURI = ""; UserPrincipalURI = GetUserPrincipal(); if (UserPrincipalURI.size() == 0){ return ServerList; } string CalendarHomeURI = ""; CalendarHomeURI = GetCalendarHome(UserPrincipalURI); string CalendarListURLAddress = BuildServerAddress(&ConnectionData, CalendarHomeURI); string CalendarListRequest = "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" ""; CalendarListSendData.readptr = &CalendarListRequest; CalendarListSendData.sizeleft = CalendarListRequest.size(); // Setup the header. struct curl_slist *CalendarListRequestHeader = NULL; CalendarListRequestHeader = curl_slist_append(CalendarListRequestHeader, "Depth: 1"); CalendarListRequestHeader = curl_slist_append(CalendarListRequestHeader, "Prefer: return-minimal"); CalendarListRequestHeader = curl_slist_append(CalendarListRequestHeader, "Content-Type: application/xml; charset=utf-8"); curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPHEADER, CalendarListRequestHeader); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, CalendarListURLAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, "PROPFIND"); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, &CalendarListSendData); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, CalDAVSend); // Process the data. ServerData.clear(); ServerHeader.clear(); CURLcode ServerResult = curl_easy_perform(ConnectionHandle); //ServerList = ProcessXMLCalendarList(); // Restore the original settings. string OriginalServerAddress = BuildServerAddress(&ConnectionData, "/principals"); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, OriginalServerAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, NULL); // Process the received XML data into a list of calendars // and locations. if (ServerResult != CURLE_OK){ return ServerList; } ServerList = ProcessXMLCalendarList(); return ServerList; } CalDAVServerResult CalDAV::AddCalendar(string CalendarName){ CalDAVServerResult ServerResult; CalDAVSendData CalendarAddSendData; // Build the server address. string UserPrincipalURI = ""; UserPrincipalURI = GetUserPrincipal(); if (UserPrincipalURI.size() == 0){ return ServerResult; } string CalendarHomeURI = ""; CalendarHomeURI = GetCalendarHome(UserPrincipalURI); // Generate the UUID. string UUIDValue = GenerateUUID(); UUIDValue.erase(UUIDValue.end()-1); string CalendarHomeURL = CalendarHomeURI; CalendarHomeURL.append(UUIDValue); CalendarHomeURL.append("/"); // Build the calendar list address. string CalendarListURLAddress = BuildServerAddress(&ConnectionData, CalendarHomeURL); string CalendarAddRequest = "\n" "\n" " \n" " \n" " "; CalendarAddRequest += CalendarName; CalendarAddRequest += "\n" " \n" " \n" " \n" " \n" " \n" " \n" ""; CalendarAddSendData.readptr = &CalendarAddRequest; CalendarAddSendData.sizeleft = CalendarAddRequest.size(); // Setup the header. struct curl_slist *CalendarRequestHeader = NULL; cout << CalendarListURLAddress << endl; //curl_easy_setopt(ConnectionHandle, CURLOPT_HTTPHEADER, CalendarRequestHeader); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, CalendarListURLAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, "MKCALENDAR"); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, &CalendarAddSendData); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, CalDAVSend); // Process the data. ServerData.clear(); ServerHeader.clear(); CURLcode ServerConnectionResult = curl_easy_perform(ConnectionHandle); if (ServerConnectionResult == CURLE_OK){ ServerResult.Result = CALDAVQUERYRESULT_OK; } else { ServerResult.Result = CALDAVQUERYRESULT_SERVERERROR; } ServerResult.Code = ServerConnectionResult; curl_easy_getinfo(ConnectionHandle, CURLINFO_RESPONSE_CODE, &ServerResult.HTTPCode); // Restore the original settings. string OriginalServerAddress = BuildServerAddress(&ConnectionData, "/principals/"); curl_easy_setopt(ConnectionHandle, CURLOPT_URL, OriginalServerAddress.c_str()); curl_easy_setopt(ConnectionHandle, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(ConnectionHandle, CURLOPT_READDATA, NULL); curl_easy_setopt(ConnectionHandle, CURLOPT_READFUNCTION, NULL); return ServerResult; } bool CalDAVObjectValidSettings(CalDAVConnectionData *ConnData){ // Check if the passed CalDAV Connection Data is has // an address set. Return false if nullptr is used. if (ConnData == nullptr){ return false; } // Check the server hostname. Return false // if no value has been set. if (ConnData->Hostname.size() == 0){ return false; } // Check the server port. Return false if // no value has been set or the port number // is less than 1 or higher than 65535. if (ConnData->Port < 1 || ConnData->Port > 65535){ return false; } // Check the server username. Return false // if no value has been set. if (ConnData->Username.size() == 0){ return false; } // Check the server password. Return false // if no value has been set. if (ConnData->Password.size() == 0){ return false; } // Cannot check UseSSL: It is either true // or false. // Cannot check Prefix: The prefix may need // to be worked out first. // No errors were found whilst checking so // return true. return true; } string BuildServerAddress(CalDAVConnectionData *ConnData, string URIAddress){ string ServerAddress; // Setup the server address. if (ConnData->UseSSL == true){ ServerAddress += "https://"; } else { ServerAddress += "http://"; } ServerAddress += ConnData->Hostname; // Check if server port is 80, otherwise // specifiy the port number in the address. if (ConnData->Port != 80){ ServerAddress += ":"; ServerAddress += to_string(ConnData->Port); } ServerAddress += URIAddress; return ServerAddress; }