Libkdav2

davprincipalsearchjob.cpp
1/*
2 Copyright (c) 2011 Grégory Oestreicher <greg@kamago.net>
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
28using 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
80void 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
182void 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();
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
297
298void 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}
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).
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.
DavJob * createReportJob(const QUrl &url, const QDomDocument &document, const QString &depth=QStringLiteral("1"))
Returns a preconfigured DAV REPORT job.
DavPrincipalSearchJob(const DavUrl &url, FilterType type, const QString &filter, QObject *parent=nullptr)
Creates a new dav principal search job.
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.
QList< Result > results() const
Get the job results.
FilterType
Types of search that are supported by this job.
void start() override
Starts the job.
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
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
QString errorText() const
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
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
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QDomElement documentElement() const const
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const const
QString text() const const
QDomNode appendChild(const QDomNode &newChild)
bool isNull() const const
QDomElement toElement() const const
QDomNode at(int index) const const
QDomNode item(int index) const const
int length() const const
int size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
TolerantMode
QString password(ComponentFormattingOptions options) const const
void setPath(const QString &path, ParsingMode mode)
QString userName(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:50:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.