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"