Libkdav2

davcollectionsfetchjob.cpp
1/*
2 Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17*/
18
19#include "davcollectionsfetchjob.h"
20
21#include "davmanager.h"
22#include "davprincipalhomesetsfetchjob.h"
23#include "davcollectionfetchjob.h"
24#include "davprotocolbase.h"
25#include "utils.h"
26#include "daverror.h"
27#include "davjob.h"
28
29#include "libkdav2_debug.h"
30
31#include <QtCore/QBuffer>
32#include <QtXmlPatterns/QXmlQuery>
33
34using namespace KDAV2;
35
37 : DavJobBase(parent), mUrl(url), mSubJobCount(0)
38{
39}
40
42{
43 if (DavManager::self()->davProtocol(mUrl.protocol())->supportsPrincipals()) {
45 connect(job, &DavPrincipalHomeSetsFetchJob::result, this, &DavCollectionsFetchJob::principalFetchFinished);
46 job->start();
47 } else {
48 doCollectionsFetch(mUrl.url());
49 }
50}
51
53{
54 return mCollections;
55}
56
58{
59 return mUrl;
60}
61
62void DavCollectionsFetchJob::doCollectionsFetch(const QUrl &url)
63{
64 ++mSubJobCount;
65
66 const QDomDocument collectionQuery = DavManager::self()->davProtocol(mUrl.protocol())->collectionsQuery()->buildQuery();
67
68 auto job = DavManager::self()->createPropFindJob(url, collectionQuery);
69 connect(job, &DavJob::result, this, &DavCollectionsFetchJob::collectionsFetchFinished);
70}
71
72void DavCollectionsFetchJob::principalFetchFinished(KJob *job)
73{
75 if (davJob->error()) {
76 // Just give up here.
77 setDavError(davJob->davError());
78 emitResult();
79
80 return;
81 }
82
83 const QStringList homeSets = davJob->homeSets();
84 qCDebug(KDAV2_LOG) << "Found " << homeSets.size() << " homesets\n " << homeSets;
85
86 //Update the url in case of redirects
87 mUrl.setUrl(davJob->url());
88
89 if (homeSets.isEmpty()) {
90 // Same as above, retry as if it were a calendar URL.
91 doCollectionsFetch(mUrl.url());
92 return;
93 }
94
95 foreach (const QString &homeSet, homeSets) {
96 QUrl url = mUrl.url();
97
98 if (homeSet.startsWith(QLatin1Char('/'))) {
99 // homeSet is only a path, use request url to complete
100 url.setPath(homeSet, QUrl::TolerantMode);
101 } else {
102 // homeSet is a complete url
103 QUrl tmpUrl(homeSet);
104 tmpUrl.setUserName(url.userName());
105 tmpUrl.setPassword(url.password());
106 url = tmpUrl;
107 }
108
109 doCollectionsFetch(url);
110 }
111}
112
113void DavCollectionsFetchJob::collectionsFetchFinished(KJob *job)
114{
115 auto davJob = static_cast<DavJob *>(job);
116
117 if (davJob->error()) {
118 setErrorFromJob(davJob);
119 } else {
120 // For use in the collectionDiscovered() signal
121 QUrl _jobUrl = mUrl.url();
122 _jobUrl.setUserInfo(QString());
123 const QString jobUrl = _jobUrl.toDisplayString();
124
125 // Validate that we got a valid PROPFIND response
126 QDomElement rootElement = davJob->response().documentElement();
127 if (rootElement.localName().compare(QStringLiteral("multistatus"), Qt::CaseInsensitive) != 0) {
128 setError(ERR_COLLECTIONFETCH);
129 setErrorTextFromDavError();
130 subjobFinished();
131 return;
132 }
133
134 QByteArray resp(davJob->response().toByteArray());
135 QBuffer buffer(&resp);
136 buffer.open(QIODevice::ReadOnly);
137
138 QXmlQuery xquery;
139 if (!xquery.setFocus(&buffer)) {
140 setError(ERR_COLLECTIONFETCH_XQUERY_SETFOCUS);
141 setErrorTextFromDavError();
142 subjobFinished();
143 return;
144 }
145
146 xquery.setQuery(DavManager::self()->davProtocol(mUrl.protocol())->collectionsXQuery());
147 if (!xquery.isValid()) {
148 setError(ERR_COLLECTIONFETCH_XQUERY_INVALID);
149 setErrorTextFromDavError();
150 subjobFinished();
151 return;
152 }
153
154 QString responsesStr;
155 xquery.evaluateTo(&responsesStr);
156 responsesStr.prepend(QStringLiteral("<responses>"));
157 responsesStr.append(QStringLiteral("</responses>"));
158
159 QDomDocument document;
160 if (!document.setContent(responsesStr, true)) {
161 setError(ERR_COLLECTIONFETCH);
162 setErrorTextFromDavError();
163 subjobFinished();
164 return;
165 }
166
167 if (!error()) {
168 /*
169 * Extract information from a document like the following:
170 *
171 * <responses>
172 * <response xmlns="DAV:">
173 * <href xmlns="DAV:">/caldav.php/test1.user/home/</href>
174 * <propstat xmlns="DAV:">
175 * <prop xmlns="DAV:">
176 * <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav">
177 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/>
178 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/>
179 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/>
180 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/>
181 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/>
182 * </C:supported-calendar-component-set>
183 * <resourcetype xmlns="DAV:">
184 * <collection xmlns="DAV:"/>
185 * <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
186 * <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
187 * </resourcetype>
188 * <displayname xmlns="DAV:">Test1 User</displayname>
189 * <current-user-privilege-set xmlns="DAV:">
190 * <privilege xmlns="DAV:">
191 * <read xmlns="DAV:"/>
192 * </privilege>
193 * </current-user-privilege-set>
194 * <getctag xmlns="http://calendarserver.org/ns/">12345</getctag>
195 * </prop>
196 * <status xmlns="DAV:">HTTP/1.1 200 OK</status>
197 * </propstat>
198 * </response>
199 * </responses>
200 */
201
202 const QDomElement responsesElement = document.documentElement();
203
204 QDomElement responseElement = Utils::firstChildElementNS(
205 responsesElement, QStringLiteral("DAV:"), QStringLiteral("response"));
206 while (!responseElement.isNull()) {
207
208 DavCollection collection;
209 if (!Utils::extractCollection(responseElement, mUrl, collection)) {
210 continue;
211 }
212
213 QUrl url = collection.url().url();
214
215 // don't add this resource if it has already been detected
216 bool alreadySeen = false;
217 foreach (const DavCollection &seen, mCollections) {
218 if (seen.url().toDisplayString() == url.toDisplayString()) {
219 alreadySeen = true;
220 }
221 }
222 if (alreadySeen) {
223 responseElement = Utils::nextSiblingElementNS(
224 responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
225 continue;
226 }
227
228 bool protocolSupportsCTags = DavManager::self()->davProtocol(mUrl.protocol())->supportsCTags();
229 if (protocolSupportsCTags && collection.CTag() == "") {
230 qCDebug(KDAV2_LOG) << "No CTag found for"
231 << collection.url().url().toDisplayString()
232 << "from the home set, trying from the direct URL";
233 refreshIndividualCollection(collection);
234
235 responseElement = Utils::nextSiblingElementNS(
236 responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
237 continue;
238 }
239
240 mCollections << collection;
242
243 responseElement = Utils::nextSiblingElementNS(
244 responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
245 }
246 }
247 }
248
249 subjobFinished();
250}
251
252// This is a workaroud for Google who doesn't support providing the CTag
253// directly from the home set. We refresh the collections individually from
254// their own URLs, but only if we haven't found a CTag with the home set
255// request.
256void DavCollectionsFetchJob::refreshIndividualCollection(const DavCollection &collection)
257{
258 ++mSubJobCount;
259 auto individualFetchJob = new DavCollectionFetchJob(collection, this);
260 connect(individualFetchJob, &DavCollectionFetchJob::result, this, &DavCollectionsFetchJob::individualCollectionRefreshed);
261 individualFetchJob->start();
262}
263
264void DavCollectionsFetchJob::individualCollectionRefreshed(KJob *job)
265{
266 const auto *davJob = qobject_cast<DavCollectionFetchJob *>(job);
267
268 if (davJob->error()) {
269 setDavError(davJob->davError());
270 emitResult();
271 return;
272 }
273
274 qCDebug(KDAV2_LOG) << "Collection"
275 << davJob->collection().url().url().toDisplayString() << "refreshed";
276
277 if (davJob->collection().CTag() == "") {
278 qWarning() << "Collection with an empty CTag";
279 }
280
281 mCollections << davJob->collection();
282 subjobFinished();
283}
284
285void DavCollectionsFetchJob::subjobFinished()
286{
287 if (--mSubJobCount == 0) {
288 emitResult();
289 }
290}
291
A job that fetches a DAV collection from the DAV server.
A helper class to store information about DAV collection.
DavUrl url() const
Returns the url that identifies the collection.
QString CTag() const
Returns this collection CTag.
DavCollection::List collections() const
Returns the list of fetched DAV collections.
DavUrl davUrl() const
Return the DavUrl used by this job.
void collectionDiscovered(int protocol, const QString &collectionUrl, const QString &configuredUrl)
This signal is emitted every time a new collection has been discovered.
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:38
void setErrorFromJob(DavJob *, ErrorNumber jobErrorCode=ERR_PROBLEM_WITH_REQUEST)
Set the error of this job from a failed DavJob (executed by this job).
Error davError() const
Returns a instance of the KDAV2:Error to be able to translate the error.
DavJob * createPropFindJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV PROPFIND job.
static DavManager * self()
Returns the global instance of the DAV manager.
const DavProtocolBase * davProtocol(Protocol protocol)
Returns the DAV protocol dialect object for the given DAV protocol.
A job that fetches home sets for a principal.
QStringList homeSets() const
Returns the found home sets.
virtual XMLQueryBuilder::Ptr collectionsQuery() const =0
Returns the XML document that represents the DAV query to list all available DAV collections.
virtual bool supportsCTags() const =0
Return whether the dav protocol dialect supports CTags.
A helper class to combine url and protocol of a DAV url.
Definition davurl.h:36
QUrl url() const
Returns the url that identifies the DAV object.
Definition davurl.cpp:41
Protocol protocol() const
Returns the DAV protocol dialect that is used to retrieve the DAV object.
Definition davurl.cpp:51
void setUrl(const QUrl &url)
Sets the url that identifies the DAV object.
Definition davurl.cpp:36
QString toDisplayString() const
Returns the url in a userfriendly way without login informations.
Definition davurl.cpp:56
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
bool extractCollection(const QDomElement &response, DavUrl url, DavCollection &collection)
Extract a DavCollection from the response element of a PROPFIND result.
Definition utils.cpp:185
QDomElement KPIMKDAV2_EXPORT firstChildElementNS(const QDomElement &parent, const QString &namespaceUri, const QString &tagName)
Returns the first child element of parent that has the given tagName and is part of the namespaceUri.
Definition utils.cpp:37
QDomElement KPIMKDAV2_EXPORT nextSiblingElementNS(const QDomElement &element, const QString &namespaceUri, const QString &tagName)
Returns the next sibling element of element that has the given tagName and is part of the namespaceUr...
Definition utils.cpp:51
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
bool isNull() const const
QString localName() const const
bool isEmpty() const const
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QString & append(QChar ch)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString & prepend(QChar ch)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
CaseInsensitive
TolerantMode
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 url(FormattingOptions options) const const
QString userName(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.