1/*
2 SPDX-FileCopyrightText: 2009 Grégory Oestreicher <greg@kamago.net>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "caldavprotocol_p.h"
8#include "common/utils_p.h"
9
10#include <QDomDocument>
11#include <QStringList>
12#include <QUrl>
13
14using namespace KDAV;
15
16class CaldavCollectionQueryBuilder : public XMLQueryBuilder
17{
18public:
19 QDomDocument buildQuery() const override
20 {
21 QDomDocument document;
22
23 QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
24 document.appendChild(newChild: propfindElement);
25
26 QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
27 propfindElement.appendChild(newChild: propElement);
28
29 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname")));
30 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")));
31 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("http://apple.com/ns/ical/"), QStringLiteral("calendar-color")));
32 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set")));
33 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-privilege-set")));
34 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("http://calendarserver.org/ns/"), QStringLiteral("getctag")));
35
36 return document;
37 }
38
39 QString mimeType() const override
40 {
41 return QString();
42 }
43};
44
45class CaldavListEventQueryBuilder : public XMLQueryBuilder
46{
47public:
48 QDomDocument buildQuery() const override
49 {
50 QString startTime = parameter(QStringLiteral("start")).toString();
51 QString endTime = parameter(QStringLiteral("end")).toString();
52 QDomDocument document;
53
54 QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
55 document.appendChild(newChild: queryElement);
56
57 QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
58 queryElement.appendChild(newChild: propElement);
59
60 QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
61 propElement.appendChild(newChild: getetagElement);
62
63 QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
64 propElement.appendChild(newChild: getRTypeElement);
65
66 QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
67 queryElement.appendChild(newChild: filterElement);
68
69 QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
70
71 QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
72 nameAttribute.setValue(QStringLiteral("VCALENDAR"));
73 compfilterElement.setAttributeNode(nameAttribute);
74 filterElement.appendChild(newChild: compfilterElement);
75
76 QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
77 nameAttribute = document.createAttribute(QStringLiteral("name"));
78 nameAttribute.setValue(QStringLiteral("VEVENT"));
79 subcompfilterElement.setAttributeNode(nameAttribute);
80
81 if (!startTime.isEmpty() || !endTime.isEmpty()) {
82 QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
83
84 if (!startTime.isEmpty()) {
85 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
86 startAttribute.setValue(startTime);
87 timeRangeElement.setAttributeNode(startAttribute);
88 }
89
90 if (!endTime.isEmpty()) {
91 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
92 endAttribute.setValue(endTime);
93 timeRangeElement.setAttributeNode(endAttribute);
94 }
95
96 subcompfilterElement.appendChild(newChild: timeRangeElement);
97 }
98
99 compfilterElement.appendChild(newChild: subcompfilterElement);
100
101 return document;
102 }
103
104 QString mimeType() const override
105 {
106 return QStringLiteral("application/x-vnd.akonadi.calendar.event");
107 }
108};
109
110class CaldavListTodoQueryBuilder : public XMLQueryBuilder
111{
112public:
113 QDomDocument buildQuery() const override
114 {
115 QString startTime = parameter(QStringLiteral("start")).toString();
116 QString endTime = parameter(QStringLiteral("end")).toString();
117 QDomDocument document;
118
119 QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
120 document.appendChild(newChild: queryElement);
121
122 QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
123 queryElement.appendChild(newChild: propElement);
124
125 QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
126 propElement.appendChild(newChild: getetagElement);
127
128 QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
129 propElement.appendChild(newChild: getRTypeElement);
130
131 QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
132 queryElement.appendChild(newChild: filterElement);
133
134 QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
135
136 QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
137 nameAttribute.setValue(QStringLiteral("VCALENDAR"));
138 compfilterElement.setAttributeNode(nameAttribute);
139 filterElement.appendChild(newChild: compfilterElement);
140
141 QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
142 nameAttribute = document.createAttribute(QStringLiteral("name"));
143 nameAttribute.setValue(QStringLiteral("VTODO"));
144 subcompfilterElement.setAttributeNode(nameAttribute);
145
146 if (!startTime.isEmpty() || !endTime.isEmpty()) {
147 QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
148
149 if (!startTime.isEmpty()) {
150 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
151 startAttribute.setValue(startTime);
152 timeRangeElement.setAttributeNode(startAttribute);
153 }
154
155 if (!endTime.isEmpty()) {
156 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
157 endAttribute.setValue(endTime);
158 timeRangeElement.setAttributeNode(endAttribute);
159 }
160
161 subcompfilterElement.appendChild(newChild: timeRangeElement);
162 }
163
164 compfilterElement.appendChild(newChild: subcompfilterElement);
165
166 return document;
167 }
168
169 QString mimeType() const override
170 {
171 return QStringLiteral("application/x-vnd.akonadi.calendar.todo");
172 }
173};
174
175class CaldavListJournalQueryBuilder : public XMLQueryBuilder
176{
177public:
178 QDomDocument buildQuery() const override
179 {
180 QString startTime = parameter(QStringLiteral("start")).toString();
181 QString endTime = parameter(QStringLiteral("end")).toString();
182 QDomDocument document;
183
184 QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query"));
185 document.appendChild(newChild: queryElement);
186
187 QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
188 queryElement.appendChild(newChild: propElement);
189
190 QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"));
191 propElement.appendChild(newChild: getetagElement);
192
193 QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
194 propElement.appendChild(newChild: getRTypeElement);
195
196 QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter"));
197 queryElement.appendChild(newChild: filterElement);
198
199 QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
200
201 QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name"));
202 nameAttribute.setValue(QStringLiteral("VCALENDAR"));
203 compfilterElement.setAttributeNode(nameAttribute);
204 filterElement.appendChild(newChild: compfilterElement);
205
206 QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter"));
207 nameAttribute = document.createAttribute(QStringLiteral("name"));
208 nameAttribute.setValue(QStringLiteral("VJOURNAL"));
209 subcompfilterElement.setAttributeNode(nameAttribute);
210
211 if (!startTime.isEmpty() || !endTime.isEmpty()) {
212 QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range"));
213
214 if (!startTime.isEmpty()) {
215 QDomAttr startAttribute = document.createAttribute(QStringLiteral("start"));
216 startAttribute.setValue(startTime);
217 timeRangeElement.setAttributeNode(startAttribute);
218 }
219
220 if (!endTime.isEmpty()) {
221 QDomAttr endAttribute = document.createAttribute(QStringLiteral("end"));
222 endAttribute.setValue(endTime);
223 timeRangeElement.setAttributeNode(endAttribute);
224 }
225
226 subcompfilterElement.appendChild(newChild: timeRangeElement);
227 }
228
229 compfilterElement.appendChild(newChild: subcompfilterElement);
230
231 return document;
232 }
233
234 QString mimeType() const override
235 {
236 return QStringLiteral("application/x-vnd.akonadi.calendar.journal");
237 }
238};
239
240class CaldavMultigetQueryBuilder : public XMLQueryBuilder
241{
242public:
243 QDomDocument buildQuery() const override
244 {
245 QDomDocument document;
246 const QStringList urls = parameter(QStringLiteral("urls")).toStringList();
247 if (urls.isEmpty()) {
248 return document;
249 }
250
251 QDomElement multigetElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-multiget"));
252 document.appendChild(newChild: multigetElement);
253
254 QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
255 multigetElement.appendChild(newChild: propElement);
256
257 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")));
258 propElement.appendChild(newChild: document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-data")));
259
260 for (const QString &url : urls) {
261 QDomElement hrefElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("href"));
262 const QUrl pathUrl = QUrl::fromUserInput(userInput: url);
263 const QDomText textNode = document.createTextNode(data: pathUrl.path());
264 hrefElement.appendChild(newChild: textNode);
265
266 multigetElement.appendChild(newChild: hrefElement);
267 }
268
269 return document;
270 }
271
272 QString mimeType() const override
273 {
274 return QString();
275 }
276};
277
278CaldavProtocol::CaldavProtocol()
279{
280}
281
282bool CaldavProtocol::supportsPrincipals() const
283{
284 return true;
285}
286
287bool CaldavProtocol::useReport() const
288{
289 return true;
290}
291
292bool CaldavProtocol::useMultiget() const
293{
294 return true;
295}
296
297QString CaldavProtocol::principalHomeSet() const
298{
299 return QStringLiteral("calendar-home-set");
300}
301
302QString CaldavProtocol::principalHomeSetNS() const
303{
304 return QStringLiteral("urn:ietf:params:xml:ns:caldav");
305}
306
307XMLQueryBuilder::Ptr CaldavProtocol::collectionsQuery() const
308{
309 return XMLQueryBuilder::Ptr(new CaldavCollectionQueryBuilder());
310}
311
312bool CaldavProtocol::containsCollection(const QDomElement &propElem) const
313{
314 return !propElem.elementsByTagNameNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar")).isEmpty();
315}
316
317QList<XMLQueryBuilder::Ptr> CaldavProtocol::itemsQueries() const
318{
319 QList<XMLQueryBuilder::Ptr> ret;
320 ret << XMLQueryBuilder::Ptr(new CaldavListEventQueryBuilder());
321 ret << XMLQueryBuilder::Ptr(new CaldavListTodoQueryBuilder());
322 ret << XMLQueryBuilder::Ptr(new CaldavListJournalQueryBuilder());
323 return ret;
324}
325
326XMLQueryBuilder::Ptr CaldavProtocol::itemsReportQuery(const QStringList &urls) const
327{
328 XMLQueryBuilder::Ptr ret(new CaldavMultigetQueryBuilder());
329 ret->setParameter(QStringLiteral("urls"), value: urls);
330 return ret;
331}
332
333QString CaldavProtocol::responseNamespace() const
334{
335 return QStringLiteral("urn:ietf:params:xml:ns:caldav");
336}
337
338QString CaldavProtocol::dataTagName() const
339{
340 return QStringLiteral("calendar-data");
341}
342
343DavCollection::ContentTypes CaldavProtocol::collectionContentTypes(const QDomElement &propstatElement) const
344{
345 /*
346 * Extract the content type information from a propstat like the following
347 * <propstat xmlns="DAV:">
348 * <prop xmlns="DAV:">
349 * <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav">
350 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/>
351 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/>
352 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/>
353 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/>
354 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/>
355 * </C:supported-calendar-component-set>
356 * <resourcetype xmlns="DAV:">
357 * <collection xmlns="DAV:"/>
358 * <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
359 * <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
360 * </resourcetype>
361 * <displayname xmlns="DAV:">Test1 User</displayname>
362 * </prop>
363 * <status xmlns="DAV:">HTTP/1.1 200 OK</status>
364 * </propstat>
365 */
366
367 const QDomElement propElement = Utils::firstChildElementNS(parent: propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
368 const QDomElement supportedcomponentElement = Utils::firstChildElementNS(parent: propElement, //
369 QStringLiteral("urn:ietf:params:xml:ns:caldav"),
370 QStringLiteral("supported-calendar-component-set"));
371
372 DavCollection::ContentTypes contentTypes;
373 QDomElement compElement = Utils::firstChildElementNS(parent: supportedcomponentElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
374
375 /*
376 * Assign the content-type if the server didn't return anything.
377 * According to RFC4791, §5.2.3:
378 * In the absence of this property, the server MUST accept all
379 * component types, and the client can assume that all component
380 * types are accepted.
381 */
382 if (compElement.isNull()) {
383 contentTypes |= DavCollection::Calendar;
384 contentTypes |= DavCollection::Events;
385 contentTypes |= DavCollection::Todos;
386 contentTypes |= DavCollection::FreeBusy;
387 contentTypes |= DavCollection::Journal;
388 }
389
390 while (!compElement.isNull()) {
391 const QString type = compElement.attribute(QStringLiteral("name")).toLower();
392 if (type == QLatin1String("vcalendar")) {
393 contentTypes |= DavCollection::Calendar;
394 } else if (type == QLatin1String("vevent")) {
395 contentTypes |= DavCollection::Events;
396 } else if (type == QLatin1String("vtodo")) {
397 contentTypes |= DavCollection::Todos;
398 } else if (type == QLatin1String("vfreebusy")) {
399 contentTypes |= DavCollection::FreeBusy;
400 } else if (type == QLatin1String("vjournal")) {
401 contentTypes |= DavCollection::Journal;
402 }
403
404 compElement = Utils::nextSiblingElementNS(element: compElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp"));
405 }
406
407 return contentTypes;
408}
409

source code of kdav/src/protocols/caldavprotocol.cpp