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 }