File indexing completed on 2025-04-20 12:25:14
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