// 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);
return CalendarHomeURI;
}
CalDAVCalendarList CalDAV::GetCalendars(){
CalDAVCalendarList ServerList;
return ServerList;
}
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;
}