File indexing completed on 2024-05-05 07:55:01
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"