File indexing completed on 2024-05-05 03:54:25

0001 /*
0002     SPDX-FileCopyrightText: 2009 Grégory Oestreicher <greg@kamago.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "caldavprotocol_p.h"
0008 #include "common/utils_p.h"
0009 
0010 #include <QDomDocument>
0011 #include <QStringList>
0012 #include <QUrl>
0013 
0014 using namespace KDAV;
0015 
0016 class CaldavCollectionQueryBuilder : public XMLQueryBuilder
0017 {
0018 public:
0019     QDomDocument buildQuery() const override
0020     {
0021         QDomDocument document;
0022 
0023         QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
0024         document.appendChild(propfindElement);
0025 
0026         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0027         propfindElement.appendChild(propElement);
0028 
0029         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname")));
0030         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")));
0031         propElement.appendChild(document.createElementNS(QStringLiteral("http://apple.com/ns/ical/"), QStringLiteral("calendar-color")));
0032         propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set")));
0033         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-privilege-set")));
0034         propElement.appendChild(document.createElementNS(QStringLiteral("http://calendarserver.org/ns/"), QStringLiteral("getctag")));
0035 
0036         return document;
0037     }
0038 
0039     QString mimeType() const override
0040     {
0041         return QString();
0042     }
0043 };
0044 
0045 class CaldavListEventQueryBuilder : public XMLQueryBuilder
0046 {
0047 public:
0048     QDomDocument buildQuery() const override
0049     {
0050         QString startTime = parameter(QStringLiteral("start")).toString();
0051         QString endTime = parameter(QStringLiteral("end")).toString();
0052         QDomDocument document;
0053 
0054         QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
0055         document.appendChild(queryElement);
0056 
0057         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0058         queryElement.appendChild(propElement);
0059 
0060         QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
0061         propElement.appendChild(getetagElement);
0062 
0063         QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
0064         propElement.appendChild(getRTypeElement);
0065 
0066         QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
0067         queryElement.appendChild(filterElement);
0068 
0069         QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0070 
0071         QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
0072         nameAttribute.setValue(QStringLiteral("VCALENDAR"));
0073         compfilterElement.setAttributeNode(nameAttribute);
0074         filterElement.appendChild(compfilterElement);
0075 
0076         QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0077         nameAttribute = document.createAttribute(QStringLiteral("name"));
0078         nameAttribute.setValue(QStringLiteral("VEVENT"));
0079         subcompfilterElement.setAttributeNode(nameAttribute);
0080 
0081         if (!startTime.isEmpty() || !endTime.isEmpty()) {
0082             QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
0083 
0084             if (!startTime.isEmpty()) {
0085                 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
0086                 startAttribute.setValue(startTime);
0087                 timeRangeElement.setAttributeNode(startAttribute);
0088             }
0089 
0090             if (!endTime.isEmpty()) {
0091                 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
0092                 endAttribute.setValue(endTime);
0093                 timeRangeElement.setAttributeNode(endAttribute);
0094             }
0095 
0096             subcompfilterElement.appendChild(timeRangeElement);
0097         }
0098 
0099         compfilterElement.appendChild(subcompfilterElement);
0100 
0101         return document;
0102     }
0103 
0104     QString mimeType() const override
0105     {
0106         return QStringLiteral("application/x-vnd.akonadi.calendar.event");
0107     }
0108 };
0109 
0110 class CaldavListTodoQueryBuilder : public XMLQueryBuilder
0111 {
0112 public:
0113     QDomDocument buildQuery() const override
0114     {
0115         QString startTime = parameter(QStringLiteral("start")).toString();
0116         QString endTime = parameter(QStringLiteral("end")).toString();
0117         QDomDocument document;
0118 
0119         QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
0120         document.appendChild(queryElement);
0121 
0122         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0123         queryElement.appendChild(propElement);
0124 
0125         QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
0126         propElement.appendChild(getetagElement);
0127 
0128         QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
0129         propElement.appendChild(getRTypeElement);
0130 
0131         QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
0132         queryElement.appendChild(filterElement);
0133 
0134         QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0135 
0136         QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
0137         nameAttribute.setValue(QStringLiteral("VCALENDAR"));
0138         compfilterElement.setAttributeNode(nameAttribute);
0139         filterElement.appendChild(compfilterElement);
0140 
0141         QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0142         nameAttribute = document.createAttribute(QStringLiteral("name"));
0143         nameAttribute.setValue(QStringLiteral("VTODO"));
0144         subcompfilterElement.setAttributeNode(nameAttribute);
0145 
0146         if (!startTime.isEmpty() || !endTime.isEmpty()) {
0147             QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
0148 
0149             if (!startTime.isEmpty()) {
0150                 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
0151                 startAttribute.setValue(startTime);
0152                 timeRangeElement.setAttributeNode(startAttribute);
0153             }
0154 
0155             if (!endTime.isEmpty()) {
0156                 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
0157                 endAttribute.setValue(endTime);
0158                 timeRangeElement.setAttributeNode(endAttribute);
0159             }
0160 
0161             subcompfilterElement.appendChild(timeRangeElement);
0162         }
0163 
0164         compfilterElement.appendChild(subcompfilterElement);
0165 
0166         return document;
0167     }
0168 
0169     QString mimeType() const override
0170     {
0171         return QStringLiteral("application/x-vnd.akonadi.calendar.todo");
0172     }
0173 };
0174 
0175 class CaldavListJournalQueryBuilder : public XMLQueryBuilder
0176 {
0177 public:
0178     QDomDocument buildQuery() const override
0179     {
0180         QString startTime = parameter(QStringLiteral("start")).toString();
0181         QString endTime = parameter(QStringLiteral("end")).toString();
0182         QDomDocument document;
0183 
0184         QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
0185         document.appendChild(queryElement);
0186 
0187         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0188         queryElement.appendChild(propElement);
0189 
0190         QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
0191         propElement.appendChild(getetagElement);
0192 
0193         QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
0194         propElement.appendChild(getRTypeElement);
0195 
0196         QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
0197         queryElement.appendChild(filterElement);
0198 
0199         QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0200 
0201         QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
0202         nameAttribute.setValue(QStringLiteral("VCALENDAR"));
0203         compfilterElement.setAttributeNode(nameAttribute);
0204         filterElement.appendChild(compfilterElement);
0205 
0206         QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0207         nameAttribute = document.createAttribute(QStringLiteral("name"));
0208         nameAttribute.setValue(QStringLiteral("VJOURNAL"));
0209         subcompfilterElement.setAttributeNode(nameAttribute);
0210 
0211         if (!startTime.isEmpty() || !endTime.isEmpty()) {
0212             QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
0213 
0214             if (!startTime.isEmpty()) {
0215                 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
0216                 startAttribute.setValue(startTime);
0217                 timeRangeElement.setAttributeNode(startAttribute);
0218             }
0219 
0220             if (!endTime.isEmpty()) {
0221                 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
0222                 endAttribute.setValue(endTime);
0223                 timeRangeElement.setAttributeNode(endAttribute);
0224             }
0225 
0226             subcompfilterElement.appendChild(timeRangeElement);
0227         }
0228 
0229         compfilterElement.appendChild(subcompfilterElement);
0230 
0231         return document;
0232     }
0233 
0234     QString mimeType() const override
0235     {
0236         return QStringLiteral("application/x-vnd.akonadi.calendar.journal");
0237     }
0238 };
0239 
0240 class CaldavMultigetQueryBuilder : public XMLQueryBuilder
0241 {
0242 public:
0243     QDomDocument buildQuery() const override
0244     {
0245         QDomDocument document;
0246         const QStringList urls = parameter(QStringLiteral("urls")).toStringList();
0247         if (urls.isEmpty()) {
0248             return document;
0249         }
0250 
0251         QDomElement multigetElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-multiget"));
0252         document.appendChild(multigetElement);
0253 
0254         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0255         multigetElement.appendChild(propElement);
0256 
0257         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")));
0258         propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-data")));
0259 
0260         for (const QString &url : urls) {
0261             QDomElement hrefElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("href"));
0262             const QUrl pathUrl = QUrl::fromUserInput(url);
0263             const QDomText textNode = document.createTextNode(pathUrl.path());
0264             hrefElement.appendChild(textNode);
0265 
0266             multigetElement.appendChild(hrefElement);
0267         }
0268 
0269         return document;
0270     }
0271 
0272     QString mimeType() const override
0273     {
0274         return QString();
0275     }
0276 };
0277 
0278 CaldavProtocol::CaldavProtocol()
0279 {
0280 }
0281 
0282 bool CaldavProtocol::supportsPrincipals() const
0283 {
0284     return true;
0285 }
0286 
0287 bool CaldavProtocol::useReport() const
0288 {
0289     return true;
0290 }
0291 
0292 bool CaldavProtocol::useMultiget() const
0293 {
0294     return true;
0295 }
0296 
0297 QString CaldavProtocol::principalHomeSet() const
0298 {
0299     return QStringLiteral("calendar-home-set");
0300 }
0301 
0302 QString CaldavProtocol::principalHomeSetNS() const
0303 {
0304     return QStringLiteral("urn:ietf:params:xml:ns:caldav");
0305 }
0306 
0307 XMLQueryBuilder::Ptr CaldavProtocol::collectionsQuery() const
0308 {
0309     return XMLQueryBuilder::Ptr(new CaldavCollectionQueryBuilder());
0310 }
0311 
0312 bool CaldavProtocol::containsCollection(const QDomElement &propElem) const
0313 {
0314     return !propElem.elementsByTagNameNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar")).isEmpty();
0315 }
0316 
0317 QList<XMLQueryBuilder::Ptr> CaldavProtocol::itemsQueries() const
0318 {
0319     QList<XMLQueryBuilder::Ptr> ret;
0320     ret << XMLQueryBuilder::Ptr(new CaldavListEventQueryBuilder());
0321     ret << XMLQueryBuilder::Ptr(new CaldavListTodoQueryBuilder());
0322     ret << XMLQueryBuilder::Ptr(new CaldavListJournalQueryBuilder());
0323     return ret;
0324 }
0325 
0326 XMLQueryBuilder::Ptr CaldavProtocol::itemsReportQuery(const QStringList &urls) const
0327 {
0328     XMLQueryBuilder::Ptr ret(new CaldavMultigetQueryBuilder());
0329     ret->setParameter(QStringLiteral("urls"), urls);
0330     return ret;
0331 }
0332 
0333 QString CaldavProtocol::responseNamespace() const
0334 {
0335     return QStringLiteral("urn:ietf:params:xml:ns:caldav");
0336 }
0337 
0338 QString CaldavProtocol::dataTagName() const
0339 {
0340     return QStringLiteral("calendar-data");
0341 }
0342 
0343 DavCollection::ContentTypes CaldavProtocol::collectionContentTypes(const QDomElement &propstatElement) const
0344 {
0345     /*
0346      * Extract the content type information from a propstat like the following
0347      *   <propstat xmlns="DAV:">
0348      *     <prop xmlns="DAV:">
0349      *       <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav">
0350      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/>
0351      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/>
0352      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/>
0353      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/>
0354      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/>
0355      *       </C:supported-calendar-component-set>
0356      *       <resourcetype xmlns="DAV:">
0357      *         <collection xmlns="DAV:"/>
0358      *         <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
0359      *         <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
0360      *       </resourcetype>
0361      *       <displayname xmlns="DAV:">Test1 User</displayname>
0362      *     </prop>
0363      *     <status xmlns="DAV:">HTTP/1.1 200 OK</status>
0364      *   </propstat>
0365      */
0366 
0367     const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
0368     const QDomElement supportedcomponentElement = Utils::firstChildElementNS(propElement, //
0369                                                                              QStringLiteral("urn:ietf:params:xml:ns:caldav"),
0370                                                                              QStringLiteral("supported-calendar-component-set"));
0371 
0372     DavCollection::ContentTypes contentTypes;
0373     QDomElement compElement = Utils::firstChildElementNS(supportedcomponentElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
0374 
0375     /*
0376      * Assign the content-type if the server didn't return anything.
0377      * According to RFC4791, §5.2.3:
0378      * In the absence of this property, the server MUST accept all
0379      * component types, and the client can assume that all component
0380      * types are accepted.
0381      */
0382     if (compElement.isNull()) {
0383         contentTypes |= DavCollection::Calendar;
0384         contentTypes |= DavCollection::Events;
0385         contentTypes |= DavCollection::Todos;
0386         contentTypes |= DavCollection::FreeBusy;
0387         contentTypes |= DavCollection::Journal;
0388     }
0389 
0390     while (!compElement.isNull()) {
0391         const QString type = compElement.attribute(QStringLiteral("name")).toLower();
0392         if (type == QLatin1String("vcalendar")) {
0393             contentTypes |= DavCollection::Calendar;
0394         } else if (type == QLatin1String("vevent")) {
0395             contentTypes |= DavCollection::Events;
0396         } else if (type == QLatin1String("vtodo")) {
0397             contentTypes |= DavCollection::Todos;
0398         } else if (type == QLatin1String("vfreebusy")) {
0399             contentTypes |= DavCollection::FreeBusy;
0400         } else if (type == QLatin1String("vjournal")) {
0401             contentTypes |= DavCollection::Journal;
0402         }
0403 
0404         compElement = Utils::nextSiblingElementNS(compElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
0405     }
0406 
0407     return contentTypes;
0408 }