File indexing completed on 2024-04-28 11:41:11

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"