File indexing completed on 2024-05-26 05:17:00
0001 /* 0002 Copyright (c) 2010 Tobias Koenig <tokoe@kde.org> 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 "davcollectionsfetchjob.h" 0020 0021 #include "davmanager.h" 0022 #include "davprincipalhomesetsfetchjob.h" 0023 #include "davcollectionfetchjob.h" 0024 #include "davprotocolbase.h" 0025 #include "utils.h" 0026 #include "daverror.h" 0027 #include "davjob.h" 0028 0029 #include "libkdav2_debug.h" 0030 0031 #include <QtCore/QBuffer> 0032 #include <QtXmlPatterns/QXmlQuery> 0033 0034 using namespace KDAV2; 0035 0036 DavCollectionsFetchJob::DavCollectionsFetchJob(const DavUrl &url, QObject *parent) 0037 : DavJobBase(parent), mUrl(url), mSubJobCount(0) 0038 { 0039 } 0040 0041 void DavCollectionsFetchJob::start() 0042 { 0043 if (DavManager::self()->davProtocol(mUrl.protocol())->supportsPrincipals()) { 0044 DavPrincipalHomeSetsFetchJob *job = new DavPrincipalHomeSetsFetchJob(mUrl); 0045 connect(job, &DavPrincipalHomeSetsFetchJob::result, this, &DavCollectionsFetchJob::principalFetchFinished); 0046 job->start(); 0047 } else { 0048 doCollectionsFetch(mUrl.url()); 0049 } 0050 } 0051 0052 DavCollection::List DavCollectionsFetchJob::collections() const 0053 { 0054 return mCollections; 0055 } 0056 0057 DavUrl DavCollectionsFetchJob::davUrl() const 0058 { 0059 return mUrl; 0060 } 0061 0062 void DavCollectionsFetchJob::doCollectionsFetch(const QUrl &url) 0063 { 0064 ++mSubJobCount; 0065 0066 const QDomDocument collectionQuery = DavManager::self()->davProtocol(mUrl.protocol())->collectionsQuery()->buildQuery(); 0067 0068 auto job = DavManager::self()->createPropFindJob(url, collectionQuery); 0069 connect(job, &DavJob::result, this, &DavCollectionsFetchJob::collectionsFetchFinished); 0070 } 0071 0072 void DavCollectionsFetchJob::principalFetchFinished(KJob *job) 0073 { 0074 const DavPrincipalHomeSetsFetchJob *davJob = qobject_cast<DavPrincipalHomeSetsFetchJob *>(job); 0075 if (davJob->error()) { 0076 // Just give up here. 0077 setDavError(davJob->davError()); 0078 emitResult(); 0079 0080 return; 0081 } 0082 0083 const QStringList homeSets = davJob->homeSets(); 0084 qCDebug(KDAV2_LOG) << "Found " << homeSets.size() << " homesets\n " << homeSets; 0085 0086 //Update the url in case of redirects 0087 mUrl.setUrl(davJob->url()); 0088 0089 if (homeSets.isEmpty()) { 0090 // Same as above, retry as if it were a calendar URL. 0091 doCollectionsFetch(mUrl.url()); 0092 return; 0093 } 0094 0095 foreach (const QString &homeSet, homeSets) { 0096 QUrl url = mUrl.url(); 0097 0098 if (homeSet.startsWith(QLatin1Char('/'))) { 0099 // homeSet is only a path, use request url to complete 0100 url.setPath(homeSet, QUrl::TolerantMode); 0101 } else { 0102 // homeSet is a complete url 0103 QUrl tmpUrl(homeSet); 0104 tmpUrl.setUserName(url.userName()); 0105 tmpUrl.setPassword(url.password()); 0106 url = tmpUrl; 0107 } 0108 0109 doCollectionsFetch(url); 0110 } 0111 } 0112 0113 void DavCollectionsFetchJob::collectionsFetchFinished(KJob *job) 0114 { 0115 auto davJob = static_cast<DavJob *>(job); 0116 0117 if (davJob->error()) { 0118 setErrorFromJob(davJob); 0119 } else { 0120 // For use in the collectionDiscovered() signal 0121 QUrl _jobUrl = mUrl.url(); 0122 _jobUrl.setUserInfo(QString()); 0123 const QString jobUrl = _jobUrl.toDisplayString(); 0124 0125 // Validate that we got a valid PROPFIND response 0126 QDomElement rootElement = davJob->response().documentElement(); 0127 if (rootElement.localName().compare(QStringLiteral("multistatus"), Qt::CaseInsensitive) != 0) { 0128 setError(ERR_COLLECTIONFETCH); 0129 setErrorTextFromDavError(); 0130 subjobFinished(); 0131 return; 0132 } 0133 0134 QByteArray resp(davJob->response().toByteArray()); 0135 QBuffer buffer(&resp); 0136 buffer.open(QIODevice::ReadOnly); 0137 0138 QXmlQuery xquery; 0139 if (!xquery.setFocus(&buffer)) { 0140 setError(ERR_COLLECTIONFETCH_XQUERY_SETFOCUS); 0141 setErrorTextFromDavError(); 0142 subjobFinished(); 0143 return; 0144 } 0145 0146 xquery.setQuery(DavManager::self()->davProtocol(mUrl.protocol())->collectionsXQuery()); 0147 if (!xquery.isValid()) { 0148 setError(ERR_COLLECTIONFETCH_XQUERY_INVALID); 0149 setErrorTextFromDavError(); 0150 subjobFinished(); 0151 return; 0152 } 0153 0154 QString responsesStr; 0155 xquery.evaluateTo(&responsesStr); 0156 responsesStr.prepend(QStringLiteral("<responses>")); 0157 responsesStr.append(QStringLiteral("</responses>")); 0158 0159 QDomDocument document; 0160 if (!document.setContent(responsesStr, true)) { 0161 setError(ERR_COLLECTIONFETCH); 0162 setErrorTextFromDavError(); 0163 subjobFinished(); 0164 return; 0165 } 0166 0167 if (!error()) { 0168 /* 0169 * Extract information from a document like the following: 0170 * 0171 * <responses> 0172 * <response xmlns="DAV:"> 0173 * <href xmlns="DAV:">/caldav.php/test1.user/home/</href> 0174 * <propstat xmlns="DAV:"> 0175 * <prop xmlns="DAV:"> 0176 * <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav"> 0177 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VEVENT"/> 0178 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTODO"/> 0179 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VJOURNAL"/> 0180 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VTIMEZONE"/> 0181 * <C:comp xmlns:C="urn:ietf:params:xml:ns:caldav" name="VFREEBUSY"/> 0182 * </C:supported-calendar-component-set> 0183 * <resourcetype xmlns="DAV:"> 0184 * <collection xmlns="DAV:"/> 0185 * <C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/> 0186 * <C:schedule-calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/> 0187 * </resourcetype> 0188 * <displayname xmlns="DAV:">Test1 User</displayname> 0189 * <current-user-privilege-set xmlns="DAV:"> 0190 * <privilege xmlns="DAV:"> 0191 * <read xmlns="DAV:"/> 0192 * </privilege> 0193 * </current-user-privilege-set> 0194 * <getctag xmlns="http://calendarserver.org/ns/">12345</getctag> 0195 * </prop> 0196 * <status xmlns="DAV:">HTTP/1.1 200 OK</status> 0197 * </propstat> 0198 * </response> 0199 * </responses> 0200 */ 0201 0202 const QDomElement responsesElement = document.documentElement(); 0203 0204 QDomElement responseElement = Utils::firstChildElementNS( 0205 responsesElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0206 while (!responseElement.isNull()) { 0207 0208 DavCollection collection; 0209 if (!Utils::extractCollection(responseElement, mUrl, collection)) { 0210 continue; 0211 } 0212 0213 QUrl url = collection.url().url(); 0214 0215 // don't add this resource if it has already been detected 0216 bool alreadySeen = false; 0217 foreach (const DavCollection &seen, mCollections) { 0218 if (seen.url().toDisplayString() == url.toDisplayString()) { 0219 alreadySeen = true; 0220 } 0221 } 0222 if (alreadySeen) { 0223 responseElement = Utils::nextSiblingElementNS( 0224 responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0225 continue; 0226 } 0227 0228 bool protocolSupportsCTags = DavManager::self()->davProtocol(mUrl.protocol())->supportsCTags(); 0229 if (protocolSupportsCTags && collection.CTag() == "") { 0230 qCDebug(KDAV2_LOG) << "No CTag found for" 0231 << collection.url().url().toDisplayString() 0232 << "from the home set, trying from the direct URL"; 0233 refreshIndividualCollection(collection); 0234 0235 responseElement = Utils::nextSiblingElementNS( 0236 responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0237 continue; 0238 } 0239 0240 mCollections << collection; 0241 Q_EMIT collectionDiscovered(mUrl.protocol(), url.toDisplayString(), jobUrl); 0242 0243 responseElement = Utils::nextSiblingElementNS( 0244 responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0245 } 0246 } 0247 } 0248 0249 subjobFinished(); 0250 } 0251 0252 // This is a workaroud for Google who doesn't support providing the CTag 0253 // directly from the home set. We refresh the collections individually from 0254 // their own URLs, but only if we haven't found a CTag with the home set 0255 // request. 0256 void DavCollectionsFetchJob::refreshIndividualCollection(const DavCollection &collection) 0257 { 0258 ++mSubJobCount; 0259 auto individualFetchJob = new DavCollectionFetchJob(collection, this); 0260 connect(individualFetchJob, &DavCollectionFetchJob::result, this, &DavCollectionsFetchJob::individualCollectionRefreshed); 0261 individualFetchJob->start(); 0262 } 0263 0264 void DavCollectionsFetchJob::individualCollectionRefreshed(KJob *job) 0265 { 0266 const auto *davJob = qobject_cast<DavCollectionFetchJob *>(job); 0267 0268 if (davJob->error()) { 0269 setDavError(davJob->davError()); 0270 emitResult(); 0271 return; 0272 } 0273 0274 qCDebug(KDAV2_LOG) << "Collection" 0275 << davJob->collection().url().url().toDisplayString() << "refreshed"; 0276 0277 if (davJob->collection().CTag() == "") { 0278 qWarning() << "Collection with an empty CTag"; 0279 } 0280 0281 mCollections << davJob->collection(); 0282 subjobFinished(); 0283 } 0284 0285 void DavCollectionsFetchJob::subjobFinished() 0286 { 0287 if (--mSubJobCount == 0) { 0288 emitResult(); 0289 } 0290 } 0291