File indexing completed on 2024-04-14 03:53:02

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 "worker_p.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("content-type"));
0103         }
0104 
0105         if (d->m_redirectionHandlingEnabled) {
0106             // Honour the redirection
0107             // We take the approach of "redirecting this same job"
0108             // Another solution would be to create a subjob, but the same problem
0109             // happens (unpacking+repacking)
0110             d->staticData.truncate(0);
0111             d->m_incomingMetaData.clear();
0112             if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) {
0113                 addMetaData(QStringLiteral("cache"), QStringLiteral("refresh"));
0114             }
0115             d->m_internalSuspended = false;
0116             // The very tricky part is the packed arguments business
0117             QUrl dummyUrl;
0118             QDataStream istream(d->m_packedArgs);
0119             switch (d->m_command) {
0120             case CMD_GET:
0121             case CMD_STAT:
0122             case CMD_DEL: {
0123                 d->m_packedArgs.truncate(0);
0124                 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0125                 stream << d->m_redirectionURL;
0126                 break;
0127             }
0128             case CMD_PUT: {
0129                 int permissions;
0130                 qint8 iOverwrite;
0131                 qint8 iResume;
0132                 istream >> dummyUrl >> iOverwrite >> iResume >> permissions;
0133                 d->m_packedArgs.truncate(0);
0134                 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0135                 stream << d->m_redirectionURL << iOverwrite << iResume << permissions;
0136                 break;
0137             }
0138             case CMD_SPECIAL: {
0139                 int specialcmd;
0140                 istream >> specialcmd;
0141                 if (specialcmd == 1) { // HTTP POST
0142                     d->m_outgoingMetaData.remove(QStringLiteral("content-type"));
0143                     addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
0144                     d->m_packedArgs.truncate(0);
0145                     QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
0146                     stream << d->m_redirectionURL;
0147                     d->m_command = CMD_GET;
0148                 }
0149                 break;
0150             }
0151             }
0152             d->restartAfterRedirection(&d->m_redirectionURL);
0153             return;
0154         }
0155     }
0156 
0157     SimpleJob::slotFinished();
0158 }
0159 
0160 void TransferJob::setAsyncDataEnabled(bool enabled)
0161 {
0162     Q_D(TransferJob);
0163     if (enabled) {
0164         d->m_extraFlags |= JobPrivate::EF_TransferJobAsync;
0165     } else {
0166         d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync;
0167     }
0168 }
0169 
0170 void TransferJob::sendAsyncData(const QByteArray &dataForWorker)
0171 {
0172     Q_D(TransferJob);
0173     if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) {
0174         if (d->m_worker) {
0175             d->m_worker->send(MSG_DATA, dataForWorker);
0176         }
0177         if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress
0178             KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForWorker.size();
0179             setProcessedAmount(KJob::Bytes, size);
0180         }
0181     }
0182 
0183     d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData;
0184 }
0185 
0186 QString TransferJob::mimetype() const
0187 {
0188     return d_func()->m_mimetype;
0189 }
0190 
0191 QUrl TransferJob::redirectUrl() const
0192 {
0193     return d_func()->m_redirectionURL;
0194 }
0195 
0196 // Worker requests data
0197 void TransferJob::slotDataReq()
0198 {
0199     Q_D(TransferJob);
0200     QByteArray dataForWorker;
0201 
0202     d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
0203 
0204     if (!d->staticData.isEmpty()) {
0205         dataForWorker = d->staticData;
0206         d->staticData.clear();
0207     } else {
0208         Q_EMIT dataReq(this, dataForWorker);
0209 
0210         if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) {
0211             return;
0212         }
0213     }
0214 
0215     static const int max_size = 14 * 1024 * 1024;
0216     if (dataForWorker.size() > max_size) {
0217         // qDebug() << "send" << dataForWorker.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix
0218         // the application.";
0219         d->staticData = QByteArray(dataForWorker.data() + max_size, dataForWorker.size() - max_size);
0220         dataForWorker.truncate(max_size);
0221     }
0222 
0223     sendAsyncData(dataForWorker);
0224 }
0225 
0226 void TransferJob::slotMimetype(const QString &type)
0227 {
0228     Q_D(TransferJob);
0229     d->m_mimetype = type;
0230     if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) {
0231         qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url;
0232     }
0233     d->m_isMimetypeEmitted = true;
0234     Q_EMIT mimeTypeFound(this, type);
0235 }
0236 
0237 void TransferJobPrivate::internalSuspend()
0238 {
0239     m_internalSuspended = true;
0240     if (m_worker) {
0241         m_worker->suspend();
0242     }
0243 }
0244 
0245 void TransferJobPrivate::internalResume()
0246 {
0247     m_internalSuspended = false;
0248     if (m_worker && !q_func()->isSuspended()) {
0249         m_worker->resume();
0250     }
0251 }
0252 
0253 bool TransferJob::doResume()
0254 {
0255     Q_D(TransferJob);
0256     if (!SimpleJob::doResume()) {
0257         return false;
0258     }
0259     if (d->m_internalSuspended) {
0260         d->internalSuspend();
0261     }
0262     return true;
0263 }
0264 
0265 bool TransferJob::isErrorPage() const
0266 {
0267     return d_func()->m_errorPage;
0268 }
0269 
0270 void TransferJobPrivate::start(Worker *worker)
0271 {
0272     Q_Q(TransferJob);
0273     Q_ASSERT(worker);
0274     JobPrivate::emitTransferring(q, m_url);
0275     q->connect(worker, &WorkerInterface::data, q, &TransferJob::slotData);
0276 
0277     if (m_outgoingDataSource) {
0278         if (m_extraFlags & JobPrivate::EF_TransferJobAsync) {
0279             auto dataReqFunc = [this]() {
0280                 slotDataReqFromDevice();
0281             };
0282             q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, dataReqFunc);
0283             auto ioClosedFunc = [this]() {
0284                 slotIODeviceClosed();
0285             };
0286             q->connect(m_outgoingDataSource, &QIODevice::readChannelFinished, q, ioClosedFunc);
0287             // We don't really need to disconnect since we're never checking
0288             // m_closedBeforeStart again but it's the proper thing to do logically
0289             QObject::disconnect(m_readChannelFinishedConnection);
0290             if (m_closedBeforeStart) {
0291                 QMetaObject::invokeMethod(q, ioClosedFunc, Qt::QueuedConnection);
0292             } else if (m_outgoingDataSource->bytesAvailable() > 0) {
0293                 QMetaObject::invokeMethod(q, dataReqFunc, Qt::QueuedConnection);
0294             }
0295         } else {
0296             q->connect(worker, &WorkerInterface::dataReq, q, [this]() {
0297                 slotDataReqFromDevice();
0298             });
0299         }
0300     } else {
0301         q->connect(worker, &WorkerInterface::dataReq, q, &TransferJob::slotDataReq);
0302     }
0303 
0304     q->connect(worker, &WorkerInterface::redirection, q, &TransferJob::slotRedirection);
0305 
0306     q->connect(worker, &WorkerInterface::mimeType, q, &TransferJob::slotMimetype);
0307 
0308     q->connect(worker, &WorkerInterface::errorPage, q, [this]() {
0309         m_errorPage = true;
0310     });
0311 
0312     q->connect(worker, &WorkerInterface::canResume, q, [q](KIO::filesize_t offset) {
0313         Q_EMIT q->canResume(q, offset);
0314     });
0315 
0316     if (worker->suspended()) {
0317         m_mimetype = QStringLiteral("unknown");
0318         // WABA: The worker was put on hold. Resume operation.
0319         worker->resume();
0320     }
0321 
0322     SimpleJobPrivate::start(worker);
0323     if (m_internalSuspended) {
0324         worker->suspend();
0325     }
0326 }
0327 
0328 void TransferJobPrivate::slotDataReqFromDevice()
0329 {
0330     Q_Q(TransferJob);
0331 
0332     bool done = false;
0333     QByteArray dataForWorker;
0334 
0335     m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
0336 
0337     if (m_outgoingDataSource) {
0338         dataForWorker.resize(MAX_READ_BUF_SIZE);
0339 
0340         // Code inspired in QNonContiguousByteDevice
0341         qint64 bytesRead = m_outgoingDataSource->read(dataForWorker.data(), MAX_READ_BUF_SIZE);
0342         if (bytesRead >= 0) {
0343             dataForWorker.resize(bytesRead);
0344         } else {
0345             dataForWorker.clear();
0346         }
0347         done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential()));
0348     }
0349 
0350     if (dataForWorker.isEmpty()) {
0351         Q_EMIT q->dataReq(q, dataForWorker);
0352         if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) {
0353             return;
0354         }
0355     }
0356 
0357     q->sendAsyncData(dataForWorker);
0358 }
0359 
0360 void TransferJobPrivate::slotIODeviceClosedBeforeStart()
0361 {
0362     m_closedBeforeStart = true;
0363 }
0364 
0365 void TransferJobPrivate::slotIODeviceClosed()
0366 {
0367     Q_Q(TransferJob);
0368     const QByteArray remainder = m_outgoingDataSource->readAll();
0369     if (!remainder.isEmpty()) {
0370         m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
0371         q->sendAsyncData(remainder);
0372     }
0373 
0374     m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
0375 
0376     // We send an empty data array to indicate the stream is over
0377     q->sendAsyncData(QByteArray());
0378 }
0379 
0380 void TransferJob::setModificationTime(const QDateTime &mtime)
0381 {
0382     addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate));
0383 }
0384 
0385 TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags)
0386 {
0387     // Send decoded path and encoded query
0388     KIO_ARGS << url;
0389     TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags);
0390     if (reload == Reload) {
0391         job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
0392     }
0393     return job;
0394 }
0395 
0396 #include "moc_transferjob.cpp"