File indexing completed on 2024-05-12 07:48:40
0001 /* 0002 SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "davitemslistjob.h" 0008 #include "davjobbase_p.h" 0009 0010 #include "daverror.h" 0011 #include "davmanager_p.h" 0012 #include "davprotocolbase_p.h" 0013 #include "davurl.h" 0014 #include "etagcache.h" 0015 #include "utils_p.h" 0016 0017 #include <KIO/DavJob> 0018 #include <KIO/Job> 0019 0020 #include <set> 0021 0022 using namespace KDAV; 0023 0024 namespace KDAV 0025 { 0026 class DavItemsListJobPrivate : public DavJobBasePrivate 0027 { 0028 public: 0029 void davJobFinished(KJob *job); 0030 0031 DavUrl mUrl; 0032 std::shared_ptr<EtagCache> mEtagCache; 0033 QStringList mMimeTypes; 0034 QString mRangeStart; 0035 QString mRangeEnd; 0036 DavItem::List mItems; 0037 std::set<QString> mSeenUrls; // to prevent events duplication with some servers 0038 DavItem::List mChangedItems; 0039 QStringList mDeletedItems; 0040 uint mSubJobCount = 0; 0041 }; 0042 } 0043 0044 DavItemsListJob::DavItemsListJob(const DavUrl &url, const std::shared_ptr<EtagCache> &cache, QObject *parent) 0045 : DavJobBase(new DavItemsListJobPrivate, parent) 0046 { 0047 Q_D(DavItemsListJob); 0048 d->mUrl = url; 0049 d->mEtagCache = cache; 0050 } 0051 0052 DavItemsListJob::~DavItemsListJob() = default; 0053 0054 void DavItemsListJob::setContentMimeTypes(const QStringList &types) 0055 { 0056 Q_D(DavItemsListJob); 0057 d->mMimeTypes = types; 0058 } 0059 0060 void DavItemsListJob::setTimeRange(const QString &start, const QString &end) 0061 { 0062 Q_D(DavItemsListJob); 0063 d->mRangeStart = start; 0064 d->mRangeEnd = end; 0065 } 0066 0067 void DavItemsListJob::start() 0068 { 0069 Q_D(DavItemsListJob); 0070 const DavProtocolBase *protocol = DavManager::davProtocol(d->mUrl.protocol()); 0071 Q_ASSERT(protocol); 0072 0073 const auto queries = protocol->itemsQueries(); 0074 for (XMLQueryBuilder::Ptr builder : queries) { 0075 if (!d->mRangeStart.isEmpty()) { 0076 builder->setParameter(QStringLiteral("start"), d->mRangeStart); 0077 } 0078 if (!d->mRangeEnd.isEmpty()) { 0079 builder->setParameter(QStringLiteral("end"), d->mRangeEnd); 0080 } 0081 0082 const QDomDocument props = builder->buildQuery(); 0083 const QString mimeType = builder->mimeType(); 0084 0085 if (d->mMimeTypes.isEmpty() || d->mMimeTypes.contains(mimeType)) { 0086 ++d->mSubJobCount; 0087 if (protocol->useReport()) { 0088 KIO::DavJob *job = DavManager::self()->createReportJob(d->mUrl.url(), props.toString()); 0089 job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); 0090 job->setProperty("davType", QStringLiteral("report")); 0091 job->setProperty("itemsMimeType", mimeType); 0092 connect(job, &KIO::DavJob::result, this, [d](KJob *job) { 0093 d->davJobFinished(job); 0094 }); 0095 } else { 0096 KIO::DavJob *job = DavManager::self()->createPropFindJob(d->mUrl.url(), props.toString()); 0097 job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); 0098 job->setProperty("davType", QStringLiteral("propFind")); 0099 job->setProperty("itemsMimeType", mimeType); 0100 connect(job, &KIO::DavJob::result, this, [d](KJob *job) { 0101 d->davJobFinished(job); 0102 }); 0103 } 0104 } 0105 } 0106 0107 if (d->mSubJobCount == 0) { 0108 setError(ERR_ITEMLIST_NOMIMETYPE); 0109 d->setErrorTextFromDavError(); 0110 emitResult(); 0111 } 0112 } 0113 0114 DavItem::List DavItemsListJob::items() const 0115 { 0116 Q_D(const DavItemsListJob); 0117 return d->mItems; 0118 } 0119 0120 DavItem::List DavItemsListJob::changedItems() const 0121 { 0122 Q_D(const DavItemsListJob); 0123 return d->mChangedItems; 0124 } 0125 0126 QStringList DavItemsListJob::deletedItems() const 0127 { 0128 Q_D(const DavItemsListJob); 0129 return d->mDeletedItems; 0130 } 0131 0132 void DavItemsListJobPrivate::davJobFinished(KJob *job) 0133 { 0134 KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(job); 0135 const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode")).isEmpty() // 0136 ? 0 0137 : davJob->queryMetaData(QStringLiteral("responsecode")).toInt(); 0138 0139 // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx 0140 if (davJob->error() || (responseCode >= 400 && responseCode < 600)) { 0141 setLatestResponseCode(responseCode); 0142 setError(ERR_PROBLEM_WITH_REQUEST); 0143 setJobErrorText(davJob->errorText()); 0144 setJobError(davJob->error()); 0145 setErrorTextFromDavError(); 0146 } else { 0147 /* 0148 * Extract data from a document like the following: 0149 * 0150 * <multistatus xmlns="DAV:"> 0151 * <response xmlns="DAV:"> 0152 * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-166749289.780.ics</href> 0153 * <propstat xmlns="DAV:"> 0154 * <prop xmlns="DAV:"> 0155 * <getetag xmlns="DAV:">"b4bbea0278f4f63854c4167a7656024a"</getetag> 0156 * </prop> 0157 * <status xmlns="DAV:">HTTP/1.1 200 OK</status> 0158 * </propstat> 0159 * </response> 0160 * <response xmlns="DAV:"> 0161 * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-399416366.464.ics</href> 0162 * <propstat xmlns="DAV:"> 0163 * <prop xmlns="DAV:"> 0164 * <getetag xmlns="DAV:">"52eb129018398a7da4f435b2bc4c6cd5"</getetag> 0165 * </prop> 0166 * <status xmlns="DAV:">HTTP/1.1 200 OK</status> 0167 * </propstat> 0168 * </response> 0169 * </multistatus> 0170 */ 0171 0172 const QString itemsMimeType = job->property("itemsMimeType").toString(); 0173 QDomDocument document; 0174 document.setContent(davJob->responseData(), QDomDocument::ParseOption::UseNamespaceProcessing); 0175 const QDomElement documentElement = document.documentElement(); 0176 0177 QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0178 while (!responseElement.isNull()) { 0179 QDomElement propstatElement; 0180 0181 // check for the valid propstat, without giving up on first error 0182 { 0183 const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); 0184 for (int i = 0; i < propstats.length(); ++i) { 0185 const QDomElement propstatCandidate = propstats.item(i).toElement(); 0186 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status")); 0187 if (statusElement.text().contains(QLatin1String("200"))) { 0188 propstatElement = propstatCandidate; 0189 } 0190 } 0191 } 0192 0193 if (propstatElement.isNull()) { 0194 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0195 continue; 0196 } 0197 0198 const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); 0199 0200 // check whether it is a DAV collection ... 0201 const QDomElement resourcetypeElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("resourcetype")); 0202 if (!resourcetypeElement.isNull()) { 0203 const QDomElement collectionElement = Utils::firstChildElementNS(resourcetypeElement, QStringLiteral("DAV:"), QStringLiteral("collection")); 0204 if (!collectionElement.isNull()) { 0205 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0206 continue; 0207 } 0208 } 0209 0210 // ... if not it is an item 0211 DavItem item; 0212 item.setContentType(itemsMimeType); 0213 0214 // extract path 0215 const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href")); 0216 const QString href = hrefElement.text(); 0217 0218 QUrl url = davJob->url(); 0219 url.setUserInfo(QString()); 0220 if (href.startsWith(QLatin1Char('/'))) { 0221 // href is only a path, use request url to complete 0222 url.setPath(href, QUrl::TolerantMode); 0223 } else { 0224 // href is a complete url 0225 url = QUrl::fromUserInput(href); 0226 } 0227 0228 const QString itemUrl = url.toDisplayString(); 0229 const auto [it, isInserted] = mSeenUrls.insert(itemUrl); 0230 if (!isInserted) { 0231 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0232 continue; 0233 } 0234 0235 auto _url = url; 0236 _url.setUserInfo(mUrl.url().userInfo()); 0237 item.setUrl(DavUrl(_url, mUrl.protocol())); 0238 0239 // extract ETag 0240 const QDomElement getetagElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("getetag")); 0241 0242 item.setEtag(getetagElement.text()); 0243 0244 mItems << item; 0245 0246 if (mEtagCache->etagChanged(itemUrl, item.etag())) { 0247 mChangedItems << item; 0248 } 0249 0250 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); 0251 } 0252 } 0253 0254 mDeletedItems.clear(); 0255 0256 const auto map = mEtagCache->etagCacheMap(); 0257 for (auto it = map.cbegin(); it != map.cend(); ++it) { 0258 const QString remoteId = it.key(); 0259 if (mSeenUrls.find(remoteId) == mSeenUrls.cend()) { 0260 mDeletedItems.append(remoteId); 0261 } 0262 } 0263 0264 if (--mSubJobCount == 0) { 0265 emitResult(); 0266 } 0267 } 0268 0269 #include "moc_davitemslistjob.cpp"