KDAV

davcollectionsfetchjob.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "davcollectionsfetchjob.h"
8#include "davjobbase_p.h"
9
10#include "daverror.h"
11#include "davmanager_p.h"
12#include "davprincipalhomesetsfetchjob.h"
13#include "davprotocolbase_p.h"
14#include "utils_p.h"
15
16#include "libkdav_debug.h"
17#include <KIO/DavJob>
18#include <KIO/Job>
19
20#include <QBuffer>
21#include <QColor>
22
23using namespace KDAV;
24
25namespace KDAV
26{
27class DavCollectionsFetchJobPrivate : public DavJobBasePrivate
28{
29public:
30 void principalFetchFinished(KJob *job);
31 void collectionsFetchFinished(KJob *job);
32 void doCollectionsFetch(const QUrl &url);
33 void subjobFinished();
34
35 DavUrl mUrl;
36 DavCollection::List mCollections;
37 uint mSubJobCount = 0;
38
39 Q_DECLARE_PUBLIC(DavCollectionsFetchJob)
40};
41}
42
44 : DavJobBase(new DavCollectionsFetchJobPrivate, parent)
45{
47 d->mUrl = url;
48}
49
51{
53 if (DavManager::davProtocol(d->mUrl.protocol())->supportsPrincipals()) {
56 d->principalFetchFinished(job);
57 });
58 job->start();
59 } else {
60 d->doCollectionsFetch(d->mUrl.url());
61 }
62}
63
65{
67 return d->mCollections;
68}
69
71{
73 return d->mUrl;
74}
75
76void DavCollectionsFetchJobPrivate::doCollectionsFetch(const QUrl &url)
77{
78 ++mSubJobCount;
79
80 const QDomDocument collectionQuery = DavManager::davProtocol(mUrl.protocol())->collectionsQuery()->buildQuery();
81
82 KIO::DavJob *job = DavManager::self()->createPropFindJob(url, collectionQuery.toString());
83 QObject::connect(job, &KIO::DavJob::result, q_ptr, [this](KJob *job) {
84 collectionsFetchFinished(job);
85 });
86 job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
87}
88
89void DavCollectionsFetchJobPrivate::principalFetchFinished(KJob *job)
90{
91 const DavPrincipalHomeSetsFetchJob *davJob = qobject_cast<DavPrincipalHomeSetsFetchJob *>(job);
92
93 if (davJob->error()) {
94 if (davJob->canRetryLater()) {
95 // If we have a non-persistent HTTP error code then this may mean that
96 // the URL was not a principal URL. Retry as if it were a calendar URL.
97 qCDebug(KDAV_LOG) << job->errorText();
98 doCollectionsFetch(mUrl.url());
99 } else {
100 // Just give up here.
101 setDavError(davJob->davError());
102 setErrorTextFromDavError();
103 emitResult();
104 }
105
106 return;
107 }
108
109 const QStringList homeSets = davJob->homeSets();
110 qCDebug(KDAV_LOG) << "Found" << homeSets.size() << "homesets";
111 qCDebug(KDAV_LOG) << homeSets;
112
113 if (homeSets.isEmpty()) {
114 // Same as above, retry as if it were a calendar URL.
115 doCollectionsFetch(mUrl.url());
116 return;
117 }
118
119 for (const QString &homeSet : homeSets) {
120 QUrl url = mUrl.url();
121
122 if (homeSet.startsWith(QLatin1Char('/'))) {
123 // homeSet is only a path, use request url to complete
124 url.setPath(homeSet, QUrl::TolerantMode);
125 } else {
126 // homeSet is a complete url
127 QUrl tmpUrl(homeSet);
128 tmpUrl.setUserName(url.userName());
129 tmpUrl.setPassword(url.password());
130 url = tmpUrl;
131 }
132
133 doCollectionsFetch(url);
134 }
135}
136
137void DavCollectionsFetchJobPrivate::collectionsFetchFinished(KJob *job)
138{
140 KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(job);
141 const QString responseCodeStr = davJob->queryMetaData(QStringLiteral("responsecode"));
142 const int responseCode = responseCodeStr.isEmpty() ? 0 : responseCodeStr.toInt();
143
144 // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx
145 if (davJob->error() || (responseCode >= 400 && responseCode < 600)) {
146 if (davJob->url() != mUrl.url()) {
147 // Retry as if the initial URL was a calendar URL.
148 // We can end up here when retrieving a homeset on
149 // which a PROPFIND resulted in an error
150 doCollectionsFetch(mUrl.url());
151 --mSubJobCount;
152 return;
153 }
154
155 setLatestResponseCode(responseCode);
156 setError(ERR_PROBLEM_WITH_REQUEST);
157 setJobErrorText(davJob->errorText());
158 setJobError(davJob->error());
159 setErrorTextFromDavError();
160 } else {
161 // For use in the collectionDiscovered() signal
162 QUrl _jobUrl = mUrl.url();
163 _jobUrl.setUserInfo(QString());
164 const QString jobUrl = _jobUrl.toDisplayString();
165
166 // Validate that we got a valid PROPFIND response
167 QDomDocument response;
168 response.setContent(davJob->responseData(), QDomDocument::ParseOption::UseNamespaceProcessing);
169 QDomElement rootElement = response.documentElement();
170 if (rootElement.tagName().compare(QLatin1String("multistatus"), Qt::CaseInsensitive) != 0) {
171 setError(ERR_COLLECTIONFETCH);
172 setErrorTextFromDavError();
173 subjobFinished();
174 return;
175 }
176
177 QByteArray resp = davJob->responseData();
178 QDomDocument document;
179 if (!document.setContent(resp, QDomDocument::ParseOption::UseNamespaceProcessing)) {
180 setError(ERR_COLLECTIONFETCH);
181 setErrorTextFromDavError();
182 subjobFinished();
183 return;
184 }
185
186 if (!q->error()) {
187 /*
188 * Extract information from a document like the following:
189 *
190 * <responses>
191 * <response xmlns="DAV:">
192 * <href xmlns="DAV:">/caldav.php/test1.user/home/</href>
193 * <propstat xmlns="DAV:">
194 * <prop xmlns="DAV:">
195 * <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav">
196 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/>
197 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/>
198 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/>
199 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/>
200 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/>
201 * </C:supported-calendar-component-set>
202 * <resourcetype xmlns="DAV:">
203 * <collection xmlns="DAV:"/>
204 * <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
205 * <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
206 * </resourcetype>
207 * <displayname xmlns="DAV:">Test1 User</displayname>
208 * <current-user-privilege-set xmlns="DAV:">
209 * <privilege xmlns="DAV:">
210 * <read xmlns="DAV:"/>
211 * </privilege>
212 * </current-user-privilege-set>
213 * <getctag xmlns="http://calendarserver.org/ns/">12345</getctag>
214 * </prop>
215 * <status xmlns="DAV:">HTTP/1.1 200 OK</status>
216 * </propstat>
217 * </response>
218 * </responses>
219 */
220
221 const QDomElement responsesElement = document.documentElement();
222
223 QDomElement responseElement = Utils::firstChildElementNS(responsesElement, QStringLiteral("DAV:"), QStringLiteral("response"));
224 while (!responseElement.isNull()) {
225 QDomElement propstatElement;
226
227 // check for the valid propstat, without giving up on first error
228 {
229 const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
230 for (int i = 0; i < propstats.length(); ++i) {
231 const QDomElement propstatCandidate = propstats.item(i).toElement();
232 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
233 if (statusElement.text().contains(QLatin1String("200"))) {
234 propstatElement = propstatCandidate;
235 }
236 }
237 }
238
239 if (propstatElement.isNull()) {
240 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
241 continue;
242 }
243
244 // extract url
245 const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href"));
246 if (hrefElement.isNull()) {
247 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
248 continue;
249 }
250
251 QString href = hrefElement.text();
252 if (!href.endsWith(QLatin1Char('/'))) {
253 href.append(QLatin1Char('/'));
254 }
255
256 QUrl url = davJob->url();
257 url.setUserInfo(QString());
258 if (href.startsWith(QLatin1Char('/'))) {
259 // href is only a path, use request url to complete
260 url.setPath(href, QUrl::TolerantMode);
261 } else {
262 // href is a complete url
263 url = QUrl::fromUserInput(href);
264 }
265
266 // don't add this resource if it has already been detected
267 bool alreadySeen = false;
268 for (const DavCollection &seen : std::as_const(mCollections)) {
269 if (seen.url().toDisplayString() == url.toDisplayString()) {
270 alreadySeen = true;
271 }
272 }
273 if (alreadySeen) {
274 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
275 continue;
276 }
277
278 // extract display name
279 const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
280 if (!DavManager::davProtocol(mUrl.protocol())->containsCollection(propElement)) {
281 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
282 continue;
283 }
284 const QDomElement displaynameElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("displayname"));
285 const QString displayName = displaynameElement.text();
286
287 // Extract CTag
288 const QDomElement CTagElement = Utils::firstChildElementNS(propElement, //
289 QStringLiteral("http://calendarserver.org/ns/"),
290 QStringLiteral("getctag"));
291 QString CTag;
292 if (!CTagElement.isNull()) {
293 CTag = CTagElement.text();
294 }
295
296 // extract calendar color if provided
297 const QDomElement colorElement = Utils::firstChildElementNS(propElement, //
298 QStringLiteral("http://apple.com/ns/ical/"),
299 QStringLiteral("calendar-color"));
300 QColor color;
301 if (!colorElement.isNull()) {
302 QString colorValue = colorElement.text();
303 if (QColor::isValidColorName(colorValue)) {
304 // Color is either #RRGGBBAA or #RRGGBB but QColor expects #AARRGGBB
305 // so we put the AA in front if the string's length is 9.
306 if (colorValue.size() == 9) {
307 QString fixedColorValue = QStringLiteral("#") + colorValue.mid(7, 2) + colorValue.mid(1, 6);
308 color = QColor::fromString(fixedColorValue);
309 } else {
310 color = QColor::fromString(colorValue);
311 }
312 }
313 }
314
315 // extract allowed content types
316 const DavCollection::ContentTypes contentTypes = DavManager::davProtocol(mUrl.protocol())->collectionContentTypes(propstatElement);
317
318 auto _url = url;
319 _url.setUserInfo(mUrl.url().userInfo());
320 DavCollection collection(DavUrl(_url, mUrl.protocol()), displayName, contentTypes);
321
322 collection.setCTag(CTag);
323 if (color.isValid()) {
324 collection.setColor(color);
325 }
326
327 // extract privileges
328 const QDomElement currentPrivsElement = Utils::firstChildElementNS(propElement, //
329 QStringLiteral("DAV:"),
330 QStringLiteral("current-user-privilege-set"));
331 if (currentPrivsElement.isNull()) {
332 // Assume that we have all privileges
333 collection.setPrivileges(KDAV::All);
334 } else {
335 Privileges privileges = Utils::extractPrivileges(currentPrivsElement);
336 collection.setPrivileges(privileges);
337 }
338
339 qCDebug(KDAV_LOG) << url.toDisplayString() << "PRIVS: " << collection.privileges();
340 mCollections << collection;
341 Q_EMIT q->collectionDiscovered(mUrl.protocol(), url.toDisplayString(), jobUrl);
342
343 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
344 }
345 }
346 }
347
348 subjobFinished();
349}
350
351void DavCollectionsFetchJobPrivate::subjobFinished()
352{
353 if (--mSubJobCount == 0) {
354 emitResult();
355 }
356}
357
358#include "moc_davcollectionsfetchjob.cpp"
A helper class to store information about DAV collection.
A job that fetches all DAV collection.
DavCollection::List collections() const
Returns the list of fetched DAV collections.
DavUrl davUrl() const
Return the DavUrl used by this job.
void start() override
Starts the job.
DavCollectionsFetchJob(const DavUrl &url, QObject *parent=nullptr)
Creates a new DAV collections fetch job.
base class for the jobs used by the resource.
Definition davjobbase.h:27
bool canRetryLater() const
Check if the job can be retried later.
Error davError() const
Returns a instance of the KDAV:Error to be able to translate the error.
A job that fetches home sets for a principal.
QStringList homeSets() const
Returns the found home sets.
A helper class to combine URL and protocol of a DAV URL.
Definition davurl.h:27
QUrl url() const
Returns the URL that identifies the DAV object.
Definition davurl.cpp:45
Protocol protocol() const
Returns the DAV protocol dialect that is used to retrieve the DAV object.
Definition davurl.cpp:55
QByteArray responseData() const
QString queryMetaData(const QString &key)
const QUrl & url() const
int error() const
void result(KJob *job)
QString errorText() const
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
The KDAV namespace.
QColor fromString(QAnyStringView name)
bool isValid() const const
bool isValidColorName(QAnyStringView name)
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString toString(int indent) const const
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const const
QString tagName() const const
QString text() const const
bool isNull() const const
QDomElement toElement() const const
QDomNode item(int index) const const
int length() const const
bool isEmpty() const const
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString & append(QChar ch)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
CaseInsensitive
TolerantMode
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
QString password(ComponentFormattingOptions options) const const
void setPath(const QString &path, ParsingMode mode)
void setUserInfo(const QString &userInfo, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString userInfo(ComponentFormattingOptions options) const const
QString userName(ComponentFormattingOptions options) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:34 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.