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 }
DavJob * createPropFindJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV PROPFIND job.
Definition: davmanager.cpp:66
DavPrincipalSearchJob(const DavUrl &url, FilterType type, const QString &filter, QObject *parent=nullptr)
Creates a new dav principal search job.
QUrl url() const
Returns the url that identifies the DAV object.
Definition: davurl.cpp:41
QDomNode item(int index) const const
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const const
void emitResult()
QString userName(QUrl::ComponentFormattingOptions options) const const
QDomNode appendChild(const QDomNode &newChild)
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
QList< Result > results() const
Get the job results.
void setError(int errorCode)
void setPassword(const QString &password, QUrl::ParsingMode mode)
TolerantMode
QDomElement documentElement() const const
QDomElement createElementNS(const QString &nsURI, const QString &qName)
A helper class to combine url and protocol of a DAV url.
Definition: davurl.h:35
QDomElement toElement() const const
void setPath(const QString &path, QUrl::ParsingMode mode)
void setErrorText(const QString &errorText)
QString text() const const
FilterType
Types of search that are supported by this job.
DavJob * createReportJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV REPORT job.
Definition: davmanager.cpp:73
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
DavUrl davUrl() const
Return the DavUrl used by this job.
void fetchProperty(const QString &name, const QString &ns=QString())
Add a new property to fetch from the server.
Simple struct to hold the search job results.
QString password(QUrl::ComponentFormattingOptions options) const const
QDomText createTextNode(const QString &value)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void setUserName(const QString &userName, QUrl::ParsingMode mode)
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
void start() override
Starts the job.
void result(KJob *job)
int size() const const
int length() 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)
QString errorText() const
int error() const
QDomNode at(int index) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Aug 11 2020 23:22:04 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.