File indexing completed on 2024-11-17 04:44:02

0001 /*
0002     Copyright (c) 2010 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 "davprincipalhomesetsfetchjob.h"
0020 
0021 #include "davmanager.h"
0022 #include "davprotocolbase.h"
0023 #include "daverror.h"
0024 #include "utils.h"
0025 #include "davjob.h"
0026 
0027 using namespace KDAV2;
0028 
0029 DavPrincipalHomeSetsFetchJob::DavPrincipalHomeSetsFetchJob(const DavUrl &url, QObject *parent)
0030     : DavJobBase(parent), mUrl(url)
0031 {
0032 }
0033 
0034 void DavPrincipalHomeSetsFetchJob::start()
0035 {
0036     fetchHomeSets(false);
0037 }
0038 
0039 void DavPrincipalHomeSetsFetchJob::fetchHomeSets(bool homeSetsOnly)
0040 {
0041     QDomDocument document;
0042 
0043     QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
0044     document.appendChild(propfindElement);
0045 
0046     QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
0047     propfindElement.appendChild(propElement);
0048 
0049     const QString homeSet = DavManager::self()->davProtocol(mUrl.protocol())->principalHomeSet();
0050     const QString homeSetNS = DavManager::self()->davProtocol(mUrl.protocol())->principalHomeSetNS();
0051     propElement.appendChild(document.createElementNS(homeSetNS, homeSet));
0052 
0053     if (!homeSetsOnly) {
0054         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-principal")));
0055         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("principal-URL")));
0056     }
0057 
0058     DavJob *job = DavManager::self()->createPropFindJob(mUrl.url(), document, QStringLiteral("0"));
0059     connect(job, &DavJob::result, this, &DavPrincipalHomeSetsFetchJob::davJobFinished);
0060 }
0061 
0062 QStringList DavPrincipalHomeSetsFetchJob::homeSets() const
0063 {
0064     return mHomeSets;
0065 }
0066 
0067 QUrl DavPrincipalHomeSetsFetchJob::url() const
0068 {
0069     return mUrl.url();
0070 }
0071 void DavPrincipalHomeSetsFetchJob::davJobFinished(KJob *job)
0072 {
0073     auto davJob = static_cast<DavJob*>(job);
0074     if (davJob->error()) {
0075         setErrorFromJob(davJob);
0076         emitResult();
0077         return;
0078     }
0079 
0080     /*
0081      * Extract information from a document like the following (if no homeset is defined) :
0082      *
0083      * <D:multistatus xmlns:D="DAV:">
0084      *  <D:response xmlns:D="DAV:">
0085      *   <D:href xmlns:D="DAV:">/dav/</D:href>
0086      *   <D:propstat xmlns:D="DAV:">
0087      *    <D:status xmlns:D="DAV:">HTTP/1.1 200 OK</D:status>
0088      *    <D:prop xmlns:D="DAV:">
0089      *     <D:current-user-principal xmlns:D="DAV:">
0090      *      <D:href xmlns:D="DAV:">/principals/users/gdacoin/</D:href>
0091      *     </D:current-user-principal>
0092      *    </D:prop>
0093      *   </D:propstat>
0094      *   <D:propstat xmlns:D="DAV:">
0095      *    <D:status xmlns:D="DAV:">HTTP/1.1 404 Not Found</D:status>
0096      *    <D:prop xmlns:D="DAV:">
0097      *     <principal-URL xmlns="DAV:"/>
0098      *     <calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav"/>
0099      *    </D:prop>
0100      *   </D:propstat>
0101      *  </D:response>
0102      * </D:multistatus>
0103      *
0104      * Or like this (if the homeset is defined):
0105      *
0106      *  <?xml version="1.0" encoding="utf-8" ?>
0107      *  <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
0108      *    <response>
0109      *      <href>/principals/users/greg%40kamago.net/</href>
0110      *      <propstat>
0111      *        <prop>
0112      *          <C:calendar-home-set>
0113      *            <href>/greg%40kamago.net/</href>
0114      *          </C:calendar-home-set>
0115      *        </prop>
0116      *        <status>HTTP/1.1 200 OK</status>
0117      *      </propstat>
0118      *    </response>
0119      *  </multistatus>
0120      */
0121 
0122     mUrl.setUrl(davJob->url());
0123     const QString homeSet = DavManager::self()->davProtocol(mUrl.protocol())->principalHomeSet();
0124     const QString homeSetNS = DavManager::self()->davProtocol(mUrl.protocol())->principalHomeSetNS();
0125     QString nextRoundHref; // The content of the href element that will be used if no homeset was found.
0126     // This is either given by current-user-principal or by principal-URL.
0127 
0128     const QDomDocument document = davJob->response();
0129     const QDomElement multistatusElement = document.documentElement();
0130 
0131     QDomElement responseElement = Utils::firstChildElementNS(multistatusElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0132     while (!responseElement.isNull()) {
0133 
0134         const QDomElement propstatElement = [&] {
0135             // check for the valid propstat, without giving up on first error
0136             const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
0137             for (int i = 0; i < propstats.length(); ++i) {
0138                 const QDomElement propstatCandidate = propstats.item(i).toElement();
0139                 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
0140                 if (statusElement.text().contains(QLatin1String("200"))) {
0141                     return propstatCandidate;
0142                 }
0143             }
0144             return QDomElement{};
0145         }();
0146 
0147         if (propstatElement.isNull()) {
0148             responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0149             continue;
0150         }
0151 
0152         // extract home sets
0153         const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
0154         const QDomElement homeSetElement = Utils::firstChildElementNS(propElement, homeSetNS, homeSet);
0155 
0156         if (!homeSetElement.isNull()) {
0157             QDomElement hrefElement = Utils::firstChildElementNS(homeSetElement, QStringLiteral("DAV:"), QStringLiteral("href"));
0158 
0159             while (!hrefElement.isNull()) {
0160                 const QString href = hrefElement.text();
0161                 if (!mHomeSets.contains(href)) {
0162                     mHomeSets << href;
0163                 }
0164 
0165                 hrefElement = Utils::nextSiblingElementNS(hrefElement, QStringLiteral("DAV:"), QStringLiteral("href"));
0166             }
0167         } else {
0168             // Trying to get the principal url, given either by current-user-principal or principal-URL
0169             QDomElement urlHolder = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("current-user-principal"));
0170             if (urlHolder.isNull()) {
0171                 urlHolder = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("principal-URL"));
0172             }
0173 
0174             if (!urlHolder.isNull()) {
0175                 // Getting the href that will be used for the next round
0176                 QDomElement hrefElement = Utils::firstChildElementNS(urlHolder, QStringLiteral("DAV:"), QStringLiteral("href"));
0177                 if (!hrefElement.isNull()) {
0178                     nextRoundHref = hrefElement.text();
0179                 }
0180             }
0181         }
0182 
0183         responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
0184     }
0185 
0186     /*
0187      * Now either we got one or more homesets, or we got an href for the next round
0188      * or nothing can be found by this job.
0189      * If we have homesets, we're done here and can notify the caller.
0190      * Else we must ensure that we have an href for the next round.
0191      */
0192     if (!mHomeSets.isEmpty() || nextRoundHref.isEmpty()) {
0193         emitResult();
0194     } else {
0195         QUrl nextRoundUrl(mUrl.url());
0196 
0197         if (nextRoundHref.startsWith(QLatin1Char('/'))) {
0198             // nextRoundHref is only a path, use request url to complete
0199             nextRoundUrl.setPath(nextRoundHref, QUrl::TolerantMode);
0200         } else {
0201             // href is a complete url
0202             nextRoundUrl = QUrl::fromUserInput(nextRoundHref);
0203             nextRoundUrl.setUserName(mUrl.url().userName());
0204             nextRoundUrl.setPassword(mUrl.url().password());
0205         }
0206 
0207         mUrl.setUrl(nextRoundUrl);
0208         // And one more round, fetching only homesets
0209         fetchHomeSets(true);
0210     }
0211 }