// 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; } }