Warning, file /frameworks/kdav/src/common/davprincipalhomesetsfetchjob.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2010 Grégory Oestreicher <greg@kamago.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "davprincipalhomesetsfetchjob.h"
0008 #include "davjobbase_p.h"
0009 
0010 #include "daverror.h"
0011 #include "davmanager_p.h"
0012 #include "davprotocolbase_p.h"
0013 #include "protocolinfo.h"
0014 #include "utils_p.h"
0015 
0016 #include <KIO/DavJob>
0017 #include <KIO/Job>
0018 
0019 using namespace KDAV;
0020 
0021 namespace KDAV
0022 {
0023 class DavPrincipalHomeSetsFetchJobPrivate : public DavJobBasePrivate
0024 {
0025 public:
0026     void davJobFinished(KJob *job);
0027     /**
0028      * Start the fetch process.
0029      *
0030      * There may be two rounds necessary if the first request
0031      * does not returns the home sets, but only the current-user-principal
0032      * or the principal-URL. The bool flag is here to prevent requesting
0033      * those last two on each request, as they are only fetched in
0034      * the first round.
0035      *
0036      * @param fetchHomeSetsOnly If set to true the request will not include
0037      *        the current-user-principal and principal-URL props.
0038      */
0039     void fetchHomeSets(bool fetchHomeSetsOnly);
0040 
0041     DavUrl mUrl;
0042     QStringList mHomeSets;
0043 };
0044 }
0045 
0046 DavPrincipalHomeSetsFetchJob::DavPrincipalHomeSetsFetchJob(const DavUrl &url, QObject *parent)
0047     : DavJobBase(new DavPrincipalHomeSetsFetchJobPrivate, parent)
0048 {
0049     Q_D(DavPrincipalHomeSetsFetchJob);
0050     d->mUrl = url;
0051 }
0052 
0053 void DavPrincipalHomeSetsFetchJob::start()
0054 {
0055     Q_D(DavPrincipalHomeSetsFetchJob);
0056     d->fetchHomeSets(false);
0057 }
0058 
0059 void DavPrincipalHomeSetsFetchJobPrivate::fetchHomeSets(bool homeSetsOnly)
0060 {
0061     QDomDocument document;
0062 
0063     QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
0064     document.appendChild(propfindElement);
0065 
0066     QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0067     propfindElement.appendChild(propElement);
0068 
0069     const QString homeSet = ProtocolInfo::principalHomeSet(mUrl.protocol());
0070     const QString homeSetNS = ProtocolInfo::principalHomeSetNS(mUrl.protocol());
0071     propElement.appendChild(document.createElementNS(homeSetNS, homeSet));
0072 
0073     if (!homeSetsOnly) {
0074         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-principal")));
0075         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-URL")));
0076     }
0077 
0078     KIO::DavJob *job = DavManager::self()->createPropFindJob(mUrl.url(), document.toString(), QStringLiteral("0"));
0079     job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
0080     QObject::connect(job, &KIO::DavJob::result, q_ptr, [this](KJob *job) {
0081         davJobFinished(job);
0082     });
0083 }
0084 
0085 QStringList DavPrincipalHomeSetsFetchJob::homeSets() const
0086 {
0087     Q_D(const DavPrincipalHomeSetsFetchJob);
0088     return d->mHomeSets;
0089 }
0090 
0091 void DavPrincipalHomeSetsFetchJobPrivate::davJobFinished(KJob *job)
0092 {
0093     KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(job);
0094     const QString responseCodeStr = davJob->queryMetaData(QStringLiteral("responsecode"));
0095     const int responseCode = responseCodeStr.isEmpty() ? 0 : responseCodeStr.toInt();
0096 
0097     // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx
0098     if (davJob->error() || (responseCode >= 400 && responseCode < 600)) {
0099         QString err;
0100         if (davJob->error() && davJob->error() != KIO::ERR_WORKER_DEFINED) {
0101             err = KIO::buildErrorString(davJob->error(), davJob->errorText());
0102         } else {
0103             err = davJob->errorText();
0104         }
0105 
0106         setLatestResponseCode(responseCode);
0107         setError(ERR_PROBLEM_WITH_REQUEST);
0108         setJobErrorText(davJob->errorText());
0109         setJobError(davJob->error());
0110         setErrorTextFromDavError();
0111 
0112         emitResult();
0113         return;
0114     }
0115 
0116     /*
0117      * Extract information from a document like the following (if no homeset is defined) :
0118      *
0119      * <D:multistatus xmlns:D="DAV:">
0120      *  <D:response xmlns:D="DAV:">
0121      *   <D:href xmlns:D="DAV:">/dav/</D:href>
0122      *   <D:propstat xmlns:D="DAV:">
0123      *    <D:status xmlns:D="DAV:">HTTP/1.1 200 OK</D:status>
0124      *    <D:prop xmlns:D="DAV:">
0125      *     <D:current-user-principal xmlns:D="DAV:">
0126      *      <D:href xmlns:D="DAV:">/principals/users/gdacoin/</D:href>
0127      *     </D:current-user-principal>
0128      *    </D:prop>
0129      *   </D:propstat>
0130      *   <D:propstat xmlns:D="DAV:">
0131      *    <D:status xmlns:D="DAV:">HTTP/1.1 404 Not Found</D:status>
0132      *    <D:prop xmlns:D="DAV:">
0133      *     <principal-URL xmlns="DAV:"/>
0134      *     <calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav"/>
0135      *    </D:prop>
0136      *   </D:propstat>
0137      *  </D:response>
0138      * </D:multistatus>
0139      *
0140      * Or like this (if the homeset is defined):
0141      *
0142      *  <?xml version="1.0" encoding="utf-8" ?>
0143      *  <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
0144      *    <response>
0145      *      <href>/principals/users/greg%40kamago.net/</href>
0146      *      <propstat>
0147      *        <prop>
0148      *          <C:calendar-home-set>
0149      *            <href>/greg%40kamago.net/</href>
0150      *          </C:calendar-home-set>
0151      *        </prop>
0152      *        <status>HTTP/1.1 200 OK</status>
0153      *      </propstat>
0154      *    </response>
0155      *  </multistatus>
0156      */
0157 
0158     const QString homeSet = ProtocolInfo::principalHomeSet(mUrl.protocol());
0159     const QString homeSetNS = ProtocolInfo::principalHomeSetNS(mUrl.protocol());
0160     QString nextRoundHref; // The content of the href element that will be used if no homeset was found.
0161     // This is either given by current-user-principal or by principal-URL.
0162 
0163     QDomDocument document;
0164     document.setContent(davJob->responseData(), QDomDocument::ParseOption::UseNamespaceProcessing);
0165     const QDomElement multistatusElement = document.documentElement();
0166 
0167     QDomElement responseElement = Utils::firstChildElementNS(multistatusElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0168     while (!responseElement.isNull()) {
0169         QDomElement propstatElement;
0170 
0171         // check for the valid propstat, without giving up on first error
0172         {
0173             const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
0174             for (int i = 0; i < propstats.length(); ++i) {
0175                 const QDomElement propstatCandidate = propstats.item(i).toElement();
0176                 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
0177                 if (statusElement.text().contains(QLatin1String("200"))) {
0178                     propstatElement = propstatCandidate;
0179                 }
0180             }
0181         }
0182 
0183         if (propstatElement.isNull()) {
0184             responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0185             continue;
0186         }
0187 
0188         // extract home sets
0189         const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
0190         const QDomElement homeSetElement = Utils::firstChildElementNS(propElement, homeSetNS, homeSet);
0191 
0192         if (!homeSetElement.isNull()) {
0193             QDomElement hrefElement = Utils::firstChildElementNS(homeSetElement, QStringLiteral("DAV:"), QStringLiteral("href"));
0194 
0195             while (!hrefElement.isNull()) {
0196                 const QString href = hrefElement.text();
0197                 if (!mHomeSets.contains(href)) {
0198                     mHomeSets << href;
0199                 }
0200 
0201                 hrefElement = Utils::nextSiblingElementNS(hrefElement, QStringLiteral("DAV:"), QStringLiteral("href"));
0202             }
0203         } else {
0204             // Trying to get the principal url, given either by current-user-principal or principal-URL
0205             QDomElement urlHolder = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("current-user-principal"));
0206             if (urlHolder.isNull()) {
0207                 urlHolder = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("principal-URL"));
0208             }
0209 
0210             if (!urlHolder.isNull()) {
0211                 // Getting the href that will be used for the next round
0212                 QDomElement hrefElement = Utils::firstChildElementNS(urlHolder, QStringLiteral("DAV:"), QStringLiteral("href"));
0213                 if (!hrefElement.isNull()) {
0214                     nextRoundHref = hrefElement.text();
0215                 }
0216             }
0217         }
0218 
0219         responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0220     }
0221 
0222     /*
0223      * Now either we got one or more homesets, or we got an href for the next round
0224      * or nothing can be found by this job.
0225      * If we have homesets, we're done here and can notify the caller.
0226      * Else we must ensure that we have an href for the next round.
0227      */
0228     if (!mHomeSets.isEmpty() || nextRoundHref.isEmpty()) {
0229         emitResult();
0230     } else {
0231         QUrl nextRoundUrl(mUrl.url());
0232 
0233         if (nextRoundHref.startsWith(QLatin1Char('/'))) {
0234             // nextRoundHref is only a path, use request url to complete
0235             nextRoundUrl.setPath(nextRoundHref, QUrl::TolerantMode);
0236         } else {
0237             // href is a complete url
0238             nextRoundUrl = QUrl::fromUserInput(nextRoundHref);
0239             nextRoundUrl.setUserName(mUrl.url().userName());
0240             nextRoundUrl.setPassword(mUrl.url().password());
0241         }
0242 
0243         mUrl.setUrl(nextRoundUrl);
0244         // And one more round, fetching only homesets
0245         fetchHomeSets(true);
0246     }
0247 }
0248 
0249 #include "moc_davprincipalhomesetsfetchjob.cpp"