1 | /* |
2 | SPDX-FileCopyrightText: 2000-2003 Waldo Bastian <bastian@kde.org> |
3 | SPDX-FileCopyrightText: 2000-2002 George Staikos <staikos@kde.org> |
4 | SPDX-FileCopyrightText: 2000-2002 Dawit Alemayehu <adawit@kde.org> |
5 | SPDX-FileCopyrightText: 2001, 2002 Hamish Rodda <rodda@kde.org> |
6 | SPDX-FileCopyrightText: 2007 Nick Shaforostoff <shafff@ukr.net> |
7 | SPDX-FileCopyrightText: 2007-2018 Daniel Nicoletti <dantti12@gmail.com> |
8 | SPDX-FileCopyrightText: 2008, 2009 Andreas Hartmetz <ahartmetz@gmail.com> |
9 | SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> |
10 | SPDX-FileCopyrightText: 2023 Nicolas Fella <nicolas.fella@gmx.de> |
11 | |
12 | SPDX-License-Identifier: LGPL-2.0-or-later |
13 | */ |
14 | |
15 | #include "http.h" |
16 | #include "debug.h" |
17 | #include "kioglobal_p.h" |
18 | |
19 | #include <QAuthenticator> |
20 | #include <QBuffer> |
21 | #include <QCoreApplication> |
22 | #include <QDomDocument> |
23 | #include <QMimeDatabase> |
24 | #include <QNetworkAccessManager> |
25 | #include <QNetworkCookie> |
26 | #include <QNetworkCookieJar> |
27 | #include <QNetworkProxy> |
28 | #include <QNetworkReply> |
29 | #include <QSslCipher> |
30 | |
31 | #include <KLocalizedString> |
32 | |
33 | #include <authinfo.h> |
34 | #include <ksslcertificatemanager.h> |
35 | |
36 | // Pseudo plugin class to embed meta data |
37 | class KIOPluginForMetaData : public QObject |
38 | { |
39 | Q_OBJECT |
40 | Q_PLUGIN_METADATA(IID "org.kde.kio.worker.http" FILE "http.json" ) |
41 | }; |
42 | |
43 | extern "C" { |
44 | int Q_DECL_EXPORT kdemain(int argc, char **argv) |
45 | { |
46 | QCoreApplication app(argc, argv); |
47 | app.setApplicationName(QStringLiteral("kio_http" )); |
48 | |
49 | // start the worker |
50 | HTTPProtocol worker(argv[1], argv[2], argv[3]); |
51 | worker.dispatchLoop(); |
52 | return 0; |
53 | } |
54 | } |
55 | |
56 | class Cookies : public QNetworkCookieJar |
57 | { |
58 | Q_OBJECT |
59 | public: |
60 | Q_SIGNAL void cookiesAdded(const QString &cookieString); |
61 | Q_SIGNAL void queryCookies(QString &cookieString); |
62 | |
63 | QList<QNetworkCookie> m_cookies; |
64 | |
65 | QList<QNetworkCookie> cookiesForUrl(const QUrl & /*url*/) const override |
66 | { |
67 | return m_cookies; |
68 | } |
69 | |
70 | bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl & /*url*/) override |
71 | { |
72 | QString cookieString; |
73 | |
74 | for (const QNetworkCookie &cookie : cookieList) { |
75 | cookieString += QStringLiteral("Set-Cookie: " ) + QString::fromUtf8(ba: cookie.toRawForm()) + QLatin1Char('\n'); |
76 | } |
77 | |
78 | Q_EMIT cookiesAdded(cookieString); |
79 | |
80 | return true; |
81 | } |
82 | |
83 | void setCookies(const QString &cookieString) |
84 | { |
85 | const QStringList cookiePieces = cookieString.mid(position: 8).split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts); |
86 | |
87 | for (const QString &cookiePiece : cookiePieces) { |
88 | const QString name = cookiePiece.left(n: cookiePiece.indexOf(c: QLatin1Char('='))); |
89 | const QString value = cookiePiece.mid(position: cookiePiece.indexOf(c: QLatin1Char('=')) + 1); |
90 | |
91 | QNetworkCookie cookie(name.toUtf8(), value.toUtf8()); |
92 | m_cookies << cookie; |
93 | } |
94 | } |
95 | }; |
96 | |
97 | HTTPProtocol::HTTPProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) |
98 | : WorkerBase(protocol, pool, app) |
99 | { |
100 | } |
101 | |
102 | HTTPProtocol::~HTTPProtocol() |
103 | { |
104 | } |
105 | |
106 | QString readMimeType(QNetworkReply *reply) |
107 | { |
108 | const QString contentType = reply->header(header: QNetworkRequest::ContentTypeHeader).toString(); |
109 | |
110 | return contentType.left(n: contentType.indexOf(c: QLatin1Char(';'))); |
111 | } |
112 | |
113 | void HTTPProtocol::handleSslErrors(QNetworkReply *reply, const QList<QSslError> errors) |
114 | { |
115 | if (!metaData(QStringLiteral("ssl_no_ui" )).isEmpty() && metaData(QStringLiteral("ssl_no_ui" )).compare(other: QLatin1String("false" ), cs: Qt::CaseInsensitive)) { |
116 | return; |
117 | } |
118 | |
119 | QList<QSslCertificate> certs = reply->sslConfiguration().peerCertificateChain(); |
120 | |
121 | QStringList peerCertChain; |
122 | for (const QSslCertificate &cert : certs) { |
123 | peerCertChain += QString::fromUtf8(ba: cert.toPem()); |
124 | } |
125 | |
126 | auto sslErrors = errors; |
127 | |
128 | const QList<QSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(errors: sslErrors); |
129 | if (!fatalErrors.isEmpty()) { |
130 | qCWarning(KIOHTTP_LOG) << "SSL errors that cannot be ignored occured" << fatalErrors; |
131 | Q_EMIT errorOut(error: KIO::ERR_CANNOT_CONNECT); |
132 | return; |
133 | } |
134 | |
135 | KSslCertificateRule rule = KSslCertificateManager::self()->rule(cert: certs.first(), hostName: m_hostName); |
136 | |
137 | // remove previously seen and acknowledged errors |
138 | const QList<QSslError> remainingErrors = rule.filterErrors(errors: sslErrors); |
139 | if (remainingErrors.isEmpty()) { |
140 | reply->ignoreSslErrors(); |
141 | return; |
142 | } |
143 | |
144 | // try to fill in the blanks, i.e. missing certificates, and just assume that |
145 | // those belong to the peer (==website or similar) certificate. |
146 | for (int i = 0; i < sslErrors.count(); i++) { |
147 | if (sslErrors[i].certificate().isNull()) { |
148 | sslErrors[i] = QSslError(sslErrors[i].error(), certs[0]); |
149 | } |
150 | } |
151 | |
152 | QStringList certificateErrors; |
153 | // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators |
154 | for (const QSslCertificate &cert : certs) { |
155 | QString errorStr; |
156 | for (const QSslError &error : std::as_const(t&: sslErrors)) { |
157 | if (error.certificate() == cert) { |
158 | errorStr = QString::number(static_cast<int>(error.error())) + QLatin1Char('\t'); |
159 | } |
160 | } |
161 | if (errorStr.endsWith(c: QLatin1Char('\t'))) { |
162 | errorStr.chop(n: 1); |
163 | } |
164 | certificateErrors << errorStr; |
165 | } |
166 | |
167 | const QSslCipher cipher = reply->sslConfiguration().sessionCipher(); |
168 | |
169 | const QVariantMap sslData{ |
170 | {QStringLiteral("hostname" ), m_hostName}, |
171 | {QStringLiteral("protocol" ), cipher.protocolString()}, |
172 | {QStringLiteral("sslError" ), errors.first().errorString()}, |
173 | {QStringLiteral("peerCertChain" ), peerCertChain}, |
174 | {QStringLiteral("certificateErrors" ), certificateErrors}, |
175 | {QStringLiteral("cipher" ), cipher.name()}, |
176 | {QStringLiteral("bits" ), cipher.supportedBits()}, |
177 | {QStringLiteral("usedBits" ), cipher.usedBits()}, |
178 | }; |
179 | |
180 | int result = sslError(sslData); |
181 | |
182 | if (result == 1) { |
183 | QDateTime ruleExpiry = QDateTime::currentDateTime(); |
184 | |
185 | const int result = messageBox(type: WarningTwoActionsCancel, |
186 | i18n("Would you like to accept this " |
187 | "certificate forever without " |
188 | "being prompted?" ), |
189 | i18n("Server Authentication" ), |
190 | i18n("&Forever" ), |
191 | i18n("&Current Session only" )); |
192 | if (result == WorkerBase::PrimaryAction) { |
193 | // accept forever ("for a very long time") |
194 | ruleExpiry = ruleExpiry.addYears(years: 1000); |
195 | } else if (result == WorkerBase::SecondaryAction) { |
196 | // accept "for a short time", half an hour. |
197 | ruleExpiry = ruleExpiry.addSecs(secs: 30 * 60); |
198 | } else { |
199 | Q_EMIT errorOut(error: KIO::ERR_CANNOT_CONNECT); |
200 | return; |
201 | } |
202 | |
203 | rule.setExpiryDateTime(ruleExpiry); |
204 | rule.setIgnoredErrors(sslErrors); |
205 | KSslCertificateManager::self()->setRule(rule); |
206 | |
207 | reply->ignoreSslErrors(); |
208 | } else { |
209 | Q_EMIT errorOut(error: KIO::ERR_CANNOT_CONNECT); |
210 | } |
211 | } |
212 | |
213 | HTTPProtocol::Response HTTPProtocol::makeDavRequest(const QUrl &url, |
214 | KIO::HTTP_METHOD method, |
215 | QByteArray &inputData, |
216 | DataMode dataMode, |
217 | const QMap<QByteArray, QByteArray> &) |
218 | { |
219 | auto = extraHeaders; |
220 | const QString locks = davProcessLocks(); |
221 | |
222 | if (!headers.contains(key: "Content-Type" )) { |
223 | headers.insert(key: "Content-Type" , value: "text/xml; charset=utf-8" ); |
224 | } |
225 | |
226 | if (!locks.isEmpty()) { |
227 | headers.insert(key: "If" , value: locks.toLatin1()); |
228 | } |
229 | |
230 | return makeRequest(url, method, inputData, dataMode, extraHeaders: headers); |
231 | } |
232 | |
233 | HTTPProtocol::Response |
234 | HTTPProtocol::makeRequest(const QUrl &url, KIO::HTTP_METHOD method, QByteArray &inputData, DataMode dataMode, const QMap<QByteArray, QByteArray> &) |
235 | { |
236 | QBuffer buffer(&inputData); |
237 | return makeRequest(url, method, inputData: &buffer, dataMode, extraHeaders); |
238 | } |
239 | |
240 | static QString protocolForProxyType(QNetworkProxy::ProxyType type) |
241 | { |
242 | switch (type) { |
243 | case QNetworkProxy::DefaultProxy: |
244 | break; |
245 | case QNetworkProxy::Socks5Proxy: |
246 | return QStringLiteral("socks" ); |
247 | case QNetworkProxy::NoProxy: |
248 | break; |
249 | case QNetworkProxy::HttpProxy: |
250 | case QNetworkProxy::HttpCachingProxy: |
251 | case QNetworkProxy::FtpCachingProxy: |
252 | break; |
253 | } |
254 | |
255 | return QStringLiteral("http" ); |
256 | } |
257 | |
258 | HTTPProtocol::Response HTTPProtocol::makeRequest(const QUrl &url, |
259 | KIO::HTTP_METHOD method, |
260 | QIODevice *inputData, |
261 | HTTPProtocol::DataMode dataMode, |
262 | const QMap<QByteArray, QByteArray> &) |
263 | { |
264 | QNetworkAccessManager nam; |
265 | |
266 | // Disable automatic redirect handling from Qt. We need to intercept redirects |
267 | // to let KIO handle them |
268 | nam.setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); |
269 | |
270 | auto cookies = new Cookies; |
271 | |
272 | if (metaData(QStringLiteral("cookies" )) == QStringLiteral("manual" )) { |
273 | cookies->setCookies(metaData(QStringLiteral("setcookies" ))); |
274 | |
275 | connect(sender: cookies, signal: &Cookies::cookiesAdded, context: this, slot: [this](const QString &cookiesString) { |
276 | setMetaData(QStringLiteral("setcookies" ), value: cookiesString); |
277 | }); |
278 | } |
279 | |
280 | nam.setCookieJar(cookies); |
281 | |
282 | QUrl properUrl = url; |
283 | if (url.scheme() == QLatin1String("webdav" )) { |
284 | properUrl.setScheme(QStringLiteral("http" )); |
285 | } |
286 | if (url.scheme() == QLatin1String("webdavs" )) { |
287 | properUrl.setScheme(QStringLiteral("https" )); |
288 | } |
289 | |
290 | m_hostName = properUrl.host(); |
291 | |
292 | connect(sender: &nam, signal: &QNetworkAccessManager::authenticationRequired, context: this, slot: [this, url](QNetworkReply * /*reply*/, QAuthenticator *authenticator) { |
293 | if (configValue(QStringLiteral("no-www-auth" ), defaultValue: false)) { |
294 | return; |
295 | } |
296 | |
297 | KIO::AuthInfo authinfo; |
298 | authinfo.url = url; |
299 | authinfo.username = url.userName(); |
300 | authinfo.prompt = i18n( |
301 | "You need to supply a username and a " |
302 | "password to access this site." ); |
303 | authinfo.commentLabel = i18n("Site:" ); |
304 | |
305 | // try to get credentials from kpasswdserver's cache, then try asking the user. |
306 | authinfo.verifyPath = false; // we have realm, no path based checking please! |
307 | authinfo.realmValue = authenticator->realm(); |
308 | |
309 | // Save the current authinfo url because it can be modified by the call to |
310 | // checkCachedAuthentication. That way we can restore it if the call |
311 | // modified it. |
312 | const QUrl reqUrl = authinfo.url; |
313 | |
314 | if (checkCachedAuthentication(info&: authinfo)) { |
315 | authenticator->setUser(authinfo.username); |
316 | authenticator->setPassword(authinfo.password); |
317 | } else { |
318 | // Reset url to the saved url... |
319 | authinfo.url = reqUrl; |
320 | authinfo.keepPassword = true; |
321 | authinfo.comment = i18n("<b>%1</b> at <b>%2</b>" , authinfo.realmValue.toHtmlEscaped(), authinfo.url.host()); |
322 | |
323 | const int errorCode = openPasswordDialog(info&: authinfo, errorMsg: QString()); |
324 | |
325 | if (!errorCode) { |
326 | authenticator->setUser(authinfo.username); |
327 | authenticator->setPassword(authinfo.password); |
328 | if (authinfo.keepPassword) { |
329 | cacheAuthentication(info: authinfo); |
330 | } |
331 | } |
332 | } |
333 | }); |
334 | |
335 | connect(sender: &nam, signal: &QNetworkAccessManager::proxyAuthenticationRequired, context: this, slot: [this](const QNetworkProxy &proxy, QAuthenticator *authenticator) { |
336 | if (configValue(QStringLiteral("no-proxy-auth" ), defaultValue: false)) { |
337 | return; |
338 | } |
339 | |
340 | QUrl proxyUrl; |
341 | |
342 | proxyUrl.setScheme(protocolForProxyType(type: proxy.type())); |
343 | proxyUrl.setUserName(userName: proxy.user()); |
344 | proxyUrl.setHost(host: proxy.hostName()); |
345 | proxyUrl.setPort(proxy.port()); |
346 | |
347 | KIO::AuthInfo authinfo; |
348 | authinfo.url = proxyUrl; |
349 | authinfo.username = proxyUrl.userName(); |
350 | authinfo.prompt = i18n( |
351 | "You need to supply a username and a password for " |
352 | "the proxy server listed below before you are allowed " |
353 | "to access any sites." ); |
354 | authinfo.keepPassword = true; |
355 | authinfo.commentLabel = i18n("Proxy:" ); |
356 | |
357 | // try to get credentials from kpasswdserver's cache, then try asking the user. |
358 | authinfo.verifyPath = false; // we have realm, no path based checking please! |
359 | authinfo.realmValue = authenticator->realm(); |
360 | authinfo.comment = i18n("<b>%1</b> at <b>%2</b>" , authinfo.realmValue.toHtmlEscaped(), proxyUrl.host()); |
361 | |
362 | // Save the current authinfo url because it can be modified by the call to |
363 | // checkCachedAuthentication. That way we can restore it if the call |
364 | // modified it. |
365 | const QUrl reqUrl = authinfo.url; |
366 | |
367 | if (checkCachedAuthentication(info&: authinfo)) { |
368 | authenticator->setUser(authinfo.username); |
369 | authenticator->setPassword(authinfo.password); |
370 | } else { |
371 | // Reset url to the saved url... |
372 | authinfo.url = reqUrl; |
373 | authinfo.keepPassword = true; |
374 | authinfo.comment = i18n("<b>%1</b> at <b>%2</b>" , authinfo.realmValue.toHtmlEscaped(), authinfo.url.host()); |
375 | |
376 | const int errorCode = openPasswordDialog(info&: authinfo, errorMsg: QString()); |
377 | |
378 | if (!errorCode) { |
379 | authenticator->setUser(authinfo.username); |
380 | authenticator->setPassword(authinfo.password); |
381 | if (authinfo.keepPassword) { |
382 | cacheAuthentication(info: authinfo); |
383 | } |
384 | } |
385 | } |
386 | }); |
387 | |
388 | QNetworkRequest request(properUrl); |
389 | |
390 | const QByteArray contentType = getContentType().toUtf8(); |
391 | |
392 | if (!contentType.isEmpty()) { |
393 | request.setHeader(header: QNetworkRequest::ContentTypeHeader, value: contentType); |
394 | } |
395 | |
396 | const QString referrer = metaData(QStringLiteral("referrer" )); |
397 | if (!referrer.isEmpty()) { |
398 | request.setRawHeader(headerName: "Referer" /* sic! */, value: referrer.toUtf8()); |
399 | } |
400 | |
401 | const QString userAgent = metaData(QStringLiteral("UserAgent" )); |
402 | if (!userAgent.isEmpty()) { |
403 | request.setHeader(header: QNetworkRequest::UserAgentHeader, value: userAgent.toUtf8()); |
404 | } |
405 | |
406 | const QString accept = metaData(QStringLiteral("accept" )); |
407 | if (!accept.isEmpty()) { |
408 | request.setRawHeader(headerName: "Accept" , value: accept.toUtf8()); |
409 | } |
410 | |
411 | if (metaData(QStringLiteral("HttpVersion" )) == QStringLiteral("http1" )) { |
412 | request.setAttribute(code: QNetworkRequest::Http2AllowedAttribute, value: false); |
413 | } |
414 | |
415 | for (auto [key, value] : extraHeaders.asKeyValueRange()) { |
416 | request.setRawHeader(headerName: key, value); |
417 | } |
418 | |
419 | const QString = metaData(QStringLiteral("customHTTPHeader" )); |
420 | if (!customHeaders.isEmpty()) { |
421 | const QStringList = customHeaders.split(sep: QLatin1String("\r\n" )); |
422 | |
423 | for (const QString & : headers) { |
424 | const QStringList split = header.split(sep: QLatin1String(": " )); |
425 | Q_ASSERT(split.size() == 2); |
426 | |
427 | request.setRawHeader(headerName: split[0].toUtf8(), value: split[1].toUtf8()); |
428 | } |
429 | } |
430 | |
431 | QNetworkReply *reply = nam.sendCustomRequest(request, verb: methodToString(method), data: inputData); |
432 | |
433 | bool mimeTypeEmitted = false; |
434 | |
435 | QEventLoop loop; |
436 | |
437 | QObject::connect(sender: reply, signal: &QNetworkReply::sslErrors, context: &loop, slot: [this, reply](const QList<QSslError> errors) { |
438 | handleSslErrors(reply, errors); |
439 | }); |
440 | |
441 | qint64 lastTotalSize = -1; |
442 | |
443 | QObject::connect(sender: reply, signal: &QNetworkReply::downloadProgress, context: this, slot: [this, &lastTotalSize](qint64 received, qint64 total) { |
444 | if (total != lastTotalSize) { |
445 | lastTotalSize = total; |
446 | totalSize(bytes: total); |
447 | } |
448 | |
449 | processedSize(bytes: received); |
450 | }); |
451 | |
452 | QObject::connect(sender: reply, signal: &QNetworkReply::metaDataChanged, slot: [this, &mimeTypeEmitted, reply, dataMode, url, method]() { |
453 | handleRedirection(method, originalUrl: url, reply); |
454 | |
455 | if (!mimeTypeEmitted) { |
456 | mimeType(type: readMimeType(reply)); |
457 | mimeTypeEmitted = true; |
458 | } |
459 | |
460 | if (dataMode == Emit) { |
461 | // Limit how much data we fetch at a time to avoid storing it all in RAM |
462 | // do it in metaDataChanged to work around https://bugreports.qt.io/browse/QTBUG-15065 |
463 | reply->setReadBufferSize(2048); |
464 | } |
465 | }); |
466 | |
467 | if (dataMode == Emit) { |
468 | QObject::connect(sender: reply, signal: &QNetworkReply::readyRead, context: &nam, slot: [this, reply] { |
469 | while (reply->bytesAvailable() > 0) { |
470 | QByteArray buf(2048, Qt::Uninitialized); |
471 | qint64 readBytes = reply->read(data: buf.data(), maxlen: 2048); |
472 | if (readBytes == 0) { |
473 | // End of data => don't emit the final data() call yet, the reply metadata is not yet complete! |
474 | break; |
475 | } |
476 | buf.truncate(pos: readBytes); |
477 | data(data: buf); |
478 | } |
479 | }); |
480 | } |
481 | |
482 | QObject::connect(sender: reply, signal: &QNetworkReply::finished, context: &loop, slot: &QEventLoop::quit); |
483 | QObject::connect(sender: this, signal: &HTTPProtocol::errorOut, context: &loop, slot: [this, &loop](KIO::Error error) { |
484 | lastError = error; |
485 | loop.quit(); |
486 | }); |
487 | loop.exec(); |
488 | |
489 | // make sure data is emitted at least once |
490 | // NOTE: emitting an empty data set means "end of data" and must not happen |
491 | // before we have set up our metadata properties etc. Only emit this at the |
492 | // very end of the function if applicable. |
493 | auto emitDataOnce = qScopeGuard(f: [this] { |
494 | data(data: QByteArray()); |
495 | }); |
496 | |
497 | if (reply->error() == QNetworkReply::AuthenticationRequiredError) { |
498 | reply->deleteLater(); |
499 | return {.httpCode: 0, .data: QByteArray(), .kioCode: KIO::ERR_ACCESS_DENIED}; |
500 | } |
501 | |
502 | if (configValue(QStringLiteral("PropagateHttpHeader" ), defaultValue: false)) { |
503 | QStringList ; |
504 | |
505 | const auto = reply->rawHeaderPairs(); |
506 | for (auto [key, value] : headerPairs) { |
507 | headers << QString::fromLatin1(ba: key + ": " + value); |
508 | } |
509 | |
510 | setMetaData(QStringLiteral("HTTP-Headers" ), value: headers.join(sep: QLatin1Char('\n'))); |
511 | } |
512 | |
513 | QByteArray returnData; |
514 | |
515 | if (dataMode == Return) { |
516 | returnData = reply->readAll(); |
517 | } |
518 | |
519 | int statusCode = reply->attribute(code: QNetworkRequest::HttpStatusCodeAttribute).toInt(); |
520 | |
521 | setMetaData(QStringLiteral("responsecode" ), value: QString::number(statusCode)); |
522 | setMetaData(QStringLiteral("content-type" ), value: readMimeType(reply)); |
523 | |
524 | reply->deleteLater(); |
525 | |
526 | return {.httpCode: statusCode, .data: returnData}; |
527 | } |
528 | |
529 | KIO::WorkerResult HTTPProtocol::get(const QUrl &url) |
530 | { |
531 | QByteArray inputData = getData(); |
532 | Response response = makeRequest(url, method: KIO::HTTP_GET, inputData, dataMode: DataMode::Emit); |
533 | |
534 | return sendHttpError(url, method: KIO::HTTP_GET, response); |
535 | } |
536 | |
537 | KIO::WorkerResult HTTPProtocol::put(const QUrl &url, int /*_mode*/, KIO::JobFlags flags) |
538 | { |
539 | if (url.scheme().startsWith(s: QLatin1String("webdav" ))) { |
540 | if (!(flags & KIO::Overwrite)) { |
541 | // Checks if the destination exists and return an error if it does. |
542 | if (davDestinationExists(url)) { |
543 | return KIO::WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: url.fileName()); |
544 | } |
545 | } |
546 | } |
547 | |
548 | QByteArray inputData = getData(); |
549 | Response response = makeRequest(url, method: KIO::HTTP_PUT, inputData, dataMode: DataMode::Emit); |
550 | |
551 | return sendHttpError(url, method: KIO::HTTP_PUT, response); |
552 | } |
553 | |
554 | KIO::WorkerResult HTTPProtocol::mimetype(const QUrl &url) |
555 | { |
556 | QByteArray inputData = getData(); |
557 | Response response = makeRequest(url, method: KIO::HTTP_HEAD, inputData, dataMode: DataMode::Discard); |
558 | |
559 | return sendHttpError(url, method: KIO::HTTP_HEAD, response); |
560 | } |
561 | |
562 | KIO::WorkerResult HTTPProtocol::post(const QUrl &url, qint64 /*size*/) |
563 | { |
564 | QByteArray inputData = getData(); |
565 | Response response = makeRequest(url, method: KIO::HTTP_POST, inputData, dataMode: DataMode::Emit); |
566 | |
567 | return sendHttpError(url, method: KIO::HTTP_POST, response); |
568 | } |
569 | |
570 | KIO::WorkerResult HTTPProtocol::special(const QByteArray &data) |
571 | { |
572 | int tmp; |
573 | QDataStream stream(data); |
574 | |
575 | stream >> tmp; |
576 | switch (tmp) { |
577 | case 1: { // HTTP POST |
578 | QUrl url; |
579 | qint64 size; |
580 | stream >> url >> size; |
581 | return post(url, size); |
582 | } |
583 | case 7: { // Generic WebDAV |
584 | QUrl url; |
585 | int method; |
586 | qint64 size; |
587 | stream >> url >> method >> size; |
588 | return davGeneric(url, method: (KIO::HTTP_METHOD)method, size); |
589 | } |
590 | } |
591 | return KIO::WorkerResult::pass(); |
592 | } |
593 | |
594 | QByteArray HTTPProtocol::getData() |
595 | { |
596 | // TODO this is probably not great. Instead create a QIODevice that calls readData and pass that to QNAM? |
597 | QByteArray dataBuffer; |
598 | |
599 | while (true) { |
600 | dataReq(); |
601 | |
602 | QByteArray buffer; |
603 | const int bytesRead = readData(buffer); |
604 | |
605 | dataBuffer += buffer; |
606 | |
607 | // On done... |
608 | if (bytesRead == 0) { |
609 | // sendOk = (bytesSent == m_iPostDataSize); |
610 | break; |
611 | } |
612 | } |
613 | |
614 | return dataBuffer; |
615 | } |
616 | |
617 | QString HTTPProtocol::getContentType() |
618 | { |
619 | QString contentType = metaData(QStringLiteral("content-type" )); |
620 | if (contentType.startsWith(s: QLatin1String("Content-Type: " ), cs: Qt::CaseInsensitive)) { |
621 | contentType.remove(s: QLatin1String("Content-Type: " ), cs: Qt::CaseInsensitive); |
622 | } |
623 | return contentType; |
624 | } |
625 | |
626 | void HTTPProtocol::handleRedirection(KIO::HTTP_METHOD method, const QUrl &originalUrl, QNetworkReply *reply) |
627 | { |
628 | int statusCode = reply->attribute(code: QNetworkRequest::HttpStatusCodeAttribute).toInt(); |
629 | |
630 | auto redirect = [this, originalUrl, reply] { |
631 | const QString redir = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute).toString(); |
632 | redirection(url: originalUrl.resolved(relative: QUrl(redir))); |
633 | }; |
634 | |
635 | if (statusCode == 301) { |
636 | setMetaData(QStringLiteral("permanent-redirect" ), QStringLiteral("true" )); |
637 | redirect(); |
638 | } else if (statusCode == 302) { |
639 | if (method == KIO::HTTP_POST) { |
640 | setMetaData(QStringLiteral("redirect-to-get" ), QStringLiteral("true" )); |
641 | } |
642 | |
643 | redirect(); |
644 | } else if (statusCode == 303) { |
645 | if (method != KIO::HTTP_HEAD) { |
646 | setMetaData(QStringLiteral("redirect-to-get" ), QStringLiteral("true" )); |
647 | } |
648 | |
649 | redirect(); |
650 | } else if (statusCode == 307) { |
651 | redirect(); |
652 | } else if (statusCode == 308) { |
653 | setMetaData(QStringLiteral("permanent-redirect" ), QStringLiteral("true" )); |
654 | redirect(); |
655 | } |
656 | } |
657 | |
658 | KIO::WorkerResult HTTPProtocol::listDir(const QUrl &url) |
659 | { |
660 | return davStatList(url, stat: false); |
661 | } |
662 | |
663 | KIO::WorkerResult HTTPProtocol::davStatList(const QUrl &url, bool stat) |
664 | { |
665 | KIO::UDSEntry entry; |
666 | |
667 | QMimeDatabase db; |
668 | |
669 | KIO::HTTP_METHOD method; |
670 | QByteArray inputData; |
671 | |
672 | // Maybe it's a disguised SEARCH... |
673 | QString query = metaData(QStringLiteral("davSearchQuery" )); |
674 | if (!query.isEmpty()) { |
675 | inputData = |
676 | "<?xml version=\"1.0\"?>\r\n" |
677 | "<D:searchrequest xmlns:D=\"DAV:\">\r\n" |
678 | + query.toUtf8() + "</D:searchrequest>\r\n" ; |
679 | |
680 | method = KIO::DAV_SEARCH; |
681 | } else { |
682 | // We are only after certain features... |
683 | inputData = |
684 | "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" |
685 | "<D:propfind xmlns:D=\"DAV:\">" |
686 | "<D:prop>" |
687 | "<D:creationdate/>" |
688 | "<D:getcontentlength/>" |
689 | "<D:displayname/>" |
690 | "<D:source/>" |
691 | "<D:getcontentlanguage/>" |
692 | "<D:getcontenttype/>" |
693 | "<D:getlastmodified/>" |
694 | "<D:getetag/>" |
695 | "<D:supportedlock/>" |
696 | "<D:lockdiscovery/>" |
697 | "<D:resourcetype/>" |
698 | "<D:quota-available-bytes/>" |
699 | "<D:quota-used-bytes/>" |
700 | "</D:prop>" |
701 | "</D:propfind>" ; |
702 | method = KIO::DAV_PROPFIND; |
703 | } |
704 | |
705 | const QMap<QByteArray, QByteArray> = { |
706 | {"Depth" , stat ? "0" : "1" }, |
707 | }; |
708 | |
709 | Response response = makeDavRequest(url, method, inputData, dataMode: DataMode::Return, extraHeaders); |
710 | |
711 | // TODO |
712 | // if (!stat) { |
713 | // Utils::appendSlashToPath(m_request.url); |
714 | // } |
715 | |
716 | // Has a redirection already been called? If so, we're done. |
717 | // if (m_isRedirection || m_kioError) { |
718 | // if (m_isRedirection) { |
719 | // return davFinished(); |
720 | // } |
721 | // return WorkerResult::pass(); |
722 | // } |
723 | |
724 | QDomDocument multiResponse; |
725 | multiResponse.setContent(data: response.data, options: QDomDocument::ParseOption::UseNamespaceProcessing); |
726 | |
727 | bool hasResponse = false; |
728 | |
729 | for (QDomNode n = multiResponse.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) { |
730 | QDomElement thisResponse = n.toElement(); |
731 | if (thisResponse.isNull()) { |
732 | continue; |
733 | } |
734 | |
735 | hasResponse = true; |
736 | |
737 | QDomElement href = thisResponse.namedItem(QStringLiteral("href" )).toElement(); |
738 | if (!href.isNull()) { |
739 | entry.clear(); |
740 | |
741 | const QUrl thisURL(href.text()); // href.text() is a percent-encoded url. |
742 | if (thisURL.isValid()) { |
743 | const QUrl adjustedThisURL = thisURL.adjusted(options: QUrl::StripTrailingSlash); |
744 | const QUrl adjustedUrl = url.adjusted(options: QUrl::StripTrailingSlash); |
745 | |
746 | // base dir of a listDir(): name should be "." |
747 | QString name; |
748 | if (!stat && adjustedThisURL.path() == adjustedUrl.path()) { |
749 | name = QLatin1Char('.'); |
750 | } else { |
751 | name = adjustedThisURL.fileName(); |
752 | } |
753 | |
754 | entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, value: name.isEmpty() ? href.text() : name); |
755 | } |
756 | |
757 | QDomNodeList propstats = thisResponse.elementsByTagName(QStringLiteral("propstat" )); |
758 | |
759 | davParsePropstats(propstats, entry); |
760 | |
761 | // Since a lot of webdav servers seem not to send the content-type information |
762 | // for the requested directory listings, we attempt to guess the MIME type from |
763 | // the resource name so long as the resource is not a directory. |
764 | if (entry.stringValue(field: KIO::UDSEntry::UDS_MIME_TYPE).isEmpty() && entry.numberValue(field: KIO::UDSEntry::UDS_FILE_TYPE) != S_IFDIR) { |
765 | QMimeType mime = db.mimeTypeForFile(fileName: thisURL.path(), mode: QMimeDatabase::MatchExtension); |
766 | if (mime.isValid() && !mime.isDefault()) { |
767 | // qCDebug(KIO_HTTP) << "Setting" << mime.name() << "as guessed MIME type for" << thisURL.path(); |
768 | entry.fastInsert(field: KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, value: mime.name()); |
769 | } |
770 | } |
771 | |
772 | if (stat) { |
773 | // return an item |
774 | statEntry(entry: entry); |
775 | return KIO::WorkerResult::pass(); |
776 | } |
777 | listEntry(entry); |
778 | } else { |
779 | // qCDebug(KIO_HTTP) << "Error: no URL contained in response to PROPFIND on" << url; |
780 | } |
781 | } |
782 | |
783 | if (stat || !hasResponse) { |
784 | return KIO::WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
785 | } |
786 | |
787 | return KIO::WorkerResult::pass(); |
788 | } |
789 | |
790 | void HTTPProtocol::davParsePropstats(const QDomNodeList &propstats, KIO::UDSEntry &entry) |
791 | { |
792 | QString mimeType; |
793 | bool foundExecutable = false; |
794 | bool isDirectory = false; |
795 | uint lockCount = 0; |
796 | uint supportedLockCount = 0; |
797 | qlonglong quotaUsed = -1; |
798 | qlonglong quotaAvailable = -1; |
799 | |
800 | for (int i = 0; i < propstats.count(); i++) { |
801 | QDomElement propstat = propstats.item(index: i).toElement(); |
802 | |
803 | QDomElement status = propstat.namedItem(QStringLiteral("status" )).toElement(); |
804 | if (status.isNull()) { |
805 | // error, no status code in this propstat |
806 | // qCDebug(KIO_HTTP) << "Error, no status code in this propstat"; |
807 | return; |
808 | } |
809 | |
810 | int code = codeFromResponse(response: status.text()); |
811 | |
812 | if (code != 200) { |
813 | // qCDebug(KIO_HTTP) << "Got status code" << code << "(this may mean that some properties are unavailable)"; |
814 | continue; |
815 | } |
816 | |
817 | QDomElement prop = propstat.namedItem(QStringLiteral("prop" )).toElement(); |
818 | if (prop.isNull()) { |
819 | // qCDebug(KIO_HTTP) << "Error: no prop segment in this propstat."; |
820 | return; |
821 | } |
822 | |
823 | // TODO unnecessary? |
824 | if (hasMetaData(QStringLiteral("davRequestResponse" ))) { |
825 | QDomDocument doc; |
826 | doc.appendChild(newChild: prop); |
827 | entry.replace(field: KIO::UDSEntry::UDS_XML_PROPERTIES, value: doc.toString()); |
828 | } |
829 | |
830 | for (QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling()) { |
831 | QDomElement property = n.toElement(); |
832 | if (property.isNull()) { |
833 | continue; |
834 | } |
835 | |
836 | if (property.namespaceURI() != QLatin1String("DAV:" )) { |
837 | // break out - we're only interested in properties from the DAV namespace |
838 | continue; |
839 | } |
840 | |
841 | if (property.tagName() == QLatin1String("creationdate" )) { |
842 | // Resource creation date. Should be is ISO 8601 format. |
843 | auto datetime = parseDateTime(input: property.text(), type: property.attribute(QStringLiteral("dt" ))); |
844 | if (datetime.isValid()) { |
845 | entry.replace(field: KIO::UDSEntry::UDS_CREATION_TIME, l: datetime.toSecsSinceEpoch()); |
846 | } else { |
847 | qWarning() << "Failed to parse creationdate" << property.text() << property.attribute(QStringLiteral("dt" )); |
848 | } |
849 | } else if (property.tagName() == QLatin1String("getcontentlength" )) { |
850 | // Content length (file size) |
851 | entry.replace(field: KIO::UDSEntry::UDS_SIZE, l: property.text().toULong()); |
852 | } else if (property.tagName() == QLatin1String("displayname" )) { |
853 | // Name suitable for presentation to the user |
854 | setMetaData(QStringLiteral("davDisplayName" ), value: property.text()); |
855 | } else if (property.tagName() == QLatin1String("source" )) { |
856 | // Source template location |
857 | QDomElement source = property.namedItem(QStringLiteral("link" )).toElement().namedItem(QStringLiteral("dst" )).toElement(); |
858 | if (!source.isNull()) { |
859 | setMetaData(QStringLiteral("davSource" ), value: source.text()); |
860 | } |
861 | } else if (property.tagName() == QLatin1String("getcontentlanguage" )) { |
862 | // equiv. to Content-Language header on a GET |
863 | setMetaData(QStringLiteral("davContentLanguage" ), value: property.text()); |
864 | } else if (property.tagName() == QLatin1String("getcontenttype" )) { |
865 | // Content type (MIME type) |
866 | // This may require adjustments for other server-side webdav implementations |
867 | // (tested with Apache + mod_dav 1.0.3) |
868 | if (property.text() == QLatin1String("httpd/unix-directory" )) { |
869 | isDirectory = true; |
870 | } else if (property.text() != QLatin1String("application/octet-stream" )) { |
871 | // The server could be lazy and always return application/octet-stream; |
872 | // we will guess the MIME type later in that case. |
873 | mimeType = property.text(); |
874 | } |
875 | } else if (property.tagName() == QLatin1String("executable" )) { |
876 | // File executable status |
877 | if (property.text() == QLatin1Char('T')) { |
878 | foundExecutable = true; |
879 | } |
880 | |
881 | } else if (property.tagName() == QLatin1String("getlastmodified" )) { |
882 | // Last modification date |
883 | auto datetime = parseDateTime(input: property.text(), type: property.attribute(QStringLiteral("dt" ))); |
884 | if (datetime.isValid()) { |
885 | entry.replace(field: KIO::UDSEntry::UDS_MODIFICATION_TIME, l: datetime.toSecsSinceEpoch()); |
886 | } else { |
887 | qWarning() << "Failed to parse getlastmodified" << property.text() << property.attribute(QStringLiteral("dt" )); |
888 | } |
889 | } else if (property.tagName() == QLatin1String("getetag" )) { |
890 | // Entity tag |
891 | setMetaData(QStringLiteral("davEntityTag" ), value: property.text()); |
892 | } else if (property.tagName() == QLatin1String("supportedlock" )) { |
893 | // Supported locking specifications |
894 | for (QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) { |
895 | QDomElement lockEntry = n2.toElement(); |
896 | if (lockEntry.tagName() == QLatin1String("lockentry" )) { |
897 | QDomElement lockScope = lockEntry.namedItem(QStringLiteral("lockscope" )).toElement(); |
898 | QDomElement lockType = lockEntry.namedItem(QStringLiteral("locktype" )).toElement(); |
899 | if (!lockScope.isNull() && !lockType.isNull()) { |
900 | // Lock type was properly specified |
901 | supportedLockCount++; |
902 | const QString lockCountStr = QString::number(supportedLockCount); |
903 | const QString scope = lockScope.firstChild().toElement().tagName(); |
904 | const QString type = lockType.firstChild().toElement().tagName(); |
905 | |
906 | setMetaData(key: QLatin1String("davSupportedLockScope" ) + lockCountStr, value: scope); |
907 | setMetaData(key: QLatin1String("davSupportedLockType" ) + lockCountStr, value: type); |
908 | } |
909 | } |
910 | } |
911 | } else if (property.tagName() == QLatin1String("lockdiscovery" )) { |
912 | // Lists the available locks |
913 | davParseActiveLocks(activeLocks: property.elementsByTagName(QStringLiteral("activelock" )), lockCount); |
914 | } else if (property.tagName() == QLatin1String("resourcetype" )) { |
915 | // Resource type. "Specifies the nature of the resource." |
916 | if (!property.namedItem(QStringLiteral("collection" )).toElement().isNull()) { |
917 | // This is a collection (directory) |
918 | isDirectory = true; |
919 | } |
920 | } else if (property.tagName() == QLatin1String("quota-used-bytes" )) { |
921 | // Quota-used-bytes. "Contains the amount of storage already in use." |
922 | quotaUsed = property.text().toLongLong(); |
923 | } else if (property.tagName() == QLatin1String("quota-available-bytes" )) { |
924 | // Quota-available-bytes. "Indicates the maximum amount of additional storage available." |
925 | quotaAvailable = property.text().toLongLong(); |
926 | } else { |
927 | // qCDebug(KIO_HTTP) << "Found unknown webdav property:" << property.tagName(); |
928 | } |
929 | } |
930 | } |
931 | |
932 | setMetaData(QStringLiteral("davLockCount" ), value: QString::number(lockCount)); |
933 | setMetaData(QStringLiteral("davSupportedLockCount" ), value: QString::number(supportedLockCount)); |
934 | |
935 | entry.replace(field: KIO::UDSEntry::UDS_FILE_TYPE, l: isDirectory ? S_IFDIR : S_IFREG); |
936 | |
937 | if (foundExecutable || isDirectory) { |
938 | // File was executable, or is a directory. |
939 | entry.replace(field: KIO::UDSEntry::UDS_ACCESS, l: 0700); |
940 | } else { |
941 | entry.replace(field: KIO::UDSEntry::UDS_ACCESS, l: 0600); |
942 | } |
943 | |
944 | if (!isDirectory && !mimeType.isEmpty()) { |
945 | entry.replace(field: KIO::UDSEntry::UDS_MIME_TYPE, value: mimeType); |
946 | } |
947 | |
948 | if (quotaUsed >= 0 && quotaAvailable >= 0) { |
949 | // Only used and available storage properties exist, the total storage size has to be calculated. |
950 | setMetaData(QStringLiteral("total" ), value: QString::number(quotaUsed + quotaAvailable)); |
951 | setMetaData(QStringLiteral("available" ), value: QString::number(quotaAvailable)); |
952 | } |
953 | } |
954 | |
955 | void HTTPProtocol::davParseActiveLocks(const QDomNodeList &activeLocks, uint &lockCount) |
956 | { |
957 | for (int i = 0; i < activeLocks.count(); i++) { |
958 | const QDomElement activeLock = activeLocks.item(index: i).toElement(); |
959 | |
960 | lockCount++; |
961 | // required |
962 | const QDomElement lockScope = activeLock.namedItem(QStringLiteral("lockscope" )).toElement(); |
963 | const QDomElement lockType = activeLock.namedItem(QStringLiteral("locktype" )).toElement(); |
964 | const QDomElement lockDepth = activeLock.namedItem(QStringLiteral("depth" )).toElement(); |
965 | // optional |
966 | const QDomElement lockOwner = activeLock.namedItem(QStringLiteral("owner" )).toElement(); |
967 | const QDomElement lockTimeout = activeLock.namedItem(QStringLiteral("timeout" )).toElement(); |
968 | const QDomElement lockToken = activeLock.namedItem(QStringLiteral("locktoken" )).toElement(); |
969 | |
970 | if (!lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull()) { |
971 | // lock was properly specified |
972 | lockCount++; |
973 | const QString lockCountStr = QString::number(lockCount); |
974 | const QString scope = lockScope.firstChild().toElement().tagName(); |
975 | const QString type = lockType.firstChild().toElement().tagName(); |
976 | const QString depth = lockDepth.text(); |
977 | |
978 | setMetaData(key: QLatin1String("davLockScope" ) + lockCountStr, value: scope); |
979 | setMetaData(key: QLatin1String("davLockType" ) + lockCountStr, value: type); |
980 | setMetaData(key: QLatin1String("davLockDepth" ) + lockCountStr, value: depth); |
981 | |
982 | if (!lockOwner.isNull()) { |
983 | setMetaData(key: QLatin1String("davLockOwner" ) + lockCountStr, value: lockOwner.text()); |
984 | } |
985 | |
986 | if (!lockTimeout.isNull()) { |
987 | setMetaData(key: QLatin1String("davLockTimeout" ) + lockCountStr, value: lockTimeout.text()); |
988 | } |
989 | |
990 | if (!lockToken.isNull()) { |
991 | QDomElement tokenVal = lockScope.namedItem(QStringLiteral("href" )).toElement(); |
992 | if (!tokenVal.isNull()) { |
993 | setMetaData(key: QLatin1String("davLockToken" ) + lockCountStr, value: tokenVal.text()); |
994 | } |
995 | } |
996 | } |
997 | } |
998 | } |
999 | |
1000 | QDateTime HTTPProtocol::parseDateTime(const QString &input, const QString &type) |
1001 | { |
1002 | if (type == QLatin1String("dateTime.tz" )) { |
1003 | return QDateTime::fromString(string: input, format: Qt::ISODate); |
1004 | } |
1005 | |
1006 | // Qt decided to no longer support "GMT" for some reason: QTBUG-114681 |
1007 | QString inputUtc = input; |
1008 | inputUtc.replace(before: QLatin1String("GMT" ), after: QLatin1String("+0000" )); |
1009 | |
1010 | if (type == QLatin1String("dateTime.rfc1123" )) { |
1011 | return QDateTime::fromString(string: inputUtc, format: Qt::RFC2822Date); |
1012 | } |
1013 | |
1014 | // format not advertised... try to parse anyway |
1015 | QDateTime time = QDateTime::fromString(string: inputUtc, format: Qt::RFC2822Date); |
1016 | if (time.isValid()) { |
1017 | return time; |
1018 | } |
1019 | |
1020 | return QDateTime::fromString(string: input, format: Qt::ISODate); |
1021 | } |
1022 | |
1023 | int HTTPProtocol::codeFromResponse(const QString &response) |
1024 | { |
1025 | const int firstSpace = response.indexOf(c: QLatin1Char(' ')); |
1026 | const int secondSpace = response.indexOf(c: QLatin1Char(' '), from: firstSpace + 1); |
1027 | |
1028 | return QStringView(response).mid(pos: firstSpace + 1, n: secondSpace - firstSpace - 1).toInt(); |
1029 | } |
1030 | |
1031 | KIO::WorkerResult HTTPProtocol::stat(const QUrl &url) |
1032 | { |
1033 | if (url.scheme() != QLatin1String("webdav" ) && url.scheme() != QLatin1String("webdavs" )) { |
1034 | QString statSide = metaData(QStringLiteral("statSide" )); |
1035 | if (statSide != QLatin1String("source" )) { |
1036 | // When uploading we assume the file does not exist. |
1037 | return KIO::WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
1038 | } |
1039 | |
1040 | // When downloading we assume it exists |
1041 | KIO::UDSEntry entry; |
1042 | entry.reserve(size: 3); |
1043 | entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, value: url.fileName()); |
1044 | entry.fastInsert(field: KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); // a file |
1045 | entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); // readable by everybody |
1046 | |
1047 | statEntry(entry: entry); |
1048 | return KIO::WorkerResult::pass(); |
1049 | } |
1050 | |
1051 | return davStatList(url, stat: true); |
1052 | } |
1053 | |
1054 | KIO::WorkerResult HTTPProtocol::mkdir(const QUrl &url, int) |
1055 | { |
1056 | QByteArray inputData; |
1057 | Response response = makeDavRequest(url, method: KIO::DAV_MKCOL, inputData, dataMode: DataMode::Discard); |
1058 | |
1059 | if (response.httpCode != 201) { |
1060 | return davError(method: KIO::DAV_MKCOL, url, response); |
1061 | } |
1062 | return KIO::WorkerResult::pass(); |
1063 | } |
1064 | |
1065 | KIO::WorkerResult HTTPProtocol::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) |
1066 | { |
1067 | QMap<QByteArray, QByteArray> = { |
1068 | {"Destination" , dest.toString(options: QUrl::FullyEncoded).toUtf8()}, |
1069 | {"Overwrite" , (flags & KIO::Overwrite) ? "T" : "F" }, |
1070 | {"Depth" , "infinity" }, |
1071 | }; |
1072 | |
1073 | QByteArray inputData; |
1074 | Response response = makeDavRequest(url: src, method: KIO::DAV_MOVE, inputData, dataMode: DataMode::Discard, extraHeaders); |
1075 | |
1076 | // Work around strict Apache-2 WebDAV implementation which refuses to cooperate |
1077 | // with webdav://host/directory, instead requiring webdav://host/directory/ |
1078 | // (strangely enough it accepts Destination: without a trailing slash) |
1079 | // See BR# 209508 and BR#187970 |
1080 | // TODO |
1081 | // if (m_request.responseCode == 301) { |
1082 | // QUrl redir = m_request.redirectUrl; |
1083 | // |
1084 | // resetSessionSettings(); |
1085 | // |
1086 | // m_request.url = redir; |
1087 | // m_request.method = DAV_MOVE; |
1088 | // m_request.davData.desturl = newDest.toString(); |
1089 | // m_request.davData.overwrite = (flags & KIO::Overwrite); |
1090 | // m_request.url.setQuery(QString()); |
1091 | // m_request.cacheTag.policy = CC_Reload; |
1092 | // |
1093 | // (void)/* handling result via dav codes */ proceedUntilResponseHeader(); |
1094 | // } |
1095 | |
1096 | // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion |
1097 | if (response.httpCode == 201 || response.httpCode == 204) { |
1098 | return KIO::WorkerResult::pass(); |
1099 | } |
1100 | return davError(method: KIO::DAV_MOVE, url: src, response); |
1101 | } |
1102 | |
1103 | KIO::WorkerResult HTTPProtocol::copy(const QUrl &src, const QUrl &dest, int, KIO::JobFlags flags) |
1104 | { |
1105 | const bool isSourceLocal = src.isLocalFile(); |
1106 | const bool isDestinationLocal = dest.isLocalFile(); |
1107 | |
1108 | if (isSourceLocal && !isDestinationLocal) { |
1109 | return copyPut(src, dest, flags); |
1110 | } |
1111 | |
1112 | if (!(flags & KIO::Overwrite)) { |
1113 | // Checks if the destination exists and return an error if it does. |
1114 | if (davDestinationExists(url: dest)) { |
1115 | return KIO::WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: dest.fileName()); |
1116 | } |
1117 | } |
1118 | |
1119 | QMap<QByteArray, QByteArray> = { |
1120 | {"Destination" , dest.toString(options: QUrl::FullyEncoded).toUtf8()}, |
1121 | {"Overwrite" , (flags & KIO::Overwrite) ? "T" : "F" }, |
1122 | {"Depth" , "infinity" }, |
1123 | }; |
1124 | |
1125 | QByteArray inputData; |
1126 | Response response = makeDavRequest(url: src, method: KIO::DAV_COPY, inputData, dataMode: DataMode::Discard, extraHeaders); |
1127 | |
1128 | // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion |
1129 | if (response.httpCode == 201 || response.httpCode == 204) { |
1130 | return KIO::WorkerResult::pass(); |
1131 | } |
1132 | |
1133 | return davError(method: KIO::DAV_COPY, url: src, response); |
1134 | } |
1135 | |
1136 | KIO::WorkerResult HTTPProtocol::del(const QUrl &url, bool) |
1137 | { |
1138 | if (url.scheme().startsWith(s: QLatin1String("webdav" ))) { |
1139 | Response response = makeRequest(url, method: KIO::HTTP_DELETE, inputData: {}, dataMode: DataMode::Discard); |
1140 | |
1141 | // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content |
1142 | // on successful completion. |
1143 | if (response.httpCode == 200 || response.httpCode == 204 /*|| m_isRedirection*/) { |
1144 | return KIO::WorkerResult::pass(); |
1145 | } |
1146 | return davError(method: KIO::HTTP_DELETE, url, response); |
1147 | } |
1148 | |
1149 | Response response = makeRequest(url, method: KIO::HTTP_DELETE, inputData: {}, dataMode: DataMode::Discard); |
1150 | |
1151 | return sendHttpError(url, method: KIO::HTTP_DELETE, response); |
1152 | } |
1153 | |
1154 | KIO::WorkerResult HTTPProtocol::copyPut(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) |
1155 | { |
1156 | if (!(flags & KIO::Overwrite)) { |
1157 | // Checks if the destination exists and return an error if it does. |
1158 | if (davDestinationExists(url: dest)) { |
1159 | return KIO::WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: dest.fileName()); |
1160 | } |
1161 | } |
1162 | |
1163 | auto sourceFile = new QFile(src.toLocalFile()); |
1164 | if (!sourceFile->open(flags: QFile::ReadOnly)) { |
1165 | return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_READING, errorString: src.fileName()); |
1166 | } |
1167 | |
1168 | Response response = makeRequest(url: dest, method: KIO::HTTP_PUT, inputData: sourceFile, dataMode: {}); |
1169 | |
1170 | return sendHttpError(url: dest, method: KIO::HTTP_PUT, response); |
1171 | } |
1172 | |
1173 | bool HTTPProtocol::davDestinationExists(const QUrl &url) |
1174 | { |
1175 | QByteArray request( |
1176 | "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" |
1177 | "<D:propfind xmlns:D=\"DAV:\"><D:prop>" |
1178 | "<D:creationdate/>" |
1179 | "<D:getcontentlength/>" |
1180 | "<D:displayname/>" |
1181 | "<D:resourcetype/>" |
1182 | "</D:prop></D:propfind>" ); |
1183 | |
1184 | const QMap<QByteArray, QByteArray> = { |
1185 | {"Depth" , "0" }, |
1186 | }; |
1187 | |
1188 | Response response = makeDavRequest(url, method: KIO::DAV_PROPFIND, inputData&: request, dataMode: DataMode::Discard, extraHeaders); |
1189 | |
1190 | if (response.httpCode >= 200 && response.httpCode < 300) { |
1191 | // 2XX means the file exists. This includes 207 (multi-status response). |
1192 | // qCDebug(KIO_HTTP) << "davDestinationExists: file exists. code:" << m_request.responseCode; |
1193 | return true; |
1194 | } else { |
1195 | // qCDebug(KIO_HTTP) << "davDestinationExists: file does not exist. code:" << m_request.responseCode; |
1196 | } |
1197 | |
1198 | return false; |
1199 | } |
1200 | |
1201 | KIO::WorkerResult HTTPProtocol::davGeneric(const QUrl &url, KIO::HTTP_METHOD method, qint64 size) |
1202 | { |
1203 | // TODO what about size? |
1204 | Q_UNUSED(size) |
1205 | QMap<QByteArray, QByteArray> ; |
1206 | |
1207 | if (method == KIO::DAV_PROPFIND || method == KIO::DAV_REPORT) { |
1208 | int depth = 0; |
1209 | |
1210 | if (hasMetaData(QStringLiteral("davDepth" ))) { |
1211 | depth = metaData(QStringLiteral("davDepth" )).toInt(); |
1212 | } else { |
1213 | // TODO is warning here appropriate? |
1214 | qWarning() << "Performing DAV PROPFIND or REPORT without specifying davDepth" ; |
1215 | } |
1216 | |
1217 | extraHeaders.insert(key: "Depth" , value: QByteArray::number(depth)); |
1218 | } |
1219 | |
1220 | QByteArray inputData = getData(); |
1221 | Response response = makeDavRequest(url, method, inputData, dataMode: DataMode::Emit, extraHeaders); |
1222 | |
1223 | // TODO old code seems to use http error, not dav error |
1224 | return sendHttpError(url, method, response); |
1225 | } |
1226 | |
1227 | KIO::WorkerResult HTTPProtocol::fileSystemFreeSpace(const QUrl &url) |
1228 | { |
1229 | return davStatList(url, stat: true); |
1230 | } |
1231 | |
1232 | KIO::WorkerResult HTTPProtocol::davError(KIO::HTTP_METHOD method, const QUrl &url, const Response &response) |
1233 | { |
1234 | if (response.kioCode == KIO::ERR_ACCESS_DENIED) { |
1235 | return KIO::WorkerResult::fail(error: response.kioCode, errorString: url.toDisplayString()); |
1236 | } |
1237 | |
1238 | QString discard; |
1239 | return davError(errorMsg&: discard, method, code: response.httpCode, url: url, responseData: response.data); |
1240 | } |
1241 | |
1242 | QString HTTPProtocol::davProcessLocks() |
1243 | { |
1244 | if (hasMetaData(QStringLiteral("davLockCount" ))) { |
1245 | QString response; |
1246 | int numLocks = metaData(QStringLiteral("davLockCount" )).toInt(); |
1247 | bool bracketsOpen = false; |
1248 | for (int i = 0; i < numLocks; i++) { |
1249 | const QString countStr = QString::number(i); |
1250 | if (hasMetaData(key: QLatin1String("davLockToken" ) + countStr)) { |
1251 | if (hasMetaData(key: QLatin1String("davLockURL" ) + countStr)) { |
1252 | if (bracketsOpen) { |
1253 | response += QLatin1Char(')'); |
1254 | bracketsOpen = false; |
1255 | } |
1256 | response += QLatin1String(" <" ) + metaData(key: QLatin1String("davLockURL" ) + countStr) + QLatin1Char('>'); |
1257 | } |
1258 | |
1259 | if (!bracketsOpen) { |
1260 | response += QLatin1String(" (" ); |
1261 | bracketsOpen = true; |
1262 | } else { |
1263 | response += QLatin1Char(' '); |
1264 | } |
1265 | |
1266 | if (hasMetaData(key: QLatin1String("davLockNot" ) + countStr)) { |
1267 | response += QLatin1String("Not " ); |
1268 | } |
1269 | |
1270 | response += QLatin1Char('<') + metaData(key: QLatin1String("davLockToken" ) + countStr) + QLatin1Char('>'); |
1271 | } |
1272 | } |
1273 | |
1274 | if (bracketsOpen) { |
1275 | response += QLatin1Char(')'); |
1276 | } |
1277 | |
1278 | return response; |
1279 | } |
1280 | |
1281 | return QString(); |
1282 | } |
1283 | |
1284 | KIO::WorkerResult HTTPProtocol::davError(QString &errorMsg, KIO::HTTP_METHOD method, int code, const QUrl &url, const QByteArray &responseData) |
1285 | { |
1286 | QString action; |
1287 | QString errorString; |
1288 | int errorCode = KIO::ERR_WORKER_DEFINED; |
1289 | |
1290 | // for 412 Precondition Failed |
1291 | QString ow = i18n("Otherwise, the request would have succeeded." ); |
1292 | |
1293 | if (method == KIO::DAV_PROPFIND) { |
1294 | action = i18nc("request type" , "retrieve property values" ); |
1295 | } else if (method == KIO::DAV_PROPPATCH) { |
1296 | action = i18nc("request type" , "set property values" ); |
1297 | } else if (method == KIO::DAV_MKCOL) { |
1298 | action = i18nc("request type" , "create the requested folder" ); |
1299 | } else if (method == KIO::DAV_COPY) { |
1300 | action = i18nc("request type" , "copy the specified file or folder" ); |
1301 | } else if (method == KIO::DAV_MOVE) { |
1302 | action = i18nc("request type" , "move the specified file or folder" ); |
1303 | } else if (method == KIO::DAV_SEARCH) { |
1304 | action = i18nc("request type" , "search in the specified folder" ); |
1305 | } else if (method == KIO::DAV_LOCK) { |
1306 | action = i18nc("request type" , "lock the specified file or folder" ); |
1307 | } else if (method == KIO::DAV_UNLOCK) { |
1308 | action = i18nc("request type" , "unlock the specified file or folder" ); |
1309 | } else if (method == KIO::HTTP_DELETE) { |
1310 | action = i18nc("request type" , "delete the specified file or folder" ); |
1311 | } else if (method == KIO::HTTP_OPTIONS) { |
1312 | action = i18nc("request type" , "query the server's capabilities" ); |
1313 | } else if (method == KIO::HTTP_GET) { |
1314 | action = i18nc("request type" , "retrieve the contents of the specified file or folder" ); |
1315 | } else if (method == KIO::DAV_REPORT) { |
1316 | action = i18nc("request type" , "run a report in the specified folder" ); |
1317 | } else { |
1318 | // this should not happen, this function is for webdav errors only |
1319 | Q_ASSERT(0); |
1320 | } |
1321 | |
1322 | // default error message if the following code fails |
1323 | errorString = i18nc("%1: code, %2: request type" , |
1324 | "An unexpected error (%1) occurred " |
1325 | "while attempting to %2." , |
1326 | code, |
1327 | action); |
1328 | |
1329 | switch (code) { |
1330 | case 207: |
1331 | // 207 Multi-status |
1332 | { |
1333 | // our error info is in the returned XML document. |
1334 | // retrieve the XML document |
1335 | |
1336 | QStringList errors; |
1337 | QDomDocument multiResponse; |
1338 | multiResponse.setContent(data: responseData, options: QDomDocument::ParseOption::UseNamespaceProcessing); |
1339 | |
1340 | QDomElement multistatus = multiResponse.documentElement().namedItem(QStringLiteral("multistatus" )).toElement(); |
1341 | |
1342 | QDomNodeList responses = multistatus.elementsByTagName(QStringLiteral("response" )); |
1343 | |
1344 | for (int i = 0; i < responses.count(); i++) { |
1345 | int errCode; |
1346 | QUrl errUrl; |
1347 | |
1348 | QDomElement response = responses.item(index: i).toElement(); |
1349 | QDomElement code = response.namedItem(QStringLiteral("status" )).toElement(); |
1350 | |
1351 | if (!code.isNull()) { |
1352 | errCode = codeFromResponse(response: code.text()); |
1353 | QDomElement href = response.namedItem(QStringLiteral("href" )).toElement(); |
1354 | if (!href.isNull()) { |
1355 | errUrl = QUrl(href.text()); |
1356 | } |
1357 | QString error; |
1358 | (void)davError(errorMsg&: error, method, code: errCode, url: errUrl, responseData: {}); |
1359 | errors << error; |
1360 | } |
1361 | } |
1362 | |
1363 | errorString = i18nc("%1: request type, %2: url" , |
1364 | "An error occurred while attempting to %1, %2. A " |
1365 | "summary of the reasons is below." , |
1366 | action, |
1367 | url.toString()); |
1368 | |
1369 | errorString += QLatin1String("<ul>" ); |
1370 | |
1371 | for (const QString &error : std::as_const(t&: errors)) { |
1372 | errorString += QLatin1String("<li>" ) + error + QLatin1String("</li>" ); |
1373 | } |
1374 | |
1375 | errorString += QLatin1String("</ul>" ); |
1376 | } |
1377 | break; |
1378 | case 403: |
1379 | case 500: // hack: Apache mod_dav returns this instead of 403 (!) |
1380 | // 403 Forbidden |
1381 | // ERR_ACCESS_DENIED |
1382 | errorString = i18nc("%1: request type" , "Access was denied while attempting to %1." , action); |
1383 | break; |
1384 | case 405: |
1385 | // 405 Method Not Allowed |
1386 | if (method == KIO::DAV_MKCOL) { |
1387 | // ERR_DIR_ALREADY_EXIST |
1388 | errorString = url.toString(); |
1389 | errorCode = KIO::ERR_DIR_ALREADY_EXIST; |
1390 | } |
1391 | break; |
1392 | case 409: |
1393 | // 409 Conflict |
1394 | // ERR_ACCESS_DENIED |
1395 | errorString = i18n( |
1396 | "A resource cannot be created at the destination " |
1397 | "until one or more intermediate collections (folders) " |
1398 | "have been created." ); |
1399 | break; |
1400 | case 412: |
1401 | // 412 Precondition failed |
1402 | if (method == KIO::DAV_COPY || method == KIO::DAV_MOVE) { |
1403 | // ERR_ACCESS_DENIED |
1404 | errorString = i18n( |
1405 | "The server was unable to maintain the liveness of the\n" |
1406 | "properties listed in the propertybehavior XML element\n" |
1407 | "or you attempted to overwrite a file while requesting\n" |
1408 | "that files are not overwritten.\n %1" , |
1409 | ow); |
1410 | |
1411 | } else if (method == KIO::DAV_LOCK) { |
1412 | // ERR_ACCESS_DENIED |
1413 | errorString = i18n("The requested lock could not be granted. %1" , ow); |
1414 | } |
1415 | break; |
1416 | case 415: |
1417 | // 415 Unsupported Media Type |
1418 | // ERR_ACCESS_DENIED |
1419 | errorString = i18n("The server does not support the request type of the body." ); |
1420 | break; |
1421 | case 423: |
1422 | // 423 Locked |
1423 | // ERR_ACCESS_DENIED |
1424 | errorString = i18nc("%1: request type" , "Unable to %1 because the resource is locked." , action); |
1425 | break; |
1426 | case 425: |
1427 | // 424 Failed Dependency |
1428 | errorString = i18n("This action was prevented by another error." ); |
1429 | break; |
1430 | case 502: |
1431 | // 502 Bad Gateway |
1432 | if (method == KIO::DAV_COPY || method == KIO::DAV_MOVE) { |
1433 | // ERR_WRITE_ACCESS_DENIED |
1434 | errorString = i18nc("%1: request type" , |
1435 | "Unable to %1 because the destination server refuses " |
1436 | "to accept the file or folder." , |
1437 | action); |
1438 | } |
1439 | break; |
1440 | case 507: |
1441 | // 507 Insufficient Storage |
1442 | // ERR_DISK_FULL |
1443 | errorString = i18n( |
1444 | "The destination resource does not have sufficient space " |
1445 | "to record the state of the resource after the execution " |
1446 | "of this method." ); |
1447 | break; |
1448 | default: |
1449 | break; |
1450 | } |
1451 | |
1452 | errorMsg = errorString; |
1453 | return KIO::WorkerResult::fail(error: errorCode, errorString: errorString); |
1454 | } |
1455 | |
1456 | // HTTP generic error |
1457 | static int httpGenericError(int responseCode, QString *errorString) |
1458 | { |
1459 | Q_ASSERT(errorString); |
1460 | |
1461 | int errorCode = 0; |
1462 | errorString->clear(); |
1463 | |
1464 | if (responseCode == 204) { |
1465 | errorCode = KIO::ERR_NO_CONTENT; |
1466 | } |
1467 | |
1468 | if (responseCode >= 400 && responseCode <= 499) { |
1469 | errorCode = KIO::ERR_DOES_NOT_EXIST; |
1470 | } |
1471 | |
1472 | if (responseCode >= 500 && responseCode <= 599) { |
1473 | errorCode = KIO::ERR_INTERNAL_SERVER; |
1474 | } |
1475 | |
1476 | return errorCode; |
1477 | } |
1478 | |
1479 | // HTTP DELETE specific errors |
1480 | static int httpDelError(int responseCode, QString *errorString) |
1481 | { |
1482 | Q_ASSERT(errorString); |
1483 | |
1484 | int errorCode = 0; |
1485 | errorString->clear(); |
1486 | |
1487 | switch (responseCode) { |
1488 | case 204: |
1489 | errorCode = KIO::ERR_NO_CONTENT; |
1490 | break; |
1491 | default: |
1492 | break; |
1493 | } |
1494 | |
1495 | if (!errorCode && (responseCode < 200 || responseCode > 400) && responseCode != 404) { |
1496 | errorCode = KIO::ERR_WORKER_DEFINED; |
1497 | *errorString = i18n("The resource cannot be deleted." ); |
1498 | } |
1499 | |
1500 | if (responseCode >= 400 && responseCode <= 499) { |
1501 | errorCode = KIO::ERR_DOES_NOT_EXIST; |
1502 | } |
1503 | |
1504 | if (responseCode >= 500 && responseCode <= 599) { |
1505 | errorCode = KIO::ERR_INTERNAL_SERVER; |
1506 | } |
1507 | |
1508 | return errorCode; |
1509 | } |
1510 | |
1511 | // HTTP PUT specific errors |
1512 | static int httpPutError(const QUrl &url, int responseCode, QString *errorString) |
1513 | { |
1514 | Q_ASSERT(errorString); |
1515 | |
1516 | int errorCode = 0; |
1517 | const QString action(i18nc("request type" , "upload %1" , url.toDisplayString())); |
1518 | |
1519 | switch (responseCode) { |
1520 | case 403: |
1521 | case 405: |
1522 | case 500: // hack: Apache mod_dav returns this instead of 403 (!) |
1523 | // 403 Forbidden |
1524 | // 405 Method Not Allowed |
1525 | // ERR_ACCESS_DENIED |
1526 | *errorString = i18nc("%1: request type" , "Access was denied while attempting to %1." , action); |
1527 | errorCode = KIO::ERR_WORKER_DEFINED; |
1528 | break; |
1529 | case 409: |
1530 | // 409 Conflict |
1531 | // ERR_ACCESS_DENIED |
1532 | *errorString = i18n( |
1533 | "A resource cannot be created at the destination " |
1534 | "until one or more intermediate collections (folders) " |
1535 | "have been created." ); |
1536 | errorCode = KIO::ERR_WORKER_DEFINED; |
1537 | break; |
1538 | case 423: |
1539 | // 423 Locked |
1540 | // ERR_ACCESS_DENIED |
1541 | *errorString = i18nc("%1: request type" , "Unable to %1 because the resource is locked." , action); |
1542 | errorCode = KIO::ERR_WORKER_DEFINED; |
1543 | break; |
1544 | case 502: |
1545 | // 502 Bad Gateway |
1546 | // ERR_WRITE_ACCESS_DENIED; |
1547 | *errorString = i18nc("%1: request type" , |
1548 | "Unable to %1 because the destination server refuses " |
1549 | "to accept the file or folder." , |
1550 | action); |
1551 | errorCode = KIO::ERR_WORKER_DEFINED; |
1552 | break; |
1553 | case 507: |
1554 | // 507 Insufficient Storage |
1555 | // ERR_DISK_FULL |
1556 | *errorString = i18n( |
1557 | "The destination resource does not have sufficient space " |
1558 | "to record the state of the resource after the execution " |
1559 | "of this method." ); |
1560 | errorCode = KIO::ERR_WORKER_DEFINED; |
1561 | break; |
1562 | default: |
1563 | break; |
1564 | } |
1565 | |
1566 | if (!errorCode && (responseCode < 200 || responseCode > 400) && responseCode != 404) { |
1567 | errorCode = KIO::ERR_WORKER_DEFINED; |
1568 | *errorString = i18nc("%1: response code, %2: request type" , "An unexpected error (%1) occurred while attempting to %2." , responseCode, action); |
1569 | } |
1570 | |
1571 | if (responseCode >= 400 && responseCode <= 499) { |
1572 | errorCode = KIO::ERR_DOES_NOT_EXIST; |
1573 | } |
1574 | |
1575 | if (responseCode >= 500 && responseCode <= 599) { |
1576 | errorCode = KIO::ERR_INTERNAL_SERVER; |
1577 | } |
1578 | |
1579 | return errorCode; |
1580 | } |
1581 | |
1582 | KIO::WorkerResult HTTPProtocol::sendHttpError(const QUrl &url, KIO::HTTP_METHOD method, const HTTPProtocol::Response &response) |
1583 | { |
1584 | QString errorString; |
1585 | int errorCode = 0; |
1586 | |
1587 | if (response.kioCode == KIO::ERR_ACCESS_DENIED) { |
1588 | return KIO::WorkerResult::fail(error: response.kioCode, errorString: url.toDisplayString()); |
1589 | } |
1590 | |
1591 | int responseCode = response.httpCode; |
1592 | |
1593 | if (method == KIO::HTTP_PUT) { |
1594 | errorCode = httpPutError(url, responseCode, errorString: &errorString); |
1595 | } else if (method == KIO::HTTP_DELETE) { |
1596 | errorCode = httpDelError(responseCode, errorString: &errorString); |
1597 | } else { |
1598 | errorCode = httpGenericError(responseCode, errorString: &errorString); |
1599 | } |
1600 | |
1601 | if (errorCode) { |
1602 | if (errorCode == KIO::ERR_DOES_NOT_EXIST) { |
1603 | errorString = url.toDisplayString(); |
1604 | } |
1605 | |
1606 | return KIO::WorkerResult::fail(error: errorCode, errorString: errorString); |
1607 | } |
1608 | |
1609 | return KIO::WorkerResult::pass(); |
1610 | } |
1611 | |
1612 | QByteArray HTTPProtocol::methodToString(KIO::HTTP_METHOD method) |
1613 | { |
1614 | switch (method) { |
1615 | case KIO::HTTP_GET: |
1616 | return "GET" ; |
1617 | case KIO::HTTP_PUT: |
1618 | return "PUT" ; |
1619 | case KIO::HTTP_POST: |
1620 | return "POST" ; |
1621 | case KIO::HTTP_HEAD: |
1622 | return "HEAD" ; |
1623 | case KIO::HTTP_DELETE: |
1624 | return "DELETE" ; |
1625 | case KIO::HTTP_OPTIONS: |
1626 | return "OPTIONS" ; |
1627 | case KIO::DAV_PROPFIND: |
1628 | return "PROPFIND" ; |
1629 | case KIO::DAV_PROPPATCH: |
1630 | return "PROPPATCH" ; |
1631 | case KIO::DAV_MKCOL: |
1632 | return "MKCOL" ; |
1633 | case KIO::DAV_COPY: |
1634 | return "COPY" ; |
1635 | case KIO::DAV_MOVE: |
1636 | return "MOVE" ; |
1637 | case KIO::DAV_LOCK: |
1638 | return "LOCK" ; |
1639 | case KIO::DAV_UNLOCK: |
1640 | return "UNLOCK" ; |
1641 | case KIO::DAV_SEARCH: |
1642 | return "SEARCH" ; |
1643 | case KIO::DAV_SUBSCRIBE: |
1644 | return "SUBSCRIBE" ; |
1645 | case KIO::DAV_UNSUBSCRIBE: |
1646 | return "UNSUBSCRIBE" ; |
1647 | case KIO::DAV_POLL: |
1648 | return "POLL" ; |
1649 | case KIO::DAV_NOTIFY: |
1650 | return "NOTIFY" ; |
1651 | case KIO::DAV_REPORT: |
1652 | return "REPORT" ; |
1653 | default: |
1654 | Q_ASSERT(false); |
1655 | return QByteArray(); |
1656 | } |
1657 | } |
1658 | |
1659 | #include "http.moc" |
1660 | #include "moc_http.cpp" |
1661 | |