File indexing completed on 2024-11-17 04:44:02
0001 /* 0002 Copyright (c) 2011 Grégory Oestreicher <greg@kamago.net> 0003 0004 This program is free software; you can redistribute it and/or modify 0005 it under the terms of the GNU General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or 0007 (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; if not, write to the Free Software 0016 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0017 */ 0018 0019 #include "davprincipalsearchjob.h" 0020 0021 #include "davmanager.h" 0022 #include "utils.h" 0023 #include "daverror.h" 0024 #include "davjob.h" 0025 0026 #include <QtCore/QUrl> 0027 0028 using namespace KDAV2; 0029 0030 DavPrincipalSearchJob::DavPrincipalSearchJob(const DavUrl &url, DavPrincipalSearchJob::FilterType type, 0031 const QString &filter, QObject *parent) 0032 : DavJobBase(parent), mUrl(url), mType(type), mFilter(filter), mPrincipalPropertySearchSubJobCount(0), 0033 mPrincipalPropertySearchSubJobSuccessful(false) 0034 { 0035 } 0036 0037 void DavPrincipalSearchJob::fetchProperty(const QString &name, const QString &ns) 0038 { 0039 QString propNamespace = ns; 0040 if (propNamespace.isEmpty()) { 0041 propNamespace = QStringLiteral("DAV:"); 0042 } 0043 0044 mFetchProperties << QPair<QString, QString>(propNamespace, name); 0045 } 0046 0047 DavUrl DavPrincipalSearchJob::davUrl() const 0048 { 0049 return mUrl; 0050 } 0051 0052 void DavPrincipalSearchJob::start() 0053 { 0054 /* 0055 * The first step is to try to locate the URL that contains the principals. 0056 * This is done with a PROPFIND request and a XML like this: 0057 * <?xml version="1.0" encoding="utf-8" ?> 0058 * <D:propfind xmlns:D="DAV:"> 0059 * <D:prop> 0060 * <D:principal-collection-set/> 0061 * </D:prop> 0062 * </D:propfind> 0063 */ 0064 QDomDocument query; 0065 0066 QDomElement propfind = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind")); 0067 query.appendChild(propfind); 0068 0069 QDomElement prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); 0070 propfind.appendChild(prop); 0071 0072 QDomElement principalCollectionSet = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-collection-set")); 0073 prop.appendChild(principalCollectionSet); 0074 0075 DavJob *job = DavManager::self()->createPropFindJob(mUrl.url(), query); 0076 connect(job, &DavJob::result, this, &DavPrincipalSearchJob::principalCollectionSetSearchFinished); 0077 job->start(); 0078 } 0079 0080 void DavPrincipalSearchJob::principalCollectionSetSearchFinished(KJob *job) 0081 { 0082 DavJob *davJob = qobject_cast<DavJob *>(job); 0083 if (davJob->error()) { 0084 setErrorFromJob(davJob); 0085 0086 emitResult(); 0087 return; 0088 } 0089 0090 if (job->error()) { 0091 setError(job->error()); 0092 setErrorText(job->errorText()); 0093 emitResult(); 0094 return; 0095 } 0096 0097 /* 0098 * Extract information from a document like the following: 0099 * 0100 * <?xml version="1.0" encoding="utf-8" ?> 0101 * <D:multistatus xmlns:D="DAV:"> 0102 * <D:response> 0103 * <D:href>http://www.example.com/papers/</D:href> 0104 * <D:propstat> 0105 * <D:prop> 0106 * <D:principal-collection-set> 0107 * <D:href>http://www.example.com/acl/users/</D:href> 0108 * <D:href>http://www.example.com/acl/groups/</D:href> 0109 * </D:principal-collection-set> 0110 * </D:prop> 0111 * <D:status>HTTP/1.1 200 OK</D:status> 0112 * </D:propstat> 0113 * </D:response> 0114 * </D:multistatus> 0115 */ 0116 0117 QDomDocument document = davJob->response(); 0118 QDomElement documentElement = document.documentElement(); 0119 0120 QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0121 if (responseElement.isNull()) { 0122 emitResult(); 0123 return; 0124 } 0125 0126 // check for the valid propstat, without giving up on first error 0127 QDomElement propstatElement; 0128 { 0129 const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); 0130 for (int i = 0; i < propstats.length(); ++i) { 0131 const QDomElement propstatCandidate = propstats.item(i).toElement(); 0132 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status")); 0133 if (statusElement.text().contains(QStringLiteral("200"))) { 0134 propstatElement = propstatCandidate; 0135 } 0136 } 0137 } 0138 0139 if (propstatElement.isNull()) { 0140 emitResult(); 0141 return; 0142 } 0143 0144 QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); 0145 if (propElement.isNull()) { 0146 emitResult(); 0147 return; 0148 } 0149 0150 QDomElement principalCollectionSetElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("principal-collection-set")); 0151 if (principalCollectionSetElement.isNull()) { 0152 emitResult(); 0153 return; 0154 } 0155 0156 QDomNodeList hrefNodes = principalCollectionSetElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("href")); 0157 for (int i = 0; i < hrefNodes.size(); ++i) { 0158 QDomElement hrefElement = hrefNodes.at(i).toElement(); 0159 QString href = hrefElement.text(); 0160 0161 QUrl url = mUrl.url(); 0162 if (href.startsWith(QLatin1Char('/'))) { 0163 // href is only a path, use request url to complete 0164 url.setPath(href, QUrl::TolerantMode); 0165 } else { 0166 // href is a complete url 0167 QUrl tmpUrl(href); 0168 tmpUrl.setUserName(url.userName()); 0169 tmpUrl.setPassword(url.password()); 0170 url = tmpUrl; 0171 } 0172 0173 QDomDocument principalPropertySearchQuery; 0174 buildReportQuery(principalPropertySearchQuery); 0175 DavJob *reportJob = DavManager::self()->createReportJob(url, principalPropertySearchQuery); 0176 connect(reportJob, &DavJob::result, this, &DavPrincipalSearchJob::principalPropertySearchFinished); 0177 ++mPrincipalPropertySearchSubJobCount; 0178 reportJob->start(); 0179 } 0180 } 0181 0182 void DavPrincipalSearchJob::principalPropertySearchFinished(KJob *job) 0183 { 0184 --mPrincipalPropertySearchSubJobCount; 0185 0186 if (job->error() && !mPrincipalPropertySearchSubJobSuccessful) { 0187 setError(job->error()); 0188 setErrorText(job->errorText()); 0189 if (mPrincipalPropertySearchSubJobCount == 0) { 0190 emitResult(); 0191 } 0192 return; 0193 } 0194 0195 DavJob *davJob = static_cast<DavJob *>(job); 0196 0197 const int responseCode = davJob->httpStatusCode(); 0198 0199 if (responseCode > 499 && responseCode < 600 && !mPrincipalPropertySearchSubJobSuccessful) { 0200 // Server-side error, unrecoverable 0201 setErrorFromJob(davJob, ERR_SERVER_UNRECOVERABLE); 0202 if (mPrincipalPropertySearchSubJobCount == 0) { 0203 emitResult(); 0204 } 0205 return; 0206 } else if (responseCode > 399 && responseCode < 500 && !mPrincipalPropertySearchSubJobSuccessful) { 0207 setErrorFromJob(davJob); 0208 if (mPrincipalPropertySearchSubJobCount == 0) { 0209 emitResult(); 0210 } 0211 return; 0212 } 0213 0214 if (!mPrincipalPropertySearchSubJobSuccessful) { 0215 setError(0); // nope, everything went fine 0216 mPrincipalPropertySearchSubJobSuccessful = true; 0217 } 0218 0219 /* 0220 * Extract infos from a document like the following: 0221 * <?xml version="1.0" encoding="utf-8" ?> 0222 * <D:multistatus xmlns:D="DAV:" xmlns:B="http://BigCorp.com/ns/"> 0223 * <D:response> 0224 * <D:href>http://www.example.com/users/jdoe</D:href> 0225 * <D:propstat> 0226 * <D:prop> 0227 * <D:displayname>John Doe</D:displayname> 0228 * </D:prop> 0229 * <D:status>HTTP/1.1 200 OK</D:status> 0230 * </D:propstat> 0231 * </D:multistatus> 0232 */ 0233 0234 const QDomDocument document = davJob->response(); 0235 const QDomElement documentElement = document.documentElement(); 0236 0237 QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0238 if (responseElement.isNull()) { 0239 if (mPrincipalPropertySearchSubJobCount == 0) { 0240 emitResult(); 0241 } 0242 return; 0243 } 0244 0245 // check for the valid propstat, without giving up on first error 0246 QDomElement propstatElement; 0247 { 0248 const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); 0249 const int propStatsEnd(propstats.length()); 0250 for (int i = 0; i < propStatsEnd; ++i) { 0251 const QDomElement propstatCandidate = propstats.item(i).toElement(); 0252 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status")); 0253 if (statusElement.text().contains(QStringLiteral("200"))) { 0254 propstatElement = propstatCandidate; 0255 } 0256 } 0257 } 0258 0259 if (propstatElement.isNull()) { 0260 if (mPrincipalPropertySearchSubJobCount == 0) { 0261 emitResult(); 0262 } 0263 return; 0264 } 0265 0266 QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); 0267 if (propElement.isNull()) { 0268 if (mPrincipalPropertySearchSubJobCount == 0) { 0269 emitResult(); 0270 } 0271 return; 0272 } 0273 0274 // All requested properties are now under propElement, so let's find them 0275 typedef QPair<QString, QString> PropertyPair; 0276 foreach (const PropertyPair &fetchProperty, mFetchProperties) { 0277 QDomNodeList fetchNodes = propElement.elementsByTagNameNS(fetchProperty.first, fetchProperty.second); 0278 for (int i = 0; i < fetchNodes.size(); ++i) { 0279 QDomElement fetchElement = fetchNodes.at(i).toElement(); 0280 Result result; 0281 result.propertyNamespace = fetchProperty.first; 0282 result.property = fetchProperty.second; 0283 result.value = fetchElement.text(); 0284 mResults << result; 0285 } 0286 } 0287 0288 if (mPrincipalPropertySearchSubJobCount == 0) { 0289 emitResult(); 0290 } 0291 } 0292 0293 QList< DavPrincipalSearchJob::Result > DavPrincipalSearchJob::results() const 0294 { 0295 return mResults; 0296 } 0297 0298 void DavPrincipalSearchJob::buildReportQuery(QDomDocument &query) 0299 { 0300 /* 0301 * Build a document like the following, where XXX will 0302 * be replaced by the properties the user want to fetch: 0303 * 0304 * <?xml version="1.0" encoding="utf-8" ?> 0305 * <D:principal-property-search xmlns:D="DAV:"> 0306 * <D:property-search> 0307 * <D:prop> 0308 * <D:displayname/> 0309 * </D:prop> 0310 * <D:match>FILTER</D:match> 0311 * </D:property-search> 0312 * <D:prop> 0313 * XXX 0314 * </D:prop> 0315 * </D:principal-property-search> 0316 */ 0317 0318 QDomElement principalPropertySearch = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-property-search")); 0319 query.appendChild(principalPropertySearch); 0320 0321 QDomElement propertySearch = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("property-search")); 0322 principalPropertySearch.appendChild(propertySearch); 0323 0324 QDomElement prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); 0325 propertySearch.appendChild(prop); 0326 0327 if (mType == DisplayName) { 0328 QDomElement displayName = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname")); 0329 prop.appendChild(displayName); 0330 } else if (mType == EmailAddress) { 0331 QDomElement calendarUserAddressSet = query.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-user-address-set")); 0332 prop.appendChild(calendarUserAddressSet); 0333 //QDomElement hrefElement = query.createElementNS( "DAV:", "href" ); 0334 //prop.appendChild( hrefElement ); 0335 } 0336 0337 QDomElement match = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("match")); 0338 propertySearch.appendChild(match); 0339 0340 QDomText propFilter = query.createTextNode(mFilter); 0341 match.appendChild(propFilter); 0342 0343 prop = query.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); 0344 principalPropertySearch.appendChild(prop); 0345 0346 typedef QPair<QString, QString> PropertyPair; 0347 foreach (const PropertyPair &fetchProperty, mFetchProperties) { 0348 QDomElement elem = query.createElementNS(fetchProperty.first, fetchProperty.second); 0349 prop.appendChild(elem); 0350 } 0351 }