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 | |
14 | using namespace KDAV; |
15 | |
16 | class CaldavCollectionQueryBuilder : public XMLQueryBuilder |
17 | { |
18 | public: |
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 | |
45 | class CaldavListEventQueryBuilder : public XMLQueryBuilder |
46 | { |
47 | public: |
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 | |
110 | class CaldavListTodoQueryBuilder : public XMLQueryBuilder |
111 | { |
112 | public: |
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 | |
175 | class CaldavListJournalQueryBuilder : public XMLQueryBuilder |
176 | { |
177 | public: |
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 | |
240 | class CaldavMultigetQueryBuilder : public XMLQueryBuilder |
241 | { |
242 | public: |
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 | |
278 | CaldavProtocol::CaldavProtocol() |
279 | { |
280 | } |
281 | |
282 | bool CaldavProtocol::supportsPrincipals() const |
283 | { |
284 | return true; |
285 | } |
286 | |
287 | bool CaldavProtocol::useReport() const |
288 | { |
289 | return true; |
290 | } |
291 | |
292 | bool CaldavProtocol::useMultiget() const |
293 | { |
294 | return true; |
295 | } |
296 | |
297 | QString CaldavProtocol::principalHomeSet() const |
298 | { |
299 | return QStringLiteral("calendar-home-set" ); |
300 | } |
301 | |
302 | QString CaldavProtocol::principalHomeSetNS() const |
303 | { |
304 | return QStringLiteral("urn:ietf:params:xml:ns:caldav" ); |
305 | } |
306 | |
307 | XMLQueryBuilder::Ptr CaldavProtocol::collectionsQuery() const |
308 | { |
309 | return XMLQueryBuilder::Ptr(new CaldavCollectionQueryBuilder()); |
310 | } |
311 | |
312 | bool CaldavProtocol::containsCollection(const QDomElement &propElem) const |
313 | { |
314 | return !propElem.elementsByTagNameNS(QStringLiteral("urn:ietf:params:xml:ns:caldav" ), QStringLiteral("calendar" )).isEmpty(); |
315 | } |
316 | |
317 | QList<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 | |
326 | XMLQueryBuilder::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 | |
333 | QString CaldavProtocol::responseNamespace() const |
334 | { |
335 | return QStringLiteral("urn:ietf:params:xml:ns:caldav" ); |
336 | } |
337 | |
338 | QString CaldavProtocol::dataTagName() const |
339 | { |
340 | return QStringLiteral("calendar-data" ); |
341 | } |
342 | |
343 | DavCollection::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 | |