KIO

transferjob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000-2013 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "transferjob.h"
10#include "job_p.h"
11#include "worker_p.h"
12#include <QDebug>
13#include <kurlauthorized.h>
14
15using namespace KIO;
16
17static const int MAX_READ_BUF_SIZE = (64 * 1024); // 64 KB at a time seems reasonable...
18
19TransferJob::TransferJob(TransferJobPrivate &dd)
20 : SimpleJob(dd)
21{
23 if (d->m_command == CMD_PUT) {
24 d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent;
25 }
26
27 if (d->m_outgoingDataSource) {
28 d->m_readChannelFinishedConnection = connect(d->m_outgoingDataSource, &QIODevice::readChannelFinished, this, [d]() {
29 d->slotIODeviceClosedBeforeStart();
30 });
31 }
32}
33
34TransferJob::~TransferJob()
35{
36}
37
38// Worker sends data
39void TransferJob::slotData(const QByteArray &_data)
40{
42 if (d->m_command == CMD_GET && !d->m_isMimetypeEmitted) {
43 qCWarning(KIO_CORE) << "mimeType() not emitted when sending first data!; job URL =" << d->m_url << "data size =" << _data.size();
44 }
45 // shut up the warning, HACK: downside is that it changes the meaning of the variable
46 d->m_isMimetypeEmitted = true;
47
48 if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) {
49 Q_EMIT data(this, _data);
50 }
51}
52
54{
55 setTotalAmount(KJob::Bytes, bytes);
56}
57
58// Worker got a redirection request
59void TransferJob::slotRedirection(const QUrl &url)
60{
62 // qDebug() << url;
63 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), d->m_url, url)) {
64 qCWarning(KIO_CORE) << "Redirection from" << d->m_url << "to" << url << "REJECTED!";
65 return;
66 }
67
68 // Some websites keep redirecting to themselves where each redirection
69 // acts as the stage in a state-machine. We define "endless redirections"
70 // as 5 redirections to the same URL.
71 if (d->m_redirectionList.count(url) > 5) {
72 // qDebug() << "CYCLIC REDIRECTION!";
73 setError(ERR_CYCLIC_LINK);
74 setErrorText(d->m_url.toDisplayString());
75 } else {
76 d->m_redirectionURL = url; // We'll remember that when the job finishes
77 d->m_redirectionList.append(url);
78 QString sslInUse = queryMetaData(QStringLiteral("ssl_in_use"));
79 if (!sslInUse.isNull()) { // the key is present
80 addMetaData(QStringLiteral("ssl_was_in_use"), sslInUse);
81 } else {
82 addMetaData(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE"));
83 }
84 // Tell the user that we haven't finished yet
85 Q_EMIT redirection(this, d->m_redirectionURL);
86 }
87}
88
89void TransferJob::slotFinished()
90{
92
93 // qDebug() << d->m_url;
94 if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) {
95 // qDebug() << "Redirection to" << m_redirectionURL;
96 if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) {
97 Q_EMIT permanentRedirection(this, d->m_url, d->m_redirectionURL);
98 }
99
100 if (queryMetaData(QStringLiteral("redirect-to-get")) == QLatin1String("true")) {
101 d->m_command = CMD_GET;
102 d->m_outgoingMetaData.remove(QStringLiteral("content-type"));
103 }
104
105 if (d->m_redirectionHandlingEnabled) {
106 // Honour the redirection
107 // We take the approach of "redirecting this same job"
108 // Another solution would be to create a subjob, but the same problem
109 // happens (unpacking+repacking)
110 d->staticData.truncate(0);
111 d->m_incomingMetaData.clear();
112 if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) {
113 addMetaData(QStringLiteral("cache"), QStringLiteral("refresh"));
114 }
115 d->m_internalSuspended = false;
116 // The very tricky part is the packed arguments business
117 QUrl dummyUrl;
118 QDataStream istream(d->m_packedArgs);
119 switch (d->m_command) {
120 case CMD_GET:
121 case CMD_STAT:
122 case CMD_DEL: {
123 d->m_packedArgs.truncate(0);
124 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
125 stream << d->m_redirectionURL;
126 break;
127 }
128 case CMD_PUT: {
129 int permissions;
130 qint8 iOverwrite;
131 qint8 iResume;
132 istream >> dummyUrl >> iOverwrite >> iResume >> permissions;
133 d->m_packedArgs.truncate(0);
134 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
135 stream << d->m_redirectionURL << iOverwrite << iResume << permissions;
136 break;
137 }
138 case CMD_SPECIAL: {
139 int specialcmd;
140 istream >> specialcmd;
141 if (specialcmd == 1) { // HTTP POST
142 d->m_outgoingMetaData.remove(QStringLiteral("content-type"));
143 addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
144 d->m_packedArgs.truncate(0);
145 QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
146 stream << d->m_redirectionURL;
147 d->m_command = CMD_GET;
148 }
149 break;
150 }
151 }
152 d->restartAfterRedirection(&d->m_redirectionURL);
153 return;
154 }
155 }
156
158}
159
161{
163 if (enabled) {
164 d->m_extraFlags |= JobPrivate::EF_TransferJobAsync;
165 } else {
166 d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync;
167 }
168}
169
170void TransferJob::sendAsyncData(const QByteArray &dataForWorker)
171{
173 if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) {
174 if (d->m_worker) {
175 d->m_worker->send(MSG_DATA, dataForWorker);
176 }
177 if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress
178 KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForWorker.size();
180 }
181 }
182
183 d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData;
184}
185
187{
188 return d_func()->m_mimetype;
189}
190
192{
193 return d_func()->m_redirectionURL;
194}
195
196// Worker requests data
197void TransferJob::slotDataReq()
198{
200 QByteArray dataForWorker;
201
202 d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
203
204 if (!d->staticData.isEmpty()) {
205 dataForWorker = d->staticData;
206 d->staticData.clear();
207 } else {
208 Q_EMIT dataReq(this, dataForWorker);
209
210 if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) {
211 return;
212 }
213 }
214
215 static const int max_size = 14 * 1024 * 1024;
216 if (dataForWorker.size() > max_size) {
217 // qDebug() << "send" << dataForWorker.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix
218 // the application.";
219 d->staticData = QByteArray(dataForWorker.data() + max_size, dataForWorker.size() - max_size);
220 dataForWorker.truncate(max_size);
221 }
222
223 sendAsyncData(dataForWorker);
224}
225
226void TransferJob::slotMimetype(const QString &type)
227{
229 d->m_mimetype = type;
230 if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) {
231 qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url;
232 }
233 d->m_isMimetypeEmitted = true;
234 Q_EMIT mimeTypeFound(this, type);
235}
236
237void TransferJobPrivate::internalSuspend()
238{
239 m_internalSuspended = true;
240 if (m_worker) {
241 m_worker->suspend();
242 }
243}
244
245void TransferJobPrivate::internalResume()
246{
247 m_internalSuspended = false;
248 if (m_worker && !q_func()->isSuspended()) {
249 m_worker->resume();
250 }
251}
252
254{
256 if (!SimpleJob::doResume()) {
257 return false;
258 }
259 if (d->m_internalSuspended) {
260 d->internalSuspend();
261 }
262 return true;
263}
264
265#if KIOCORE_ENABLE_DEPRECATED_SINCE(6, 3)
267{
268 return false;
269}
270#endif
271
272void TransferJobPrivate::start(Worker *worker)
273{
274 Q_Q(TransferJob);
275 Q_ASSERT(worker);
276
277 q->startElapsedTimer();
278 JobPrivate::emitTransferring(q, m_url);
279 q->connect(worker, &WorkerInterface::data, q, &TransferJob::slotData);
280
281 if (m_outgoingDataSource) {
282 if (m_extraFlags & JobPrivate::EF_TransferJobAsync) {
283 auto dataReqFunc = [this]() {
284 slotDataReqFromDevice();
285 };
286 q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, dataReqFunc);
287 auto ioClosedFunc = [this]() {
288 slotIODeviceClosed();
289 };
290 q->connect(m_outgoingDataSource, &QIODevice::readChannelFinished, q, ioClosedFunc);
291 // We don't really need to disconnect since we're never checking
292 // m_closedBeforeStart again but it's the proper thing to do logically
293 QObject::disconnect(m_readChannelFinishedConnection);
294 if (m_closedBeforeStart) {
296 } else if (m_outgoingDataSource->bytesAvailable() > 0) {
298 }
299 } else {
300 q->connect(worker, &WorkerInterface::dataReq, q, [this]() {
301 slotDataReqFromDevice();
302 });
303 }
304 } else {
305 q->connect(worker, &WorkerInterface::dataReq, q, &TransferJob::slotDataReq);
306 }
307
308 q->connect(worker, &WorkerInterface::redirection, q, &TransferJob::slotRedirection);
309
310 q->connect(worker, &WorkerInterface::mimeType, q, &TransferJob::slotMimetype);
311
312 q->connect(worker, &WorkerInterface::canResume, q, [q](KIO::filesize_t offset) {
313 Q_EMIT q->canResume(q, offset);
314 });
315
316 if (worker->suspended()) {
317 m_mimetype = QStringLiteral("unknown");
318 // WABA: The worker was put on hold. Resume operation.
319 worker->resume();
320 }
321
322 SimpleJobPrivate::start(worker);
323 if (m_internalSuspended) {
324 worker->suspend();
325 }
326}
327
328void TransferJobPrivate::slotDataReqFromDevice()
329{
330 Q_Q(TransferJob);
331
332 bool done = false;
333 QByteArray dataForWorker;
334
335 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
336
337 if (m_outgoingDataSource) {
338 dataForWorker.resize(MAX_READ_BUF_SIZE);
339
340 // Code inspired in QNonContiguousByteDevice
341 qint64 bytesRead = m_outgoingDataSource->read(dataForWorker.data(), MAX_READ_BUF_SIZE);
342 if (bytesRead >= 0) {
343 dataForWorker.resize(bytesRead);
344 } else {
345 dataForWorker.clear();
346 }
347 done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential()));
348 }
349
350 if (dataForWorker.isEmpty()) {
351 Q_EMIT q->dataReq(q, dataForWorker);
352 if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) {
353 return;
354 }
355 }
356
357 q->sendAsyncData(dataForWorker);
358}
359
360void TransferJobPrivate::slotIODeviceClosedBeforeStart()
361{
362 m_closedBeforeStart = true;
363}
364
365void TransferJobPrivate::slotIODeviceClosed()
366{
367 Q_Q(TransferJob);
368 const QByteArray remainder = m_outgoingDataSource->readAll();
369 if (!remainder.isEmpty()) {
370 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
371 q->sendAsyncData(remainder);
372 }
373
374 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
375
376 // We send an empty data array to indicate the stream is over
377 q->sendAsyncData(QByteArray());
378}
379
381{
382 addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate));
383}
384
385TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags)
386{
387 // Send decoded path and encoded query
388 KIO_ARGS << url;
389 TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags);
390 if (reload == Reload) {
391 job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
392 }
393 return job;
394}
395
396#include "moc_transferjob.cpp"
QString queryMetaData(const QString &key)
Query meta data received from the worker.
Definition job.cpp:210
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the worker.
Definition job.cpp:221
A simple job (one url and one command).
virtual void slotFinished()
Called when the worker marks the job as finished.
bool doResume() override
Resume this job.
Definition simplejob.cpp:61
const QUrl & url() const
Returns the SimpleJob's URL.
Definition simplejob.cpp:70
The transfer job pumps data into and/or out of a KIO worker.
void mimeTypeFound(KIO::Job *job, const QString &mimeType)
MIME type determined.
void dataReq(KIO::Job *job, QByteArray &data)
Request for data.
bool doResume() override
Reimplemented for internal reasons.
void redirection(KIO::Job *job, const QUrl &url)
Signals a redirection.
void permanentRedirection(KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl)
Signals a permanent redirection.
void sendAsyncData(const QByteArray &data)
Provide data to the job when async data is enabled.
QUrl redirectUrl() const
After the job has finished, it will return the final url in case a redirection has happened.
QString mimetype() const
Call this in the slot connected to result, and only after making sure no error happened.
void data(KIO::Job *job, const QByteArray &data)
Data from the worker has arrived.
bool isErrorPage() const
Checks whether we got an error page.
void setModificationTime(const QDateTime &mtime)
Sets the modification time of the file to be created (by KIO::put) Note that some KIO workers might i...
void setTotalSize(KIO::filesize_t bytes)
Set the total size of data that we are going to send in a put job.
void setAsyncDataEnabled(bool enabled)
Enable the async data mode.
void setErrorText(const QString &errorText)
int error() const
Q_SCRIPTABLE qulonglong processedAmount(Unit unit) const
void setError(int errorCode)
void setProcessedAmount(Unit unit, qulonglong amount)
Type type(const QSqlDatabase &db)
A namespace for KIO globals.
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
Get (means: read).
qulonglong filesize_t
64-bit file size
Definition global.h:35
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
void clear()
char * data()
bool isEmpty() const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
void truncate(qsizetype pos)
QString toString(QStringView format, QCalendar cal) const const
void readChannelFinished()
void readyRead()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
bool disconnect(const QMetaObject::Connection &connection)
bool isNull() const const
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.