// CalDAV-XMLProcessing.cpp - CalDAV Connection Object - XML Processing. // // (c) 2016-2017 Xestia Software Development. // // This file is part of Xestia Calendar. // // Xestia Calendar 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 Calendar 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; string CalDAV::ProcessXMLUserPrincipal(){ string userPrincipalURI; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek; bool nodeFound = false; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = nodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeFound = true; break; } } if (nodeFound == false){ return userPrincipalURI; } // Look for response. if (nodeFound == false){ return userPrincipalURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "response"); // Look for propstat. if (nodeFound == false){ return userPrincipalURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "propstat"); // Look for prop. if (nodeFound == false){ return userPrincipalURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "prop"); // Look for current-user-principal. if (nodeFound == false){ return userPrincipalURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "current-user-principal"); // Look for href. if (nodeFound == false){ return userPrincipalURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "href"); // Get the data from href. userPrincipalURI = FetchXMLData(&nodeSeek); xmlFreeDoc(xmlCalDAVDoc); return userPrincipalURI; } string CalDAV::ProcessXMLCalendarHome(){ string calendarHomeURI; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek; bool nodeFound = false; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = nodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeFound = true; break; } } if (nodeFound == false){ return calendarHomeURI; } // Look for response. if (nodeFound == false){ return calendarHomeURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "response"); // Look for propstat. if (nodeFound == false){ return calendarHomeURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "propstat"); // Look for prop. if (nodeFound == false){ return calendarHomeURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "prop"); // Look for calendar-home-set. if (nodeFound == false){ return calendarHomeURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "calendar-home-set"); // Look for href. if (nodeFound == false){ return calendarHomeURI; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "href"); // Get the data from href. CalendarHomeURI = FetchXMLData(&nodeSeek); xmlFreeDoc(xmlCalDAVDoc); return calendarHomeURI; } CalDAVCalendarList CalDAV::ProcessXMLCalendarList(){ CalDAVCalendarList calendarList; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek = NULL; xmlNodePtr nodeResponse = NULL; xmlNodePtr nodeMatch = NULL; xmlNodePtr nodeData = NULL; bool nodeFound = false; int responseCount = 0; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = NodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeResponse = nodeSeek->children; nodeFound = true; break; } } if (nodeFound == false){ return calendarList; } for (nodeResponse = nodeSeek->children; nodeResponse != nullptr; nodeResponse = nodeResponse->next) { // Go through each of the responses and find the calendars. nodeMatch = xmlCopyNode(nodeResponse, 1); if (MatchXMLName(&nodeMatch, "response")){ nodeData = xmlCopyNode(nodeMatch, 1); // Check the resource type is a calendar. if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "resourcetype")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "calendar")){ continue; } // Get the HREF. nodeData = xmlCopyNode(nodeMatch, 1); if (!MatchXMLNameTransverse(&nodeData, "href")){ continue; } string hrefAddress = FetchXMLData(&nodeData); // Get the calendar name. nodeData = xmlCopyNode(nodeMatch, 1); if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "displayname")){ continue; } string calendarName = FetchXMLData(&nodeData); // Get the calendar description. nodeData = xmlCopyNode(nodeMatch, 1); string calendarDescription = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "calendar-description")){ calendarDescription = FetchXMLData(&nodeData); } // Get the calendar colour. nodeData = xmlCopyNode(nodeMatch, 1); Colour calendarColour; bool colourResult = false; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "calendar-color")){ string calendarColourString = ""; string calendarColourHexValue = ""; int colourNumber; bool keepRunning = true; calendarColourString = FetchXMLData(&nodeData); while(keepRunning == true){ if (calendarColourString.substr(0,1) == "#" && calendarColourString.length() == 9){ // Get the red colour. calendarColourHexValue = calendarColourString.substr(1,2); if (!HexToInt(&calendarColourHexValue, &colourNumber)){ break; } calendarColour.red = colourNumber; // Get the green colour. calendarColourHexValue = calendarColourString.substr(3,2); if (!HexToInt(&calendarColourHexValue, &colourNumber)){ break; } calendarColour.green = colourNumber; // Get the blue colour. calendarColourHexValue = calendarColourString.substr(5,2); if (!HexToInt(&calendarColourHexValue, &colourNumber)){ break; }; calendarColour.blue = colourNumber; // Get the alpha. calendarColourHexValue = calendarColourString.substr(7,2); if (!HexToInt(&calendarColourHexValue, &colourNumber)){ break; }; calendarColour.alpha = colourNumber; colourResult = true; } else { colourResult = false; } break; } } if (colourResult == false){ calendarColour.red = 0; calendarColour.blue = 0; calendarColour.green = 0; calendarColour.alpha = 0; } // Get the calendar order. nodeData = xmlCopyNode(nodeMatch, 1); int calendarOrder = 0; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "calendar-order")){ string calendarOrderString = FetchXMLData(&nodeData); if (!HexToInt(&calendarOrderString, &calendarOrder)){ calendarOrder = 0; } } // Get the calendar tag. nodeData = xmlCopyNode(nodeMatch, 1); string calendarTag = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "getctag")){ calendarTag = FetchXMLData(&nodeData); } // Get the calendar tag URL. nodeData = xmlCopyNode(nodeMatch, 1); string calendarTagURL = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "sync-token")){ calendarTagURL = FetchXMLData(&nodeData); } // Insert the calendar information into the // list if all the information is there. calendarList.name.insert(make_pair(responseCount, calendarName)); calendarList.description.insert(make_pair(responseCount, calendarDescription)); calendarList.href.insert(make_pair(responseCount, hrefAddress)); calendarList.calColour.insert(make_pair(responseCount, calendarColour)); calendarList.order.insert(make_pair(responseCount, calendarOrder)); calendarList.tag.insert(make_pair(responseCount, calendarTag)); calendarList.tagURL.insert(make_pair(responseCount, calendarTagURL)); responseCount++; } } xmlFreeDoc(xmlCalDAVDoc); return calendarList; } string CalDAV::ProcessXMLEntryETag(){ string entryETag; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek; bool nodeFound = false; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = nodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeFound = true; break; } } if (nodeFound == false){ return entryETag; } // Look for response. if (nodeFound == false){ return entryETag; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "response"); // Look for propstat. if (nodeFound == false){ return entryETag; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "propstat"); // Look for prop. if (nodeFound == false){ return entryETag; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "prop"); // Look for calendar-home-set. if (nodeFound == false){ return entryETag; } else { nodeFound = false; } nodeFound = MatchXMLNameTransverse(&nodeSeek, "getetag"); // Get the data from href. entryETag = FetchXMLData(&nodeSeek); xmlFreeDoc(xmlCalDAVDoc); // Check if the entity tag contains quote marks // at the start and end and remove them (if needed). if (entryETag.substr(0,1) == "\"" && entryETag.substr(entryETag.size()-1, 1) == "\"" && entryETag.size() > 2){ entryETag.erase(entryETag.begin()); entryETag.erase(entryETag.end()-1); } return entryETag; } CalDAVEntryList CalDAV::ProcessXMLEntryList(){ CalDAVEntryList entryList; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek = NULL; xmlNodePtr nodeResponse = NULL; xmlNodePtr nodeMatch = NULL; xmlNodePtr nodeData = NULL; bool nodeFound = false; int responseCount = 0; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = NodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeResponse = nodeSeek->children; nodeFound = true; break; } } if (nodeFound == false){ return entryList; } for (nodeResponse = nodeSeek->children; nodeResponse != nullptr; nodeResponse = nodeResponse->next) { // Go through each of the responses and find the calendars. nodeMatch = xmlCopyNode(nodeResponse, 1); if (MatchXMLName(&nodeMatch, "response")){ nodeData = xmlCopyNode(nodeMatch, 1); // Get the HREF. nodeData = xmlCopyNode(nodeMatch, 1); if (!MatchXMLNameTransverse(&nodeData, "href")){ continue; } string hrefAddress = FetchXMLData(&nodeData); // Get the calendar data. nodeData = xmlCopyNode(nodeMatch, 1); string entryDescription = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "calendar-data")){ // Note: libxml2 will strip the CDATA part at the start and // end of each calendar-data section. entryDescription = FetchXMLData(&nodeData); } // Get the entry entity tag. nodeData = xmlCopyNode(nodeMatch, 1); string entryEntityTag = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "getetag")){ entryEntityTag = FetchXMLData(&nodeData); } // Insert the calendar information into the // list if all the information is there. entryList.href.insert(make_pair(responseCount, hrefAddress)); entryList.data.insert(make_pair(responseCount, entryDescription)); entryList.tag.insert(make_pair(responseCount, entryEntityTag)); responseCount++; } } xmlFreeDoc(xmlCalDAVDoc); return entryList; } CalDAVEntryList CalDAV::ProcessXMLSyncTokenList(){ CalDAVEntryList entryList; xmlDocPtr xmlCalDAVDoc; xmlCalDAVDoc = xmlReadMemory(serverData.c_str(), (int)serverData.size(), "noname.xml", NULL, 0); xmlNodePtr nodeSeek = NULL; xmlNodePtr nodeResponse = NULL; xmlNodePtr nodeMatch = NULL; xmlNodePtr nodeData = NULL; bool nodeFound = false; int responseCount = 0; // Start with the first node, look for multistatus. for (nodeSeek = xmlCalDAVDoc->children; nodeSeek != NULL; nodeSeek = nodeSeek->next) { if (!xmlStrcmp(nodeSeek->name, (const xmlChar *)"multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"d:multistatus") || !xmlStrcmp(nodeSeek->name, (const xmlChar *)"D:multistatus") ){ nodeResponse = nodeSeek->children; nodeFound = true; break; } } if (nodeFound == false){ return entryList; } for (nodeResponse = nodeSeek->children; nodeResponse != nullptr; nodeResponse = nodeResponse->next) { // Go through each of the responses and find the calendars. nodeMatch = xmlCopyNode(nodeResponse, 1); if (MatchXMLName(&nodeMatch, "response")){ nodeData = xmlCopyNode(nodeMatch, 1); // Get the HREF. nodeData = xmlCopyNode(nodeMatch, 1); if (!MatchXMLNameTransverse(&nodeData, "href")){ continue; } string hrefAddress = FetchXMLData(&nodeData); // Get the entry entity tag. nodeData = xmlCopyNode(nodeMatch, 1); string entryEntityTag = ""; if (!MatchXMLNameTransverse(&nodeData, "propstat")){ continue; } if (!MatchXMLNameTransverse(&nodeData, "prop")){ continue; } if (MatchXMLNameTransverse(&nodeData, "getetag")){ entryEntityTag = FetchXMLData(&nodeData); } // Insert the calendar information into the // list if all the information is there. entryList.href.insert(make_pair(responseCount, hrefAddress)); entryList.data.insert(make_pair(responseCount, "")); entryList.tag.insert(make_pair(responseCount, entryEntityTag)); responseCount++; } } xmlFreeDoc(xmlCalDAVDoc); return entryList; } bool CalDAV::MatchXMLNameTransverse(xmlNodePtr *nodePtr, string nodeName){ string nodeNameSmallD = "d:" + nodeName; string nodeNameLargeD = "D:" + nodeName; for ((*nodePtr) = (*nodePtr)->children; (*nodePtr) != NULL; (*nodePtr) = (*nodePtr)->next) { if (!xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeName.c_str()) || !xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeNameSmallD.c_str()) || !xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeNameLargeD.c_str()) ){ return true; } } return false; } bool CalDAV::MatchXMLName(xmlNodePtr *nodePtrOriginal, string nodeName){ if (nodePtrOriginal == nullptr){ return false; } string nodeNameSmallD = "d:" + NodeName; string nodeNameLargeD = "D:" + NodeName; xmlNodePtr *nodePtr = nodePtrOriginal; if (!xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeName.c_str()) || !xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeNameSmallD.c_str()) || !xmlStrcmp((*nodePtr)->name, (const xmlChar *)nodeNameLargeD.c_str()) ){ return true; } else { return false; } return false; } string CalDAV::FetchXMLData(xmlNodePtr *nodePtr){ for ((*nodePtr) = (*nodePtr)->children; (*nodePtr) != NULL; (*nodePtr) = (*nodePtr)->next) { return (const char*)(*nodePtr)->content; } }