File indexing completed on 2023-09-24 04:09:16
0001 /* 0002 This file is part of the KDE project. 0003 SPDX-FileCopyrightText: 2008 Alex Merry <alex.merry @ kdemail.net> 0004 SPDX-FileCopyrightText: 2008-2009 Urs Wolfer <uwolfer @ kde.org> 0005 SPDX-FileCopyrightText: 2009-2012 Dawit Alemayehu <adawit @ kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "accessmanagerreply_p.h" 0011 #include "accessmanager.h" 0012 #include "job.h" 0013 #include "kio_widgets_debug.h" 0014 0015 #include <kprotocolinfo.h> 0016 #include <kurlauthorized.h> 0017 0018 #include <QMimeDatabase> 0019 #include <QSslConfiguration> 0020 #include <QtMath> 0021 0022 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 107) 0023 0024 namespace KDEPrivate 0025 { 0026 AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, 0027 const QNetworkRequest &request, 0028 KIO::SimpleJob *kioJob, 0029 bool emitReadyReadOnMetaDataChange, 0030 QObject *parent) 0031 : QNetworkReply(parent) 0032 , m_offset(0) 0033 , m_metaDataRead(false) 0034 , m_ignoreContentDisposition(false) 0035 , m_emitReadyReadOnMetaDataChange(emitReadyReadOnMetaDataChange) 0036 , m_kioJob(kioJob) 0037 0038 { 0039 setRequest(request); 0040 setOpenMode(QIODevice::ReadOnly); 0041 setUrl(request.url()); 0042 setOperation(op); 0043 setError(NoError, QString()); 0044 0045 if (!request.sslConfiguration().isNull()) { 0046 setSslConfiguration(request.sslConfiguration()); 0047 } 0048 0049 connect(kioJob, &KJob::percentChanged, this, &AccessManagerReply::slotPercent); 0050 0051 if (auto *statJob = qobject_cast<KIO::StatJob *>(kioJob)) { 0052 connect(statJob, &KIO::StatJob::redirection, this, &AccessManagerReply::slotRedirection); 0053 connect(statJob, &KJob::result, this, &AccessManagerReply::slotStatResult); 0054 } else if (auto *tJob = qobject_cast<KIO::TransferJob *>(kioJob)) { 0055 connect(tJob, &KJob::result, this, &AccessManagerReply::slotResult); 0056 connect(tJob, &KIO::TransferJob::redirection, this, &AccessManagerReply::slotRedirection); 0057 connect(tJob, &KIO::TransferJob::data, this, &AccessManagerReply::slotData); 0058 connect(tJob, &KIO::TransferJob::mimeTypeFound, this, &AccessManagerReply::slotMimeType); 0059 } else { 0060 connect(kioJob, &KJob::result, this, &AccessManagerReply::slotResult); 0061 } 0062 } 0063 0064 AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, 0065 const QNetworkRequest &request, 0066 const QByteArray &data, 0067 const QUrl &url, 0068 const KIO::MetaData &metaData, 0069 QObject *parent) 0070 : QNetworkReply(parent) 0071 , m_data(data) 0072 , m_offset(0) 0073 , m_ignoreContentDisposition(false) 0074 , m_emitReadyReadOnMetaDataChange(false) 0075 { 0076 setRequest(request); 0077 setOpenMode(QIODevice::ReadOnly); 0078 setUrl((url.isValid() ? url : request.url())); 0079 setOperation(op); 0080 setHeaderFromMetaData(metaData); 0081 0082 if (!request.sslConfiguration().isNull()) { 0083 setSslConfiguration(request.sslConfiguration()); 0084 } 0085 0086 setError(NoError, QString()); 0087 emitFinished(true, Qt::QueuedConnection); 0088 } 0089 0090 AccessManagerReply::AccessManagerReply(const QNetworkAccessManager::Operation op, 0091 const QNetworkRequest &request, 0092 QNetworkReply::NetworkError errorCode, 0093 const QString &errorMessage, 0094 QObject *parent) 0095 : QNetworkReply(parent) 0096 , m_offset(0) 0097 { 0098 setRequest(request); 0099 setOpenMode(QIODevice::ReadOnly); 0100 setUrl(request.url()); 0101 setOperation(op); 0102 setError(static_cast<QNetworkReply::NetworkError>(errorCode), errorMessage); 0103 const auto networkError = error(); 0104 if (networkError != QNetworkReply::NoError) { 0105 auto occurrFunc = [this, networkError]() { 0106 Q_EMIT errorOccurred(networkError); 0107 }; 0108 QMetaObject::invokeMethod(this, occurrFunc, Qt::QueuedConnection); 0109 } 0110 0111 emitFinished(true, Qt::QueuedConnection); 0112 } 0113 0114 AccessManagerReply::~AccessManagerReply() 0115 { 0116 } 0117 0118 void AccessManagerReply::abort() 0119 { 0120 if (m_kioJob) { 0121 m_kioJob.data()->disconnect(this); 0122 } 0123 m_kioJob.clear(); 0124 m_data.clear(); 0125 m_offset = 0; 0126 m_metaDataRead = false; 0127 } 0128 0129 qint64 AccessManagerReply::bytesAvailable() const 0130 { 0131 return (QNetworkReply::bytesAvailable() + m_data.length() - m_offset); 0132 } 0133 0134 qint64 AccessManagerReply::readData(char *data, qint64 maxSize) 0135 { 0136 const qint64 length = qMin(qint64(m_data.length() - m_offset), maxSize); 0137 0138 if (length <= 0) { 0139 return 0; 0140 } 0141 0142 memcpy(data, m_data.constData() + m_offset, length); 0143 m_offset += length; 0144 0145 if (m_data.length() == m_offset) { 0146 m_data.clear(); 0147 m_offset = 0; 0148 } 0149 0150 return length; 0151 } 0152 0153 bool AccessManagerReply::ignoreContentDisposition(const KIO::MetaData &metaData) 0154 { 0155 if (m_ignoreContentDisposition) { 0156 return true; 0157 } 0158 0159 if (!metaData.contains(QLatin1String("content-disposition-type"))) { 0160 return true; 0161 } 0162 0163 bool ok = false; 0164 const int statusCode = attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); 0165 if (!ok || statusCode < 200 || statusCode > 299) { 0166 return true; 0167 } 0168 0169 return false; 0170 } 0171 0172 void AccessManagerReply::setHeaderFromMetaData(const KIO::MetaData &_metaData) 0173 { 0174 if (_metaData.isEmpty()) { 0175 return; 0176 } 0177 0178 KIO::MetaData metaData(_metaData); 0179 0180 // Set the encryption attribute and values... 0181 QSslConfiguration sslConfig; 0182 const bool isEncrypted = KIO::Integration::sslConfigFromMetaData(metaData, sslConfig); 0183 setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, isEncrypted); 0184 if (isEncrypted) { 0185 setSslConfiguration(sslConfig); 0186 } 0187 0188 // Set the raw header information... 0189 const QStringList httpHeaders(metaData.value(QStringLiteral("HTTP-Headers")).split(QLatin1Char('\n'), Qt::SkipEmptyParts)); 0190 if (httpHeaders.isEmpty()) { 0191 const auto charSetIt = metaData.constFind(QStringLiteral("charset")); 0192 if (charSetIt != metaData.constEnd()) { 0193 QString mimeType = header(QNetworkRequest::ContentTypeHeader).toString(); 0194 mimeType += QLatin1String(" ; charset=") + *charSetIt; 0195 // qDebug() << "changed content-type to" << mimeType; 0196 setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); 0197 } 0198 } else { 0199 for (const QString &httpHeader : httpHeaders) { 0200 int index = httpHeader.indexOf(QLatin1Char(':')); 0201 // Handle HTTP status line... 0202 if (index == -1) { 0203 // Except for the status line, all HTTP header must be an nvpair of 0204 // type "<name>:<value>" 0205 if (!httpHeader.startsWith(QLatin1String("HTTP/"), Qt::CaseInsensitive)) { 0206 continue; 0207 } 0208 0209 QStringList statusLineAttrs(httpHeader.split(QLatin1Char(' '), Qt::SkipEmptyParts)); 0210 if (statusLineAttrs.count() > 1) { 0211 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusLineAttrs.at(1)); 0212 } 0213 0214 if (statusLineAttrs.count() > 2) { 0215 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusLineAttrs.at(2)); 0216 } 0217 0218 continue; 0219 } 0220 0221 const auto headerName = QStringView(httpHeader).left(index); 0222 // Ignore cookie header since it is handled by the http KIO worker. 0223 if (headerName.startsWith(QLatin1String("set-cookie"), Qt::CaseInsensitive)) { 0224 continue; 0225 } 0226 0227 if (headerName.startsWith(QLatin1String("content-disposition"), Qt::CaseInsensitive) && ignoreContentDisposition(metaData)) { 0228 continue; 0229 } 0230 0231 QString headerValue = httpHeader.mid(index + 1); 0232 // Without overriding the corrected mime-type sent by kio_http, add 0233 // back the "charset=" portion of the content-type header if present. 0234 if (headerName.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive)) { 0235 QString mimeType(header(QNetworkRequest::ContentTypeHeader).toString()); 0236 0237 if (m_ignoreContentDisposition) { 0238 // If the server returned application/octet-stream, try to determine the 0239 // real content type from the disposition filename. 0240 if (mimeType == QLatin1String("application/octet-stream")) { 0241 const QString fileName(metaData.value(QStringLiteral("content-disposition-filename"))); 0242 QMimeDatabase db; 0243 QMimeType mime = db.mimeTypeForFile((fileName.isEmpty() ? url().path() : fileName), QMimeDatabase::MatchExtension); 0244 mimeType = mime.name(); 0245 } 0246 metaData.remove(QStringLiteral("content-disposition-type")); 0247 metaData.remove(QStringLiteral("content-disposition-filename")); 0248 } 0249 0250 if (!headerValue.contains(mimeType, Qt::CaseInsensitive)) { 0251 index = headerValue.indexOf(QLatin1Char(';')); 0252 if (index == -1) { 0253 headerValue = mimeType; 0254 } else { 0255 headerValue.replace(0, index, mimeType); 0256 } 0257 // qDebug() << "Changed mime-type from" << mimeType << "to" << headerValue; 0258 } 0259 } 0260 setRawHeader(headerName.trimmed().toUtf8(), headerValue.trimmed().toUtf8()); 0261 } 0262 } 0263 0264 // Set the returned meta data as attribute... 0265 setAttribute(static_cast<QNetworkRequest::Attribute>(KIO::Integration::AccessManager::MetaData), metaData.toVariant()); 0266 } 0267 0268 void AccessManagerReply::setIgnoreContentDisposition(bool on) 0269 { 0270 // qDebug() << on; 0271 m_ignoreContentDisposition = on; 0272 } 0273 0274 void AccessManagerReply::putOnHold() 0275 { 0276 if (!m_kioJob || isFinished()) { 0277 return; 0278 } 0279 0280 // qDebug() << m_kioJob << m_data; 0281 m_kioJob.data()->disconnect(this); 0282 m_kioJob.data()->putOnHold(); 0283 m_kioJob.clear(); 0284 } 0285 0286 bool AccessManagerReply::isLocalRequest(const QUrl &url) 0287 { 0288 const QString scheme(url.scheme()); 0289 return (KProtocolInfo::isKnownProtocol(scheme) && KProtocolInfo::protocolClass(scheme).compare(QStringLiteral(":local"), Qt::CaseInsensitive) == 0); 0290 } 0291 0292 void AccessManagerReply::readHttpResponseHeaders(KIO::Job *job) 0293 { 0294 if (!job || m_metaDataRead) { 0295 return; 0296 } 0297 0298 KIO::MetaData metaData(job->metaData()); 0299 if (metaData.isEmpty()) { 0300 // Allow handling of local resources such as man pages and file url... 0301 if (isLocalRequest(url())) { 0302 setHeader(QNetworkRequest::ContentLengthHeader, job->totalAmount(KJob::Bytes)); 0303 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, QStringLiteral("200")); 0304 Q_EMIT metaDataChanged(); 0305 } 0306 return; 0307 } 0308 0309 setHeaderFromMetaData(metaData); 0310 m_metaDataRead = true; 0311 Q_EMIT metaDataChanged(); 0312 } 0313 0314 int AccessManagerReply::jobError(KJob *kJob) 0315 { 0316 const int errCode = kJob->error(); 0317 switch (errCode) { 0318 case 0: 0319 break; // No error; 0320 case KIO::ERR_WORKER_DEFINED: 0321 case KIO::ERR_NO_CONTENT: // Sent by a 204 response is not an error condition. 0322 setError(QNetworkReply::NoError, kJob->errorText()); 0323 break; 0324 case KIO::ERR_IS_DIRECTORY: 0325 // This error condition can happen if you click on an ftp link that points 0326 // to a directory instead of a file, e.g. ftp://ftp.kde.org/pub 0327 setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("inode/directory")); 0328 setError(QNetworkReply::NoError, kJob->errorText()); 0329 break; 0330 case KIO::ERR_CANNOT_CONNECT: 0331 setError(QNetworkReply::ConnectionRefusedError, kJob->errorText()); 0332 break; 0333 case KIO::ERR_UNKNOWN_HOST: 0334 setError(QNetworkReply::HostNotFoundError, kJob->errorText()); 0335 break; 0336 case KIO::ERR_SERVER_TIMEOUT: 0337 setError(QNetworkReply::TimeoutError, kJob->errorText()); 0338 break; 0339 case KIO::ERR_USER_CANCELED: 0340 case KIO::ERR_ABORTED: 0341 setError(QNetworkReply::OperationCanceledError, kJob->errorText()); 0342 break; 0343 case KIO::ERR_UNKNOWN_PROXY_HOST: 0344 setError(QNetworkReply::ProxyNotFoundError, kJob->errorText()); 0345 break; 0346 case KIO::ERR_ACCESS_DENIED: 0347 setError(QNetworkReply::ContentAccessDenied, kJob->errorText()); 0348 break; 0349 case KIO::ERR_WRITE_ACCESS_DENIED: 0350 setError(QNetworkReply::ContentOperationNotPermittedError, kJob->errorText()); 0351 break; 0352 case KIO::ERR_DOES_NOT_EXIST: 0353 setError(QNetworkReply::ContentNotFoundError, kJob->errorText()); 0354 break; 0355 case KIO::ERR_CANNOT_AUTHENTICATE: 0356 setError(QNetworkReply::AuthenticationRequiredError, kJob->errorText()); 0357 break; 0358 case KIO::ERR_UNSUPPORTED_PROTOCOL: 0359 case KIO::ERR_NO_SOURCE_PROTOCOL: 0360 setError(QNetworkReply::ProtocolUnknownError, kJob->errorText()); 0361 break; 0362 case KIO::ERR_CONNECTION_BROKEN: 0363 setError(QNetworkReply::RemoteHostClosedError, kJob->errorText()); 0364 break; 0365 case KIO::ERR_UNSUPPORTED_ACTION: 0366 setError(QNetworkReply::ProtocolInvalidOperationError, kJob->errorText()); 0367 break; 0368 default: 0369 setError(QNetworkReply::UnknownNetworkError, kJob->errorText()); 0370 } 0371 0372 return errCode; 0373 } 0374 0375 void AccessManagerReply::slotData(KIO::Job *kioJob, const QByteArray &data) 0376 { 0377 Q_UNUSED(kioJob); 0378 if (data.isEmpty()) { 0379 return; 0380 } 0381 0382 qint64 newSizeWithOffset = m_data.size() + data.size(); 0383 if (newSizeWithOffset <= m_data.capacity()) { 0384 // Already enough space 0385 } else if (newSizeWithOffset - m_offset <= m_data.capacity()) { 0386 // We get enough space with ::remove. 0387 m_data.remove(0, m_offset); 0388 m_offset = 0; 0389 } else { 0390 // We have to resize the array, which implies an expensive memmove. 0391 // Do it ourselves to save m_offset bytes. 0392 QByteArray newData; 0393 // Leave some free space to avoid that every slotData call results in 0394 // a reallocation. qNextPowerOfTwo is what QByteArray does internally. 0395 newData.reserve(qNextPowerOfTwo(newSizeWithOffset - m_offset)); 0396 newData.append(m_data.constData() + m_offset, m_data.size() - m_offset); 0397 m_data = newData; 0398 m_offset = 0; 0399 } 0400 0401 m_data += data; 0402 0403 Q_EMIT readyRead(); 0404 } 0405 0406 void AccessManagerReply::slotMimeType(KIO::Job *kioJob, const QString &mimeType) 0407 { 0408 // qDebug() << kioJob << mimeType; 0409 setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); 0410 readHttpResponseHeaders(kioJob); 0411 if (m_emitReadyReadOnMetaDataChange) { 0412 Q_EMIT readyRead(); 0413 } 0414 } 0415 0416 void AccessManagerReply::slotResult(KJob *kJob) 0417 { 0418 const int errcode = jobError(kJob); 0419 0420 const QUrl redirectUrl = attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0421 if (!redirectUrl.isValid()) { 0422 setAttribute(static_cast<QNetworkRequest::Attribute>(KIO::Integration::AccessManager::KioError), errcode); 0423 if (errcode && errcode != KIO::ERR_NO_CONTENT) { 0424 const auto networkError = error(); 0425 Q_EMIT errorOccurred(networkError); 0426 } 0427 } 0428 0429 // Make sure HTTP response headers are always set. 0430 if (!m_metaDataRead) { 0431 readHttpResponseHeaders(qobject_cast<KIO::Job *>(kJob)); 0432 } 0433 0434 emitFinished(true); 0435 } 0436 0437 void AccessManagerReply::slotStatResult(KJob *kJob) 0438 { 0439 if (jobError(kJob)) { 0440 const auto networkError = error(); 0441 Q_EMIT errorOccurred(networkError); 0442 emitFinished(true); 0443 return; 0444 } 0445 0446 KIO::StatJob *statJob = qobject_cast<KIO::StatJob *>(kJob); 0447 Q_ASSERT(statJob); 0448 0449 KIO::UDSEntry entry = statJob->statResult(); 0450 QString mimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); 0451 if (mimeType.isEmpty() && entry.isDir()) { 0452 mimeType = QStringLiteral("inode/directory"); 0453 } 0454 0455 if (!mimeType.isEmpty()) { 0456 setHeader(QNetworkRequest::ContentTypeHeader, mimeType.toUtf8()); 0457 } 0458 0459 emitFinished(true); 0460 } 0461 0462 void AccessManagerReply::slotRedirection(KIO::Job *job, const QUrl &u) 0463 { 0464 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), url(), u)) { 0465 qCWarning(KIO_WIDGETS) << "Redirection from" << url() << "to" << u << "REJECTED by policy!"; 0466 setError(QNetworkReply::ContentAccessDenied, u.toString()); 0467 const auto networkError = error(); 0468 Q_EMIT errorOccurred(networkError); 0469 return; 0470 } 0471 setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl(u)); 0472 if (job->queryMetaData(QStringLiteral("redirect-to-get")) == QLatin1String("true")) { 0473 setOperation(QNetworkAccessManager::GetOperation); 0474 } 0475 } 0476 0477 void AccessManagerReply::slotPercent(KJob *job, unsigned long percent) 0478 { 0479 qulonglong bytesTotal = job->totalAmount(KJob::Bytes); 0480 qulonglong bytesProcessed = (bytesTotal * percent) / 100; 0481 if (operation() == QNetworkAccessManager::PutOperation || operation() == QNetworkAccessManager::PostOperation) { 0482 Q_EMIT uploadProgress(bytesProcessed, bytesTotal); 0483 return; 0484 } 0485 Q_EMIT downloadProgress(bytesProcessed, bytesTotal); 0486 } 0487 0488 void AccessManagerReply::emitFinished(bool state, Qt::ConnectionType type) 0489 { 0490 setFinished(state); 0491 Q_EMIT QMetaObject::invokeMethod(this, &AccessManagerReply::finished, type); 0492 } 0493 0494 } 0495 0496 #include "moc_accessmanagerreply_p.cpp" 0497 0498 #endif