Libkdav2

davcollectionsfetchjob.cpp
1 /*
2  Copyright (c) 2010 Tobias Koenig <[email protected]>
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 
34 using 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 
62 void 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 
72 void 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 
113 void 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;
241  Q_EMIT collectionDiscovered(mUrl.protocol(), url.toDisplayString(), jobUrl);
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.
256 void 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 
264 void 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 
285 void DavCollectionsFetchJob::subjobFinished()
286 {
287  if (--mSubJobCount == 0) {
288  emitResult();
289  }
290 }
291 
DavJob * createPropFindJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV PROPFIND job.
Definition: davmanager.cpp:66
QUrl url() const
Returns the url that identifies the DAV object.
Definition: davurl.cpp:41
void setFocus(const QXmlItem &item)
QString & append(QChar ch)
DavUrl url() const
Returns the url that identifies the collection.
QString toDisplayString(QUrl::FormattingOptions options) const const
QString CTag() const
Returns this collection CTag.
A helper class to store information about DAV collection.
Definition: davcollection.h:49
void emitResult()
QString userName(QUrl::ComponentFormattingOptions options) const const
static DavManager * self()
Returns the global instance of the DAV manager.
Definition: davmanager.cpp:52
base class for the jobs used by the resource.
Definition: davjobbase.h:37
QString & prepend(QChar ch)
void setError(int errorCode)
void setPassword(const QString &password, QUrl::ParsingMode mode)
TolerantMode
QDomElement documentElement() const const
void start() override
Starts the job.
void setUrl(const QUrl &url)
Sets the url that identifies the DAV object.
Definition: davurl.cpp:36
A helper class to combine url and protocol of a DAV url.
Definition: davurl.h:35
int size() const const
void setPath(const QString &path, QUrl::ParsingMode mode)
QString toDisplayString() const
Returns the url in a userfriendly way without login informations.
Definition: davurl.cpp:56
QString localName() const const
void setQuery(const QString &sourceCode, const QUrl &documentURI)
bool extractCollection(const QDomElement &response, DavUrl url, DavCollection &collection)
Extract a DavCollection from the response element of a PROPFIND result.
Definition: utils.cpp:185
DavUrl davUrl() const
Return the DavUrl used by this job.
const DavProtocolBase * davProtocol(Protocol protocol)
Returns the DAV protocol dialect object for the given DAV protocol.
Definition: davmanager.cpp:140
CaseInsensitive
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
A job that fetches a DAV collection from the DAV server.
QStringList homeSets() const
Returns the found home sets.
QString password(QUrl::ComponentFormattingOptions options) const const
void setUserName(const QString &userName, QUrl::ParsingMode mode)
Error davError() const
Returns a instance of the KDAV2:Error to be able to translate the error.
Definition: davjobbase.cpp:82
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
bool isNull() const const
Protocol protocol() const
Returns the DAV protocol dialect that is used to retrieve the DAV object.
Definition: davurl.cpp:51
DavCollectionsFetchJob(const DavUrl &url, QObject *parent=nullptr)
Creates a new dav collections fetch job.
void evaluateTo(QXmlResultItems *result) const const
A job that fetches home sets for a principal.
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
DavCollection::List collections() const
Returns the list of fetched DAV collections.
void result(KJob *job)
void setErrorFromJob(DavJob *, ErrorNumber jobErrorCode=ERR_PROBLEM_WITH_REQUEST)
Set the error of this job from a failed DavJob (executed by this job).
Definition: davjobbase.cpp:99
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
void setUserInfo(const QString &userInfo, QUrl::ParsingMode mode)
int compare(const QString &other, Qt::CaseSensitivity cs) const const
void collectionDiscovered(int protocol, const QString &collectionUrl, const QString &configuredUrl)
This signal is emitted every time a new collection has been discovered.
Q_EMITQ_EMIT
int error() const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jan 27 2022 23:10:33 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.