File indexing completed on 2024-04-28 15:27:17

0001 /*
0002     This file is part of the KDE project.
0003     SPDX-FileCopyrightText: 2009-2012 Dawit Alemayehu <adawit @ kde.org>
0004     SPDX-FileCopyrightText: 2008-2009 Urs Wolfer <uwolfer @ kde.org>
0005     SPDX-FileCopyrightText: 2007 Trolltech ASA
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "accessmanager.h"
0011 
0012 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 107)
0013 
0014 #include "accessmanagerreply_p.h"
0015 #include "kio_widgets_debug.h"
0016 #include "scheduler.h"
0017 #include <kio/mimetypejob.h>
0018 #include <kio/statjob.h>
0019 #include <kio/storedtransferjob.h>
0020 #include <kprotocolinfo.h>
0021 
0022 #include <KConfigGroup>
0023 #include <KJobWidgets>
0024 #include <KLocalizedString>
0025 #include <KSharedConfig>
0026 
0027 #include <QDBusInterface>
0028 #include <QDBusReply>
0029 #include <QNetworkCookie>
0030 #include <QNetworkReply>
0031 #include <QPointer>
0032 #include <QSslCertificate>
0033 #include <QSslCipher>
0034 #include <QSslConfiguration>
0035 #include <QUrl>
0036 #include <QWidget>
0037 
0038 static const QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = QNetworkRequest::SynchronousRequestAttribute;
0039 
0040 static qint64 sizeFromRequest(const QNetworkRequest &req)
0041 {
0042     const QVariant size = req.header(QNetworkRequest::ContentLengthHeader);
0043     if (!size.isValid()) {
0044         return -1;
0045     }
0046     bool ok = false;
0047     const qlonglong value = size.toLongLong(&ok);
0048     return (ok ? value : -1);
0049 }
0050 
0051 namespace KIO
0052 {
0053 class Q_DECL_HIDDEN AccessManager::AccessManagerPrivate
0054 {
0055 public:
0056     AccessManagerPrivate()
0057         : externalContentAllowed(true)
0058         , emitReadyReadOnMetaDataChange(false)
0059         , window(nullptr)
0060     {
0061     }
0062 
0063     void setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData);
0064 
0065     bool externalContentAllowed;
0066     bool emitReadyReadOnMetaDataChange;
0067     KIO::MetaData requestMetaData;
0068     KIO::MetaData sessionMetaData;
0069     QPointer<QWidget> window;
0070 };
0071 
0072 namespace Integration
0073 {
0074 class Q_DECL_HIDDEN CookieJar::CookieJarPrivate
0075 {
0076 public:
0077     CookieJarPrivate()
0078         : windowId((WId)-1)
0079         , isEnabled(true)
0080         , isStorageDisabled(false)
0081     {
0082     }
0083 
0084     WId windowId;
0085     bool isEnabled;
0086     bool isStorageDisabled;
0087 };
0088 
0089 }
0090 
0091 }
0092 
0093 using namespace KIO;
0094 
0095 AccessManager::AccessManager(QObject *parent)
0096     : QNetworkAccessManager(parent)
0097     , d(new AccessManager::AccessManagerPrivate())
0098 {
0099     // KDE Cookiejar (KCookieJar) integration...
0100     setCookieJar(new KIO::Integration::CookieJar);
0101 }
0102 
0103 AccessManager::~AccessManager() = default;
0104 
0105 void AccessManager::setExternalContentAllowed(bool allowed)
0106 {
0107     d->externalContentAllowed = allowed;
0108 }
0109 
0110 bool AccessManager::isExternalContentAllowed() const
0111 {
0112     return d->externalContentAllowed;
0113 }
0114 
0115 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 0)
0116 void AccessManager::setCookieJarWindowId(WId id)
0117 {
0118     QWidget *window = QWidget::find(id);
0119     if (!window) {
0120         return;
0121     }
0122 
0123     KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *>(cookieJar());
0124     if (jar) {
0125         jar->setWindowId(id);
0126     }
0127 
0128     d->window = window->isWindow() ? window : window->window();
0129 }
0130 #endif
0131 
0132 void AccessManager::setWindow(QWidget *widget)
0133 {
0134     if (!widget) {
0135         return;
0136     }
0137 
0138     d->window = widget->isWindow() ? widget : widget->window();
0139 
0140     if (!d->window) {
0141         return;
0142     }
0143 
0144     KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *>(cookieJar());
0145     if (jar) {
0146         jar->setWindowId(d->window->winId());
0147     }
0148 }
0149 
0150 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 0)
0151 WId AccessManager::cookieJarWindowid() const
0152 {
0153     KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *>(cookieJar());
0154     if (jar) {
0155         return jar->windowId();
0156     }
0157 
0158     return 0;
0159 }
0160 #endif
0161 
0162 QWidget *AccessManager::window() const
0163 {
0164     return d->window;
0165 }
0166 
0167 KIO::MetaData &AccessManager::requestMetaData()
0168 {
0169     return d->requestMetaData;
0170 }
0171 
0172 KIO::MetaData &AccessManager::sessionMetaData()
0173 {
0174     return d->sessionMetaData;
0175 }
0176 
0177 void AccessManager::putReplyOnHold(QNetworkReply *reply)
0178 {
0179     KDEPrivate::AccessManagerReply *r = qobject_cast<KDEPrivate::AccessManagerReply *>(reply);
0180     if (!r) {
0181         return;
0182     }
0183 
0184     r->putOnHold();
0185 }
0186 
0187 void AccessManager::setEmitReadyReadOnMetaDataChange(bool enable)
0188 {
0189     d->emitReadyReadOnMetaDataChange = enable;
0190 }
0191 
0192 QNetworkReply *AccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
0193 {
0194     const QUrl reqUrl(req.url());
0195 
0196     if (!d->externalContentAllowed && !KDEPrivate::AccessManagerReply::isLocalRequest(reqUrl) && reqUrl.scheme() != QLatin1String("data")) {
0197         // qDebug() << "Blocked: " << reqUrl;
0198         return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ContentAccessDenied, i18n("Blocked request."), this);
0199     }
0200 
0201     // Check if the internal ignore content disposition header is set.
0202     const bool ignoreContentDisposition = req.hasRawHeader("x-kdewebkit-ignore-disposition");
0203 
0204     // Retrieve the KIO meta data...
0205     KIO::MetaData metaData;
0206     d->setMetaDataForRequest(req, metaData);
0207 
0208     KIO::SimpleJob *kioJob = nullptr;
0209 
0210     switch (op) {
0211     case HeadOperation: {
0212         // qDebug() << "HeadOperation:" << reqUrl;
0213         kioJob = KIO::mimetype(reqUrl, KIO::HideProgressInfo);
0214         break;
0215     }
0216     case GetOperation: {
0217         // qDebug() << "GetOperation:" << reqUrl;
0218         if (!reqUrl.path().isEmpty() || reqUrl.host().isEmpty()) {
0219             kioJob = KIO::storedGet(reqUrl, KIO::NoReload, KIO::HideProgressInfo);
0220         } else {
0221             kioJob = KIO::stat(reqUrl, KIO::HideProgressInfo);
0222         }
0223 
0224         // WORKAROUND: Avoid the brain damaged stuff QtWebKit does when a POST
0225         // operation is redirected! See BR# 268694.
0226         metaData.remove(QStringLiteral("content-type")); // Remove the content-type from a GET/HEAD request!
0227         break;
0228     }
0229     case PutOperation: {
0230         // qDebug() << "PutOperation:" << reqUrl;
0231         if (outgoingData) {
0232             Q_ASSERT(outgoingData->isReadable());
0233             StoredTransferJob *storedJob = KIO::storedPut(outgoingData, reqUrl, -1, KIO::HideProgressInfo);
0234             storedJob->setAsyncDataEnabled(outgoingData->isSequential());
0235 
0236             QVariant len = req.header(QNetworkRequest::ContentLengthHeader);
0237             if (len.isValid()) {
0238                 storedJob->setTotalSize(len.toULongLong());
0239             }
0240 
0241             kioJob = storedJob;
0242         } else {
0243             kioJob = KIO::put(reqUrl, -1, KIO::HideProgressInfo);
0244         }
0245         break;
0246     }
0247     case PostOperation: {
0248         kioJob = KIO::storedHttpPost(outgoingData, reqUrl, sizeFromRequest(req), KIO::HideProgressInfo);
0249         if (!metaData.contains(QLatin1String("content-type"))) {
0250             const QVariant header = req.header(QNetworkRequest::ContentTypeHeader);
0251             if (header.isValid()) {
0252                 metaData.insert(QStringLiteral("content-type"), (QStringLiteral("Content-Type: ") + header.toString()));
0253             } else {
0254                 metaData.insert(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded"));
0255             }
0256         }
0257         break;
0258     }
0259     case DeleteOperation: {
0260         // qDebug() << "DeleteOperation:" << reqUrl;
0261         kioJob = KIO::http_delete(reqUrl, KIO::HideProgressInfo);
0262         break;
0263     }
0264     case CustomOperation: {
0265         const QByteArray &method = req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
0266         // qDebug() << "CustomOperation:" << reqUrl << "method:" << method << "outgoing data:" << outgoingData;
0267 
0268         if (method.isEmpty()) {
0269             return new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::ProtocolUnknownError, i18n("Unknown HTTP verb."), this);
0270         }
0271 
0272         const qint64 size = sizeFromRequest(req);
0273         if (size > 0) {
0274             kioJob = KIO::http_post(reqUrl, outgoingData, size, KIO::HideProgressInfo);
0275         } else {
0276             kioJob = KIO::get(reqUrl, KIO::NoReload, KIO::HideProgressInfo);
0277         }
0278 
0279         metaData.insert(QStringLiteral("CustomHTTPMethod"), QString::fromUtf8(method));
0280         break;
0281     }
0282     default: {
0283         qCWarning(KIO_WIDGETS) << "Unsupported KIO operation requested! Deferring to QNetworkAccessManager...";
0284         return QNetworkAccessManager::createRequest(op, req, outgoingData);
0285     }
0286     }
0287 
0288     // Set the job priority
0289     switch (req.priority()) {
0290     case QNetworkRequest::HighPriority:
0291         KIO::Scheduler::setSimpleJobPriority(kioJob, -5);
0292         break;
0293     case QNetworkRequest::LowPriority:
0294         KIO::Scheduler::setSimpleJobPriority(kioJob, 5);
0295         break;
0296     default:
0297         break;
0298     }
0299 
0300     KDEPrivate::AccessManagerReply *reply;
0301 
0302     /*
0303       NOTE: Here we attempt to handle synchronous XHR requests. Unfortunately,
0304       due to the fact that QNAM is both synchronous and multi-thread while KIO
0305       is completely the opposite (asynchronous and not thread safe), the code
0306       below might cause crashes like the one reported in bug# 287778 (nested
0307       event loops are inherently dangerous).
0308 
0309       Unfortunately, all attempts to address the crash has so far failed due to
0310       the many regressions they caused, e.g. bug# 231932 and 297954. Hence, until
0311       a solution is found, we have to live with the side effects of creating
0312       nested event loops.
0313     */
0314     if (req.attribute(gSynchronousNetworkRequestAttribute).toBool()) {
0315         KJobWidgets::setWindow(kioJob, d->window);
0316         kioJob->setRedirectionHandlingEnabled(true);
0317         if (kioJob->exec()) {
0318             QByteArray data;
0319             if (StoredTransferJob *storedJob = qobject_cast<KIO::StoredTransferJob *>(kioJob)) {
0320                 data = storedJob->data();
0321             }
0322             reply = new KDEPrivate::AccessManagerReply(op, req, data, kioJob->url(), kioJob->metaData(), this);
0323             // qDebug() << "Synchronous XHR:" << reply << reqUrl;
0324         } else {
0325             qCWarning(KIO_WIDGETS) << "Failed to create a synchronous XHR for" << reqUrl;
0326             qCWarning(KIO_WIDGETS) << "REASON:" << kioJob->errorString();
0327             reply = new KDEPrivate::AccessManagerReply(op, req, QNetworkReply::UnknownNetworkError, kioJob->errorText(), this);
0328         }
0329     } else {
0330         // Set the window on the KIO ui delegate
0331         if (d->window) {
0332             KJobWidgets::setWindow(kioJob, d->window);
0333         }
0334 
0335         // Disable internal automatic redirection handling
0336         kioJob->setRedirectionHandlingEnabled(false);
0337 
0338         // Set the job priority
0339         switch (req.priority()) {
0340         case QNetworkRequest::HighPriority:
0341             KIO::Scheduler::setSimpleJobPriority(kioJob, -5);
0342             break;
0343         case QNetworkRequest::LowPriority:
0344             KIO::Scheduler::setSimpleJobPriority(kioJob, 5);
0345             break;
0346         default:
0347             break;
0348         }
0349 
0350         // Set the meta data for this job...
0351         kioJob->setMetaData(metaData);
0352 
0353         // Create the reply...
0354         reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadyReadOnMetaDataChange, this);
0355         // qDebug() << reply << reqUrl;
0356     }
0357 
0358     if (ignoreContentDisposition && reply) {
0359         // qDebug() << "Content-Disposition WILL BE IGNORED!";
0360         reply->setIgnoreContentDisposition(ignoreContentDisposition);
0361     }
0362 
0363     return reply;
0364 }
0365 
0366 static inline void moveMetaData(KIO::MetaData &metaData, const QString &metaDataKey, QNetworkRequest &request, const QByteArray &requestKey)
0367 {
0368     if (request.hasRawHeader(requestKey)) {
0369         metaData.insert(metaDataKey, QString::fromUtf8(request.rawHeader(requestKey)));
0370         request.setRawHeader(requestKey, QByteArray());
0371     }
0372 }
0373 
0374 void AccessManager::AccessManagerPrivate::setMetaDataForRequest(QNetworkRequest request, KIO::MetaData &metaData)
0375 {
0376     // Add any meta data specified within request...
0377     QVariant userMetaData = request.attribute(static_cast<QNetworkRequest::Attribute>(MetaData));
0378     if (userMetaData.isValid() && userMetaData.type() == QVariant::Map) {
0379         metaData += userMetaData.toMap();
0380     }
0381 
0382     metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
0383 
0384     moveMetaData(metaData, QStringLiteral("UserAgent"), request, QByteArrayLiteral("User-Agent"));
0385     moveMetaData(metaData, QStringLiteral("accept"), request, QByteArrayLiteral("Accept"));
0386     moveMetaData(metaData, QStringLiteral("Charsets"), request, QByteArrayLiteral("Accept-Charset"));
0387     moveMetaData(metaData, QStringLiteral("Languages"), request, QByteArrayLiteral("Accept-Language"));
0388     moveMetaData(metaData, QStringLiteral("referrer"), request, QByteArrayLiteral("Referer")); // Don't try to correct spelling!
0389     moveMetaData(metaData, QStringLiteral("content-type"), request, QByteArrayLiteral("Content-Type"));
0390 
0391     if (request.attribute(QNetworkRequest::AuthenticationReuseAttribute) == QNetworkRequest::Manual) {
0392         metaData.insert(QStringLiteral("no-preemptive-auth-reuse"), QStringLiteral("true"));
0393     }
0394 
0395     request.setRawHeader("Content-Length", QByteArray());
0396     request.setRawHeader("Connection", QByteArray());
0397     request.setRawHeader("If-None-Match", QByteArray());
0398     request.setRawHeader("If-Modified-Since", QByteArray());
0399     request.setRawHeader("x-kdewebkit-ignore-disposition", QByteArray());
0400 
0401     QStringList customHeaders;
0402     const QList<QByteArray> list = request.rawHeaderList();
0403     for (const QByteArray &key : list) {
0404         const QByteArray value = request.rawHeader(key);
0405         if (value.length()) {
0406             customHeaders << (QString::fromUtf8(key) + QLatin1String(": ") + QString::fromUtf8(value));
0407         }
0408     }
0409 
0410     if (!customHeaders.isEmpty()) {
0411         metaData.insert(QStringLiteral("customHTTPHeader"), customHeaders.join(QLatin1String("\r\n")));
0412     }
0413 
0414     // Append per request meta data, if any...
0415     if (!requestMetaData.isEmpty()) {
0416         metaData += requestMetaData;
0417         // Clear per request meta data...
0418         requestMetaData.clear();
0419     }
0420 
0421     // Append per session meta data, if any...
0422     if (!sessionMetaData.isEmpty()) {
0423         metaData += sessionMetaData;
0424     }
0425 }
0426 
0427 using namespace KIO::Integration;
0428 
0429 // The strings come from qtbase/src/network/ssl (grep for protocolString)
0430 static QSsl::SslProtocol qSslProtocolFromString(const QString &str)
0431 {
0432     if (str.compare(QStringLiteral("TLSv1"), Qt::CaseInsensitive) == 0) {
0433         return QSsl::TlsV1_0;
0434     }
0435 
0436     if (str.compare(QStringLiteral("TLSv1.1"), Qt::CaseInsensitive) == 0) {
0437         return QSsl::TlsV1_1;
0438     }
0439 
0440     if (str.compare(QStringLiteral("TLSv1.2"), Qt::CaseInsensitive) == 0) {
0441         return QSsl::TlsV1_2;
0442     }
0443 
0444     if (str.compare(QStringLiteral("TLSv1.3"), Qt::CaseInsensitive) == 0) {
0445         return QSsl::TlsV1_3;
0446     }
0447 
0448     return QSsl::AnyProtocol;
0449 }
0450 
0451 bool KIO::Integration::sslConfigFromMetaData(const KIO::MetaData &metadata, QSslConfiguration &sslconfig)
0452 {
0453     bool success = false;
0454 
0455     if (metadata.value(QStringLiteral("ssl_in_use")) == QLatin1String("TRUE")) {
0456         const QSsl::SslProtocol sslProto = qSslProtocolFromString(metadata.value(QStringLiteral("ssl_protocol_version")));
0457         QList<QSslCipher> cipherList;
0458         cipherList << QSslCipher(metadata.value(QStringLiteral("ssl_cipher_name")), sslProto);
0459         sslconfig.setCaCertificates(QSslCertificate::fromData(metadata.value(QStringLiteral("ssl_peer_chain")).toUtf8()));
0460         sslconfig.setCiphers(cipherList);
0461         sslconfig.setProtocol(sslProto);
0462         success = sslconfig.isNull();
0463     }
0464 
0465     return success;
0466 }
0467 
0468 CookieJar::CookieJar(QObject *parent)
0469     : QNetworkCookieJar(parent)
0470     , d(new CookieJar::CookieJarPrivate)
0471 {
0472     reparseConfiguration();
0473 }
0474 
0475 CookieJar::~CookieJar() = default;
0476 
0477 WId CookieJar::windowId() const
0478 {
0479     return d->windowId;
0480 }
0481 
0482 bool CookieJar::isCookieStorageDisabled() const
0483 {
0484     return d->isStorageDisabled;
0485 }
0486 
0487 QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const
0488 {
0489     QList<QNetworkCookie> cookieList;
0490 
0491     if (!d->isEnabled) {
0492         return cookieList;
0493     }
0494     QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"));
0495     QDBusReply<QString> reply = kcookiejar.call(QStringLiteral("findDOMCookies"), url.toString(QUrl::RemoveUserInfo), (qlonglong)d->windowId);
0496 
0497     if (!reply.isValid()) {
0498         qCWarning(KIO_WIDGETS) << "Unable to communicate with the cookiejar!";
0499         return cookieList;
0500     }
0501 
0502     const QString cookieStr = reply.value();
0503     const QStringList cookies = cookieStr.split(QStringLiteral("; "), Qt::SkipEmptyParts);
0504     for (const QString &cookie : cookies) {
0505         const int index = cookie.indexOf(QLatin1Char('='));
0506         const QStringView cookieView(cookie);
0507         const auto name = cookieView.left(index);
0508         const auto value = cookieView.right(cookie.length() - index - 1);
0509         cookieList << QNetworkCookie(name.toUtf8(), value.toUtf8());
0510         // qDebug() << "cookie: name=" << name << ", value=" << value;
0511     }
0512 
0513     return cookieList;
0514 }
0515 
0516 bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
0517 {
0518     if (!d->isEnabled) {
0519         return false;
0520     }
0521 
0522     QDBusInterface kcookiejar(QStringLiteral("org.kde.kcookiejar5"), QStringLiteral("/modules/kcookiejar"), QStringLiteral("org.kde.KCookieServer"));
0523     for (const QNetworkCookie &cookie : cookieList) {
0524         QByteArray cookieHeader("Set-Cookie: ");
0525         if (d->isStorageDisabled && !cookie.isSessionCookie()) {
0526             QNetworkCookie sessionCookie(cookie);
0527             sessionCookie.setExpirationDate(QDateTime());
0528             cookieHeader += sessionCookie.toRawForm();
0529         } else {
0530             cookieHeader += cookie.toRawForm();
0531         }
0532         kcookiejar.call(QStringLiteral("addCookies"), url.toString(QUrl::RemoveUserInfo), cookieHeader, (qlonglong)d->windowId);
0533         // qDebug() << "[" << d->windowId << "]" << cookieHeader << " from " << url;
0534     }
0535 
0536     return !kcookiejar.lastError().isValid();
0537 }
0538 
0539 void CookieJar::setDisableCookieStorage(bool disable)
0540 {
0541     d->isStorageDisabled = disable;
0542 }
0543 
0544 void CookieJar::setWindowId(WId id)
0545 {
0546     d->windowId = id;
0547 }
0548 
0549 void CookieJar::reparseConfiguration()
0550 {
0551     KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("kcookiejarrc"), KConfig::NoGlobals)->group("Cookie Policy");
0552     d->isEnabled = cfg.readEntry("Cookies", true);
0553 }
0554 
0555 #include "moc_accessmanager.cpp"
0556 
0557 #endif