File indexing completed on 2023-09-24 04:08:45
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000-2013 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "transferjob.h" 0010 #include "job_p.h" 0011 #include "slave.h" 0012 #include <QDebug> 0013 #include <kurlauthorized.h> 0014 0015 using namespace KIO; 0016 0017 static const int MAX_READ_BUF_SIZE = (64 * 1024); // 64 KB at a time seems reasonable... 0018 0019 TransferJob::TransferJob(TransferJobPrivate &dd) 0020 : SimpleJob(dd) 0021 { 0022 Q_D(TransferJob); 0023 if (d->m_command == CMD_PUT) { 0024 d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; 0025 } 0026 0027 if (d->m_outgoingDataSource) { 0028 d->m_readChannelFinishedConnection = connect(d->m_outgoingDataSource, &QIODevice::readChannelFinished, this, [d]() { 0029 d->slotIODeviceClosedBeforeStart(); 0030 }); 0031 } 0032 } 0033 0034 TransferJob::~TransferJob() 0035 { 0036 } 0037 0038 // Worker sends data 0039 void TransferJob::slotData(const QByteArray &_data) 0040 { 0041 Q_D(TransferJob); 0042 if (d->m_command == CMD_GET && !d->m_isMimetypeEmitted) { 0043 qCWarning(KIO_CORE) << "mimeType() not emitted when sending first data!; job URL =" << d->m_url << "data size =" << _data.size(); 0044 } 0045 // shut up the warning, HACK: downside is that it changes the meaning of the variable 0046 d->m_isMimetypeEmitted = true; 0047 0048 if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) { 0049 Q_EMIT data(this, _data); 0050 } 0051 } 0052 0053 void KIO::TransferJob::setTotalSize(KIO::filesize_t bytes) 0054 { 0055 setTotalAmount(KJob::Bytes, bytes); 0056 } 0057 0058 // Worker got a redirection request 0059 void TransferJob::slotRedirection(const QUrl &url) 0060 { 0061 Q_D(TransferJob); 0062 // qDebug() << url; 0063 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), d->m_url, url)) { 0064 qCWarning(KIO_CORE) << "Redirection from" << d->m_url << "to" << url << "REJECTED!"; 0065 return; 0066 } 0067 0068 // Some websites keep redirecting to themselves where each redirection 0069 // acts as the stage in a state-machine. We define "endless redirections" 0070 // as 5 redirections to the same URL. 0071 if (d->m_redirectionList.count(url) > 5) { 0072 // qDebug() << "CYCLIC REDIRECTION!"; 0073 setError(ERR_CYCLIC_LINK); 0074 setErrorText(d->m_url.toDisplayString()); 0075 } else { 0076 d->m_redirectionURL = url; // We'll remember that when the job finishes 0077 d->m_redirectionList.append(url); 0078 QString sslInUse = queryMetaData(QStringLiteral("ssl_in_use")); 0079 if (!sslInUse.isNull()) { // the key is present 0080 addMetaData(QStringLiteral("ssl_was_in_use"), sslInUse); 0081 } else { 0082 addMetaData(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE")); 0083 } 0084 // Tell the user that we haven't finished yet 0085 Q_EMIT redirection(this, d->m_redirectionURL); 0086 } 0087 } 0088 0089 void TransferJob::slotFinished() 0090 { 0091 Q_D(TransferJob); 0092 0093 // qDebug() << d->m_url; 0094 if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) { 0095 // qDebug() << "Redirection to" << m_redirectionURL; 0096 if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) { 0097 Q_EMIT permanentRedirection(this, d->m_url, d->m_redirectionURL); 0098 } 0099 0100 if (queryMetaData(QStringLiteral("redirect-to-get")) == QLatin1String("true")) { 0101 d->m_command = CMD_GET; 0102 d->m_outgoingMetaData.remove(QStringLiteral("CustomHTTPMethod")); 0103 d->m_outgoingMetaData.remove(QStringLiteral("content-type")); 0104 } 0105 0106 if (d->m_redirectionHandlingEnabled) { 0107 // Honour the redirection 0108 // We take the approach of "redirecting this same job" 0109 // Another solution would be to create a subjob, but the same problem 0110 // happens (unpacking+repacking) 0111 d->staticData.truncate(0); 0112 d->m_incomingMetaData.clear(); 0113 if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) { 0114 addMetaData(QStringLiteral("cache"), QStringLiteral("refresh")); 0115 } 0116 d->m_internalSuspended = false; 0117 // The very tricky part is the packed arguments business 0118 QUrl dummyUrl; 0119 QDataStream istream(d->m_packedArgs); 0120 switch (d->m_command) { 0121 case CMD_GET: 0122 case CMD_STAT: 0123 case CMD_DEL: { 0124 d->m_packedArgs.truncate(0); 0125 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); 0126 stream << d->m_redirectionURL; 0127 break; 0128 } 0129 case CMD_PUT: { 0130 int permissions; 0131 qint8 iOverwrite; 0132 qint8 iResume; 0133 istream >> dummyUrl >> iOverwrite >> iResume >> permissions; 0134 d->m_packedArgs.truncate(0); 0135 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); 0136 stream << d->m_redirectionURL << iOverwrite << iResume << permissions; 0137 break; 0138 } 0139 case CMD_SPECIAL: { 0140 int specialcmd; 0141 istream >> specialcmd; 0142 if (specialcmd == 1) { // HTTP POST 0143 d->m_outgoingMetaData.remove(QStringLiteral("content-type")); 0144 addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); 0145 d->m_packedArgs.truncate(0); 0146 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); 0147 stream << d->m_redirectionURL; 0148 d->m_command = CMD_GET; 0149 } 0150 break; 0151 } 0152 } 0153 d->restartAfterRedirection(&d->m_redirectionURL); 0154 return; 0155 } 0156 } 0157 0158 SimpleJob::slotFinished(); 0159 } 0160 0161 void TransferJob::setAsyncDataEnabled(bool enabled) 0162 { 0163 Q_D(TransferJob); 0164 if (enabled) { 0165 d->m_extraFlags |= JobPrivate::EF_TransferJobAsync; 0166 } else { 0167 d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync; 0168 } 0169 } 0170 0171 void TransferJob::sendAsyncData(const QByteArray &dataForWorker) 0172 { 0173 Q_D(TransferJob); 0174 if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) { 0175 if (d->m_slave) { 0176 d->m_slave->send(MSG_DATA, dataForWorker); 0177 } 0178 if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress 0179 KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForWorker.size(); 0180 setProcessedAmount(KJob::Bytes, size); 0181 } 0182 } 0183 0184 d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData; 0185 } 0186 0187 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 3) 0188 void TransferJob::setReportDataSent(bool enabled) 0189 { 0190 Q_D(TransferJob); 0191 if (enabled) { 0192 d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; 0193 } else { 0194 d->m_extraFlags &= ~JobPrivate::EF_TransferJobDataSent; 0195 } 0196 } 0197 #endif 0198 0199 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 3) 0200 bool TransferJob::reportDataSent() const 0201 { 0202 return (d_func()->m_extraFlags & JobPrivate::EF_TransferJobDataSent); 0203 } 0204 #endif 0205 0206 QString TransferJob::mimetype() const 0207 { 0208 return d_func()->m_mimetype; 0209 } 0210 0211 QUrl TransferJob::redirectUrl() const 0212 { 0213 return d_func()->m_redirectionURL; 0214 } 0215 0216 // Worker requests data 0217 void TransferJob::slotDataReq() 0218 { 0219 Q_D(TransferJob); 0220 QByteArray dataForWorker; 0221 0222 d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData; 0223 0224 if (!d->staticData.isEmpty()) { 0225 dataForWorker = d->staticData; 0226 d->staticData.clear(); 0227 } else { 0228 Q_EMIT dataReq(this, dataForWorker); 0229 0230 if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) { 0231 return; 0232 } 0233 } 0234 0235 static const int max_size = 14 * 1024 * 1024; 0236 if (dataForWorker.size() > max_size) { 0237 // qDebug() << "send" << dataForWorker.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix 0238 // the application."; 0239 d->staticData = QByteArray(dataForWorker.data() + max_size, dataForWorker.size() - max_size); 0240 dataForWorker.truncate(max_size); 0241 } 0242 0243 sendAsyncData(dataForWorker); 0244 0245 if (d->m_subJob) { 0246 // Bitburger protocol in action 0247 d->internalSuspend(); // Wait for more data from subJob. 0248 d->m_subJob->d_func()->internalResume(); // Ask for more! 0249 } 0250 } 0251 0252 void TransferJob::slotMimetype(const QString &type) 0253 { 0254 Q_D(TransferJob); 0255 d->m_mimetype = type; 0256 if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) { 0257 qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url; 0258 } 0259 d->m_isMimetypeEmitted = true; 0260 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 0261 Q_EMIT mimetype(this, type); 0262 #endif 0263 Q_EMIT mimeTypeFound(this, type); 0264 } 0265 0266 void TransferJobPrivate::internalSuspend() 0267 { 0268 m_internalSuspended = true; 0269 if (m_slave) { 0270 m_slave->suspend(); 0271 } 0272 } 0273 0274 void TransferJobPrivate::internalResume() 0275 { 0276 m_internalSuspended = false; 0277 if (m_slave && !q_func()->isSuspended()) { 0278 m_slave->resume(); 0279 } 0280 } 0281 0282 bool TransferJob::doResume() 0283 { 0284 Q_D(TransferJob); 0285 if (!SimpleJob::doResume()) { 0286 return false; 0287 } 0288 if (d->m_internalSuspended) { 0289 d->internalSuspend(); 0290 } 0291 return true; 0292 } 0293 0294 bool TransferJob::isErrorPage() const 0295 { 0296 return d_func()->m_errorPage; 0297 } 0298 0299 void TransferJobPrivate::start(Slave *slave) 0300 { 0301 Q_Q(TransferJob); 0302 Q_ASSERT(slave); 0303 JobPrivate::emitTransferring(q, m_url); 0304 q->connect(slave, &SlaveInterface::data, q, &TransferJob::slotData); 0305 0306 if (m_outgoingDataSource) { 0307 if (m_extraFlags & JobPrivate::EF_TransferJobAsync) { 0308 auto dataReqFunc = [this]() { 0309 slotDataReqFromDevice(); 0310 }; 0311 q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, dataReqFunc); 0312 auto ioClosedFunc = [this]() { 0313 slotIODeviceClosed(); 0314 }; 0315 q->connect(m_outgoingDataSource, &QIODevice::readChannelFinished, q, ioClosedFunc); 0316 // We don't really need to disconnect since we're never checking 0317 // m_closedBeforeStart again but it's the proper thing to do logically 0318 QObject::disconnect(m_readChannelFinishedConnection); 0319 if (m_closedBeforeStart) { 0320 QMetaObject::invokeMethod(q, ioClosedFunc, Qt::QueuedConnection); 0321 } else if (m_outgoingDataSource->bytesAvailable() > 0) { 0322 QMetaObject::invokeMethod(q, dataReqFunc, Qt::QueuedConnection); 0323 } 0324 } else { 0325 q->connect(slave, &SlaveInterface::dataReq, q, [this]() { 0326 slotDataReqFromDevice(); 0327 }); 0328 } 0329 } else { 0330 q->connect(slave, &SlaveInterface::dataReq, q, &TransferJob::slotDataReq); 0331 } 0332 0333 q->connect(slave, &SlaveInterface::redirection, q, &TransferJob::slotRedirection); 0334 0335 q->connect(slave, &SlaveInterface::mimeType, q, &TransferJob::slotMimetype); 0336 0337 q->connect(slave, &SlaveInterface::errorPage, q, [this]() { 0338 m_errorPage = true; 0339 }); 0340 0341 q->connect(slave, &SlaveInterface::needSubUrlData, q, [this]() { 0342 slotNeedSubUrlData(); 0343 }); 0344 0345 q->connect(slave, &SlaveInterface::canResume, q, [q](KIO::filesize_t offset) { 0346 Q_EMIT q->canResume(q, offset); 0347 }); 0348 0349 if (slave->suspended()) { 0350 m_mimetype = QStringLiteral("unknown"); 0351 // WABA: The worker was put on hold. Resume operation. 0352 slave->resume(); 0353 } 0354 0355 SimpleJobPrivate::start(slave); 0356 if (m_internalSuspended) { 0357 slave->suspend(); 0358 } 0359 } 0360 0361 void TransferJobPrivate::slotNeedSubUrlData() 0362 { 0363 Q_Q(TransferJob); 0364 // Job needs data from subURL. 0365 m_subJob = KIO::get(m_subUrl, NoReload, HideProgressInfo); 0366 internalSuspend(); // Put job on hold until we have some data. 0367 q->connect(m_subJob, &TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) { 0368 slotSubUrlData(job, data); 0369 }); 0370 q->addSubjob(m_subJob); 0371 } 0372 0373 void TransferJobPrivate::slotSubUrlData(KIO::Job *, const QByteArray &data) 0374 { 0375 // The Alternating Bitburg protocol in action again. 0376 staticData = data; 0377 m_subJob->d_func()->internalSuspend(); // Put job on hold until we have delivered the data. 0378 internalResume(); // Activate ourselves again. 0379 } 0380 0381 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 101) 0382 void TransferJob::slotMetaData(const KIO::MetaData &_metaData) 0383 { 0384 SimpleJob::slotMetaData(_metaData); 0385 } 0386 #endif 0387 0388 void TransferJobPrivate::slotDataReqFromDevice() 0389 { 0390 Q_Q(TransferJob); 0391 0392 bool done = false; 0393 QByteArray dataForWorker; 0394 0395 m_extraFlags |= JobPrivate::EF_TransferJobNeedData; 0396 0397 if (m_outgoingDataSource) { 0398 dataForWorker.resize(MAX_READ_BUF_SIZE); 0399 0400 // Code inspired in QNonContiguousByteDevice 0401 qint64 bytesRead = m_outgoingDataSource->read(dataForWorker.data(), MAX_READ_BUF_SIZE); 0402 if (bytesRead >= 0) { 0403 dataForWorker.resize(bytesRead); 0404 } else { 0405 dataForWorker.clear(); 0406 } 0407 done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential())); 0408 } 0409 0410 if (dataForWorker.isEmpty()) { 0411 Q_EMIT q->dataReq(q, dataForWorker); 0412 if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) { 0413 return; 0414 } 0415 } 0416 0417 q->sendAsyncData(dataForWorker); 0418 0419 if (m_subJob) { 0420 // Bitburger protocol in action 0421 internalSuspend(); // Wait for more data from subJob. 0422 m_subJob->d_func()->internalResume(); // Ask for more! 0423 } 0424 } 0425 0426 void TransferJobPrivate::slotIODeviceClosedBeforeStart() 0427 { 0428 m_closedBeforeStart = true; 0429 } 0430 0431 void TransferJobPrivate::slotIODeviceClosed() 0432 { 0433 Q_Q(TransferJob); 0434 const QByteArray remainder = m_outgoingDataSource->readAll(); 0435 if (!remainder.isEmpty()) { 0436 m_extraFlags |= JobPrivate::EF_TransferJobNeedData; 0437 q->sendAsyncData(remainder); 0438 } 0439 0440 m_extraFlags |= JobPrivate::EF_TransferJobNeedData; 0441 0442 // We send an empty data array to indicate the stream is over 0443 q->sendAsyncData(QByteArray()); 0444 0445 if (m_subJob) { 0446 // Bitburger protocol in action 0447 internalSuspend(); // Wait for more data from subJob. 0448 m_subJob->d_func()->internalResume(); // Ask for more! 0449 } 0450 } 0451 0452 void TransferJob::slotResult(KJob *job) 0453 { 0454 Q_D(TransferJob); 0455 // This can only be our suburl. 0456 Q_ASSERT(job == d->m_subJob); 0457 0458 SimpleJob::slotResult(job); 0459 0460 if (!error() && job == d->m_subJob) { 0461 d->m_subJob = nullptr; // No action required 0462 d->internalResume(); // Make sure we get the remaining data. 0463 } 0464 } 0465 0466 void TransferJob::setModificationTime(const QDateTime &mtime) 0467 { 0468 addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate)); 0469 } 0470 0471 TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags) 0472 { 0473 // Send decoded path and encoded query 0474 KIO_ARGS << url; 0475 TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags); 0476 if (reload == Reload) { 0477 job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); 0478 } 0479 return job; 0480 } 0481 0482 #include "moc_transferjob.cpp"