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 "simplejob.h"
10 #include "job_p.h"
11 #include "kprotocolinfo.h"
12 #include "scheduler.h"
13 #include "slave.h"
14 #include <QDebug>
15 #include <QTimer>
16 #include <kdirnotify.h>
17 
18 using namespace KIO;
19 
SimpleJob(SimpleJobPrivate & dd)20 SimpleJob::SimpleJob(SimpleJobPrivate &dd)
21     : Job(dd)
22 {
23     d_func()->simpleJobInit();
24 }
25 
simpleJobInit()26 void SimpleJobPrivate::simpleJobInit()
27 {
28     Q_Q(SimpleJob);
29     if (!m_url.isValid() || m_url.scheme().isEmpty()) {
30         qCWarning(KIO_CORE) << "Invalid URL:" << m_url;
31         q->setError(ERR_MALFORMED_URL);
32         q->setErrorText(m_url.toString());
33         QTimer::singleShot(0, q, &SimpleJob::slotFinished);
34         return;
35     }
36 
37     Scheduler::doJob(q);
38 }
39 
doKill()40 bool SimpleJob::doKill()
41 {
42     Q_D(SimpleJob);
43     if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) {
44         d->m_extraFlags |= JobPrivate::EF_KillCalled;
45         Scheduler::cancelJob(this); // deletes the slave if not 0
46     } else {
47         qCWarning(KIO_CORE) << this << "killed twice, this is overkill";
48     }
49     return Job::doKill();
50 }
51 
doSuspend()52 bool SimpleJob::doSuspend()
53 {
54     Q_D(SimpleJob);
55     if (d->m_slave) {
56         d->m_slave->suspend();
57     }
58     return Job::doSuspend();
59 }
60 
doResume()61 bool SimpleJob::doResume()
62 {
63     Q_D(SimpleJob);
64     if (d->m_slave) {
65         d->m_slave->resume();
66     }
67     return Job::doResume();
68 }
69 
url() const70 const QUrl &SimpleJob::url() const
71 {
72     return d_func()->m_url;
73 }
74 
putOnHold()75 void SimpleJob::putOnHold()
76 {
77     Q_D(SimpleJob);
78     Q_ASSERT(d->m_slave);
79     if (d->m_slave) {
80         Scheduler::putSlaveOnHold(this, d->m_url);
81     }
82     // we should now be disassociated from the slave
83     Q_ASSERT(!d->m_slave);
84     kill(Quietly);
85 }
86 
removeOnHold()87 void SimpleJob::removeOnHold()
88 {
89     Scheduler::removeSlaveOnHold();
90 }
91 
isRedirectionHandlingEnabled() const92 bool SimpleJob::isRedirectionHandlingEnabled() const
93 {
94     return d_func()->m_redirectionHandlingEnabled;
95 }
96 
setRedirectionHandlingEnabled(bool handle)97 void SimpleJob::setRedirectionHandlingEnabled(bool handle)
98 {
99     Q_D(SimpleJob);
100     d->m_redirectionHandlingEnabled = handle;
101 }
102 
~SimpleJob()103 SimpleJob::~SimpleJob()
104 {
105     Q_D(SimpleJob);
106     // last chance to remove this job from the scheduler!
107     if (d->m_schedSerial) {
108         // qDebug() << "Killing job" << this << "in destructor!"/* << qBacktrace()*/;
109         Scheduler::cancelJob(this);
110     }
111 }
112 
start(Slave * slave)113 void SimpleJobPrivate::start(Slave *slave)
114 {
115     Q_Q(SimpleJob);
116     m_slave = slave;
117 
118     // Slave::setJob can send us SSL metadata if there is a persistent connection
119     QObject::connect(slave, &Slave::metaData, q, &SimpleJob::slotMetaData);
120 
121     slave->setJob(q);
122 
123     QObject::connect(slave, &Slave::error, q, &SimpleJob::slotError);
124 
125     QObject::connect(slave, &Slave::warning, q, &SimpleJob::slotWarning);
126 
127     QObject::connect(slave, &Slave::finished, q, &SimpleJob::slotFinished);
128 
129     QObject::connect(slave, &Slave::infoMessage, q, [this](const QString &message) {
130         _k_slotSlaveInfoMessage(message);
131     });
132 
133     QObject::connect(slave, &Slave::connected, q, [this]() {
134         slotConnected();
135     });
136 
137     QObject::connect(slave, &Slave::privilegeOperationRequested, q, [this]() {
138         slotPrivilegeOperationRequested();
139     });
140 
141     if ((m_extraFlags & EF_TransferJobDataSent) == 0) { // this is a "get" job
142         QObject::connect(slave, &Slave::totalSize, q, [this](KIO::filesize_t size) {
143             slotTotalSize(size);
144         });
145 
146         QObject::connect(slave, &Slave::processedSize, q, [this](KIO::filesize_t size) {
147             slotProcessedSize(size);
148         });
149 
150         QObject::connect(slave, &Slave::speed, q, [this](ulong speed) {
151             slotSpeed(speed);
152         });
153     }
154 
155     const QVariant windowIdProp = q->property("window-id"); // see KJobWidgets::setWindow
156     if (windowIdProp.isValid()) {
157         m_outgoingMetaData.insert(QStringLiteral("window-id"), QString::number(windowIdProp.toULongLong()));
158     }
159 
160     const QVariant userTimestampProp = q->property("userTimestamp"); // see KJobWidgets::updateUserTimestamp
161     if (userTimestampProp.isValid()) {
162         m_outgoingMetaData.insert(QStringLiteral("user-timestamp"), QString::number(userTimestampProp.toULongLong()));
163     }
164 
165     if (q->uiDelegate() == nullptr) { // not interactive
166         m_outgoingMetaData.insert(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
167     }
168 
169     if (!m_outgoingMetaData.isEmpty()) {
170         KIO_ARGS << m_outgoingMetaData;
171         slave->send(CMD_META_DATA, packedArgs);
172     }
173 
174     if (!m_subUrl.isEmpty()) {
175         KIO_ARGS << m_subUrl;
176         slave->send(CMD_SUBURL, packedArgs);
177     }
178 
179     slave->send(m_command, m_packedArgs);
180     if (q->isSuspended()) {
181         slave->suspend();
182     }
183 }
184 
slaveDone()185 void SimpleJobPrivate::slaveDone()
186 {
187     Q_Q(SimpleJob);
188     if (m_slave) {
189         if (m_command == CMD_OPEN) {
190             m_slave->send(CMD_CLOSE);
191         }
192         q->disconnect(m_slave); // Remove all signals between slave and job
193     }
194     // only finish a job once; Scheduler::jobFinished() resets schedSerial to zero.
195     if (m_schedSerial) {
196         Scheduler::jobFinished(q, m_slave);
197     }
198 }
199 
slotFinished()200 void SimpleJob::slotFinished()
201 {
202     Q_D(SimpleJob);
203     // Return slave to the scheduler
204     d->slaveDone();
205 
206     if (!hasSubjobs()) {
207         if (!error() && (d->m_command == CMD_MKDIR || d->m_command == CMD_RENAME)) {
208             if (d->m_command == CMD_MKDIR) {
209                 const QUrl urlDir = url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
210 #ifndef KIO_ANDROID_STUB
211                 org::kde::KDirNotify::emitFilesAdded(urlDir);
212 #endif
213             } else { /*if ( m_command == CMD_RENAME )*/
214                 QUrl src;
215                 QUrl dst;
216                 QDataStream str(d->m_packedArgs);
217                 str >> src >> dst;
218                 if (src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename) // For the user, moving isn't renaming. Only renaming is.
219                     && !KProtocolInfo::slaveHandlesNotify(dst.scheme()).contains(QLatin1String("Rename"))) {
220 #ifndef KIO_ANDROID_STUB
221                     org::kde::KDirNotify::emitFileRenamed(src, dst);
222 #endif
223                 }
224 
225 #ifndef KIO_ANDROID_STUB
226                 org::kde::KDirNotify::emitFileMoved(src, dst);
227 #endif
228                 if (d->m_uiDelegateExtension) {
229                     d->m_uiDelegateExtension->updateUrlInClipboard(src, dst);
230                 }
231             }
232         }
233         emitResult();
234     }
235 }
236 
slotError(int err,const QString & errorText)237 void SimpleJob::slotError(int err, const QString &errorText)
238 {
239     Q_D(SimpleJob);
240     setError(err);
241     setErrorText(errorText);
242     if ((error() == ERR_UNKNOWN_HOST) && d->m_url.host().isEmpty()) {
243         setErrorText(QString());
244     }
245     // error terminates the job
246     slotFinished();
247 }
248 
slotWarning(const QString & errorText)249 void SimpleJob::slotWarning(const QString &errorText)
250 {
251     Q_EMIT warning(this, errorText);
252 }
253 
_k_slotSlaveInfoMessage(const QString & msg)254 void SimpleJobPrivate::_k_slotSlaveInfoMessage(const QString &msg)
255 {
256     Q_EMIT q_func()->infoMessage(q_func(), msg);
257 }
258 
slotConnected()259 void SimpleJobPrivate::slotConnected()
260 {
261     Q_EMIT q_func()->connected(q_func());
262 }
263 
slotTotalSize(KIO::filesize_t size)264 void SimpleJobPrivate::slotTotalSize(KIO::filesize_t size)
265 {
266     Q_Q(SimpleJob);
267     if (size != q->totalAmount(KJob::Bytes)) {
268         q->setTotalAmount(KJob::Bytes, size);
269     }
270 }
271 
slotProcessedSize(KIO::filesize_t size)272 void SimpleJobPrivate::slotProcessedSize(KIO::filesize_t size)
273 {
274     Q_Q(SimpleJob);
275     // qDebug() << KIO::number(size);
276     q->setProcessedAmount(KJob::Bytes, size);
277 }
278 
slotSpeed(unsigned long speed)279 void SimpleJobPrivate::slotSpeed(unsigned long speed)
280 {
281     // qDebug() << speed;
282     q_func()->emitSpeed(speed);
283 }
284 
restartAfterRedirection(QUrl * redirectionUrl)285 void SimpleJobPrivate::restartAfterRedirection(QUrl *redirectionUrl)
286 {
287     Q_Q(SimpleJob);
288     // Return slave to the scheduler while we still have the old URL in place; the scheduler
289     // requires a job URL to stay invariant while the job is running.
290     slaveDone();
291 
292     m_url = *redirectionUrl;
293     redirectionUrl->clear();
294     if ((m_extraFlags & EF_KillCalled) == 0) {
295         Scheduler::doJob(q);
296     }
297 }
298 
requestMessageBox(int _type,const QString & text,const QString & caption,const QString & buttonYes,const QString & buttonNo,const QString & iconYes,const QString & iconNo,const QString & dontAskAgainName,const KIO::MetaData & sslMetaData)299 int SimpleJobPrivate::requestMessageBox(int _type,
300                                         const QString &text,
301                                         const QString &caption,
302                                         const QString &buttonYes,
303                                         const QString &buttonNo,
304                                         const QString &iconYes,
305                                         const QString &iconNo,
306                                         const QString &dontAskAgainName,
307                                         const KIO::MetaData &sslMetaData)
308 {
309     if (m_uiDelegateExtension) {
310         const JobUiDelegateExtension::MessageBoxType type = static_cast<JobUiDelegateExtension::MessageBoxType>(_type);
311         return m_uiDelegateExtension->requestMessageBox(type, text, caption, buttonYes, buttonNo, iconYes, iconNo, dontAskAgainName, sslMetaData);
312     }
313     qCWarning(KIO_CORE) << "JobUiDelegate not set! Returning -1";
314     return -1;
315 }
316 
slotMetaData(const KIO::MetaData & _metaData)317 void SimpleJob::slotMetaData(const KIO::MetaData &_metaData)
318 {
319     Q_D(SimpleJob);
320     QMapIterator<QString, QString> it(_metaData);
321     while (it.hasNext()) {
322         it.next();
323         if (it.key().startsWith(QLatin1String("{internal~"), Qt::CaseInsensitive)) {
324             d->m_internalMetaData.insert(it.key(), it.value());
325         } else {
326             d->m_incomingMetaData.insert(it.key(), it.value());
327         }
328     }
329 
330     // Update the internal meta-data values as soon as possible. Waiting until
331     // the ioslave is finished has unintended consequences if the client starts
332     // a new connection without waiting for the ioslave to finish.
333     if (!d->m_internalMetaData.isEmpty()) {
334         Scheduler::updateInternalMetaData(this);
335     }
336 }
337 
storeSSLSessionFromJob(const QUrl & redirectionURL)338 void SimpleJob::storeSSLSessionFromJob(const QUrl &redirectionURL)
339 {
340     Q_UNUSED(redirectionURL);
341 }
342 
slotPrivilegeOperationRequested()343 void SimpleJobPrivate::slotPrivilegeOperationRequested()
344 {
345     m_slave->send(MSG_PRIVILEGE_EXEC, privilegeOperationData());
346 }
347 
348 //////////
rmdir(const QUrl & url)349 SimpleJob *KIO::rmdir(const QUrl &url)
350 {
351     // qDebug() << "rmdir " << url;
352     KIO_ARGS << url << qint8(false); // isFile is false
353     return SimpleJobPrivate::newJob(url, CMD_DEL, packedArgs);
354 }
355 
chmod(const QUrl & url,int permissions)356 SimpleJob *KIO::chmod(const QUrl &url, int permissions)
357 {
358     // qDebug() << "chmod " << url;
359     KIO_ARGS << url << permissions;
360     return SimpleJobPrivate::newJob(url, CMD_CHMOD, packedArgs);
361 }
362 
chown(const QUrl & url,const QString & owner,const QString & group)363 SimpleJob *KIO::chown(const QUrl &url, const QString &owner, const QString &group)
364 {
365     KIO_ARGS << url << owner << group;
366     return SimpleJobPrivate::newJob(url, CMD_CHOWN, packedArgs);
367 }
368 
setModificationTime(const QUrl & url,const QDateTime & mtime)369 SimpleJob *KIO::setModificationTime(const QUrl &url, const QDateTime &mtime)
370 {
371     // qDebug() << "setModificationTime " << url << " " << mtime;
372     KIO_ARGS << url << mtime;
373     return SimpleJobPrivate::newJobNoUi(url, CMD_SETMODIFICATIONTIME, packedArgs);
374 }
375 
rename(const QUrl & src,const QUrl & dest,JobFlags flags)376 SimpleJob *KIO::rename(const QUrl &src, const QUrl &dest, JobFlags flags)
377 {
378     // qDebug() << "rename " << src << " " << dest;
379     KIO_ARGS << src << dest << (qint8)(flags & Overwrite);
380     return SimpleJobPrivate::newJob(src, CMD_RENAME, packedArgs, flags);
381 }
382 
symlink(const QString & target,const QUrl & dest,JobFlags flags)383 SimpleJob *KIO::symlink(const QString &target, const QUrl &dest, JobFlags flags)
384 {
385     // qDebug() << "symlink target=" << target << " " << dest;
386     KIO_ARGS << target << dest << (qint8)(flags & Overwrite);
387     return SimpleJobPrivate::newJob(dest, CMD_SYMLINK, packedArgs, flags);
388 }
389 
special(const QUrl & url,const QByteArray & data,JobFlags flags)390 SimpleJob *KIO::special(const QUrl &url, const QByteArray &data, JobFlags flags)
391 {
392     // qDebug() << "special " << url;
393     return SimpleJobPrivate::newJob(url, CMD_SPECIAL, data, flags);
394 }
395 
mount(bool ro,const QByteArray & fstype,const QString & dev,const QString & point,JobFlags flags)396 SimpleJob *KIO::mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags)
397 {
398     KIO_ARGS << int(1) << qint8(ro ? 1 : 0) << QString::fromLatin1(fstype) << dev << point;
399     SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags);
400     if (!(flags & HideProgressInfo)) {
401         KIO::JobPrivate::emitMounting(job, dev, point);
402     }
403     return job;
404 }
405 
unmount(const QString & point,JobFlags flags)406 SimpleJob *KIO::unmount(const QString &point, JobFlags flags)
407 {
408     KIO_ARGS << int(2) << point;
409     SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags);
410     if (!(flags & HideProgressInfo)) {
411         KIO::JobPrivate::emitUnmounting(job, point);
412     }
413     return job;
414 }
415 
416 //////////
417 
http_update_cache(const QUrl & url,bool no_cache,const QDateTime & expireDate)418 SimpleJob *KIO::http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate)
419 {
420     Q_ASSERT(url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("https"));
421     // Send http update_cache command (2)
422     KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate.toMSecsSinceEpoch() / 1000);
423     SimpleJob *job = SimpleJobPrivate::newJob(url, CMD_SPECIAL, packedArgs);
424     Scheduler::setJobPriority(job, 1);
425     return job;
426 }
427 
428 #include "moc_simplejob.cpp"
429