Libkdav2

davprincipalsearchjob.cpp
1 /*
2  Copyright (c) 2011 GrĂ©gory Oestreicher <[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 "davprincipalsearchjob.h"
20 
21 #include "davmanager.h"
22 #include "utils.h"
23 #include "daverror.h"
24 #include "davjob.h"
25 
26 #include <QtCore/QUrl>
27 
28 using namespace KDAV2;
29 
31  const QString &filter, QObject *parent)
32  : DavJobBase(parent), mUrl(url), mType(type), mFilter(filter), mPrincipalPropertySearchSubJobCount(0),
33  mPrincipalPropertySearchSubJobSuccessful(false)
34 {
35 }
36 
38 {
39  QString propNamespace = ns;
40  if (propNamespace.isEmpty()) {
41  propNamespace = QStringLiteral("DAV:");
42  }
43 
44  mFetchProperties << QPair<QString, QString>(propNamespace, name);
45 }
46 
48 {
49  return mUrl;
50 }
51 
53 {
54  /*
55  * The first step is to try to locate the URL that contains the principals.
56  * This is done with a PROPFIND request and a XML like this:
57  * <?xml version="1.0" encoding="utf-8" ?>
58  * <D:propfind xmlns:D="DAV:">
59  * <D:prop>
60  * <D:principal-collection-set/>
61  * </D:prop>
62  * </D:propfind>
63  */
64  QDomDocument query;
65 
66  QDomElement propfind = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
67  query.appendChild(propfind);
68 
69  QDomElement prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
70  propfind.appendChild(prop);
71 
72  QDomElement principalCollectionSet = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-collection-set"));
73  prop.appendChild(principalCollectionSet);
74 
75  DavJob *job = DavManager::self()->createPropFindJob(mUrl.url(), query);
76  connect(job, &DavJob::result, this, &DavPrincipalSearchJob::principalCollectionSetSearchFinished);
77  job->start();
78 }
79 
80 void DavPrincipalSearchJob::principalCollectionSetSearchFinished(KJob *job)
81 {
82  DavJob *davJob = qobject_cast<DavJob *>(job);
83  if (davJob->error()) {
84  setErrorFromJob(davJob);
85 
86  emitResult();
87  return;
88  }
89 
90  if (job->error()) {
91  setError(job->error());
92  setErrorText(job->errorText());
93  emitResult();
94  return;
95  }
96 
97  /*
98  * Extract information from a document like the following:
99  *
100  * <?xml version="1.0" encoding="utf-8" ?>
101  * <D:multistatus xmlns:D="DAV:">
102  * <D:response>
103  * <D:href>http://www.example.com/papers/</D:href>
104  * <D:propstat>
105  * <D:prop>
106  * <D:principal-collection-set>
107  * <D:href>http://www.example.com/acl/users/</D:href>
108  * <D:href>http://www.example.com/acl/groups/</D:href>
109  * </D:principal-collection-set>
110  * </D:prop>
111  * <D:status>HTTP/1.1 200 OK</D:status>
112  * </D:propstat>
113  * </D:response>
114  * </D:multistatus>
115  */
116 
117  QDomDocument document = davJob->response();
118  QDomElement documentElement = document.documentElement();
119 
120  QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response"));
121  if (responseElement.isNull()) {
122  emitResult();
123  return;
124  }
125 
126  // check for the valid propstat, without giving up on first error
127  QDomElement propstatElement;
128  {
129  const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
130  for (int i = 0; i < propstats.length(); ++i) {
131  const QDomElement propstatCandidate = propstats.item(i).toElement();
132  const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
133  if (statusElement.text().contains(QStringLiteral("200"))) {
134  propstatElement = propstatCandidate;
135  }
136  }
137  }
138 
139  if (propstatElement.isNull()) {
140  emitResult();
141  return;
142  }
143 
144  QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
145  if (propElement.isNull()) {
146  emitResult();
147  return;
148  }
149 
150  QDomElement principalCollectionSetElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("principal-collection-set"));
151  if (principalCollectionSetElement.isNull()) {
152  emitResult();
153  return;
154  }
155 
156  QDomNodeList hrefNodes = principalCollectionSetElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("href"));
157  for (int i = 0; i < hrefNodes.size(); ++i) {
158  QDomElement hrefElement = hrefNodes.at(i).toElement();
159  QString href = hrefElement.text();
160 
161  QUrl url = mUrl.url();
162  if (href.startsWith(QLatin1Char('/'))) {
163  // href is only a path, use request url to complete
164  url.setPath(href, QUrl::TolerantMode);
165  } else {
166  // href is a complete url
167  QUrl tmpUrl(href);
168  tmpUrl.setUserName(url.userName());
169  tmpUrl.setPassword(url.password());
170  url = tmpUrl;
171  }
172 
173  QDomDocument principalPropertySearchQuery;
174  buildReportQuery(principalPropertySearchQuery);
175  DavJob *reportJob = DavManager::self()->createReportJob(url, principalPropertySearchQuery);
176  connect(reportJob, &DavJob::result, this, &DavPrincipalSearchJob::principalPropertySearchFinished);
177  ++mPrincipalPropertySearchSubJobCount;
178  reportJob->start();
179  }
180 }
181 
182 void DavPrincipalSearchJob::principalPropertySearchFinished(KJob *job)
183 {
184  --mPrincipalPropertySearchSubJobCount;
185 
186  if (job->error() && !mPrincipalPropertySearchSubJobSuccessful) {
187  setError(job->error());
188  setErrorText(job->errorText());
189  if (mPrincipalPropertySearchSubJobCount == 0) {
190  emitResult();
191  }
192  return;
193  }
194 
195  DavJob *davJob = static_cast<DavJob *>(job);
196 
197  const int responseCode = davJob->httpStatusCode();
198 
199  if (responseCode > 499 && responseCode < 600 && !mPrincipalPropertySearchSubJobSuccessful) {
200  // Server-side error, unrecoverable
201  setErrorFromJob(davJob, ERR_SERVER_UNRECOVERABLE);
202  if (mPrincipalPropertySearchSubJobCount == 0) {
203  emitResult();
204  }
205  return;
206  } else if (responseCode > 399 && responseCode < 500 && !mPrincipalPropertySearchSubJobSuccessful) {
207  setErrorFromJob(davJob);
208  if (mPrincipalPropertySearchSubJobCount == 0) {
209  emitResult();
210  }
211  return;
212  }
213 
214  if (!mPrincipalPropertySearchSubJobSuccessful) {
215  setError(0); // nope, everything went fine
216  mPrincipalPropertySearchSubJobSuccessful = true;
217  }
218 
219  /*
220  * Extract infos from a document like the following:
221  * <?xml version="1.0" encoding="utf-8" ?>
222  * <D:multistatus xmlns:D="DAV:" xmlns:B="http://BigCorp.com/ns/">
223  * <D:response>
224  * <D:href>http://www.example.com/users/jdoe</D:href>
225  * <D:propstat>
226  * <D:prop>
227  * <D:displayname>John Doe</D:displayname>
228  * </D:prop>
229  * <D:status>HTTP/1.1 200 OK</D:status>
230  * </D:propstat>
231  * </D:multistatus>
232  */
233 
234  const QDomDocument document = davJob->response();
235  const QDomElement documentElement = document.documentElement();
236 
237  QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response"));
238  if (responseElement.isNull()) {
239  if (mPrincipalPropertySearchSubJobCount == 0) {
240  emitResult();
241  }
242  return;
243  }
244 
245  // check for the valid propstat, without giving up on first error
246  QDomElement propstatElement;
247  {
248  const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
249  const int propStatsEnd(propstats.length());
250  for (int i = 0; i < propStatsEnd; ++i) {
251  const QDomElement propstatCandidate = propstats.item(i).toElement();
252  const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
253  if (statusElement.text().contains(QStringLiteral("200"))) {
254  propstatElement = propstatCandidate;
255  }
256  }
257  }
258 
259  if (propstatElement.isNull()) {
260  if (mPrincipalPropertySearchSubJobCount == 0) {
261  emitResult();
262  }
263  return;
264  }
265 
266  QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
267  if (propElement.isNull()) {
268  if (mPrincipalPropertySearchSubJobCount == 0) {
269  emitResult();
270  }
271  return;
272  }
273 
274  // All requested properties are now under propElement, so let's find them
275  typedef QPair<QString, QString> PropertyPair;
276  foreach (const PropertyPair &fetchProperty, mFetchProperties) {
277  QDomNodeList fetchNodes = propElement.elementsByTagNameNS(fetchProperty.first, fetchProperty.second);
278  for (int i = 0; i < fetchNodes.size(); ++i) {
279  QDomElement fetchElement = fetchNodes.at(i).toElement();
280  Result result;
281  result.propertyNamespace = fetchProperty.first;
282  result.property = fetchProperty.second;
283  result.value = fetchElement.text();
284  mResults << result;
285  }
286  }
287 
288  if (mPrincipalPropertySearchSubJobCount == 0) {
289  emitResult();
290  }
291 }
292 
294 {
295  return mResults;
296 }
297 
298 void DavPrincipalSearchJob::buildReportQuery(QDomDocument &query)
299 {
300  /*
301  * Build a document like the following, where XXX will
302  * be replaced by the properties the user want to fetch:
303  *
304  * <?xml version="1.0" encoding="utf-8" ?>
305  * <D:principal-property-search xmlns:D="DAV:">
306  * <D:property-search>
307  * <D:prop>
308  * <D:displayname/>
309  * </D:prop>
310  * <D:match>FILTER</D:match>
311  * </D:property-search>
312  * <D:prop>
313  * XXX
314  * </D:prop>
315  * </D:principal-property-search>
316  */
317 
318  QDomElement principalPropertySearch = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-property-search"));
319  query.appendChild(principalPropertySearch);
320 
321  QDomElement propertySearch = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("property-search"));
322  principalPropertySearch.appendChild(propertySearch);
323 
324  QDomElement prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
325  propertySearch.appendChild(prop);
326 
327  if (mType == DisplayName) {
328  QDomElement displayName = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname"));
329  prop.appendChild(displayName);
330  } else if (mType == EmailAddress) {
331  QDomElement calendarUserAddressSet = query.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-user-address-set"));
332  prop.appendChild(calendarUserAddressSet);
333  //QDomElement hrefElement = query.createElementNS( "DAV:", "href" );
334  //prop.appendChild( hrefElement );
335  }
336 
337  QDomElement match = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("match"));
338  propertySearch.appendChild(match);
339 
340  QDomText propFilter = query.createTextNode(mFilter);
341  match.appendChild(propFilter);
342 
343  prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
344  principalPropertySearch.appendChild(prop);
345 
346  typedef QPair<QString, QString> PropertyPair;
347  foreach (const PropertyPair &fetchProperty, mFetchProperties) {
348  QDomElement elem = query.createElementNS(fetchProperty.first, fetchProperty.second);
349  prop.appendChild(elem);
350  }
351 }
QString text() const const
QDomElement toElement() const const
void setErrorText(const QString &errorText)
void result(KJob *job)
static DavManager * self()
Returns the global instance of the DAV manager.
Definition: davmanager.cpp:52
A helper class to combine url and protocol of a DAV url.
Definition: davurl.h:35
bool isNull() const const
QString userName(QUrl::ComponentFormattingOptions options) const const
TolerantMode
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const const
base class for the jobs used by the resource.
Definition: davjobbase.h:37
DavJob * createReportJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV REPORT job.
Definition: davmanager.cpp:73
QDomNode at(int index) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int size() const const
void fetchProperty(const QString &name, const QString &ns=QString())
Add a new property to fetch from the server.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
DavUrl davUrl() const
Return the DavUrl used by this job.
QDomNode item(int index) const const
bool isEmpty() const const
QString errorText() const
QDomElement documentElement() const const
FilterType
Types of search that are supported by this job.
DavPrincipalSearchJob(const DavUrl &url, FilterType type, const QString &filter, QObject *parent=nullptr)
Creates a new dav principal search job.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void start() override
Starts the job.
QDomNode appendChild(const QDomNode &newChild)
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
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
void setPath(const QString &path, QUrl::ParsingMode mode)
QString password(QUrl::ComponentFormattingOptions options) const const
void emitResult()
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
int error() const
QList< Result > results() const
Get the job results.
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QUrl url() const
Returns the url that identifies the DAV object.
Definition: davurl.cpp:41
int length() const const
DavJob * createPropFindJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV PROPFIND job.
Definition: davmanager.cpp:66
void setError(int errorCode)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 8 2022 04:15:11 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.