1 | /* |
2 | SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "davitemslistjob.h" |
8 | #include "davjobbase_p.h" |
9 | |
10 | #include "daverror.h" |
11 | #include "davmanager_p.h" |
12 | #include "davprotocolbase_p.h" |
13 | #include "davurl.h" |
14 | #include "etagcache.h" |
15 | #include "utils_p.h" |
16 | |
17 | #include <KIO/DavJob> |
18 | #include <KIO/Job> |
19 | |
20 | #include <set> |
21 | |
22 | using namespace KDAV; |
23 | |
24 | namespace KDAV |
25 | { |
26 | class DavItemsListJobPrivate : public DavJobBasePrivate |
27 | { |
28 | public: |
29 | void davJobFinished(KJob *job); |
30 | |
31 | DavUrl mUrl; |
32 | std::shared_ptr<EtagCache> mEtagCache; |
33 | QStringList mMimeTypes; |
34 | QString mRangeStart; |
35 | QString mRangeEnd; |
36 | DavItem::List mItems; |
37 | std::set<QString> mSeenUrls; // to prevent events duplication with some servers |
38 | DavItem::List mChangedItems; |
39 | QStringList mDeletedItems; |
40 | uint mSubJobCount = 0; |
41 | }; |
42 | } |
43 | |
44 | DavItemsListJob::DavItemsListJob(const DavUrl &url, const std::shared_ptr<EtagCache> &cache, QObject *parent) |
45 | : DavJobBase(new DavItemsListJobPrivate, parent) |
46 | { |
47 | Q_D(DavItemsListJob); |
48 | d->mUrl = url; |
49 | d->mEtagCache = cache; |
50 | } |
51 | |
52 | DavItemsListJob::~DavItemsListJob() = default; |
53 | |
54 | void DavItemsListJob::setContentMimeTypes(const QStringList &types) |
55 | { |
56 | Q_D(DavItemsListJob); |
57 | d->mMimeTypes = types; |
58 | } |
59 | |
60 | void DavItemsListJob::setTimeRange(const QString &start, const QString &end) |
61 | { |
62 | Q_D(DavItemsListJob); |
63 | d->mRangeStart = start; |
64 | d->mRangeEnd = end; |
65 | } |
66 | |
67 | void DavItemsListJob::start() |
68 | { |
69 | Q_D(DavItemsListJob); |
70 | const DavProtocolBase *protocol = DavManager::davProtocol(protocol: d->mUrl.protocol()); |
71 | Q_ASSERT(protocol); |
72 | |
73 | const auto queries = protocol->itemsQueries(); |
74 | for (XMLQueryBuilder::Ptr builder : queries) { |
75 | if (!d->mRangeStart.isEmpty()) { |
76 | builder->setParameter(QStringLiteral("start" ), value: d->mRangeStart); |
77 | } |
78 | if (!d->mRangeEnd.isEmpty()) { |
79 | builder->setParameter(QStringLiteral("end" ), value: d->mRangeEnd); |
80 | } |
81 | |
82 | const QDomDocument props = builder->buildQuery(); |
83 | const QString mimeType = builder->mimeType(); |
84 | |
85 | if (d->mMimeTypes.isEmpty() || d->mMimeTypes.contains(str: mimeType)) { |
86 | ++d->mSubJobCount; |
87 | if (protocol->useReport()) { |
88 | KIO::DavJob *job = DavManager::self()->createReportJob(url: d->mUrl.url(), document: props.toString()); |
89 | job->addMetaData(QStringLiteral("PropagateHttpHeader" ), QStringLiteral("true" )); |
90 | job->setProperty(name: "davType" , QStringLiteral("report" )); |
91 | job->setProperty(name: "itemsMimeType" , value: mimeType); |
92 | connect(sender: job, signal: &KIO::DavJob::result, context: this, slot: [d](KJob *job) { |
93 | d->davJobFinished(job); |
94 | }); |
95 | } else { |
96 | KIO::DavJob *job = DavManager::self()->createPropFindJob(url: d->mUrl.url(), document: props.toString()); |
97 | job->addMetaData(QStringLiteral("PropagateHttpHeader" ), QStringLiteral("true" )); |
98 | job->setProperty(name: "davType" , QStringLiteral("propFind" )); |
99 | job->setProperty(name: "itemsMimeType" , value: mimeType); |
100 | connect(sender: job, signal: &KIO::DavJob::result, context: this, slot: [d](KJob *job) { |
101 | d->davJobFinished(job); |
102 | }); |
103 | } |
104 | } |
105 | } |
106 | |
107 | if (d->mSubJobCount == 0) { |
108 | setError(ERR_ITEMLIST_NOMIMETYPE); |
109 | d->setErrorTextFromDavError(); |
110 | emitResult(); |
111 | } |
112 | } |
113 | |
114 | DavItem::List DavItemsListJob::items() const |
115 | { |
116 | Q_D(const DavItemsListJob); |
117 | return d->mItems; |
118 | } |
119 | |
120 | DavItem::List DavItemsListJob::changedItems() const |
121 | { |
122 | Q_D(const DavItemsListJob); |
123 | return d->mChangedItems; |
124 | } |
125 | |
126 | QStringList DavItemsListJob::deletedItems() const |
127 | { |
128 | Q_D(const DavItemsListJob); |
129 | return d->mDeletedItems; |
130 | } |
131 | |
132 | void DavItemsListJobPrivate::davJobFinished(KJob *job) |
133 | { |
134 | KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(object: job); |
135 | const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode" )).isEmpty() // |
136 | ? 0 |
137 | : davJob->queryMetaData(QStringLiteral("responsecode" )).toInt(); |
138 | |
139 | // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx |
140 | if (davJob->error() || (responseCode >= 400 && responseCode < 600)) { |
141 | setLatestResponseCode(responseCode); |
142 | setError(ERR_PROBLEM_WITH_REQUEST); |
143 | setJobErrorText(davJob->errorText()); |
144 | setJobError(davJob->error()); |
145 | setErrorTextFromDavError(); |
146 | } else { |
147 | /* |
148 | * Extract data from a document like the following: |
149 | * |
150 | * <multistatus xmlns="DAV:"> |
151 | * <response xmlns="DAV:"> |
152 | * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-166749289.780.ics</href> |
153 | * <propstat xmlns="DAV:"> |
154 | * <prop xmlns="DAV:"> |
155 | * <getetag xmlns="DAV:">"b4bbea0278f4f63854c4167a7656024a"</getetag> |
156 | * </prop> |
157 | * <status xmlns="DAV:">HTTP/1.1 200 OK</status> |
158 | * </propstat> |
159 | * </response> |
160 | * <response xmlns="DAV:"> |
161 | * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-399416366.464.ics</href> |
162 | * <propstat xmlns="DAV:"> |
163 | * <prop xmlns="DAV:"> |
164 | * <getetag xmlns="DAV:">"52eb129018398a7da4f435b2bc4c6cd5"</getetag> |
165 | * </prop> |
166 | * <status xmlns="DAV:">HTTP/1.1 200 OK</status> |
167 | * </propstat> |
168 | * </response> |
169 | * </multistatus> |
170 | */ |
171 | |
172 | const QString itemsMimeType = job->property(name: "itemsMimeType" ).toString(); |
173 | QDomDocument document; |
174 | document.setContent(data: davJob->responseData(), options: QDomDocument::ParseOption::UseNamespaceProcessing); |
175 | const QDomElement documentElement = document.documentElement(); |
176 | |
177 | QDomElement responseElement = Utils::firstChildElementNS(parent: documentElement, QStringLiteral("DAV:" ), QStringLiteral("response" )); |
178 | while (!responseElement.isNull()) { |
179 | QDomElement propstatElement; |
180 | |
181 | // check for the valid propstat, without giving up on first error |
182 | { |
183 | const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:" ), QStringLiteral("propstat" )); |
184 | for (int i = 0; i < propstats.length(); ++i) { |
185 | const QDomElement propstatCandidate = propstats.item(index: i).toElement(); |
186 | const QDomElement statusElement = Utils::firstChildElementNS(parent: propstatCandidate, QStringLiteral("DAV:" ), QStringLiteral("status" )); |
187 | if (statusElement.text().contains(s: QLatin1String("200" ))) { |
188 | propstatElement = propstatCandidate; |
189 | } |
190 | } |
191 | } |
192 | |
193 | if (propstatElement.isNull()) { |
194 | responseElement = Utils::nextSiblingElementNS(element: responseElement, QStringLiteral("DAV:" ), QStringLiteral("response" )); |
195 | continue; |
196 | } |
197 | |
198 | const QDomElement propElement = Utils::firstChildElementNS(parent: propstatElement, QStringLiteral("DAV:" ), QStringLiteral("prop" )); |
199 | |
200 | // check whether it is a DAV collection ... |
201 | const QDomElement resourcetypeElement = Utils::firstChildElementNS(parent: propElement, QStringLiteral("DAV:" ), QStringLiteral("resourcetype" )); |
202 | if (!resourcetypeElement.isNull()) { |
203 | const QDomElement collectionElement = Utils::firstChildElementNS(parent: resourcetypeElement, QStringLiteral("DAV:" ), QStringLiteral("collection" )); |
204 | if (!collectionElement.isNull()) { |
205 | responseElement = Utils::nextSiblingElementNS(element: responseElement, QStringLiteral("DAV:" ), QStringLiteral("response" )); |
206 | continue; |
207 | } |
208 | } |
209 | |
210 | // ... if not it is an item |
211 | DavItem item; |
212 | item.setContentType(itemsMimeType); |
213 | |
214 | // extract path |
215 | const QDomElement hrefElement = Utils::firstChildElementNS(parent: responseElement, QStringLiteral("DAV:" ), QStringLiteral("href" )); |
216 | const QString href = hrefElement.text(); |
217 | |
218 | QUrl url = davJob->url(); |
219 | url.setUserInfo(userInfo: QString()); |
220 | if (href.startsWith(c: QLatin1Char('/'))) { |
221 | // href is only a path, use request url to complete |
222 | url.setPath(path: href, mode: QUrl::TolerantMode); |
223 | } else { |
224 | // href is a complete url |
225 | url = QUrl::fromUserInput(userInput: href); |
226 | } |
227 | |
228 | const QString itemUrl = url.toDisplayString(); |
229 | const auto [it, isInserted] = mSeenUrls.insert(x: itemUrl); |
230 | if (!isInserted) { |
231 | responseElement = Utils::nextSiblingElementNS(element: responseElement, QStringLiteral("DAV:" ), QStringLiteral("response" )); |
232 | continue; |
233 | } |
234 | |
235 | auto _url = url; |
236 | _url.setUserInfo(userInfo: mUrl.url().userInfo()); |
237 | item.setUrl(DavUrl(_url, mUrl.protocol())); |
238 | |
239 | // extract ETag |
240 | const QDomElement getetagElement = Utils::firstChildElementNS(parent: propElement, QStringLiteral("DAV:" ), QStringLiteral("getetag" )); |
241 | |
242 | item.setEtag(getetagElement.text()); |
243 | |
244 | mItems << item; |
245 | |
246 | if (mEtagCache->etagChanged(remoteId: itemUrl, refEtag: item.etag())) { |
247 | mChangedItems << item; |
248 | } |
249 | |
250 | responseElement = Utils::nextSiblingElementNS(element: responseElement, QStringLiteral("DAV:" ), QStringLiteral("response" )); |
251 | } |
252 | } |
253 | |
254 | mDeletedItems.clear(); |
255 | |
256 | const auto map = mEtagCache->etagCacheMap(); |
257 | for (auto it = map.cbegin(); it != map.cend(); ++it) { |
258 | const QString remoteId = it.key(); |
259 | if (mSeenUrls.find(x: remoteId) == mSeenUrls.cend()) { |
260 | mDeletedItems.append(t: remoteId); |
261 | } |
262 | } |
263 | |
264 | if (--mSubJobCount == 0) { |
265 | emitResult(); |
266 | } |
267 | } |
268 | |
269 | #include "moc_davitemslistjob.cpp" |
270 | |