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