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 JobPrivate::emitTransferring(q, m_url);
277 q->connect(worker, &WorkerInterface::data, q, &TransferJob::slotData);
278
279 if (m_outgoingDataSource) {
280 if (m_extraFlags & JobPrivate::EF_TransferJobAsync) {
281 auto dataReqFunc = [this]() {
282 slotDataReqFromDevice();
283 };
284 q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, dataReqFunc);
285 auto ioClosedFunc = [this]() {
286 slotIODeviceClosed();
287 };
288 q->connect(m_outgoingDataSource, &QIODevice::readChannelFinished, q, ioClosedFunc);
289 // We don't really need to disconnect since we're never checking
290 // m_closedBeforeStart again but it's the proper thing to do logically
291 QObject::disconnect(m_readChannelFinishedConnection);
292 if (m_closedBeforeStart) {
294 } else if (m_outgoingDataSource->bytesAvailable() > 0) {
296 }
297 } else {
298 q->connect(worker, &WorkerInterface::dataReq, q, [this]() {
299 slotDataReqFromDevice();
300 });
301 }
302 } else {
303 q->connect(worker, &WorkerInterface::dataReq, q, &TransferJob::slotDataReq);
304 }
305
306 q->connect(worker, &WorkerInterface::redirection, q, &TransferJob::slotRedirection);
307
308 q->connect(worker, &WorkerInterface::mimeType, q, &TransferJob::slotMimetype);
309
310 q->connect(worker, &WorkerInterface::canResume, q, [q](KIO::filesize_t offset) {
311 Q_EMIT q->canResume(q, offset);
312 });
313
314 if (worker->suspended()) {
315 m_mimetype = QStringLiteral("unknown");
316 // WABA: The worker was put on hold. Resume operation.
317 worker->resume();
318 }
319
320 SimpleJobPrivate::start(worker);
321 if (m_internalSuspended) {
322 worker->suspend();
323 }
324}
325
326void TransferJobPrivate::slotDataReqFromDevice()
327{
328 Q_Q(TransferJob);
329
330 bool done = false;
331 QByteArray dataForWorker;
332
333 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
334
335 if (m_outgoingDataSource) {
336 dataForWorker.resize(MAX_READ_BUF_SIZE);
337
338 // Code inspired in QNonContiguousByteDevice
339 qint64 bytesRead = m_outgoingDataSource->read(dataForWorker.data(), MAX_READ_BUF_SIZE);
340 if (bytesRead >= 0) {
341 dataForWorker.resize(bytesRead);
342 } else {
343 dataForWorker.clear();
344 }
345 done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential()));
346 }
347
348 if (dataForWorker.isEmpty()) {
349 Q_EMIT q->dataReq(q, dataForWorker);
350 if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) {
351 return;
352 }
353 }
354
355 q->sendAsyncData(dataForWorker);
356}
357
358void TransferJobPrivate::slotIODeviceClosedBeforeStart()
359{
360 m_closedBeforeStart = true;
361}
362
363void TransferJobPrivate::slotIODeviceClosed()
364{
365 Q_Q(TransferJob);
366 const QByteArray remainder = m_outgoingDataSource->readAll();
367 if (!remainder.isEmpty()) {
368 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
369 q->sendAsyncData(remainder);
370 }
371
372 m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
373
374 // We send an empty data array to indicate the stream is over
375 q->sendAsyncData(QByteArray());
376}
377
379{
380 addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate));
381}
382
383TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags)
384{
385 // Send decoded path and encoded query
386 KIO_ARGS << url;
387 TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags);
388 if (reload == Reload) {
389 job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
390 }
391 return job;
392}
393
394#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)
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
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
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 Fri Jul 26 2024 11:54:08 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.