File indexing completed on 2024-11-17 04:44:02

0001 /*
0002     Copyright (c) 2009 Grégory Oestreicher <greg@kamago.net>
0003 
0004     This program is free software; you can redistribute it and/or modify
0005     it under the terms of the GNU General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or
0007     (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program; if not, write to the Free Software
0016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0017 */
0018 
0019 #include "caldavprotocol.h"
0020 #include "common/utils.h"
0021 
0022 #include <QtCore/QDateTime>
0023 #include <QtCore/QStringList>
0024 #include <QtCore/QUrl>
0025 #include <QtCore/QVariant>
0026 #include <QtXml/QDomDocument>
0027 
0028 using namespace KDAV2;
0029 
0030 class CaldavCollectionQueryBuilder : public XMLQueryBuilder
0031 {
0032 public:
0033     QDomDocument buildQuery() const Q_DECL_OVERRIDE
0034     {
0035         QDomDocument document;
0036 
0037         QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
0038         document.appendChild(propfindElement);
0039 
0040         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0041         propfindElement.appendChild(propElement);
0042 
0043         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname")));
0044         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")));
0045         propElement.appendChild(document.createElementNS(QStringLiteral("http://apple.com/ns/ical/"), QStringLiteral("calendar-color")));
0046         propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set")));
0047         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-privilege-set")));
0048         propElement.appendChild(document.createElementNS(QStringLiteral("http://calendarserver.org/ns/"), QStringLiteral("getctag")));
0049 
0050         return document;
0051     }
0052 
0053     QString mimeType() const Q_DECL_OVERRIDE
0054     {
0055         return QString();
0056     }
0057 };
0058 
0059 static QDomDocument listQuery(const QString &typeFilter, const QString startTime, const QString &endTime)
0060 {
0061     QDomDocument document;
0062 
0063     QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
0064     document.appendChild(queryElement);
0065 
0066     QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0067     queryElement.appendChild(propElement);
0068 
0069     QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
0070     propElement.appendChild(getetagElement);
0071 
0072     QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
0073     propElement.appendChild(getRTypeElement);
0074 
0075     QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
0076     queryElement.appendChild(filterElement);
0077 
0078     QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0079 
0080     QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
0081     nameAttribute.setValue(QStringLiteral("VCALENDAR"));
0082     compfilterElement.setAttributeNode(nameAttribute);
0083     filterElement.appendChild(compfilterElement);
0084 
0085     QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
0086     nameAttribute = document.createAttribute(QStringLiteral("name"));
0087     nameAttribute.setValue(typeFilter);
0088     subcompfilterElement.setAttributeNode(nameAttribute);
0089 
0090     if (!startTime.isEmpty() || !endTime.isEmpty()) {
0091         QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
0092 
0093         if (!startTime.isEmpty()) {
0094             QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
0095             startAttribute.setValue(startTime);
0096             timeRangeElement.setAttributeNode(startAttribute);
0097         }
0098 
0099         if (!endTime.isEmpty()) {
0100             QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
0101             endAttribute.setValue(endTime);
0102             timeRangeElement.setAttributeNode(endAttribute);
0103         }
0104 
0105         subcompfilterElement.appendChild(timeRangeElement);
0106     }
0107     compfilterElement.appendChild(subcompfilterElement);
0108 
0109     return document;
0110 }
0111 
0112 class CaldavListEventQueryBuilder : public XMLQueryBuilder
0113 {
0114 public:
0115     QDomDocument buildQuery() const Q_DECL_OVERRIDE
0116     {
0117         return listQuery(QLatin1String{"VEVENT"}, parameter(QStringLiteral("start")).toString(), parameter(QStringLiteral("end")).toString());
0118     }
0119 
0120     QString mimeType() const Q_DECL_OVERRIDE
0121     {
0122         return QStringLiteral("VEVENT");
0123     }
0124 };
0125 
0126 class CaldavListTodoQueryBuilder : public XMLQueryBuilder
0127 {
0128 public:
0129     QDomDocument buildQuery() const Q_DECL_OVERRIDE
0130     {
0131         return listQuery(QLatin1String{"VTODO"}, parameter(QStringLiteral("start")).toString(), parameter(QStringLiteral("end")).toString());
0132     }
0133 
0134     QString mimeType() const Q_DECL_OVERRIDE
0135     {
0136         return QStringLiteral("VTODO");
0137     }
0138 };
0139 
0140 class CaldavListJournalQueryBuilder : public XMLQueryBuilder
0141 {
0142 public:
0143     QDomDocument buildQuery() const Q_DECL_OVERRIDE
0144     {
0145         return listQuery(QLatin1String{"VJOURNAL"}, parameter(QStringLiteral("start")).toString(), parameter(QStringLiteral("end")).toString());
0146     }
0147 
0148     QString mimeType() const Q_DECL_OVERRIDE
0149     {
0150         return QStringLiteral("VJOURNAL");
0151     }
0152 };
0153 
0154 class CaldavMultigetQueryBuilder : public XMLQueryBuilder
0155 {
0156 public:
0157     QDomDocument buildQuery() const Q_DECL_OVERRIDE
0158     {
0159         QDomDocument document;
0160         const QStringList urls = parameter(QStringLiteral("urls")).toStringList();
0161         if (urls.isEmpty()) {
0162             return document;
0163         }
0164 
0165         QDomElement multigetElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-multiget"));
0166         document.appendChild(multigetElement);
0167 
0168         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0169         multigetElement.appendChild(propElement);
0170 
0171         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")));
0172         propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-data")));
0173 
0174         for (const QString &url : urls) {
0175             QDomElement hrefElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("href"));
0176             const QUrl pathUrl = QUrl::fromUserInput(url);
0177             const QDomText textNode = document.createTextNode(pathUrl.toString());
0178             hrefElement.appendChild(textNode);
0179 
0180             multigetElement.appendChild(hrefElement);
0181         }
0182 
0183         return document;
0184     }
0185 
0186     QString mimeType() const Q_DECL_OVERRIDE
0187     {
0188         return QString();
0189     }
0190 };
0191 
0192 CaldavProtocol::CaldavProtocol()
0193 {
0194 }
0195 
0196 bool CaldavProtocol::supportsPrincipals() const
0197 {
0198     return true;
0199 }
0200 
0201 bool CaldavProtocol::supportsCTags() const
0202 {
0203     return true;
0204 }
0205 
0206 bool CaldavProtocol::useReport() const
0207 {
0208     return true;
0209 }
0210 
0211 bool CaldavProtocol::useMultiget() const
0212 {
0213     return true;
0214 }
0215 
0216 QString CaldavProtocol::principalHomeSet() const
0217 {
0218     return QStringLiteral("calendar-home-set");
0219 }
0220 
0221 QString CaldavProtocol::principalHomeSetNS() const
0222 {
0223     return QStringLiteral("urn:ietf:params:xml:ns:caldav");
0224 }
0225 
0226 XMLQueryBuilder::Ptr CaldavProtocol::collectionsQuery() const
0227 {
0228     return XMLQueryBuilder::Ptr(new CaldavCollectionQueryBuilder());
0229 }
0230 
0231 QString CaldavProtocol::collectionsXQuery() const
0232 {
0233     //const QString query( "//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/*[local-name()='supported-calendar-component-set' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/*[local-name()='comp' and namespace-uri()='urn:ietf:params:xml:ns:caldav' and (@name='VTODO' or @name='VEVENT')]/ancestor::*[local-name()='response' and namespace-uri()='DAV:']" );
0234     const QString query(QStringLiteral("//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/ancestor::*[local-name()='response' and namespace-uri()='DAV:']"));
0235 
0236     return query;
0237 }
0238 
0239 QVector<XMLQueryBuilder::Ptr> CaldavProtocol::itemsQueries() const
0240 {
0241     QVector<XMLQueryBuilder::Ptr> ret;
0242     ret << XMLQueryBuilder::Ptr(new CaldavListEventQueryBuilder());
0243     ret << XMLQueryBuilder::Ptr(new CaldavListTodoQueryBuilder());
0244     ret << XMLQueryBuilder::Ptr(new CaldavListJournalQueryBuilder());
0245     return ret;
0246 }
0247 
0248 XMLQueryBuilder::Ptr CaldavProtocol::itemsReportQuery(const QStringList &urls) const
0249 {
0250     XMLQueryBuilder::Ptr ret(new CaldavMultigetQueryBuilder());
0251     ret->setParameter(QStringLiteral("urls"), urls);
0252     return ret;
0253 }
0254 
0255 QString CaldavProtocol::responseNamespace() const
0256 {
0257     return QStringLiteral("urn:ietf:params:xml:ns:caldav");
0258 }
0259 
0260 QString CaldavProtocol::dataTagName() const
0261 {
0262     return QStringLiteral("calendar-data");
0263 }
0264 
0265 DavCollection::ContentTypes CaldavProtocol::collectionContentTypes(const QDomElement &propstatElement) const
0266 {
0267     /*
0268      * Extract the content type information from a propstat like the following
0269      *   <propstat xmlns="DAV:">
0270      *     <prop xmlns="DAV:">
0271      *       <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav">
0272      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/>
0273      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/>
0274      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/>
0275      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/>
0276      *         <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/>
0277      *       </C:supported-calendar-component-set>
0278      *       <resourcetype xmlns="DAV:">
0279      *         <collection xmlns="DAV:"/>
0280      *         <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
0281      *         <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
0282      *       </resourcetype>
0283      *       <displayname xmlns="DAV:">Test1 User</displayname>
0284      *     </prop>
0285      *     <status xmlns="DAV:">HTTP/1.1 200 OK</status>
0286      *   </propstat>
0287      */
0288 
0289     const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
0290     const QDomElement supportedcomponentElement = Utils::firstChildElementNS(propElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set"));
0291 
0292     DavCollection::ContentTypes contentTypes;
0293     QDomElement compElement = Utils::firstChildElementNS(supportedcomponentElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
0294 
0295     /*
0296      * Assign the content-type if the server didn't return anything.
0297      * According to RFC4791, §5.2.3:
0298      * In the absence of this property, the server MUST accept all
0299      * component types, and the client can assume that all component
0300      * types are accepted.
0301      */
0302     if (compElement.isNull()) {
0303         contentTypes |= DavCollection::Calendar;
0304         contentTypes |= DavCollection::Events;
0305         contentTypes |= DavCollection::Todos;
0306         contentTypes |= DavCollection::FreeBusy;
0307         contentTypes |= DavCollection::Journal;
0308     }
0309 
0310     while (!compElement.isNull()) {
0311         const QString type = compElement.attribute(QStringLiteral("name")).toLower();
0312         if (type == QLatin1String("vcalendar")) {
0313             contentTypes |= DavCollection::Calendar;
0314         } else if (type == QLatin1String("vevent")) {
0315             contentTypes |= DavCollection::Events;
0316         } else if (type == QLatin1String("vtodo")) {
0317             contentTypes |= DavCollection::Todos;
0318         } else if (type == QLatin1String("vfreebusy")) {
0319             contentTypes |= DavCollection::FreeBusy;
0320         } else if (type == QLatin1String("vjournal")) {
0321             contentTypes |= DavCollection::Journal;
0322         }
0323 
0324         compElement = Utils::nextSiblingElementNS(compElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
0325     }
0326 
0327     return contentTypes;
0328 }