1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
6 SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-only
9*/
10
11#include "slavebase.h"
12
13#include <config-kiocore.h>
14
15#include <qplatformdefs.h>
16#include <signal.h>
17#include <stdlib.h>
18#ifdef Q_OS_WIN
19#include <process.h>
20#endif
21
22#include <QCoreApplication>
23#include <QDataStream>
24#include <QDateTime>
25#include <QElapsedTimer>
26#include <QFile>
27#include <QList>
28#include <QMap>
29#include <QSsl>
30#include <QtGlobal>
31
32#include <KConfig>
33#include <KConfigGroup>
34#include <KLocalizedString>
35#include <QThread>
36
37#ifndef Q_OS_ANDROID
38#include <KCrash>
39#endif
40
41#include "authinfo.h"
42#include "kremoteencoding.h"
43
44#include "commands_p.h"
45#include "connection_p.h"
46#include "ioworker_defaults.h"
47#include "kiocoredebug.h"
48#include "kioglobal_p.h"
49#include "kpasswdserverclient.h"
50#include "workerinterface_p.h"
51
52#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
53#include <KAuth/Action>
54#endif
55
56// TODO: Enable once file KIO worker is ported away and add endif, similar in the header file
57// #if KIOCORE_BUILD_DEPRECATED_SINCE(version where file:/ KIO worker was ported)
58
59#if KIO_ASSERT_WORKER_STATES
60#define KIO_STATE_ASSERT(cond, where, what) Q_ASSERT_X(cond, where, what)
61#else
62/* clang-format off */
63#define KIO_STATE_ASSERT(cond, where, what) \
64 do { \
65 if (!(cond)) { \
66 qCWarning(KIO_CORE) << what; \
67 } \
68 } while (false)
69#endif
70/* clang-format on */
71
72extern "C" {
73static void sigpipe_handler(int sig);
74}
75
76using namespace KIO;
77
78typedef QList<QByteArray> AuthKeysList;
79typedef QMap<QString, QByteArray> AuthKeysMap;
80
81/* clang-format off */
82#define KIO_DATA \
83 QByteArray data; \
84 QDataStream stream(&data, QIODevice::WriteOnly); \
85 stream
86/* clang-format on */
87
88static constexpr int KIO_MAX_ENTRIES_PER_BATCH = 200;
89static constexpr int KIO_MAX_SEND_BATCH_TIME = 300;
90
91namespace KIO
92{
93class SlaveBasePrivate
94{
95public:
96 SlaveBase *const q;
97 explicit SlaveBasePrivate(SlaveBase *owner)
98 : q(owner)
99 , nextTimeoutMsecs(0)
100 , m_confirmationAsked(false)
101 , m_privilegeOperationStatus(OperationNotAllowed)
102 {
103 if (!qEnvironmentVariableIsEmpty(varName: "KIOWORKER_ENABLE_TESTMODE")) {
104 QStandardPaths::setTestModeEnabled(true);
105 } else if (!qEnvironmentVariableIsEmpty(varName: "KIOSLAVE_ENABLE_TESTMODE")) {
106 QStandardPaths::setTestModeEnabled(true);
107 qCWarning(KIO_CORE)
108 << "KIOSLAVE_ENABLE_TESTMODE is deprecated for KF6, and will be unsupported soon. Please use KIOWORKER_ENABLE_TESTMODE with KF6.";
109 }
110 pendingListEntries.reserve(asize: KIO_MAX_ENTRIES_PER_BATCH);
111 appConnection.setReadMode(Connection::ReadMode::Polled);
112 }
113 ~SlaveBasePrivate() = default;
114
115 UDSEntryList pendingListEntries;
116 QElapsedTimer m_timeSinceLastBatch;
117 Connection appConnection{Connection::Type::Worker};
118 QString poolSocket;
119 bool isConnectedToApp;
120
121 QString slaveid;
122 bool resume : 1;
123 bool needSendCanResume : 1;
124 bool onHold : 1;
125 bool inOpenLoop : 1;
126 std::atomic<bool> wasKilled = false;
127 std::atomic<bool> exit_loop = false;
128 std::atomic<bool> runInThread = false;
129 MetaData configData;
130 KConfig *config = nullptr;
131 KConfigGroup *configGroup = nullptr;
132 QMap<QString, QVariant> mapConfig;
133 QUrl onHoldUrl;
134
135 QElapsedTimer lastTimeout;
136 QElapsedTimer nextTimeout;
137 qint64 nextTimeoutMsecs;
138 KIO::filesize_t totalSize;
139 KRemoteEncoding *remotefile = nullptr;
140 enum { Idle, InsideMethod, InsideTimeoutSpecial, FinishedCalled, ErrorCalled } m_state;
141 bool m_finalityCommand = true; // whether finished() or error() may/must be called
142 QByteArray timeoutData;
143
144#ifndef KIO_ANDROID_STUB
145 std::unique_ptr<KPasswdServerClient> m_passwdServerClient;
146#endif
147 bool m_rootEntryListed = false;
148
149 bool m_confirmationAsked;
150 QSet<QString> m_tempAuths;
151 QString m_warningTitle;
152 QString m_warningMessage;
153 int m_privilegeOperationStatus;
154
155 void updateTempAuthStatus()
156 {
157#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
158 QSet<QString>::iterator it = m_tempAuths.begin();
159 while (it != m_tempAuths.end()) {
160 KAuth::Action action(*it);
161 if (action.status() != KAuth::Action::AuthorizedStatus) {
162 it = m_tempAuths.erase(i: it);
163 } else {
164 ++it;
165 }
166 }
167#endif
168 }
169
170 bool hasTempAuth() const
171 {
172 return !m_tempAuths.isEmpty();
173 }
174
175 // Reconstructs configGroup from configData and mIncomingMetaData
176 void rebuildConfig()
177 {
178 mapConfig.clear();
179
180 // mIncomingMetaData cascades over config, so we write config first,
181 // to let it be overwritten
182 MetaData::ConstIterator end = configData.constEnd();
183 for (MetaData::ConstIterator it = configData.constBegin(); it != end; ++it) {
184 mapConfig.insert(key: it.key(), value: it->toUtf8());
185 }
186
187 end = q->mIncomingMetaData.constEnd();
188 for (MetaData::ConstIterator it = q->mIncomingMetaData.constBegin(); it != end; ++it) {
189 mapConfig.insert(key: it.key(), value: it->toUtf8());
190 }
191
192 delete configGroup;
193 configGroup = nullptr;
194 delete config;
195 config = nullptr;
196 }
197
198 bool finalState() const
199 {
200 return ((m_state == FinishedCalled) || (m_state == ErrorCalled));
201 }
202
203 void verifyState(const char *cmdName)
204 {
205 Q_UNUSED(cmdName)
206 KIO_STATE_ASSERT(finalState(),
207 Q_FUNC_INFO,
208 qUtf8Printable(QStringLiteral("%1 did not call finished() or error()! Please fix the %2 KIO worker.")
209 .arg(QLatin1String(cmdName))
210 .arg(QCoreApplication::applicationName())));
211 // Force the command into finished state. We'll not reach this for Debug builds
212 // that fail the assertion. For Release builds we'll have made sure that the
213 // command is actually finished after the verification regardless of what
214 // the slave did.
215 if (!finalState()) {
216 q->finished();
217 }
218 }
219
220 void verifyErrorFinishedNotCalled(const char *cmdName)
221 {
222 Q_UNUSED(cmdName)
223 KIO_STATE_ASSERT(!finalState(),
224 Q_FUNC_INFO,
225 qUtf8Printable(QStringLiteral("%1 called finished() or error(), but it's not supposed to! Please fix the %2 KIO worker.")
226 .arg(QLatin1String(cmdName))
227 .arg(QCoreApplication::applicationName())));
228 }
229
230#ifndef KIO_ANDROID_STUB
231 KPasswdServerClient *passwdServerClient()
232 {
233 if (!m_passwdServerClient) {
234 m_passwdServerClient = std::make_unique<KPasswdServerClient>();
235 }
236
237 return m_passwdServerClient.get();
238 }
239#endif
240};
241
242}
243
244static volatile bool slaveWriteError = false;
245
246#ifdef Q_OS_UNIX
247static SlaveBase *globalSlave;
248
249extern "C" {
250static void genericsig_handler(int sigNumber)
251{
252 ::signal(sig: sigNumber, SIG_IGN);
253 // WABA: Don't do anything that requires malloc, we can deadlock on it since
254 // a SIGTERM signal can come in while we are in malloc/free.
255 // qDebug()<<"kioslave : exiting due to signal "<<sigNumber;
256 // set the flag which will be checked in dispatchLoop() and which *should* be checked
257 // in lengthy operations in the various slaves
258 if (globalSlave != nullptr) {
259 globalSlave->setKillFlag();
260 }
261 ::signal(SIGALRM, SIG_DFL);
262 alarm(seconds: 5); // generate an alarm signal in 5 seconds, in this time the slave has to exit
263}
264}
265#endif
266
267//////////////
268
269SlaveBase::SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket)
270 : mProtocol(protocol)
271 , d(new SlaveBasePrivate(this))
272
273{
274 Q_ASSERT(!app_socket.isEmpty());
275 d->poolSocket = QFile::decodeName(localFileName: pool_socket);
276
277 if (QThread::currentThread() == qApp->thread()) {
278#ifndef Q_OS_ANDROID
279 KCrash::initialize();
280#endif
281
282#ifdef Q_OS_UNIX
283 struct sigaction act;
284 act.sa_handler = sigpipe_handler;
285 sigemptyset(set: &act.sa_mask);
286 act.sa_flags = 0;
287 sigaction(SIGPIPE, act: &act, oact: nullptr);
288
289 ::signal(SIGINT, handler: &genericsig_handler);
290 ::signal(SIGQUIT, handler: &genericsig_handler);
291 ::signal(SIGTERM, handler: &genericsig_handler);
292
293 globalSlave = this;
294#endif
295 }
296
297 d->isConnectedToApp = true;
298
299 // by kahl for netmgr (need a way to identify slaves)
300 d->slaveid = QString::fromUtf8(ba: protocol) + QString::number(getpid());
301 d->resume = false;
302 d->needSendCanResume = false;
303 d->mapConfig = QMap<QString, QVariant>();
304 d->onHold = false;
305 // d->processed_size = 0;
306 d->totalSize = 0;
307 connectSlave(path: QFile::decodeName(localFileName: app_socket));
308
309 d->remotefile = nullptr;
310 d->inOpenLoop = false;
311}
312
313SlaveBase::~SlaveBase()
314{
315 delete d->configGroup;
316 delete d->config;
317 delete d->remotefile;
318}
319
320void SlaveBase::dispatchLoop()
321{
322 while (!d->exit_loop) {
323 if (d->nextTimeout.isValid() && (d->nextTimeout.hasExpired(timeout: d->nextTimeoutMsecs))) {
324 QByteArray data = d->timeoutData;
325 d->nextTimeout.invalidate();
326 d->timeoutData = QByteArray();
327 d->m_state = d->InsideTimeoutSpecial;
328 special(data);
329 d->m_state = d->Idle;
330 }
331
332 Q_ASSERT(d->appConnection.inited());
333
334 int ms = -1;
335 if (d->nextTimeout.isValid()) {
336 ms = qMax<int>(a: d->nextTimeoutMsecs - d->nextTimeout.elapsed(), b: 1);
337 }
338
339 int ret = -1;
340 if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms)) {
341 // dispatch application messages
342 int cmd;
343 QByteArray data;
344 ret = d->appConnection.read(cmd: &cmd, data);
345
346 if (ret != -1) {
347 if (d->inOpenLoop) {
348 dispatchOpenCommand(command: cmd, data);
349 } else {
350 dispatch(command: cmd, data);
351 }
352 }
353 } else {
354 ret = d->appConnection.isConnected() ? 0 : -1;
355 }
356
357 if (ret == -1) { // some error occurred, perhaps no more application
358 // When the app exits, should the slave be put back in the pool ?
359 if (!d->exit_loop && d->isConnectedToApp && !d->poolSocket.isEmpty()) {
360 disconnectSlave();
361 d->isConnectedToApp = false;
362 closeConnection();
363 d->updateTempAuthStatus();
364 connectSlave(path: d->poolSocket);
365 } else {
366 break;
367 }
368 }
369
370 // I think we get here when we were killed in dispatch() and not in select()
371 if (wasKilled()) {
372 // qDebug() << "worker was killed, returning";
373 break;
374 }
375
376 // execute deferred deletes
377 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
378 }
379
380 // execute deferred deletes
381 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
382}
383
384void SlaveBase::connectSlave(const QString &address)
385{
386 d->appConnection.connectToRemote(address: QUrl(address));
387
388 if (!d->appConnection.inited()) {
389 /*qDebug() << "failed to connect to" << address << endl
390 << "Reason:" << d->appConnection.errorString();*/
391 exit();
392 }
393
394 d->inOpenLoop = false;
395}
396
397void SlaveBase::disconnectSlave()
398{
399 d->appConnection.close();
400}
401
402void SlaveBase::setMetaData(const QString &key, const QString &value)
403{
404 mOutgoingMetaData.insert(key, value); // replaces existing key if already there
405}
406
407QString SlaveBase::metaData(const QString &key) const
408{
409 auto it = mIncomingMetaData.find(key);
410 if (it != mIncomingMetaData.end()) {
411 return *it;
412 }
413 return d->configData.value(key);
414}
415
416MetaData SlaveBase::allMetaData() const
417{
418 return mIncomingMetaData;
419}
420
421bool SlaveBase::hasMetaData(const QString &key) const
422{
423 if (mIncomingMetaData.contains(key)) {
424 return true;
425 }
426 if (d->configData.contains(key)) {
427 return true;
428 }
429 return false;
430}
431
432QMap<QString, QVariant> SlaveBase::mapConfig() const
433{
434 return d->mapConfig;
435}
436
437bool SlaveBase::configValue(const QString &key, bool defaultValue) const
438{
439 return d->mapConfig.value(key, defaultValue).toBool();
440}
441
442int SlaveBase::configValue(const QString &key, int defaultValue) const
443{
444 return d->mapConfig.value(key, defaultValue).toInt();
445}
446
447QString SlaveBase::configValue(const QString &key, const QString &defaultValue) const
448{
449 return d->mapConfig.value(key, defaultValue).toString();
450}
451
452KConfigGroup *SlaveBase::config()
453{
454 if (!d->config) {
455 d->config = new KConfig(QString(), KConfig::SimpleConfig);
456
457 d->configGroup = new KConfigGroup(d->config, QString());
458
459 auto end = d->mapConfig.cend();
460 for (auto it = d->mapConfig.cbegin(); it != end; ++it) {
461 d->configGroup->writeEntry(key: it.key(), value: it->toString().toUtf8(), pFlags: KConfigGroup::WriteConfigFlags());
462 }
463 }
464
465 return d->configGroup;
466}
467
468void SlaveBase::sendMetaData()
469{
470 sendAndKeepMetaData();
471 mOutgoingMetaData.clear();
472}
473
474void SlaveBase::sendAndKeepMetaData()
475{
476 if (!mOutgoingMetaData.isEmpty()) {
477 KIO_DATA << mOutgoingMetaData;
478
479 send(cmd: INF_META_DATA, arr: data);
480 }
481}
482
483KRemoteEncoding *SlaveBase::remoteEncoding()
484{
485 if (d->remotefile) {
486 return d->remotefile;
487 }
488
489 const QByteArray charset(metaData(QStringLiteral("Charset")).toLatin1());
490 return (d->remotefile = new KRemoteEncoding(charset.constData()));
491}
492
493void SlaveBase::data(const QByteArray &data)
494{
495 sendMetaData();
496 send(cmd: MSG_DATA, arr: data);
497}
498
499void SlaveBase::dataReq()
500{
501 // sendMetaData();
502 if (d->needSendCanResume) {
503 canResume(offset: 0);
504 }
505 send(cmd: MSG_DATA_REQ);
506}
507
508void SlaveBase::opened()
509{
510 sendMetaData();
511 send(cmd: MSG_OPENED);
512 d->inOpenLoop = true;
513}
514
515void SlaveBase::error(int _errid, const QString &_text)
516{
517 if (d->m_state == d->InsideTimeoutSpecial) {
518 qWarning(catFunc: KIO_CORE) << "TimeoutSpecialCommand failed with" << _errid << _text;
519 return;
520 }
521
522 KIO_STATE_ASSERT(
523 d->m_finalityCommand,
524 Q_FUNC_INFO,
525 qUtf8Printable(QStringLiteral("error() was called, but it's not supposed to! Please fix the %1 KIO worker.").arg(QCoreApplication::applicationName())));
526
527 if (d->m_state == d->ErrorCalled) {
528 KIO_STATE_ASSERT(false,
529 Q_FUNC_INFO,
530 qUtf8Printable(QStringLiteral("error() called twice! Please fix the %1 KIO worker.").arg(QCoreApplication::applicationName())));
531 return;
532 } else if (d->m_state == d->FinishedCalled) {
533 KIO_STATE_ASSERT(
534 false,
535 Q_FUNC_INFO,
536 qUtf8Printable(QStringLiteral("error() called after finished()! Please fix the %1 KIO worker.").arg(QCoreApplication::applicationName())));
537 return;
538 }
539
540 d->m_state = d->ErrorCalled;
541 mIncomingMetaData.clear(); // Clear meta data
542 d->rebuildConfig();
543 mOutgoingMetaData.clear();
544 KIO_DATA << static_cast<qint32>(_errid) << _text;
545
546 send(cmd: MSG_ERROR, arr: data);
547 // reset
548 d->totalSize = 0;
549 d->inOpenLoop = false;
550 d->m_confirmationAsked = false;
551 d->m_privilegeOperationStatus = OperationNotAllowed;
552}
553
554void SlaveBase::connected()
555{
556 send(cmd: MSG_CONNECTED);
557}
558
559void SlaveBase::finished()
560{
561 if (d->m_state == d->InsideTimeoutSpecial) {
562 return;
563 }
564
565 if (!d->pendingListEntries.isEmpty()) {
566 if (!d->m_rootEntryListed) {
567 qCWarning(KIO_CORE) << "UDSEntry for '.' not found, creating a default one. Please fix the" << QCoreApplication::applicationName() << "KIO worker.";
568 KIO::UDSEntry entry;
569 entry.reserve(size: 4);
570 entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
571 entry.fastInsert(field: KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
572 entry.fastInsert(field: KIO::UDSEntry::UDS_SIZE, l: 0);
573 entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
574 d->pendingListEntries.append(t: entry);
575 }
576
577 listEntries(entry: d->pendingListEntries);
578 d->pendingListEntries.clear();
579 }
580
581 KIO_STATE_ASSERT(
582 d->m_finalityCommand,
583 Q_FUNC_INFO,
584 qUtf8Printable(
585 QStringLiteral("finished() was called, but it's not supposed to! Please fix the %2 KIO worker.").arg(QCoreApplication::applicationName())));
586
587 if (d->m_state == d->FinishedCalled) {
588 KIO_STATE_ASSERT(false,
589 Q_FUNC_INFO,
590 qUtf8Printable(QStringLiteral("finished() called twice! Please fix the %1 KIO worker.").arg(QCoreApplication::applicationName())));
591 return;
592 } else if (d->m_state == d->ErrorCalled) {
593 KIO_STATE_ASSERT(
594 false,
595 Q_FUNC_INFO,
596 qUtf8Printable(QStringLiteral("finished() called after error()! Please fix the %1 KIO worker.").arg(QCoreApplication::applicationName())));
597 return;
598 }
599
600 d->m_state = d->FinishedCalled;
601 mIncomingMetaData.clear(); // Clear meta data
602 d->rebuildConfig();
603 sendMetaData();
604 send(cmd: MSG_FINISHED);
605
606 // reset
607 d->totalSize = 0;
608 d->inOpenLoop = false;
609 d->m_rootEntryListed = false;
610 d->m_confirmationAsked = false;
611 d->m_privilegeOperationStatus = OperationNotAllowed;
612}
613
614void SlaveBase::slaveStatus(const QString &host, bool connected)
615{
616 qint64 pid = getpid();
617 qint8 b = connected ? 1 : 0;
618 KIO_DATA << pid << mProtocol << host << b << d->onHold << d->onHoldUrl << d->hasTempAuth();
619 send(cmd: MSG_WORKER_STATUS, arr: data);
620}
621
622void SlaveBase::canResume()
623{
624 send(cmd: MSG_CANRESUME);
625}
626
627void SlaveBase::totalSize(KIO::filesize_t _bytes)
628{
629 KIO_DATA << static_cast<quint64>(_bytes);
630 send(cmd: INF_TOTAL_SIZE, arr: data);
631
632 // this one is usually called before the first item is listed in listDir()
633 d->totalSize = _bytes;
634}
635
636void SlaveBase::processedSize(KIO::filesize_t _bytes)
637{
638 bool emitSignal = false;
639
640 if (_bytes == d->totalSize) {
641 emitSignal = true;
642 } else {
643 if (d->lastTimeout.isValid()) {
644 emitSignal = d->lastTimeout.hasExpired(timeout: 100); // emit size 10 times a second
645 } else {
646 emitSignal = true;
647 }
648 }
649
650 if (emitSignal) {
651 KIO_DATA << static_cast<quint64>(_bytes);
652 send(cmd: INF_PROCESSED_SIZE, arr: data);
653 d->lastTimeout.start();
654 }
655
656 // d->processed_size = _bytes;
657}
658
659void SlaveBase::written(KIO::filesize_t _bytes)
660{
661 KIO_DATA << static_cast<quint64>(_bytes);
662 send(cmd: MSG_WRITTEN, arr: data);
663}
664
665void SlaveBase::position(KIO::filesize_t _pos)
666{
667 KIO_DATA << static_cast<quint64>(_pos);
668 send(cmd: INF_POSITION, arr: data);
669}
670
671void SlaveBase::truncated(KIO::filesize_t _length)
672{
673 KIO_DATA << static_cast<quint64>(_length);
674 send(cmd: INF_TRUNCATED, arr: data);
675}
676
677void SlaveBase::processedPercent(float /* percent */)
678{
679 // qDebug() << "STUB";
680}
681
682void SlaveBase::speed(unsigned long _bytes_per_second)
683{
684 KIO_DATA << static_cast<quint32>(_bytes_per_second);
685 send(cmd: INF_SPEED, arr: data);
686}
687
688void SlaveBase::redirection(const QUrl &_url)
689{
690 KIO_DATA << _url;
691 send(cmd: INF_REDIRECTION, arr: data);
692}
693
694void SlaveBase::errorPage()
695{
696 send(cmd: INF_ERROR_PAGE);
697}
698
699static bool isSubCommand(int cmd)
700{
701 /* clang-format off */
702 return cmd == CMD_REPARSECONFIGURATION
703 || cmd == CMD_META_DATA
704 || cmd == CMD_CONFIG
705 || cmd == CMD_WORKER_STATUS;
706 /* clang-format on */
707}
708
709void SlaveBase::mimeType(const QString &_type)
710{
711 qCDebug(KIO_CORE) << "detected mimetype" << _type;
712 int cmd = CMD_NONE;
713 do {
714 if (wasKilled()) {
715 break;
716 }
717
718 // Send the meta-data each time we send the MIME type.
719 if (!mOutgoingMetaData.isEmpty()) {
720 qCDebug(KIO_CORE) << "sending mimetype meta data";
721 KIO_DATA << mOutgoingMetaData;
722 send(cmd: INF_META_DATA, arr: data);
723 }
724 KIO_DATA << _type;
725 send(cmd: INF_MIME_TYPE, arr: data);
726 while (true) {
727 cmd = 0;
728 int ret = -1;
729 if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms: -1)) {
730 ret = d->appConnection.read(cmd: &cmd, data);
731 }
732 if (ret == -1) {
733 qCDebug(KIO_CORE) << "read error on app connection while sending mimetype";
734 exit();
735 break;
736 }
737 qCDebug(KIO_CORE) << "got reply after sending mimetype" << cmd;
738 if (cmd == CMD_HOST) { // Ignore.
739 continue;
740 }
741 if (!isSubCommand(cmd)) {
742 break;
743 }
744
745 dispatch(command: cmd, data);
746 }
747 } while (cmd != CMD_NONE);
748 mOutgoingMetaData.clear();
749}
750
751void SlaveBase::exit() // possibly called from another thread, only use atomics in here
752{
753 d->exit_loop = true;
754 if (d->runInThread) {
755 d->wasKilled = true;
756 } else {
757 // Using ::exit() here is too much (crashes in qdbus's qglobalstatic object),
758 // so let's cleanly exit dispatchLoop() instead.
759 // Update: we do need to call exit(), otherwise a long download (get()) would
760 // keep going until it ends, even though the application exited.
761 ::exit(status: 255);
762 }
763}
764
765void SlaveBase::warning(const QString &_msg)
766{
767 KIO_DATA << _msg;
768 send(cmd: INF_WARNING, arr: data);
769}
770
771void SlaveBase::infoMessage(const QString &_msg)
772{
773 KIO_DATA << _msg;
774 send(cmd: INF_INFOMESSAGE, arr: data);
775}
776
777void SlaveBase::statEntry(const UDSEntry &entry)
778{
779 KIO_DATA << entry;
780 send(cmd: MSG_STAT_ENTRY, arr: data);
781}
782
783void SlaveBase::listEntry(const UDSEntry &entry)
784{
785 // #366795: many slaves don't create an entry for ".", so we keep track if they do
786 // and we provide a fallback in finished() otherwise.
787 if (entry.stringValue(field: KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) {
788 d->m_rootEntryListed = true;
789 }
790
791 // We start measuring the time from the point we start filling the list
792 if (d->pendingListEntries.isEmpty()) {
793 d->m_timeSinceLastBatch.restart();
794 }
795
796 d->pendingListEntries.append(t: entry);
797
798 // If more then KIO_MAX_SEND_BATCH_TIME time is passed, emit the current batch
799 // Also emit if we have piled up a large number of entries already, to save memory (and time)
800 if (d->m_timeSinceLastBatch.elapsed() > KIO_MAX_SEND_BATCH_TIME || d->pendingListEntries.size() > KIO_MAX_ENTRIES_PER_BATCH) {
801 listEntries(entry: d->pendingListEntries);
802 d->pendingListEntries.clear();
803
804 // Restart time
805 d->m_timeSinceLastBatch.restart();
806 }
807}
808
809void SlaveBase::listEntries(const UDSEntryList &list)
810{
811 QByteArray data;
812 QDataStream stream(&data, QIODevice::WriteOnly);
813
814 for (const UDSEntry &entry : list) {
815 stream << entry;
816 }
817
818 send(cmd: MSG_LIST_ENTRIES, arr: data);
819}
820
821static void sigpipe_handler(int)
822{
823 // We ignore a SIGPIPE in slaves.
824 // A SIGPIPE can happen in two cases:
825 // 1) Communication error with application.
826 // 2) Communication error with network.
827 slaveWriteError = true;
828
829 // Don't add anything else here, especially no debug output
830}
831
832void SlaveBase::setHost(QString const &, quint16, QString const &, QString const &)
833{
834}
835
836// TODO: move unsupportedActionErrorString() to workerbase.cpp
837// once SlaveBase is dissolved and folded into WorkerBase
838// forward declaration is already in workerbase.h
839namespace KIO
840{
841KIOCORE_EXPORT QString unsupportedActionErrorString(const QString &protocol, int cmd)
842{
843 switch (cmd) {
844 case CMD_CONNECT:
845 return i18n("Opening connections is not supported with the protocol %1.", protocol);
846 case CMD_DISCONNECT:
847 return i18n("Closing connections is not supported with the protocol %1.", protocol);
848 case CMD_STAT:
849 return i18n("Accessing files is not supported with the protocol %1.", protocol);
850 case CMD_PUT:
851 return i18n("Writing to %1 is not supported.", protocol);
852 case CMD_SPECIAL:
853 return i18n("There are no special actions available for protocol %1.", protocol);
854 case CMD_LISTDIR:
855 return i18n("Listing folders is not supported for protocol %1.", protocol);
856 case CMD_GET:
857 return i18n("Retrieving data from %1 is not supported.", protocol);
858 case CMD_MIMETYPE:
859 return i18n("Retrieving mime type information from %1 is not supported.", protocol);
860 case CMD_RENAME:
861 return i18n("Renaming or moving files within %1 is not supported.", protocol);
862 case CMD_SYMLINK:
863 return i18n("Creating symlinks is not supported with protocol %1.", protocol);
864 case CMD_COPY:
865 return i18n("Copying files within %1 is not supported.", protocol);
866 case CMD_DEL:
867 return i18n("Deleting files from %1 is not supported.", protocol);
868 case CMD_MKDIR:
869 return i18n("Creating folders is not supported with protocol %1.", protocol);
870 case CMD_CHMOD:
871 return i18n("Changing the attributes of files is not supported with protocol %1.", protocol);
872 case CMD_CHOWN:
873 return i18n("Changing the ownership of files is not supported with protocol %1.", protocol);
874 case CMD_OPEN:
875 return i18n("Opening files is not supported with protocol %1.", protocol);
876 default:
877 return i18n("Protocol %1 does not support action %2.", protocol, cmd);
878 } /*end switch*/
879}
880}
881
882void SlaveBase::openConnection()
883{
884 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CONNECT));
885}
886void SlaveBase::closeConnection()
887{
888} // No response!
889void SlaveBase::stat(QUrl const &)
890{
891 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_STAT));
892}
893void SlaveBase::put(QUrl const &, int, JobFlags)
894{
895 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_PUT));
896}
897void SlaveBase::special(const QByteArray &)
898{
899 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SPECIAL));
900}
901void SlaveBase::listDir(QUrl const &)
902{
903 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_LISTDIR));
904}
905void SlaveBase::get(QUrl const &)
906{
907 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_GET));
908}
909void SlaveBase::open(QUrl const &, QIODevice::OpenMode)
910{
911 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_OPEN));
912}
913void SlaveBase::read(KIO::filesize_t)
914{
915 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_READ));
916}
917void SlaveBase::write(const QByteArray &)
918{
919 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_WRITE));
920}
921void SlaveBase::seek(KIO::filesize_t)
922{
923 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SEEK));
924}
925void SlaveBase::close()
926{
927 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CLOSE));
928}
929void SlaveBase::mimetype(QUrl const &url)
930{
931 get(url);
932}
933void SlaveBase::rename(QUrl const &, QUrl const &, JobFlags)
934{
935 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_RENAME));
936}
937void SlaveBase::symlink(QString const &, QUrl const &, JobFlags)
938{
939 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SYMLINK));
940}
941void SlaveBase::copy(QUrl const &, QUrl const &, int, JobFlags)
942{
943 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_COPY));
944}
945void SlaveBase::del(QUrl const &, bool)
946{
947 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_DEL));
948}
949void SlaveBase::setLinkDest(const QUrl &, const QString &)
950{
951 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SETLINKDEST));
952}
953void SlaveBase::mkdir(QUrl const &, int)
954{
955 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_MKDIR));
956}
957void SlaveBase::chmod(QUrl const &, int)
958{
959 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CHMOD));
960}
961void SlaveBase::setModificationTime(QUrl const &, const QDateTime &)
962{
963 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SETMODIFICATIONTIME));
964}
965void SlaveBase::chown(QUrl const &, const QString &, const QString &)
966{
967 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CHOWN));
968}
969
970void SlaveBase::slave_status()
971{
972 slaveStatus(host: QString(), connected: false);
973}
974
975void SlaveBase::reparseConfiguration()
976{
977 delete d->remotefile;
978 d->remotefile = nullptr;
979}
980
981int SlaveBase::openPasswordDialogV2(AuthInfo &info, const QString &errorMsg)
982{
983 const long windowId = metaData(QStringLiteral("window-id")).toLong();
984 const unsigned long userTimestamp = metaData(QStringLiteral("user-timestamp")).toULong();
985 QString errorMessage;
986 if (metaData(QStringLiteral("no-auth-prompt")).compare(other: QLatin1String("true"), cs: Qt::CaseInsensitive) == 0) {
987 errorMessage = QStringLiteral("<NoAuthPrompt>");
988 } else {
989 errorMessage = errorMsg;
990 }
991
992 AuthInfo dlgInfo(info);
993 // Make sure the modified flag is not set.
994 dlgInfo.setModified(false);
995 // Prevent queryAuthInfo from caching the user supplied password since
996 // we need the ioslaves to first authenticate against the server with
997 // it to ensure it is valid.
998 dlgInfo.setExtraField(QStringLiteral("skip-caching-on-query"), value: true);
999
1000#ifndef KIO_ANDROID_STUB
1001 KPasswdServerClient *passwdServerClient = d->passwdServerClient();
1002 const int errCode = passwdServerClient->queryAuthInfo(info: &dlgInfo, errorMsg: errorMessage, windowId, usertime: userTimestamp);
1003 if (errCode == KJob::NoError) {
1004 info = dlgInfo;
1005 }
1006 return errCode;
1007#else
1008 return KJob::NoError;
1009#endif
1010}
1011
1012int SlaveBase::messageBox(MessageBoxType type, const QString &text, const QString &title, const QString &primaryActionText, const QString &secondaryActionText)
1013{
1014 return messageBox(text, type, title, primaryActionText, secondaryActionText, dontAskAgainName: QString());
1015}
1016
1017int SlaveBase::messageBox(const QString &text,
1018 MessageBoxType type,
1019 const QString &title,
1020 const QString &primaryActionText,
1021 const QString &secondaryActionText,
1022 const QString &dontAskAgainName)
1023{
1024 KIO_DATA << static_cast<qint32>(type) << text << title << primaryActionText << secondaryActionText << dontAskAgainName;
1025 send(cmd: INF_MESSAGEBOX, arr: data);
1026 if (waitForAnswer(expected1: CMD_MESSAGEBOXANSWER, expected2: 0, data) != -1) {
1027 QDataStream stream(data);
1028 int answer;
1029 stream >> answer;
1030 return answer;
1031 } else {
1032 return 0; // communication failure
1033 }
1034}
1035
1036int SlaveBase::sslError(const QVariantMap &sslData)
1037{
1038 KIO_DATA << sslData;
1039 send(cmd: INF_SSLERROR, arr: data);
1040 if (waitForAnswer(expected1: CMD_SSLERRORANSWER, expected2: 0, data) != -1) {
1041 QDataStream stream(data);
1042 int answer;
1043 stream >> answer;
1044 return answer;
1045 } else {
1046 return 0; // communication failure
1047 }
1048}
1049
1050bool SlaveBase::canResume(KIO::filesize_t offset)
1051{
1052 // qDebug() << "offset=" << KIO::number(offset);
1053 d->needSendCanResume = false;
1054 KIO_DATA << static_cast<quint64>(offset);
1055 send(cmd: MSG_RESUME, arr: data);
1056 if (offset) {
1057 int cmd;
1058 if (waitForAnswer(expected1: CMD_RESUMEANSWER, expected2: CMD_NONE, data, pCmd: &cmd) != -1) {
1059 // qDebug() << "returning" << (cmd == CMD_RESUMEANSWER);
1060 return cmd == CMD_RESUMEANSWER;
1061 } else {
1062 return false;
1063 }
1064 } else { // No resuming possible -> no answer to wait for
1065 return true;
1066 }
1067}
1068
1069int SlaveBase::waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd)
1070{
1071 int cmd = 0;
1072 int result = -1;
1073 for (;;) {
1074 if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms: -1)) {
1075 result = d->appConnection.read(cmd: &cmd, data);
1076 }
1077 if (result == -1) {
1078 // qDebug() << "read error.";
1079 return -1;
1080 }
1081
1082 if (cmd == expected1 || cmd == expected2) {
1083 if (pCmd) {
1084 *pCmd = cmd;
1085 }
1086 return result;
1087 }
1088 if (isSubCommand(cmd)) {
1089 dispatch(command: cmd, data);
1090 } else {
1091 qFatal(msg: "Fatal Error: Got cmd %d, while waiting for an answer!", cmd);
1092 }
1093 }
1094}
1095
1096int SlaveBase::readData(QByteArray &buffer)
1097{
1098 int result = waitForAnswer(expected1: MSG_DATA, expected2: 0, data&: buffer);
1099 // qDebug() << "readData: length = " << result << " ";
1100 return result;
1101}
1102
1103void SlaveBase::setTimeoutSpecialCommand(int timeout, const QByteArray &data)
1104{
1105 if (timeout > 0) {
1106 d->nextTimeoutMsecs = timeout * 1000; // from seconds to milliseconds
1107 d->nextTimeout.start();
1108 } else if (timeout == 0) {
1109 d->nextTimeoutMsecs = 1000; // Immediate timeout
1110 d->nextTimeout.start();
1111 } else {
1112 d->nextTimeout.invalidate(); // Canceled
1113 }
1114
1115 d->timeoutData = data;
1116}
1117
1118void SlaveBase::dispatch(int command, const QByteArray &data)
1119{
1120 QDataStream stream(data);
1121
1122 QUrl url;
1123 int i;
1124
1125 d->m_finalityCommand = true; // default
1126
1127 switch (command) {
1128 case CMD_HOST: {
1129 QString passwd;
1130 QString host;
1131 QString user;
1132 quint16 port;
1133 stream >> host >> port >> user >> passwd;
1134 d->m_state = d->InsideMethod;
1135 d->m_finalityCommand = false;
1136 setHost(host, port, user, passwd);
1137 d->m_state = d->Idle;
1138 break;
1139 }
1140 case CMD_CONNECT: {
1141 openConnection();
1142 break;
1143 }
1144 case CMD_DISCONNECT: {
1145 closeConnection();
1146 break;
1147 }
1148 case CMD_WORKER_STATUS: {
1149 d->m_state = d->InsideMethod;
1150 d->m_finalityCommand = false;
1151 slave_status();
1152 // TODO verify that the slave has called slaveStatus()?
1153 d->m_state = d->Idle;
1154 break;
1155 }
1156 case CMD_REPARSECONFIGURATION: {
1157 d->m_state = d->InsideMethod;
1158 d->m_finalityCommand = false;
1159 reparseConfiguration();
1160 d->m_state = d->Idle;
1161 break;
1162 }
1163 case CMD_CONFIG: {
1164 stream >> d->configData;
1165 d->rebuildConfig();
1166 delete d->remotefile;
1167 d->remotefile = nullptr;
1168 break;
1169 }
1170 case CMD_GET: {
1171 stream >> url;
1172 d->m_state = d->InsideMethod;
1173 get(url);
1174 d->verifyState(cmdName: "get()");
1175 d->m_state = d->Idle;
1176 break;
1177 }
1178 case CMD_OPEN: {
1179 stream >> url >> i;
1180 QIODevice::OpenMode mode = QFlag(i);
1181 d->m_state = d->InsideMethod;
1182 open(url, mode); // krazy:exclude=syscalls
1183 d->m_state = d->Idle;
1184 break;
1185 }
1186 case CMD_PUT: {
1187 int permissions;
1188 qint8 iOverwrite;
1189 qint8 iResume;
1190 stream >> url >> iOverwrite >> iResume >> permissions;
1191 JobFlags flags;
1192 if (iOverwrite != 0) {
1193 flags |= Overwrite;
1194 }
1195 if (iResume != 0) {
1196 flags |= Resume;
1197 }
1198
1199 // Remember that we need to send canResume(), TransferJob is expecting
1200 // it. Well, in theory this shouldn't be done if resume is true.
1201 // (the resume bool is currently unused)
1202 d->needSendCanResume = true /* !resume */;
1203
1204 d->m_state = d->InsideMethod;
1205 put(url, permissions, flags);
1206 d->verifyState(cmdName: "put()");
1207 d->m_state = d->Idle;
1208 break;
1209 }
1210 case CMD_STAT: {
1211 stream >> url;
1212 d->m_state = d->InsideMethod;
1213 stat(url); // krazy:exclude=syscalls
1214 d->verifyState(cmdName: "stat()");
1215 d->m_state = d->Idle;
1216 break;
1217 }
1218 case CMD_MIMETYPE: {
1219 stream >> url;
1220 d->m_state = d->InsideMethod;
1221 mimetype(url);
1222 d->verifyState(cmdName: "mimetype()");
1223 d->m_state = d->Idle;
1224 break;
1225 }
1226 case CMD_LISTDIR: {
1227 stream >> url;
1228 d->m_state = d->InsideMethod;
1229 listDir(url);
1230 d->verifyState(cmdName: "listDir()");
1231 d->m_state = d->Idle;
1232 break;
1233 }
1234 case CMD_MKDIR: {
1235 stream >> url >> i;
1236 d->m_state = d->InsideMethod;
1237 mkdir(url, i); // krazy:exclude=syscalls
1238 d->verifyState(cmdName: "mkdir()");
1239 d->m_state = d->Idle;
1240 break;
1241 }
1242 case CMD_RENAME: {
1243 qint8 iOverwrite;
1244 QUrl url2;
1245 stream >> url >> url2 >> iOverwrite;
1246 JobFlags flags;
1247 if (iOverwrite != 0) {
1248 flags |= Overwrite;
1249 }
1250 d->m_state = d->InsideMethod;
1251 rename(url, url2, flags); // krazy:exclude=syscalls
1252 d->verifyState(cmdName: "rename()");
1253 d->m_state = d->Idle;
1254 break;
1255 }
1256 case CMD_SYMLINK: {
1257 qint8 iOverwrite;
1258 QString target;
1259 stream >> target >> url >> iOverwrite;
1260 JobFlags flags;
1261 if (iOverwrite != 0) {
1262 flags |= Overwrite;
1263 }
1264 d->m_state = d->InsideMethod;
1265 symlink(target, url, flags);
1266 d->verifyState(cmdName: "symlink()");
1267 d->m_state = d->Idle;
1268 break;
1269 }
1270 case CMD_COPY: {
1271 int permissions;
1272 qint8 iOverwrite;
1273 QUrl url2;
1274 stream >> url >> url2 >> permissions >> iOverwrite;
1275 JobFlags flags;
1276 if (iOverwrite != 0) {
1277 flags |= Overwrite;
1278 }
1279 d->m_state = d->InsideMethod;
1280 copy(url, url2, permissions, flags);
1281 d->verifyState(cmdName: "copy()");
1282 d->m_state = d->Idle;
1283 break;
1284 }
1285 case CMD_DEL: {
1286 qint8 isFile;
1287 stream >> url >> isFile;
1288 d->m_state = d->InsideMethod;
1289 del(url, isFile != 0);
1290 d->verifyState(cmdName: "del()");
1291 d->m_state = d->Idle;
1292 break;
1293 }
1294 case CMD_CHMOD: {
1295 stream >> url >> i;
1296 d->m_state = d->InsideMethod;
1297 chmod(url, i);
1298 d->verifyState(cmdName: "chmod()");
1299 d->m_state = d->Idle;
1300 break;
1301 }
1302 case CMD_CHOWN: {
1303 QString owner;
1304 QString group;
1305 stream >> url >> owner >> group;
1306 d->m_state = d->InsideMethod;
1307 chown(url, owner, group);
1308 d->verifyState(cmdName: "chown()");
1309 d->m_state = d->Idle;
1310 break;
1311 }
1312 case CMD_SETMODIFICATIONTIME: {
1313 QDateTime dt;
1314 stream >> url >> dt;
1315 d->m_state = d->InsideMethod;
1316 setModificationTime(url, dt);
1317 d->verifyState(cmdName: "setModificationTime()");
1318 d->m_state = d->Idle;
1319 break;
1320 }
1321 case CMD_SPECIAL: {
1322 d->m_state = d->InsideMethod;
1323 special(data);
1324 d->verifyState(cmdName: "special()");
1325 d->m_state = d->Idle;
1326 break;
1327 }
1328 case CMD_META_DATA: {
1329 // qDebug() << "(" << getpid() << ") Incoming meta-data...";
1330 stream >> mIncomingMetaData;
1331 d->rebuildConfig();
1332 break;
1333 }
1334 case CMD_NONE: {
1335 qCWarning(KIO_CORE) << "Got unexpected CMD_NONE!";
1336 break;
1337 }
1338 case CMD_FILESYSTEMFREESPACE: {
1339 stream >> url;
1340
1341 void *data = static_cast<void *>(&url);
1342
1343 d->m_state = d->InsideMethod;
1344 virtual_hook(id: GetFileSystemFreeSpace, data);
1345 d->verifyState(cmdName: "fileSystemFreeSpace()");
1346 d->m_state = d->Idle;
1347 break;
1348 }
1349 default: {
1350 // Some command we don't understand.
1351 // Just ignore it, it may come from some future version of KIO.
1352 break;
1353 }
1354 }
1355}
1356
1357bool SlaveBase::checkCachedAuthentication(AuthInfo &info)
1358{
1359#ifndef KIO_ANDROID_STUB
1360 KPasswdServerClient *passwdServerClient = d->passwdServerClient();
1361 return (passwdServerClient->checkAuthInfo(info: &info, windowId: metaData(QStringLiteral("window-id")).toLong(), usertime: metaData(QStringLiteral("user-timestamp")).toULong()));
1362#else
1363 return false;
1364#endif
1365}
1366
1367void SlaveBase::dispatchOpenCommand(int command, const QByteArray &data)
1368{
1369 QDataStream stream(data);
1370
1371 switch (command) {
1372 case CMD_READ: {
1373 KIO::filesize_t bytes;
1374 stream >> bytes;
1375 read(bytes);
1376 break;
1377 }
1378 case CMD_WRITE: {
1379 write(data);
1380 break;
1381 }
1382 case CMD_SEEK: {
1383 KIO::filesize_t offset;
1384 stream >> offset;
1385 seek(offset);
1386 break;
1387 }
1388 case CMD_TRUNCATE: {
1389 KIO::filesize_t length;
1390 stream >> length;
1391 void *data = static_cast<void *>(&length);
1392 virtual_hook(id: Truncate, data);
1393 break;
1394 }
1395 case CMD_NONE:
1396 break;
1397 case CMD_CLOSE:
1398 close(); // must call finish(), which will set d->inOpenLoop=false
1399 break;
1400 default:
1401 // Some command we don't understand.
1402 // Just ignore it, it may come from some future version of KIO.
1403 break;
1404 }
1405}
1406
1407bool SlaveBase::cacheAuthentication(const AuthInfo &info)
1408{
1409#ifndef KIO_ANDROID_STUB
1410 KPasswdServerClient *passwdServerClient = d->passwdServerClient();
1411 passwdServerClient->addAuthInfo(info, windowId: metaData(QStringLiteral("window-id")).toLongLong());
1412#endif
1413 return true;
1414}
1415
1416int SlaveBase::connectTimeout()
1417{
1418 bool ok;
1419 QString tmp = metaData(QStringLiteral("ConnectTimeout"));
1420 int result = tmp.toInt(ok: &ok);
1421 if (ok) {
1422 return result;
1423 }
1424 return DEFAULT_CONNECT_TIMEOUT;
1425}
1426
1427int SlaveBase::proxyConnectTimeout()
1428{
1429 bool ok;
1430 QString tmp = metaData(QStringLiteral("ProxyConnectTimeout"));
1431 int result = tmp.toInt(ok: &ok);
1432 if (ok) {
1433 return result;
1434 }
1435 return DEFAULT_PROXY_CONNECT_TIMEOUT;
1436}
1437
1438int SlaveBase::responseTimeout()
1439{
1440 bool ok;
1441 QString tmp = metaData(QStringLiteral("ResponseTimeout"));
1442 int result = tmp.toInt(ok: &ok);
1443 if (ok) {
1444 return result;
1445 }
1446 return DEFAULT_RESPONSE_TIMEOUT;
1447}
1448
1449int SlaveBase::readTimeout()
1450{
1451 bool ok;
1452 QString tmp = metaData(QStringLiteral("ReadTimeout"));
1453 int result = tmp.toInt(ok: &ok);
1454 if (ok) {
1455 return result;
1456 }
1457 return DEFAULT_READ_TIMEOUT;
1458}
1459
1460bool SlaveBase::wasKilled() const
1461{
1462 return d->wasKilled;
1463}
1464
1465void SlaveBase::setKillFlag()
1466{
1467 d->wasKilled = true;
1468}
1469
1470void SlaveBase::send(int cmd, const QByteArray &arr)
1471{
1472 if (d->runInThread) {
1473 if (!d->appConnection.send(cmd, arr)) {
1474 exit();
1475 }
1476 } else {
1477 slaveWriteError = false;
1478 if (!d->appConnection.send(cmd, arr))
1479 // Note that slaveWriteError can also be set by sigpipe_handler
1480 {
1481 slaveWriteError = true;
1482 }
1483 if (slaveWriteError) {
1484 qCWarning(KIO_CORE) << "An error occurred during write. The worker terminates now.";
1485 exit();
1486 }
1487 }
1488}
1489
1490void SlaveBase::virtual_hook(int id, void *data)
1491{
1492 Q_UNUSED(data);
1493
1494 switch (id) {
1495 case GetFileSystemFreeSpace: {
1496 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_FILESYSTEMFREESPACE));
1497 break;
1498 }
1499 case Truncate: {
1500 error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_TRUNCATE));
1501 break;
1502 }
1503 }
1504}
1505
1506void SlaveBase::setRunInThread(bool b)
1507{
1508 d->runInThread = b;
1509}
1510
1511void SlaveBase::lookupHost(const QString &host)
1512{
1513 KIO_DATA << host;
1514 send(cmd: MSG_HOST_INFO_REQ, arr: data);
1515}
1516
1517int SlaveBase::waitForHostInfo(QHostInfo &info)
1518{
1519 QByteArray data;
1520 int result = waitForAnswer(expected1: CMD_HOST_INFO, expected2: 0, data);
1521
1522 if (result == -1) {
1523 info.setError(QHostInfo::UnknownError);
1524 info.setErrorString(i18n("Unknown Error"));
1525 return result;
1526 }
1527
1528 QDataStream stream(data);
1529 QString hostName;
1530 QList<QHostAddress> addresses;
1531 int error;
1532 QString errorString;
1533
1534 stream >> hostName >> addresses >> error >> errorString;
1535
1536 info.setHostName(hostName);
1537 info.setAddresses(addresses);
1538 info.setError(QHostInfo::HostInfoError(error));
1539 info.setErrorString(errorString);
1540
1541 return result;
1542}
1543
1544PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation(const QString &operationDetails)
1545{
1546 if (d->m_privilegeOperationStatus == OperationNotAllowed) {
1547 QByteArray buffer;
1548 send(cmd: MSG_PRIVILEGE_EXEC);
1549 waitForAnswer(expected1: MSG_PRIVILEGE_EXEC, expected2: 0, data&: buffer);
1550 QDataStream ds(buffer);
1551 ds >> d->m_privilegeOperationStatus >> d->m_warningTitle >> d->m_warningMessage;
1552 }
1553
1554 if (metaData(QStringLiteral("UnitTesting")) != QLatin1String("true") && d->m_privilegeOperationStatus == OperationAllowed && !d->m_confirmationAsked) {
1555 // WORKER_MESSAGEBOX_DETAILS_HACK
1556 // SlaveBase::messageBox() overloads miss a parameter to pass an details argument.
1557 // As workaround details are passed instead via metadata before and then cached by the WorkerInterface,
1558 // to be used in the upcoming messageBox call (needs WarningContinueCancelDetailed type)
1559 // TODO: add a messageBox() overload taking details and use here,
1560 // then remove or adapt all code marked with WORKER_MESSAGEBOX_DETAILS
1561 setMetaData(QStringLiteral("privilege_conf_details"), value: operationDetails);
1562 sendMetaData();
1563
1564 int result = messageBox(text: d->m_warningMessage, type: WarningContinueCancelDetailed, title: d->m_warningTitle, primaryActionText: QString(), secondaryActionText: QString(), dontAskAgainName: QString());
1565 d->m_privilegeOperationStatus = result == Continue ? OperationAllowed : OperationCanceled;
1566 d->m_confirmationAsked = true;
1567 }
1568
1569 return KIO::PrivilegeOperationStatus(d->m_privilegeOperationStatus);
1570}
1571
1572void SlaveBase::addTemporaryAuthorization(const QString &action)
1573{
1574 d->m_tempAuths.insert(value: action);
1575}
1576

source code of kio/src/core/slavebase.cpp