KIO

transferjob.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000 Stephan Kulow <[email protected]>
4  SPDX-FileCopyrightText: 2000-2013 David Faure <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "transferjob.h"
10 #include "job_p.h"
11 #include "slave.h"
12 #include <QDebug>
13 #include <kurlauthorized.h>
14 
15 using namespace KIO;
16 
17 static const int MAX_READ_BUF_SIZE = (64 * 1024); // 64 KB at a time seems reasonable...
18 
19 TransferJob::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 
34 TransferJob::~TransferJob()
35 {
36 }
37 
38 // Worker sends data
39 void 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
59 void 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 
89 void 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("CustomHTTPMethod"));
103  d->m_outgoingMetaData.remove(QStringLiteral("content-type"));
104  }
105 
106  if (d->m_redirectionHandlingEnabled) {
107  // Honour the redirection
108  // We take the approach of "redirecting this same job"
109  // Another solution would be to create a subjob, but the same problem
110  // happens (unpacking+repacking)
111  d->staticData.truncate(0);
112  d->m_incomingMetaData.clear();
113  if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) {
114  addMetaData(QStringLiteral("cache"), QStringLiteral("refresh"));
115  }
116  d->m_internalSuspended = false;
117  // The very tricky part is the packed arguments business
118  QUrl dummyUrl;
119  QDataStream istream(d->m_packedArgs);
120  switch (d->m_command) {
121  case CMD_GET:
122  case CMD_STAT:
123  case CMD_DEL: {
124  d->m_packedArgs.truncate(0);
125  QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
126  stream << d->m_redirectionURL;
127  break;
128  }
129  case CMD_PUT: {
130  int permissions;
131  qint8 iOverwrite;
132  qint8 iResume;
133  istream >> dummyUrl >> iOverwrite >> iResume >> permissions;
134  d->m_packedArgs.truncate(0);
135  QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
136  stream << d->m_redirectionURL << iOverwrite << iResume << permissions;
137  break;
138  }
139  case CMD_SPECIAL: {
140  int specialcmd;
141  istream >> specialcmd;
142  if (specialcmd == 1) { // HTTP POST
143  d->m_outgoingMetaData.remove(QStringLiteral("content-type"));
144  addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
145  d->m_packedArgs.truncate(0);
146  QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly);
147  stream << d->m_redirectionURL;
148  d->m_command = CMD_GET;
149  }
150  break;
151  }
152  }
153  d->restartAfterRedirection(&d->m_redirectionURL);
154  return;
155  }
156  }
157 
159 }
160 
162 {
163  Q_D(TransferJob);
164  if (enabled) {
165  d->m_extraFlags |= JobPrivate::EF_TransferJobAsync;
166  } else {
167  d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync;
168  }
169 }
170 
171 void TransferJob::sendAsyncData(const QByteArray &dataForWorker)
172 {
173  Q_D(TransferJob);
174  if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) {
175  if (d->m_slave) {
176  d->m_slave->send(MSG_DATA, dataForWorker);
177  }
178  if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress
179  KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForWorker.size();
181  }
182  }
183 
184  d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData;
185 }
186 
187 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 3)
189 {
190  Q_D(TransferJob);
191  if (enabled) {
192  d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent;
193  } else {
194  d->m_extraFlags &= ~JobPrivate::EF_TransferJobDataSent;
195  }
196 }
197 #endif
198 
199 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 3)
201 {
202  return (d_func()->m_extraFlags & JobPrivate::EF_TransferJobDataSent);
203 }
204 #endif
205 
207 {
208  return d_func()->m_mimetype;
209 }
210 
212 {
213  return d_func()->m_redirectionURL;
214 }
215 
216 // Worker requests data
217 void TransferJob::slotDataReq()
218 {
219  Q_D(TransferJob);
220  QByteArray dataForWorker;
221 
222  d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
223 
224  if (!d->staticData.isEmpty()) {
225  dataForWorker = d->staticData;
226  d->staticData.clear();
227  } else {
228  Q_EMIT dataReq(this, dataForWorker);
229 
230  if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) {
231  return;
232  }
233  }
234 
235  static const int max_size = 14 * 1024 * 1024;
236  if (dataForWorker.size() > max_size) {
237  // qDebug() << "send" << dataForWorker.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix
238  // the application.";
239  d->staticData = QByteArray(dataForWorker.data() + max_size, dataForWorker.size() - max_size);
240  dataForWorker.truncate(max_size);
241  }
242 
243  sendAsyncData(dataForWorker);
244 
245  if (d->m_subJob) {
246  // Bitburger protocol in action
247  d->internalSuspend(); // Wait for more data from subJob.
248  d->m_subJob->d_func()->internalResume(); // Ask for more!
249  }
250 }
251 
252 void TransferJob::slotMimetype(const QString &type)
253 {
254  Q_D(TransferJob);
255  d->m_mimetype = type;
256  if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) {
257  qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url;
258  }
259  d->m_isMimetypeEmitted = true;
260 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78)
261  Q_EMIT mimetype(this, type);
262 #endif
263  Q_EMIT mimeTypeFound(this, type);
264 }
265 
266 void TransferJobPrivate::internalSuspend()
267 {
268  m_internalSuspended = true;
269  if (m_slave) {
270  m_slave->suspend();
271  }
272 }
273 
274 void TransferJobPrivate::internalResume()
275 {
276  m_internalSuspended = false;
277  if (m_slave && !q_func()->isSuspended()) {
278  m_slave->resume();
279  }
280 }
281 
283 {
284  Q_D(TransferJob);
285  if (!SimpleJob::doResume()) {
286  return false;
287  }
288  if (d->m_internalSuspended) {
289  d->internalSuspend();
290  }
291  return true;
292 }
293 
295 {
296  return d_func()->m_errorPage;
297 }
298 
299 void TransferJobPrivate::start(Slave *slave)
300 {
301  Q_Q(TransferJob);
302  Q_ASSERT(slave);
303  JobPrivate::emitTransferring(q, m_url);
304  q->connect(slave, &SlaveInterface::data, q, &TransferJob::slotData);
305 
306  if (m_outgoingDataSource) {
307  if (m_extraFlags & JobPrivate::EF_TransferJobAsync) {
308  auto dataReqFunc = [this]() {
309  slotDataReqFromDevice();
310  };
311  q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, dataReqFunc);
312  auto ioClosedFunc = [this]() {
313  slotIODeviceClosed();
314  };
315  q->connect(m_outgoingDataSource, &QIODevice::readChannelFinished, q, ioClosedFunc);
316  // We don't really need to disconnect since we're never checking
317  // m_closedBeforeStart again but it's the proper thing to do logically
318  QObject::disconnect(m_readChannelFinishedConnection);
319  if (m_closedBeforeStart) {
321  } else if (m_outgoingDataSource->bytesAvailable() > 0) {
323  }
324  } else {
325  q->connect(slave, &SlaveInterface::dataReq, q, [this]() {
326  slotDataReqFromDevice();
327  });
328  }
329  } else {
330  q->connect(slave, &SlaveInterface::dataReq, q, &TransferJob::slotDataReq);
331  }
332 
333  q->connect(slave, &SlaveInterface::redirection, q, &TransferJob::slotRedirection);
334 
335  q->connect(slave, &SlaveInterface::mimeType, q, &TransferJob::slotMimetype);
336 
337  q->connect(slave, &SlaveInterface::errorPage, q, [this]() {
338  m_errorPage = true;
339  });
340 
341  q->connect(slave, &SlaveInterface::needSubUrlData, q, [this]() {
342  slotNeedSubUrlData();
343  });
344 
345  q->connect(slave, &SlaveInterface::canResume, q, [q](KIO::filesize_t offset) {
346  Q_EMIT q->canResume(q, offset);
347  });
348 
349  if (slave->suspended()) {
350  m_mimetype = QStringLiteral("unknown");
351  // WABA: The worker was put on hold. Resume operation.
352  slave->resume();
353  }
354 
356  if (m_internalSuspended) {
357  slave->suspend();
358  }
359 }
360 
361 void TransferJobPrivate::slotNeedSubUrlData()
362 {
363  Q_Q(TransferJob);
364  // Job needs data from subURL.
365  m_subJob = KIO::get(m_subUrl, NoReload, HideProgressInfo);
366  internalSuspend(); // Put job on hold until we have some data.
367  q->connect(m_subJob, &TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) {
368  slotSubUrlData(job, data);
369  });
370  q->addSubjob(m_subJob);
371 }
372 
373 void TransferJobPrivate::slotSubUrlData(KIO::Job *, const QByteArray &data)
374 {
375  // The Alternating Bitburg protocol in action again.
376  staticData = data;
377  m_subJob->d_func()->internalSuspend(); // Put job on hold until we have delivered the data.
378  internalResume(); // Activate ourselves again.
379 }
380 
381 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 101)
382 void TransferJob::slotMetaData(const KIO::MetaData &_metaData)
383 {
384  SimpleJob::slotMetaData(_metaData);
385 }
386 #endif
387 
388 void TransferJobPrivate::slotDataReqFromDevice()
389 {
390  Q_Q(TransferJob);
391 
392  bool done = false;
393  QByteArray dataForWorker;
394 
395  m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
396 
397  if (m_outgoingDataSource) {
398  dataForWorker.resize(MAX_READ_BUF_SIZE);
399 
400  // Code inspired in QNonContiguousByteDevice
401  qint64 bytesRead = m_outgoingDataSource->read(dataForWorker.data(), MAX_READ_BUF_SIZE);
402  if (bytesRead >= 0) {
403  dataForWorker.resize(bytesRead);
404  } else {
405  dataForWorker.clear();
406  }
407  done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential()));
408  }
409 
410  if (dataForWorker.isEmpty()) {
411  Q_EMIT q->dataReq(q, dataForWorker);
412  if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) {
413  return;
414  }
415  }
416 
417  q->sendAsyncData(dataForWorker);
418 
419  if (m_subJob) {
420  // Bitburger protocol in action
421  internalSuspend(); // Wait for more data from subJob.
422  m_subJob->d_func()->internalResume(); // Ask for more!
423  }
424 }
425 
426 void TransferJobPrivate::slotIODeviceClosedBeforeStart()
427 {
428  m_closedBeforeStart = true;
429 }
430 
431 void TransferJobPrivate::slotIODeviceClosed()
432 {
433  Q_Q(TransferJob);
434  const QByteArray remainder = m_outgoingDataSource->readAll();
435  if (!remainder.isEmpty()) {
436  m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
437  q->sendAsyncData(remainder);
438  }
439 
440  m_extraFlags |= JobPrivate::EF_TransferJobNeedData;
441 
442  // We send an empty data array to indicate the stream is over
443  q->sendAsyncData(QByteArray());
444 
445  if (m_subJob) {
446  // Bitburger protocol in action
447  internalSuspend(); // Wait for more data from subJob.
448  m_subJob->d_func()->internalResume(); // Ask for more!
449  }
450 }
451 
453 {
454  Q_D(TransferJob);
455  // This can only be our suburl.
456  Q_ASSERT(job == d->m_subJob);
457 
459 
460  if (!error() && job == d->m_subJob) {
461  d->m_subJob = nullptr; // No action required
462  d->internalResume(); // Make sure we get the remaining data.
463  }
464 }
465 
467 {
468  addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate));
469 }
470 
471 TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags)
472 {
473  // Send decoded path and encoded query
474  KIO_ARGS << url;
475  TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags);
476  if (reload == Reload) {
477  job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload"));
478  }
479  return job;
480 }
481 
482 #include "moc_transferjob.cpp"
bool isNull() const const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setTotalSize(KIO::filesize_t bytes)
Set the total size of data that we are going to send in a put job.
Definition: transferjob.cpp:53
void setErrorText(const QString &errorText)
void permanentRedirection(KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl)
Signals a permanent redirection.
Q_EMITQ_EMIT
Type type(const QSqlDatabase &db)
qulonglong filesize_t
64-bit file size
Definition: global.h:39
QString mimetype() const
Call this in the slot connected to result, and only after making sure no error happened.
Q_SCRIPTABLE Q_NOREPLY void start()
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:228
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
Get (means: read).
void clear()
void sendAsyncData(const QByteArray &data)
Provide data to the job when async data is enabled.
const QUrl & url() const
Returns the SimpleJob's URL.
Definition: simplejob.cpp:70
void redirection(KIO::Job *job, const QUrl &url)
Signals a redirection.
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
QString queryMetaData(const QString &key)
Query meta data received from the worker.
Definition: job.cpp:217
QUrl redirectUrl() const
After the job has finished, it will return the final url in case a redirection has happened.
virtual void slotFinished()
Called when the worker marks the job as finished.
Definition: simplejob.cpp:200
virtual void slotMetaData(const KIO::MetaData &_metaData)
MetaData from the worker is received.
Definition: simplejob.cpp:302
void readyRead()
QueuedConnection
bool doResume() override
Resume this job.
Definition: simplejob.cpp:61
bool isErrorPage() const
Checks whether we got an error page.
void readChannelFinished()
void setAsyncDataEnabled(bool enabled)
Enable the async data mode.
bool isEmpty() const const
void resize(int size)
void setProcessedAmount(Unit unit, qulonglong amount)
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 processedAmount(KJob *job, KJob::Unit unit, qulonglong amount)
void mimeTypeFound(KIO::Job *job, const QString &mimeType)
MIME type determined.
void slotResult(KJob *job) override
Called when m_subJob finishes.
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
A namespace for KIO globals.
virtual void slotResult(KJob *job)
int size() const const
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition: job_base.h:275
bool doResume() override
Reimplemented for internal reasons.
void truncate(int pos)
int error() const
void setReportDataSent(bool enabled)
When enabled, the job reports the amount of data that has been sent, instead of the amount of data th...
QString toString(Qt::DateFormat format) const const
void data(KIO::Job *job, const QByteArray &data)
Data from the worker has arrived.
void dataReq(KIO::Job *job, QByteArray &data)
Request for data.
bool reportDataSent() const
Returns whether the job reports the amount of data that has been sent (true), or whether the job repo...
void setError(int errorCode)
Q_D(Todo)
char * data()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Dec 5 2023 03:55:26 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.