File indexing completed on 2024-05-05 04:59:31

0001 /*
0002     fish.cpp  -  a FISH KIO worker
0003     SPDX-FileCopyrightText: 2001 Jörg Walter <trouble@garni.ch>
0004     SPDX-License-Identifier: GPL-2.0-only
0005 */
0006 
0007 /*
0008   This code contains fragments and ideas from the ftp KIO worker
0009   done by David Faure <faure@kde.org>.
0010 
0011   Structure is a bit complicated, since I made the mistake to use
0012   KProcess... now there is a lightweight homebrew async IO system
0013   inside, but if signals/slots become available for KIO workers, switching
0014   back to KProcess should be easy.
0015 */
0016 
0017 #include "fish.h"
0018 
0019 #include <config-fish.h>
0020 #include <config-runtime.h>
0021 
0022 #include <QCoreApplication>
0023 #include <QDataStream>
0024 #include <QDateTime>
0025 #include <QDebug>
0026 #include <QFile>
0027 #include <QMimeDatabase>
0028 #include <QMimeType>
0029 #include <QRegularExpression>
0030 #include <QStandardPaths>
0031 
0032 #include <stdlib.h>
0033 #include <sys/resource.h>
0034 #ifdef HAVE_PTY_H
0035 #include <pty.h>
0036 #endif
0037 
0038 #ifdef HAVE_TERMIOS_H
0039 #include <termios.h>
0040 #endif
0041 
0042 #ifdef HAVE_SYS_IOCTL_H
0043 #include <sys/ioctl.h>
0044 #endif
0045 
0046 #ifdef HAVE_LIBUTIL_H
0047 #include <libutil.h>
0048 #endif
0049 
0050 #ifdef HAVE_UTIL_H
0051 #include <util.h>
0052 #endif
0053 
0054 #ifndef Q_OS_WIN
0055 #include <errno.h>
0056 #endif
0057 
0058 #include <KLocalizedString>
0059 #include <KRemoteEncoding>
0060 
0061 #include "fishcode.h"
0062 #include "loggingcategory.h"
0063 
0064 #ifndef NDEBUG
0065 #define myDebug(x) qCDebug(KIO_FISH_DEBUG) << __LINE__ << ": " x
0066 #define dataReq()                                                                                                                                              \
0067     do {                                                                                                                                                       \
0068         myDebug(<< "_______ emitting dataReq()");                                                                                                              \
0069         dataReq();                                                                                                                                             \
0070     } while (0)
0071 #define needSubURLData()                                                                                                                                       \
0072     do {                                                                                                                                                       \
0073         myDebug(<< "_______ emitting needSubURLData()");                                                                                                       \
0074         needSubURLData();                                                                                                                                      \
0075     } while (0)
0076 #define workerStatus(x, y)                                                                                                                                     \
0077     do {                                                                                                                                                       \
0078         myDebug(<< "_______ emitting workerStatus(" << x << ", " << y << ")");                                                                                 \
0079         workerStatus(x, y);                                                                                                                                    \
0080     } while (0)
0081 #define statEntry(x)                                                                                                                                           \
0082     do {                                                                                                                                                       \
0083         myDebug(<< "_______ emitting statEntry(" << x.count() << ")");                                                                                         \
0084         statEntry(x);                                                                                                                                          \
0085     } while (0)
0086 #define listEntries(x)                                                                                                                                         \
0087     do {                                                                                                                                                       \
0088         myDebug(<< "_______ emitting listEntries(...)");                                                                                                       \
0089         listEntries(x);                                                                                                                                        \
0090     } while (0)
0091 #define canResume(x)                                                                                                                                           \
0092     do {                                                                                                                                                       \
0093         myDebug(<< "_______ emitting canResume(" << (int)x << ")");                                                                                            \
0094         canResume(x);                                                                                                                                          \
0095     } while (0)
0096 #define totalSize(x)                                                                                                                                           \
0097     do {                                                                                                                                                       \
0098         myDebug(<< "_______ emitting totalSize(" << (int)x << ")");                                                                                            \
0099         totalSize(x);                                                                                                                                          \
0100     } while (0)
0101 #define processedSize(x)                                                                                                                                       \
0102     do {                                                                                                                                                       \
0103         myDebug(<< "_______ emitting processedSize(" << x << ")");                                                                                             \
0104         processedSize(x);                                                                                                                                      \
0105     } while (0)
0106 #define speed(x)                                                                                                                                               \
0107     do {                                                                                                                                                       \
0108         myDebug(<< "_______ emitting speed(" << (int)x << ")");                                                                                                \
0109         speed(x);                                                                                                                                              \
0110     } while (0)
0111 #define redirection(x)                                                                                                                                         \
0112     do {                                                                                                                                                       \
0113         myDebug(<< "_______ emitting redirection(" << x << ")");                                                                                               \
0114         redirection(x);                                                                                                                                        \
0115     } while (0)
0116 #define errorPage()                                                                                                                                            \
0117     do {                                                                                                                                                       \
0118         myDebug(<< "_______ emitting errorPage()");                                                                                                            \
0119         errorPage();                                                                                                                                           \
0120     } while (0)
0121 #define sendmimeType(x)                                                                                                                                        \
0122     do {                                                                                                                                                       \
0123         myDebug(<< "_______ emitting mimeType(" << x << ")");                                                                                                  \
0124         mimeType(x);                                                                                                                                           \
0125     } while (0)
0126 #define warning(x)                                                                                                                                             \
0127     do {                                                                                                                                                       \
0128         myDebug(<< "_______ emitting warning(" << x << ")");                                                                                                   \
0129         warning(x);                                                                                                                                            \
0130     } while (0)
0131 #define infoMessage(x)                                                                                                                                         \
0132     do {                                                                                                                                                       \
0133         myDebug(<< "_______ emitting infoMessage(" << x << ")");                                                                                               \
0134         infoMessage(x);                                                                                                                                        \
0135     } while (0)
0136 #else
0137 #define myDebug(x)
0138 #define sendmimeType(x) mimeType(x)
0139 #endif
0140 
0141 #ifdef Q_OS_WIN
0142 #define ENDLINE "\r\n"
0143 #else
0144 #define ENDLINE '\n'
0145 #endif
0146 
0147 static char *sshPath = nullptr;
0148 static char *suPath = nullptr;
0149 // disabled: currently not needed. Didn't work reliably.
0150 // static int isOpenSSH = 0;
0151 
0152 /** the SSH process used to communicate with the remote end */
0153 #ifndef Q_OS_WIN
0154 static pid_t childPid;
0155 #else
0156 static KProcess *childPid = 0;
0157 #endif
0158 
0159 #define E(x) ((const char *)remoteEncoding()->encode(x).data())
0160 
0161 // Pseudo plugin class to embed meta data
0162 class KIOPluginForMetaData : public QObject
0163 {
0164     Q_OBJECT
0165     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.fish" FILE "fish.json")
0166 };
0167 
0168 using namespace KIO;
0169 extern "C" {
0170 
0171 int Q_DECL_EXPORT kdemain(int argc, char **argv)
0172 {
0173     QCoreApplication app(argc, argv);
0174     app.setApplicationName("kio_fish");
0175 
0176     myDebug(<< "*** Starting fish ");
0177     if (argc != 4) {
0178         myDebug(<< "Usage: kio_fish protocol domain-socket1 domain-socket2");
0179         exit(-1);
0180     }
0181 
0182     setenv("TZ", "UTC", true);
0183 
0184     fishProtocol worker(argv[2], argv[3]);
0185     worker.dispatchLoop();
0186 
0187     myDebug(<< "*** fish Done");
0188     return 0;
0189 }
0190 }
0191 
0192 const struct fishProtocol::fish_info fishProtocol::fishInfo[] = {
0193     {("FISH"),
0194      0,
0195      ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM
0196       " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' "
0197       "2>/dev/null;"),
0198      1},
0199     {("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0, ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"), 1},
0200     {("PWD"), 0, ("pwd"), 1},
0201     {("LIST"),
0202      1,
0203      ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`;"
0204       "echo '### 100';"
0205       "ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ "
0206       "\t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else "
0207       "echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
0208       "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
0209      0},
0210     {("STAT"),
0211      1,
0212      ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`;"
0213       "echo '### 100';"
0214       "ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ "
0215       "\t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else "
0216       "echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
0217       "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
0218      0},
0219     {("RETR"), 1, ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"), 1},
0220     {("STOR"),
0221      2,
0222      ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;"
0223       "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"),
0224      0},
0225     {("CWD"), 1, ("cd %1"), 0},
0226     {("CHMOD"), 2, ("chmod %1 %2"), 0},
0227     {("DELE"), 1, ("rm -f %1"), 0},
0228     {("MKD"), 1, ("mkdir %1"), 0},
0229     {("RMD"), 1, ("rmdir %1"), 0},
0230     {("RENAME"), 2, ("mv -f %1 %2"), 0},
0231     {("LINK"), 2, ("ln -f %1 %2"), 0},
0232     {("SYMLINK"), 2, ("ln -sf %1 %2"), 0},
0233     {("CHOWN"), 2, ("chown %1 %2"), 0},
0234     {("CHGRP"), 2, ("chgrp %1 %2"), 0},
0235     {("READ"),
0236      3,
0237      ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;"
0238       "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;"
0239       "dd bs=%2 count=1; ) 2>/dev/null;"),
0240      0},
0241     // Yes, this is "ibs=1", since dd "count" is input blocks.
0242     // On network connections, read() may not fill the buffer
0243     // completely (no more data immediately available), but dd
0244     // does ignore that fact by design. Sorry, writes are slow.
0245     // OTOH, WRITE is not used by the current KIO worker methods,
0246     // we use APPEND.
0247     {("WRITE"),
0248      3,
0249      (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | "
0250       "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"),
0251      0},
0252     {("COPY"), 2, ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"), 0},
0253     {("APPEND"), 2, (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"), 0},
0254     {("EXEC"), 2, ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"), 0}};
0255 
0256 fishProtocol::fishProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
0257     : WorkerBase("fish", pool_socket, app_socket)
0258     , mimeBuffer(1024, '\0')
0259     , mimeTypeSent(false)
0260 {
0261     myDebug(<< "fishProtocol::fishProtocol()");
0262     if (sshPath == nullptr) {
0263         // disabled: currently not needed. Didn't work reliably.
0264         // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null");
0265 #ifdef Q_OS_WIN
0266         sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("plink")).constData());
0267 #else
0268         sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("ssh")).constData());
0269 #endif
0270     }
0271     if (suPath == nullptr) {
0272         suPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("su")).constData());
0273     }
0274     childPid = 0;
0275     connectionPort = 0;
0276     isLoggedIn = false;
0277     writeReady = true;
0278     isRunning = false;
0279     firstLogin = true;
0280     errorCount = 0;
0281     rawRead = 0;
0282     rawWrite = -1;
0283     recvLen = -1;
0284     sendLen = -1;
0285     connectionAuth.keepPassword = true;
0286     connectionAuth.url.setScheme("fish");
0287     outBufPos = -1;
0288     outBuf = QByteArray();
0289 
0290     udsType = 0;
0291 
0292     hasAppend = false;
0293 
0294     isStat = false; // FIXME: just a workaround for konq deficiencies
0295     redirectUser = ""; // FIXME: just a workaround for konq deficiencies
0296     redirectPass = ""; // FIXME: just a workaround for konq deficiencies
0297     fishCodeLen = strlen(fishCode);
0298 }
0299 /* ---------------------------------------------------------------------------------- */
0300 
0301 fishProtocol::~fishProtocol()
0302 {
0303     myDebug(<< "fishProtocol::~fishProtocol()");
0304     shutdownConnection(true);
0305 }
0306 
0307 /* --------------------------------------------------------------------------- */
0308 
0309 /**
0310 Connects to a server and logs us in via SSH. Then starts FISH protocol.
0311 */
0312 KIO::WorkerResult fishProtocol::openConnection()
0313 {
0314     if (childPid)
0315         return KIO::WorkerResult::pass();
0316 
0317     if (connectionHost.isEmpty()) {
0318         return error(KIO::ERR_UNKNOWN_HOST, QString());
0319     }
0320 
0321     infoMessage(i18n("Connecting..."));
0322 
0323     myDebug(<< "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort);
0324     sendCommand(FISH_FISH);
0325     sendCommand(FISH_VER);
0326     const auto result = connectionStart();
0327     if (!result.success()) {
0328         shutdownConnection();
0329         return result;
0330     };
0331     myDebug(<< "subprocess is running");
0332     return result;
0333 }
0334 
0335 // XXX Use KPty! XXX
0336 #ifndef Q_OS_WIN
0337 static int open_pty_pair(int fd[2])
0338 {
0339 #if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY)
0340     /** with kind regards to The GNU C Library
0341     Reference Manual for Version 2.2.x of the GNU C Library */
0342     int master, slave;
0343     char *name;
0344     struct ::termios ti;
0345     memset(&ti, 0, sizeof(ti));
0346 
0347     ti.c_cflag = CLOCAL | CREAD | CS8;
0348     ti.c_cc[VMIN] = 1;
0349 
0350 #ifdef HAVE_GETPT
0351     master = getpt();
0352 #else
0353     master = open("/dev/ptmx", O_RDWR);
0354 #endif
0355     if (master < 0)
0356         return 0;
0357 
0358     if (grantpt(master) < 0 || unlockpt(master) < 0)
0359         goto close_master;
0360 
0361     name = ptsname(master);
0362     if (name == NULL)
0363         goto close_master;
0364 
0365     slave = open(name, O_RDWR);
0366     if (slave == -1)
0367         goto close_master;
0368 
0369 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
0370     if (isastream(slave) && (ioctl(slave, I_PUSH, "ptem") < 0 || ioctl(slave, I_PUSH, "ldterm") < 0))
0371         goto close_slave;
0372 #endif
0373 
0374     tcsetattr(slave, TCSANOW, &ti);
0375     fd[0] = master;
0376     fd[1] = slave;
0377     return 0;
0378 
0379 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
0380 close_slave:
0381 #endif
0382     close(slave);
0383 
0384 close_master:
0385     close(master);
0386     return -1;
0387 #else
0388 #ifdef HAVE_OPENPTY
0389     struct ::termios ti;
0390     memset(&ti, 0, sizeof(ti));
0391 
0392     ti.c_cflag = CLOCAL | CREAD | CS8;
0393     ti.c_cc[VMIN] = 1;
0394 
0395     return openpty(fd, fd + 1, nullptr, &ti, nullptr);
0396 #else
0397 #ifdef __GNUC__
0398 #warning "No tty support available. Password dialog won't work."
0399 #endif
0400     return socketpair(PF_UNIX, SOCK_STREAM, 0, fd);
0401 #endif
0402 #endif
0403 }
0404 #endif
0405 /**
0406 creates the subprocess
0407 */
0408 KIO::WorkerResult fishProtocol::connectionStart()
0409 {
0410     int fd[2];
0411     int rc, flags;
0412     thisFn.clear();
0413 
0414 #ifndef Q_OS_WIN
0415     rc = open_pty_pair(fd);
0416     if (rc == -1) {
0417         myDebug(<< "socketpair failed, error: " << strerror(errno));
0418         return error(ERR_CANNOT_CONNECT, connectionHost);
0419         ;
0420     }
0421 #endif
0422 
0423     myDebug(<< "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser);
0424 #ifdef Q_OS_WIN
0425     childPid = new KProcess();
0426     childPid->setOutputChannelMode(KProcess::MergedChannels);
0427     QStringList common_args;
0428     common_args << "-l" << connectionUser.toLatin1().constData() << "-x" << connectionHost.toLatin1().constData();
0429     common_args << "echo;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= "
0430                    "TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"";
0431 
0432     childPid->setProgram(sshPath, common_args);
0433     childPid->start();
0434 
0435     QByteArray buf;
0436     int offset = 0;
0437     while (!isLoggedIn) {
0438         if (outBuf.size()) {
0439             rc = childPid->write(outBuf);
0440             outBuf.clear();
0441         } else
0442             rc = 0;
0443 
0444         if (rc < 0) {
0445             myDebug(<< "write failed, rc: " << rc);
0446             outBufPos = -1;
0447             // return true;
0448         }
0449 
0450         if (childPid->waitForReadyRead(1000)) {
0451             QByteArray buf2 = childPid->readAll();
0452             buf += buf2;
0453 
0454             const auto establishConnectionResult = establishConnection(buf, rc + offset);
0455             if (!establishConnectionResult.result.success()) {
0456                 return establishConnectionResult.result;
0457             }
0458             const int noff = establishConnectionResult.remainingBufferSize;
0459             if (noff > 0)
0460                 buf = buf.mid(/*offset+*/ noff);
0461             //             offset = noff;
0462         }
0463     }
0464 #else
0465     childPid = fork();
0466     if (childPid == -1) {
0467         myDebug(<< "fork failed, error: " << strerror(errno));
0468         ::close(fd[0]);
0469         ::close(fd[1]);
0470         childPid = 0;
0471         return error(ERR_CANNOT_CONNECT, connectionHost);
0472         ;
0473     }
0474     if (childPid == 0) {
0475         // taken from konsole, see TEPty.C for details
0476         // note: if we're running on socket pairs,
0477         // this will fail, but thats what we expect
0478 
0479         for (int sig = 1; sig < NSIG; sig++)
0480             signal(sig, SIG_DFL);
0481 
0482         struct rlimit rlp;
0483         getrlimit(RLIMIT_NOFILE, &rlp);
0484         for (int i = 0; i < (int)rlp.rlim_cur; i++)
0485             if (i != fd[1])
0486                 ::close(i);
0487 
0488         dup2(fd[1], 0);
0489         dup2(fd[1], 1);
0490         dup2(fd[1], 2);
0491         if (fd[1] > 2)
0492             ::close(fd[1]);
0493 
0494         setsid();
0495 
0496 #if defined(TIOCSCTTY)
0497         ioctl(0, TIOCSCTTY, 0);
0498 #endif
0499 
0500         int pgrp = getpid();
0501 #if defined(_AIX) || defined(__hpux)
0502         tcsetpgrp(0, pgrp);
0503 #else
0504         ioctl(0, TIOCSPGRP, (char *)&pgrp);
0505 #endif
0506 
0507         const char *dev = ttyname(0);
0508         setpgid(0, 0);
0509         if (dev)
0510             ::close(::open(dev, O_WRONLY, 0));
0511         setpgid(0, 0);
0512 
0513         if (local) {
0514             execl(suPath,
0515                   "su",
0516                   "-",
0517                   connectionUser.toLatin1().constData(),
0518                   "-c",
0519                   "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= "
0520                   "TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"",
0521                   (void *)nullptr);
0522         } else {
0523 #define common_args                                                                                                                                            \
0524     "-l", connectionUser.toLatin1().constData(), "-x", "-e", "none", "-q", connectionHost.toLatin1().constData(),                                              \
0525         "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C "      \
0526         "LC_ALL=C LOCALE=C /bin/sh; fi\"",                                                                                                                     \
0527         (void *)nullptr
0528             // disabled: leave compression up to the client.
0529             // (isOpenSSH?"-C":"+C"),
0530 
0531             if (connectionPort)
0532                 execl(sshPath, "ssh", "-p", qPrintable(QString::number(connectionPort)), common_args);
0533             else
0534                 execl(sshPath, "ssh", common_args);
0535 #undef common_args
0536         }
0537         myDebug(<< "could not exec! " << strerror(errno));
0538         ::exit(-1);
0539     }
0540     ::close(fd[1]);
0541     rc = fcntl(fd[0], F_GETFL, &flags);
0542     rc = fcntl(fd[0], F_SETFL, flags | O_NONBLOCK);
0543     childFd = fd[0];
0544 
0545     fd_set rfds, wfds;
0546     FD_ZERO(&rfds);
0547     FD_ZERO(&wfds);
0548     char buf[32768];
0549     int offset = 0;
0550     while (!isLoggedIn) {
0551         FD_SET(childFd, &rfds);
0552         FD_ZERO(&wfds);
0553         if (outBufPos >= 0)
0554             FD_SET(childFd, &wfds);
0555         struct timeval timeout;
0556         timeout.tv_sec = 0;
0557         timeout.tv_usec = 1000;
0558         rc = select(childFd + 1, &rfds, &wfds, nullptr, &timeout);
0559         if (rc < 0) {
0560             if (errno == EINTR)
0561                 continue;
0562             myDebug(<< "select failed, rc: " << rc << ", error: " << strerror(errno));
0563             return error(ERR_CANNOT_CONNECT, connectionHost);
0564             ;
0565         }
0566         if (FD_ISSET(childFd, &wfds) && outBufPos >= 0) {
0567             if (!outBuf.isEmpty())
0568                 rc = ::write(childFd, outBuf.constData() + outBufPos, outBuf.length() - outBufPos);
0569             else
0570                 rc = 0;
0571 
0572             if (rc >= 0)
0573                 outBufPos += rc;
0574             else {
0575                 if (errno == EINTR)
0576                     continue;
0577                 myDebug(<< "write failed, rc: " << rc << ", error: " << strerror(errno));
0578                 outBufPos = -1;
0579                 // return true;
0580             }
0581             if (outBufPos >= outBuf.length()) {
0582                 outBufPos = -1;
0583                 outBuf = QByteArray();
0584             }
0585         } else if (FD_ISSET(childFd, &rfds)) {
0586             rc = ::read(childFd, buf + offset, sizeof(buf) - offset);
0587             if (rc > 0) {
0588                 const auto establishConnectionResult = establishConnection(buf, rc + offset);
0589                 if (!establishConnectionResult.result.success()) {
0590                     return establishConnectionResult.result;
0591                 }
0592                 const int noff = establishConnectionResult.remainingBufferSize;
0593                 if (noff > 0)
0594                     memmove(buf, buf + offset + rc - noff, noff);
0595                 offset = noff;
0596             } else {
0597                 if (errno == EINTR)
0598                     continue;
0599                 myDebug(<< "read failed, rc: " << rc << ", error: " << strerror(errno));
0600                 return error(ERR_CANNOT_CONNECT, connectionHost);
0601                 ;
0602             }
0603         }
0604     }
0605 #endif
0606     return KIO::WorkerResult::pass();
0607 }
0608 
0609 /**
0610 writes one chunk of data to stdin of child process
0611 */
0612 void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len)
0613 {
0614     if (outBufPos >= 0 && !outBuf.isEmpty()) {
0615         return;
0616     }
0617     outBuf = QByteArray(buf, len);
0618     outBufPos = 0;
0619 }
0620 
0621 /**
0622 manages initial communication setup including password queries
0623 */
0624 #ifndef Q_OS_WIN
0625 ConnectedResult fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len)
0626 {
0627     QString buf = QString::fromLatin1(buffer, len);
0628 #else
0629 ConnectedResult fishProtocol::establishConnection(const QByteArray &buffer)
0630 {
0631     QString buf = buffer;
0632 #endif
0633     int pos = 0;
0634     // Strip trailing whitespace
0635     while (buf.length() && (buf[buf.length() - 1] == ' '))
0636         buf.truncate(buf.length() - 1);
0637 
0638     myDebug(<< "establishing: got " << buf);
0639     while (childPid && ((pos = buf.indexOf('\n')) >= 0 || buf.endsWith(':') || buf.endsWith('?'))) {
0640         pos++;
0641         QString str = buf.left(pos);
0642         buf = buf.mid(pos);
0643         if (str == "\n")
0644             continue;
0645         if (str == "FISH:\n") {
0646             thisFn.clear();
0647             infoMessage(i18n("Initiating protocol..."));
0648             if (!connectionAuth.password.isEmpty()) {
0649                 connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length() - 1);
0650                 if (connectionAuth.keepPassword) {
0651                     cacheAuthentication(connectionAuth);
0652                 }
0653             }
0654             isLoggedIn = true;
0655             return {0, KIO::WorkerResult::pass()};
0656         } else if (!str.isEmpty()) {
0657             thisFn += str;
0658         } else if (buf.endsWith(':')) {
0659             if (!redirectUser.isEmpty() && connectionUser != redirectUser) {
0660                 QUrl dest = url;
0661                 dest.setUserName(redirectUser);
0662                 dest.setPassword(redirectPass);
0663                 redirection(dest);
0664                 commandList.clear();
0665                 commandCodes.clear();
0666                 isRunning = false;
0667                 redirectUser = "";
0668                 redirectPass = "";
0669                 return {-1, KIO::WorkerResult::fail()};
0670             }
0671             if (!connectionPassword.isEmpty()) {
0672                 myDebug(<< "sending cpass");
0673                 connectionAuth.password = connectionPassword + ENDLINE;
0674                 connectionPassword.clear();
0675                 // su does not like receiving a password directly after sending
0676                 // the password prompt so we wait a while.
0677                 if (local)
0678                     sleep(1);
0679                 writeChild(connectionAuth.password.toLatin1().constData(), connectionAuth.password.length());
0680             } else {
0681                 myDebug(<< "sending mpass");
0682                 connectionAuth.prompt = thisFn + buf;
0683                 if (local)
0684                     connectionAuth.caption = i18n("Local Login");
0685                 else
0686                     connectionAuth.caption = i18n("SSH Authentication");
0687                 if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) {
0688                     connectionAuth.password.clear(); // don't prefill
0689                     int errorCode = openPasswordDialog(connectionAuth);
0690                     if (errorCode != 0) {
0691                         shutdownConnection();
0692                         return {-1, error(errorCode, connectionHost)};
0693                     }
0694                 }
0695                 firstLogin = false;
0696                 connectionAuth.password += ENDLINE;
0697                 if (connectionAuth.username != connectionUser) {
0698                     QUrl dest = url;
0699                     dest.setUserName(connectionAuth.username);
0700                     dest.setPassword(connectionAuth.password);
0701                     redirection(dest);
0702                     if (isStat) { // FIXME: just a workaround for konq deficiencies
0703                         redirectUser = connectionAuth.username;
0704                         redirectPass = connectionAuth.password;
0705                     }
0706                     commandList.clear();
0707                     commandCodes.clear();
0708                     isRunning = false;
0709                     return {-1, KIO::WorkerResult::fail()};
0710                 }
0711                 myDebug(<< "sending pass");
0712                 if (local)
0713                     sleep(1);
0714                 writeChild(connectionAuth.password.toLatin1().constData(), connectionAuth.password.length());
0715             }
0716             thisFn.clear();
0717 #ifdef Q_OS_WIN
0718             return {buf.length(), KIO::WorkerResult::pass()};
0719         }
0720 #else
0721             return {0, KIO::WorkerResult::pass()};
0722         } else if (buf.endsWith('?')) {
0723             int rc = messageBox(QuestionTwoActions, thisFn + buf, QString(), i18nc("@action:button", "Yes"), i18nc("@action:button", "No"));
0724             if (rc == KIO::WorkerBase::PrimaryAction) {
0725                 writeChild("yes\n", 4);
0726             } else {
0727                 writeChild("no\n", 3);
0728             }
0729             thisFn.clear();
0730             return {0, KIO::WorkerResult::pass()};
0731         }
0732 #endif
0733         else {
0734             myDebug(<< "unmatched case in initial handling! should not happen!");
0735         }
0736 #ifdef Q_OS_WIN
0737         if (buf.endsWith(QLatin1String("(y/n)"))) {
0738             int rc = messageBox(QuestionTwoActions, thisFn + buf, QString(), i18nc("@action:button", "Yes"), i18nc("@action:button", "No"));
0739             if (rc == KIO::WorkerBase::PrimaryAction) {
0740                 writeChild("y\n", 2);
0741             } else {
0742                 writeChild("n\n", 2);
0743             }
0744             thisFn.clear();
0745             return {0, KIO::WorkerResult::pass()};
0746         }
0747 #endif
0748     }
0749     return {buf.length(), KIO::WorkerResult::pass()};
0750 }
0751 
0752 void fishProtocol::setHostInternal(const QUrl &u)
0753 {
0754     int port = u.port();
0755     if (port <= 0) // no port is -1 in QUrl, but in kde3 we used 0 and the KIO worker assume that.
0756         port = 0;
0757     setHost(u.host(), port, u.userName(), u.password());
0758 }
0759 
0760 /**
0761 sets connection information for subsequent commands
0762 */
0763 void fishProtocol::setHost(const QString &host, quint16 port, const QString &u, const QString &pass)
0764 {
0765     QString user(u);
0766 
0767     local = (host == "localhost" && port == 0);
0768     if (user.isEmpty())
0769         user = getenv("LOGNAME");
0770 
0771     if (host == connectionHost && port == connectionPort && user == connectionUser)
0772         return;
0773     myDebug(<< "setHost " << u << "@" << host);
0774 
0775     if (childPid)
0776         shutdownConnection();
0777 
0778     connectionHost = host;
0779     connectionAuth.url.setHost(host);
0780 
0781     connectionUser = user;
0782     connectionAuth.username = user;
0783     connectionAuth.url.setUserName(user);
0784 
0785     connectionPort = port;
0786     connectionPassword = pass;
0787     firstLogin = true;
0788 }
0789 
0790 /**
0791 Forced close of the connection
0792 
0793 This function gets called from the application side of the universe,
0794 it shouldn't send any response.
0795  */
0796 void fishProtocol::closeConnection()
0797 {
0798     myDebug(<< "closeConnection()");
0799     shutdownConnection(true);
0800 }
0801 
0802 /**
0803 Closes the connection
0804  */
0805 void fishProtocol::shutdownConnection(bool forced)
0806 {
0807     if (childPid) {
0808 #ifdef Q_OS_WIN
0809         childPid->terminate();
0810 #else
0811         int killStatus = kill(childPid, SIGTERM); // We may not have permission...
0812         if (killStatus == 0)
0813             waitpid(childPid, nullptr, 0);
0814 #endif
0815         childPid = 0;
0816 #ifndef Q_OS_WIN
0817         ::close(childFd); // ...in which case this should do the trick
0818         childFd = -1;
0819 #endif
0820         if (!forced) {
0821             infoMessage(i18n("Disconnected."));
0822         }
0823     }
0824     outBufPos = -1;
0825     outBuf = QByteArray();
0826     qlist.clear();
0827     commandList.clear();
0828     commandCodes.clear();
0829     isLoggedIn = false;
0830     writeReady = true;
0831     isRunning = false;
0832     rawRead = 0;
0833     rawWrite = -1;
0834     recvLen = -1;
0835     sendLen = -1;
0836 }
0837 /**
0838 builds each FISH request and sets the error counter
0839 */
0840 bool fishProtocol::sendCommand(fish_command_type cmd, ...)
0841 {
0842     const fish_info &info = fishInfo[cmd];
0843     myDebug(<< "queuing: cmd=" << cmd << "['" << info.command << "'](" << info.params << "), alt=['" << info.alt << "'], lines=" << info.lines);
0844 
0845     va_list list;
0846     va_start(list, cmd);
0847     QString realCmd = info.command;
0848     QString realAlt = info.alt;
0849     static const QRegularExpression rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
0850     for (int i = 0; i < info.params; i++) {
0851         QString arg(va_arg(list, const char *));
0852         int pos = -2;
0853         while ((pos = arg.indexOf(rx, pos + 2)) >= 0) {
0854             arg.replace(pos, 0, QString("\\"));
0855         }
0856         // myDebug( << "arg " << i << ": " << arg);
0857         realCmd.append(" ").append(arg);
0858         realAlt.replace(QRegularExpression(QLatin1Char('%') + QString::number(i + 1)), arg);
0859     }
0860     QString s("#");
0861     s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 200'\n");
0862     if (realCmd == "FISH")
0863         s.prepend(" ");
0864     commandList.append(s);
0865     commandCodes.append(cmd);
0866     va_end(list);
0867     return true;
0868 }
0869 
0870 /**
0871 checks response string for result code, converting 000 and 001 appropriately
0872 */
0873 int fishProtocol::handleResponse(const QString &str)
0874 {
0875     myDebug(<< "handling: " << str);
0876     if (str.startsWith(QLatin1String("### "))) {
0877         bool isOk = false;
0878         int result = str.mid(4, 3).toInt(&isOk);
0879         if (!isOk)
0880             result = 500;
0881         if (result == 0)
0882             result = (errorCount != 0 ? 500 : 200);
0883         if (result == 1)
0884             result = (errorCount != 0 ? 500 : 100);
0885         myDebug(<< "result: " << result << ", errorCount: " << errorCount);
0886         return result;
0887     } else {
0888         errorCount++;
0889         return 0;
0890     }
0891 }
0892 
0893 int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr)
0894 {
0895     QDateTime dt(QDateTime::currentDateTime().toUTC());
0896     int year = dt.date().year();
0897     int month = dt.date().month();
0898     int currentMonth = month;
0899     int day = dayStr.toInt();
0900 
0901     static const char *const monthNames[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
0902 
0903     for (int i = 0; i < 12; i++)
0904         if (monthStr.startsWith(monthNames[i])) {
0905             month = i + 1;
0906             break;
0907         }
0908 
0909     int pos = timeyearStr.indexOf(':');
0910     if (timeyearStr.length() == 4 && pos == -1) {
0911         year = timeyearStr.toInt();
0912     } else if (pos == -1) {
0913         return 0;
0914     } else {
0915         if (month > currentMonth + 1)
0916             year--;
0917         dt.setTime(QTime(timeyearStr.left(pos).toInt(), timeyearStr.mid(pos + 1).toInt(), 0));
0918     }
0919     dt.setDate(QDate(year, month, day));
0920 
0921     return dt.toSecsSinceEpoch();
0922 }
0923 
0924 /**
0925 parses response from server and acts accordingly
0926 */
0927 KIO::WorkerResult fishProtocol::manageConnection(const QString &l)
0928 {
0929     QString line(l);
0930     int rc = handleResponse(line);
0931     QDateTime dt;
0932     long pos, pos2, pos3;
0933     bool isOk = false;
0934     if (!rc) {
0935         switch (fishCommand) {
0936         case FISH_VER:
0937             if (line.startsWith(QLatin1String("VER 0.0.3"))) {
0938                 line.append(" ");
0939                 hasAppend = line.contains(" append ");
0940             } else {
0941                 shutdownConnection();
0942                 return error(ERR_UNSUPPORTED_PROTOCOL, line);
0943             }
0944             break;
0945         case FISH_PWD:
0946             url.setPath(line);
0947             redirection(url);
0948             break;
0949         case FISH_LIST:
0950             myDebug(<< "listReason: " << static_cast<int>(listReason));
0951         /* Fall through */
0952         case FISH_STAT:
0953             if (line.length() > 0) {
0954                 switch (line[0].cell()) {
0955                 case '0':
0956                 case '1':
0957                 case '2':
0958                 case '3':
0959                 case '4':
0960                 case '5':
0961                 case '6':
0962                 case '7':
0963                 case '8':
0964                 case '9': {
0965                     long long val = line.toLongLong(&isOk);
0966                     if (val > 0 && isOk)
0967                         errorCount--;
0968                     if ((fishCommand == FISH_LIST) && (listReason == LIST))
0969                         totalSize(val);
0970                 } break;
0971 
0972                 case 'P': {
0973                     errorCount--;
0974                     if (line[1] == 'd') {
0975                         udsMime = "inode/directory";
0976                         udsType = S_IFDIR;
0977                     } else {
0978                         if (line[1] == '-') {
0979                             udsType = S_IFREG;
0980                         } else if (line[1] == 'l') {
0981                             udsType = S_IFLNK;
0982                         } else if (line[1] == 'c') {
0983                             udsType = S_IFCHR;
0984                         } else if (line[1] == 'b') {
0985                             udsType = S_IFBLK;
0986                         } else if (line[1] == 's') {
0987                             udsType = S_IFSOCK;
0988                         } else if (line[1] == 'p') {
0989                             udsType = S_IFIFO;
0990                         } else {
0991                             myDebug(<< "unknown file type: " << line[1].cell());
0992                             errorCount++;
0993                             break;
0994                         }
0995                     }
0996                     // myDebug( << "file type: " << udsType);
0997 
0998                     long long accessVal = 0;
0999                     if (line[2] == 'r')
1000                         accessVal |= S_IRUSR;
1001                     if (line[3] == 'w')
1002                         accessVal |= S_IWUSR;
1003                     if (line[4] == 'x' || line[4] == 's')
1004                         accessVal |= S_IXUSR;
1005                     if (line[4] == 'S' || line[4] == 's')
1006                         accessVal |= S_ISUID;
1007                     if (line[5] == 'r')
1008                         accessVal |= S_IRGRP;
1009                     if (line[6] == 'w')
1010                         accessVal |= S_IWGRP;
1011                     if (line[7] == 'x' || line[7] == 's')
1012                         accessVal |= S_IXGRP;
1013                     if (line[7] == 'S' || line[7] == 's')
1014                         accessVal |= S_ISGID;
1015                     if (line[8] == 'r')
1016                         accessVal |= S_IROTH;
1017                     if (line[9] == 'w')
1018                         accessVal |= S_IWOTH;
1019                     if (line[10] == 'x' || line[10] == 't')
1020                         accessVal |= S_IXOTH;
1021                     if (line[10] == 'T' || line[10] == 't')
1022                         accessVal |= S_ISVTX;
1023                     udsEntry.replace(KIO::UDSEntry::UDS_ACCESS, accessVal);
1024 
1025                     pos = line.indexOf(':', 12);
1026                     if (pos < 0) {
1027                         errorCount++;
1028                         break;
1029                     }
1030                     udsEntry.replace(KIO::UDSEntry::UDS_USER, line.mid(12, pos - 12));
1031                     udsEntry.replace(KIO::UDSEntry::UDS_GROUP, line.mid(pos + 1));
1032                 } break;
1033 
1034                 case 'd':
1035                     pos = line.indexOf(' ');
1036                     pos2 = line.indexOf(' ', pos + 1);
1037                     if (pos < 0 || pos2 < 0)
1038                         break;
1039                     errorCount--;
1040                     udsEntry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME,
1041                                      makeTimeFromLs(line.mid(1, pos - 1), line.mid(pos + 1, pos2 - pos), line.mid(pos2 + 1)));
1042                     break;
1043 
1044                 case 'D':
1045                     pos = line.indexOf(' ');
1046                     pos2 = line.indexOf(' ', pos + 1);
1047                     pos3 = line.indexOf(' ', pos2 + 1);
1048                     if (pos < 0 || pos2 < 0 || pos3 < 0)
1049                         break;
1050                     dt.setDate(QDate(line.mid(1, pos - 1).toInt(), line.mid(pos + 1, pos2 - pos - 1).toInt(), line.mid(pos2 + 1, pos3 - pos2 - 1).toInt()));
1051                     pos = pos3;
1052                     pos2 = line.indexOf(' ', pos + 1);
1053                     pos3 = line.indexOf(' ', pos2 + 1);
1054                     if (pos < 0 || pos2 < 0 || pos3 < 0)
1055                         break;
1056                     dt.setTime(QTime(line.mid(pos + 1, pos2 - pos - 1).toInt(), line.mid(pos2 + 1, pos3 - pos2 - 1).toInt(), line.mid(pos3 + 1).toInt()));
1057                     errorCount--;
1058                     udsEntry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toSecsSinceEpoch());
1059                     break;
1060 
1061                 case 'S': {
1062                     long long sizeVal = line.mid(1).toLongLong(&isOk);
1063                     if (!isOk)
1064                         break;
1065                     errorCount--;
1066                     udsEntry.replace(KIO::UDSEntry::UDS_SIZE, sizeVal);
1067                 } break;
1068 
1069                 case 'E':
1070                     errorCount--;
1071                     break;
1072 
1073                 case ':':
1074                     pos = line.lastIndexOf('/');
1075                     thisFn = line.mid(pos < 0 ? 1 : pos + 1);
1076                     if (fishCommand == FISH_LIST) {
1077                         udsEntry.replace(KIO::UDSEntry::UDS_NAME, thisFn);
1078                     }
1079                     // By default, the mimetype comes from the extension
1080                     // We'll use the file(1) result only as fallback [like the rest of KDE does]
1081                     if (udsMime != "inode/directory") {
1082                         QUrl kurl("fish://host/" + thisFn);
1083                         QMimeDatabase db;
1084                         QMimeType mime = db.mimeTypeForUrl(kurl);
1085                         if (!mime.isDefault())
1086                             udsMime = mime.name();
1087                     }
1088                     errorCount--;
1089                     break;
1090 
1091                 case 'M':
1092                     // This is getting ugly. file(1) makes some uneducated
1093                     // guesses, so we must try to ignore them (#51274)
1094                     if (udsMime.isEmpty() && line.right(8) != "/unknown"
1095                         && (thisFn.indexOf('.') < 0 || (line.left(8) != "Mtext/x-" && line != "Mtext/plain"))) {
1096                         udsMime = line.mid(1);
1097                         if (udsMime == "inode/directory") // a symlink to a dir is a dir
1098                             udsType = S_IFDIR;
1099                     }
1100                     errorCount--;
1101                     break;
1102 
1103                 case 'L':
1104                     udsEntry.replace(KIO::UDSEntry::UDS_LINK_DEST, line.mid(1));
1105                     if (!udsType)
1106                         udsType = S_IFLNK;
1107                     errorCount--;
1108                     break;
1109                 }
1110             } else {
1111                 if (!udsMime.isNull())
1112                     udsEntry.replace(KIO::UDSEntry::UDS_MIME_TYPE, udsMime);
1113                 udsMime.clear();
1114 
1115                 udsEntry.replace(KIO::UDSEntry::UDS_FILE_TYPE, udsType);
1116                 udsType = 0;
1117 
1118                 if (fishCommand == FISH_STAT)
1119                     udsStatEntry = udsEntry;
1120                 else if (listReason == LIST) {
1121                     listEntry(udsEntry); // 1
1122                 } else if (listReason == CHECK)
1123                     checkExist = true; // 0
1124                 errorCount--;
1125                 udsEntry.clear();
1126             }
1127             break;
1128 
1129         case FISH_RETR:
1130             if (line.length() == 0) {
1131                 recvLen = 0;
1132                 return error(ERR_IS_DIRECTORY, url.toDisplayString());
1133             }
1134             recvLen = line.toLongLong(&isOk);
1135             if (!isOk) {
1136                 shutdownConnection();
1137                 return error(ERR_CANNOT_READ, url.toDisplayString());
1138             }
1139             break;
1140         default:
1141             break;
1142         }
1143 
1144     } else if (rc == 100) {
1145         switch (fishCommand) {
1146         case FISH_FISH:
1147             writeChild(fishCode, fishCodeLen);
1148             break;
1149         case FISH_READ:
1150             recvLen = 1024;
1151         /* fall through */
1152         case FISH_RETR:
1153             myDebug(<< "reading " << recvLen);
1154             if (recvLen == -1) {
1155                 shutdownConnection();
1156                 return error(ERR_CANNOT_READ, url.toDisplayString());
1157             }
1158 
1159             rawRead = recvLen;
1160             dataRead = 0;
1161             mimeTypeSent = false;
1162             if (recvLen == 0) {
1163                 mimeType("application/x-zerosize");
1164                 mimeTypeSent = true;
1165             }
1166             break;
1167         case FISH_STOR:
1168         case FISH_WRITE:
1169         case FISH_APPEND:
1170             rawWrite = sendLen;
1171             // myDebug( << "sending " << sendLen);
1172             writeChild(nullptr, 0);
1173             break;
1174         default:
1175             break;
1176         }
1177     } else if (rc / 100 != 2) {
1178         switch (fishCommand) {
1179         case FISH_STOR:
1180         case FISH_WRITE:
1181         case FISH_APPEND:
1182             shutdownConnection();
1183             return error(ERR_CANNOT_WRITE, url.toDisplayString());
1184         case FISH_RETR:
1185             shutdownConnection();
1186             return error(ERR_CANNOT_READ, url.toDisplayString());
1187         case FISH_READ:
1188             if (rc == 501) {
1189                 mimeType("inode/directory");
1190                 mimeTypeSent = true;
1191                 recvLen = 0;
1192                 finished();
1193             } else {
1194                 shutdownConnection();
1195                 return error(ERR_CANNOT_READ, url.toDisplayString());
1196             }
1197             break;
1198         case FISH_FISH:
1199         case FISH_VER:
1200             shutdownConnection();
1201             return error(ERR_WORKER_DEFINED, line);
1202         case FISH_PWD:
1203         case FISH_CWD:
1204             return error(ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString());
1205         case FISH_LIST:
1206             myDebug(<< "list error. reason: " << static_cast<int>(listReason));
1207             if (listReason == LIST)
1208                 return error(ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString());
1209             if (listReason == CHECK) {
1210                 checkExist = false;
1211                 finished();
1212             }
1213             break;
1214         case FISH_STAT:
1215             udsStatEntry.clear();
1216             return error(ERR_DOES_NOT_EXIST, url.toDisplayString());
1217         case FISH_CHMOD:
1218             return error(ERR_CANNOT_CHMOD, url.toDisplayString());
1219         case FISH_CHOWN:
1220         case FISH_CHGRP:
1221             return error(ERR_ACCESS_DENIED, url.toDisplayString());
1222         case FISH_MKD:
1223             if (rc == 501)
1224                 return error(ERR_DIR_ALREADY_EXIST, url.toDisplayString());
1225             return error(ERR_CANNOT_MKDIR, url.toDisplayString());
1226         case FISH_RMD:
1227             return error(ERR_CANNOT_RMDIR, url.toDisplayString());
1228         case FISH_DELE:
1229             return error(ERR_CANNOT_DELETE, url.toDisplayString());
1230         case FISH_RENAME:
1231             return error(ERR_CANNOT_RENAME, url.toDisplayString());
1232         case FISH_COPY:
1233         case FISH_LINK:
1234         case FISH_SYMLINK:
1235             return error(ERR_CANNOT_WRITE, url.toDisplayString());
1236         default:
1237             break;
1238         }
1239     } else {
1240         if (fishCommand == FISH_STOR)
1241             fishCommand = (hasAppend ? FISH_APPEND : FISH_WRITE);
1242         if (fishCommand == FISH_LIST) {
1243             if (listReason == CHECK && !checkOverwrite && checkExist) {
1244                 return error(ERR_FILE_ALREADY_EXIST, url.toDisplayString());
1245             }
1246         } else if (fishCommand == FISH_STAT) {
1247             udsStatEntry.replace(KIO::UDSEntry::UDS_NAME, url.fileName());
1248             statEntry(udsStatEntry);
1249         } else if (fishCommand == FISH_APPEND) {
1250             dataReq();
1251             if (readData(rawData) > 0)
1252                 sendCommand(FISH_APPEND, E(QString::number(rawData.size())), E(url.path()));
1253             else if (!checkExist && putPerm > -1)
1254                 sendCommand(FISH_CHMOD, E(QString::number(putPerm, 8)), E(url.path()));
1255             sendLen = rawData.size();
1256         } else if (fishCommand == FISH_WRITE) {
1257             dataReq();
1258             if (readData(rawData) > 0)
1259                 sendCommand(FISH_WRITE, E(QString::number(putPos)), E(QString::number(rawData.size())), E(url.path()));
1260             else if (!checkExist && putPerm > -1)
1261                 sendCommand(FISH_CHMOD, E(QString::number(putPerm, 8)), E(url.path()));
1262             putPos += rawData.size();
1263             sendLen = rawData.size();
1264         } else if (fishCommand == FISH_RETR) {
1265             data(QByteArray());
1266         }
1267         finished();
1268     }
1269     return KIO::WorkerResult::pass();
1270 }
1271 
1272 void fishProtocol::writeStdin(const QString &line)
1273 {
1274     qlist.append(E(line));
1275 
1276     if (writeReady) {
1277         writeReady = false;
1278         // myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')));
1279         myDebug(<< "Writing: " << qlist.first());
1280         myDebug(<< "---------");
1281         writeChild((const char *)qlist.first().constData(), qlist.first().length());
1282     }
1283 }
1284 
1285 void fishProtocol::sent()
1286 {
1287     if (rawWrite > 0) {
1288         myDebug(<< "writing raw: " << rawData.size() << "/" << rawWrite);
1289         writeChild(rawData.data(), (rawWrite > rawData.size() ? rawData.size() : rawWrite));
1290         rawWrite -= rawData.size();
1291         if (rawWrite > 0) {
1292             dataReq();
1293             if (readData(rawData) <= 0) {
1294                 shutdownConnection();
1295             }
1296         }
1297         return;
1298     } else if (rawWrite == 0) {
1299         // workaround: some dd's insist in reading multiples of
1300         // 8 bytes, swallowing up to seven bytes. Sending
1301         // newlines is safe even when a sane dd is used
1302         writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", 15);
1303         rawWrite = -1;
1304         return;
1305     }
1306     if (qlist.count() > 0)
1307         qlist.erase(qlist.begin());
1308     if (qlist.count() == 0) {
1309         writeReady = true;
1310     } else {
1311         // myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')));
1312         myDebug(<< "Writing: " << qlist.first());
1313         myDebug(<< "---------");
1314         writeChild((const char *)qlist.first().constData(), qlist.first().length());
1315     }
1316 }
1317 
1318 ReceivedResult fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen)
1319 {
1320     int pos = 0;
1321     do {
1322         if (buflen <= 0)
1323             break;
1324 
1325         if (rawRead > 0) {
1326             myDebug(<< "processedSize " << dataRead << ", len " << buflen << "/" << rawRead);
1327             int dataSize = (rawRead > buflen ? buflen : rawRead);
1328             if (!mimeTypeSent) {
1329                 int mimeSize = qMin(dataSize, (int)(mimeBuffer.size() - dataRead));
1330                 memcpy(mimeBuffer.data() + dataRead, buffer, mimeSize);
1331                 dataRead += mimeSize;
1332                 rawRead -= mimeSize;
1333                 buffer += mimeSize;
1334                 buflen -= mimeSize;
1335                 if (rawRead == 0) // End of data
1336                     mimeBuffer.resize(dataRead);
1337                 if (dataRead < (int)mimeBuffer.size()) {
1338                     myDebug(<< "wait for more");
1339                     break;
1340                 }
1341 
1342                 QMimeDatabase db;
1343                 sendmimeType(db.mimeTypeForFileNameAndData(url.path(), mimeBuffer).name());
1344                 mimeTypeSent = true;
1345                 if (fishCommand != FISH_READ) {
1346                     totalSize(dataRead + rawRead);
1347                     data(mimeBuffer);
1348                     processedSize(dataRead);
1349                 }
1350                 mimeBuffer.resize(1024);
1351                 pos = 0;
1352                 continue; // Process rest of buffer/buflen
1353             }
1354 
1355             QByteArray bdata(buffer, dataSize);
1356             data(bdata);
1357 
1358             dataRead += dataSize;
1359             rawRead -= dataSize;
1360             processedSize(dataRead);
1361             if (rawRead <= 0) {
1362                 buffer += dataSize;
1363                 buflen -= dataSize;
1364             } else {
1365                 return {0, KIO::WorkerResult::pass()};
1366                 ;
1367             }
1368         }
1369 
1370         if (buflen <= 0)
1371             break;
1372 
1373         pos = 0;
1374         // Find newline
1375         while ((pos < buflen) && (buffer[pos] != '\n'))
1376             ++pos;
1377 
1378         if (pos < buflen) {
1379             QString s = remoteEncoding()->decode(QByteArray(buffer, pos));
1380 
1381             buffer += pos + 1;
1382             buflen -= pos + 1;
1383 
1384             const auto result = manageConnection(s);
1385             if (!result.success()) {
1386                 return {buflen, result};
1387             }
1388 
1389             pos = 0;
1390             // Find next newline
1391             while ((pos < buflen) && (buffer[pos] != '\n'))
1392                 ++pos;
1393         }
1394     } while (childPid && buflen && (rawRead > 0 || pos < buflen));
1395     return {buflen, KIO::WorkerResult::pass()};
1396 }
1397 /** get a file */
1398 KIO::WorkerResult fishProtocol::get(const QUrl &u)
1399 {
1400     myDebug(<< "@@@@@@@@@ get " << u);
1401     setHostInternal(u);
1402     url = u;
1403     if (const auto result = openConnection(); !result.success()) {
1404         return result;
1405     }
1406     url = url.adjusted(QUrl::NormalizePathSegments);
1407     if (url.path().isEmpty()) {
1408         sendCommand(FISH_PWD);
1409     } else {
1410         recvLen = -1;
1411         sendCommand(FISH_RETR, E(url.path()));
1412     }
1413     return run();
1414 }
1415 
1416 /** put a file */
1417 KIO::WorkerResult fishProtocol::put(const QUrl &u, int permissions, KIO::JobFlags flags)
1418 {
1419     myDebug(<< "@@@@@@@@@ put " << u << " " << permissions << " " << (flags & KIO::Overwrite) << " " /* << resume */);
1420     setHostInternal(u);
1421 
1422     url = u;
1423     if (const auto result = openConnection(); !result.success()) {
1424         return result;
1425     }
1426     url = url.adjusted(QUrl::NormalizePathSegments);
1427     if (url.path().isEmpty()) {
1428         sendCommand(FISH_PWD);
1429     } else {
1430         putPerm = permissions;
1431 
1432         checkOverwrite = flags & KIO::Overwrite;
1433         checkExist = false;
1434         putPos = 0;
1435         listReason = CHECK;
1436         sendCommand(FISH_LIST, E(url.path()));
1437         sendCommand(FISH_STOR, "0", E(url.path()));
1438 
1439         const QString mtimeStr = metaData("modified");
1440         if (!mtimeStr.isEmpty()) {
1441             QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
1442             // TODO set modification time on url.path() somehow
1443             // see FileProtocol::put if using utime() to do that.
1444         }
1445     }
1446     return run();
1447 }
1448 /** executes next command in sequence or set isRunning false if all is done */
1449 void fishProtocol::finished()
1450 {
1451     if (commandList.count() > 0) {
1452         fishCommand = (fish_command_type)commandCodes.first();
1453         errorCount = -fishInfo[fishCommand].lines;
1454         rawRead = 0;
1455         rawWrite = -1;
1456         udsEntry.clear();
1457         udsStatEntry.clear();
1458         writeStdin(commandList.first());
1459         // if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending
1460         // "+(commandList.first().mid(1,commandList.first().indexOf("\n")-1))+"...");
1461         commandList.erase(commandList.begin());
1462         commandCodes.erase(commandCodes.begin());
1463     } else {
1464         isRunning = false;
1465     }
1466 }
1467 
1468 /** aborts command sequence and calls error() */
1469 KIO::WorkerResult fishProtocol::error(int type, const QString &detail)
1470 {
1471     commandList.clear();
1472     commandCodes.clear();
1473     myDebug(<< "ERROR: " << type << " - " << detail);
1474     isRunning = false;
1475     return KIO::WorkerResult::fail(type, detail);
1476 }
1477 
1478 /** executes a chain of commands */
1479 KIO::WorkerResult fishProtocol::run()
1480 /* This function writes to childFd fish commands (like #STOR 0 /tmp/test ...) that are stored in outBuf
1481 and reads from childFd the remote host's response. ChildFd is the fd to a process that communicates
1482 with .fishsrv.pl typically running on another computer. */
1483 {
1484     if (isRunning) {
1485         // should have not been running, something is wrong
1486         return KIO::WorkerResult::fail();
1487     }
1488 
1489     int rc;
1490     isRunning = true;
1491     finished();
1492 #ifndef Q_OS_WIN
1493     fd_set rfds, wfds;
1494     FD_ZERO(&rfds);
1495 #endif
1496     char buf[32768];
1497     int offset = 0;
1498     while (isRunning) {
1499 #ifndef Q_OS_WIN
1500         FD_SET(childFd, &rfds);
1501         FD_ZERO(&wfds);
1502         if (outBufPos >= 0)
1503             FD_SET(childFd, &wfds);
1504         struct timeval timeout;
1505         timeout.tv_sec = 0;
1506         timeout.tv_usec = 1000;
1507         rc = select(childFd + 1, &rfds, &wfds, nullptr, &timeout);
1508         if (rc < 0) {
1509             if (errno == EINTR)
1510                 continue;
1511             myDebug(<< "select failed, rc: " << rc << ", error: " << strerror(errno));
1512             shutdownConnection();
1513             return error(ERR_CONNECTION_BROKEN, connectionHost);
1514         }
1515         // We first write the complete buffer, including all newlines.
1516         // Do: send command and newlines, expect response then
1517         // Do not: send commands, expect response, send newlines, expect response on newlines
1518         // Newlines do not trigger a response.
1519         if (FD_ISSET(childFd, &wfds) && outBufPos >= 0) {
1520             if (outBuf.length() - outBufPos > 0) {
1521                 rc = ::write(childFd, outBuf.constData() + outBufPos, outBuf.length() - outBufPos);
1522             }
1523 #else
1524         if (outBufPos >= 0) {
1525             if (outBuf.length() - outBufPos > 0) {
1526                 rc = childPid->write(outBuf);
1527             }
1528 #endif
1529             else
1530                 rc = 0;
1531 
1532             if (rc >= 0)
1533                 outBufPos += rc;
1534             else {
1535 #ifndef Q_OS_WIN
1536                 if (errno == EINTR)
1537                     continue;
1538                 myDebug(<< "write failed, rc: " << rc << ", error: " << strerror(errno));
1539 #else
1540                 myDebug(<< "write failed, rc: " << rc);
1541 #endif
1542                 shutdownConnection();
1543                 return error(ERR_CONNECTION_BROKEN, connectionHost);
1544             }
1545             if (outBufPos >= outBuf.length()) {
1546                 outBufPos = -1;
1547                 outBuf = QByteArray();
1548                 sent();
1549             }
1550         }
1551 #ifndef Q_OS_WIN
1552         else if (FD_ISSET(childFd, &rfds)) {
1553             rc = ::read(childFd, buf + offset, sizeof(buf) - offset);
1554 #else
1555         else if (childPid->waitForReadyRead(1000)) {
1556             rc = childPid->read(buf + offset, sizeof(buf) - offset);
1557 #endif
1558             // myDebug( << "read " << rc << " bytes");
1559             if (rc <= 0) {
1560 #ifndef Q_OS_WIN
1561                 if (errno == EINTR)
1562                     continue;
1563                 myDebug(<< "read failed, rc: " << rc << ", error: " << strerror(errno));
1564 #else
1565                 myDebug(<< "read failed, rc: " << rc);
1566 #endif
1567                 shutdownConnection();
1568                 return error(ERR_CONNECTION_BROKEN, connectionHost);
1569             }
1570 
1571             const auto receivedResult = received(buf, rc + offset);
1572             if (!receivedResult.result.success()) {
1573                 return receivedResult.result;
1574             }
1575             const int noff = receivedResult.remainingBufferSize;
1576             if (noff > 0)
1577                 memmove(buf, buf + offset + rc - noff, noff);
1578             // myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset));
1579             offset = noff;
1580         }
1581         if (wasKilled()) {
1582             return KIO::WorkerResult::fail();
1583         }
1584     }
1585 
1586     return KIO::WorkerResult::pass();
1587 }
1588 
1589 /** stat a file */
1590 KIO::WorkerResult fishProtocol::stat(const QUrl &u)
1591 {
1592     myDebug(<< "@@@@@@@@@ stat " << u);
1593     setHostInternal(u);
1594     url = u;
1595     isStat = true; // FIXME: just a workaround for konq deficiencies
1596     const auto openConnectionResult = openConnection();
1597     isStat = false; // FIXME: just a workaround for konq deficiencies
1598     if (!openConnectionResult.success()) {
1599         return openConnectionResult;
1600     }
1601     url = url.adjusted(QUrl::NormalizePathSegments);
1602     if (url.path().isEmpty()) {
1603         sendCommand(FISH_PWD);
1604     } else {
1605         sendCommand(FISH_STAT, E(url.adjusted(QUrl::StripTrailingSlash).path()));
1606     }
1607     return run();
1608 }
1609 /** find mimetype for a file */
1610 KIO::WorkerResult fishProtocol::mimetype(const QUrl &u)
1611 {
1612     myDebug(<< "@@@@@@@@@ mimetype " << u);
1613     setHostInternal(u);
1614     url = u;
1615     if (const auto result = openConnection(); !result.success()) {
1616         return result;
1617     }
1618     url = url.adjusted(QUrl::NormalizePathSegments);
1619     if (url.path().isEmpty()) {
1620         sendCommand(FISH_PWD);
1621     } else {
1622         recvLen = 1024;
1623         sendCommand(FISH_READ, "0", "1024", E(url.path()));
1624     }
1625     return run();
1626 }
1627 /** list a directory */
1628 KIO::WorkerResult fishProtocol::listDir(const QUrl &u)
1629 {
1630     myDebug(<< "@@@@@@@@@ listDir " << u);
1631     setHostInternal(u);
1632     url = u;
1633     if (const auto result = openConnection(); !result.success()) {
1634         return result;
1635     }
1636     url = url.adjusted(QUrl::NormalizePathSegments);
1637     if (url.path().isEmpty()) {
1638         sendCommand(FISH_PWD);
1639     } else {
1640         listReason = LIST;
1641         sendCommand(FISH_LIST, E(url.path()));
1642     }
1643     return run();
1644 }
1645 /** create a directory */
1646 KIO::WorkerResult fishProtocol::mkdir(const QUrl &u, int permissions)
1647 {
1648     myDebug(<< "@@@@@@@@@ mkdir " << u << " " << permissions);
1649     setHostInternal(u);
1650     url = u;
1651     if (const auto result = openConnection(); !result.success()) {
1652         return result;
1653     }
1654     url = url.adjusted(QUrl::NormalizePathSegments);
1655     if (url.path().isEmpty()) {
1656         sendCommand(FISH_PWD);
1657     } else {
1658         sendCommand(FISH_MKD, E(url.path()));
1659         if (permissions > -1)
1660             sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path()));
1661     }
1662     return run();
1663 }
1664 /** rename a file */
1665 KIO::WorkerResult fishProtocol::rename(const QUrl &s, const QUrl &d, KIO::JobFlags flags)
1666 {
1667     myDebug(<< "@@@@@@@@@ rename " << s << " " << d << " " << (flags & KIO::Overwrite));
1668     if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) {
1669         return error(ERR_UNSUPPORTED_ACTION, s.toDisplayString());
1670     }
1671     setHostInternal(s);
1672     url = d;
1673     if (const auto result = openConnection(); !result.success()) {
1674         return result;
1675     }
1676     QUrl src = s;
1677     url = url.adjusted(QUrl::NormalizePathSegments);
1678     src = src.adjusted(QUrl::NormalizePathSegments);
1679     if (url.path().isEmpty()) {
1680         sendCommand(FISH_PWD);
1681     } else {
1682         if (!(flags & KIO::Overwrite)) {
1683             listReason = CHECK;
1684             checkOverwrite = false;
1685             sendCommand(FISH_LIST, E(url.path()));
1686         }
1687         sendCommand(FISH_RENAME, E(src.path()), E(url.path()));
1688     }
1689     return run();
1690 }
1691 /** create a symlink */
1692 KIO::WorkerResult fishProtocol::symlink(const QString &target, const QUrl &u, KIO::JobFlags flags)
1693 {
1694     myDebug(<< "@@@@@@@@@ symlink " << target << " " << u << " " << (flags & KIO::Overwrite));
1695     setHostInternal(u);
1696     url = u;
1697     if (const auto result = openConnection(); !result.success()) {
1698         return result;
1699     }
1700     url = url.adjusted(QUrl::NormalizePathSegments);
1701     if (url.path().isEmpty()) {
1702         sendCommand(FISH_PWD);
1703     } else {
1704         if (!(flags & KIO::Overwrite)) {
1705             listReason = CHECK;
1706             checkOverwrite = false;
1707             sendCommand(FISH_LIST, E(url.path()));
1708         }
1709         sendCommand(FISH_SYMLINK, E(target), E(url.path()));
1710     }
1711     return run();
1712 }
1713 /** change file permissions */
1714 KIO::WorkerResult fishProtocol::chmod(const QUrl &u, int permissions)
1715 {
1716     myDebug(<< "@@@@@@@@@ chmod " << u << " " << permissions);
1717     setHostInternal(u);
1718     url = u;
1719     if (const auto result = openConnection(); !result.success()) {
1720         return result;
1721     }
1722     url = url.adjusted(QUrl::NormalizePathSegments);
1723     if (url.path().isEmpty()) {
1724         sendCommand(FISH_PWD);
1725     } else {
1726         // TODO: no error?
1727         if (permissions < 0) {
1728             return KIO::WorkerResult::pass();
1729         }
1730         sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path()));
1731     }
1732     return run();
1733 }
1734 /** copies a file */
1735 KIO::WorkerResult fishProtocol::copy(const QUrl &s, const QUrl &d, int permissions, KIO::JobFlags flags)
1736 {
1737     myDebug(<< "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << (flags & KIO::Overwrite));
1738     if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) {
1739         return error(ERR_UNSUPPORTED_ACTION, s.toDisplayString());
1740     }
1741     // myDebug( << s << endl << d);
1742     setHostInternal(s);
1743     url = d;
1744     if (const auto result = openConnection(); !result.success()) {
1745         return result;
1746     }
1747     QUrl src = s;
1748     url = url.adjusted(QUrl::NormalizePathSegments);
1749     src = src.adjusted(QUrl::NormalizePathSegments);
1750     if (src.path().isEmpty()) {
1751         sendCommand(FISH_PWD);
1752     } else {
1753         if (!(flags & KIO::Overwrite)) {
1754             listReason = CHECK;
1755             checkOverwrite = false;
1756             sendCommand(FISH_LIST, E(url.path()));
1757         }
1758         sendCommand(FISH_COPY, E(src.path()), E(url.path()));
1759         if (permissions > -1)
1760             sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path()));
1761     }
1762     return run();
1763 }
1764 /** removes a file or directory */
1765 KIO::WorkerResult fishProtocol::del(const QUrl &u, bool isFile)
1766 {
1767     myDebug(<< "@@@@@@@@@ del " << u << " " << isFile);
1768     setHostInternal(u);
1769     url = u;
1770     if (const auto result = openConnection(); !result.success()) {
1771         return result;
1772     }
1773     url = url.adjusted(QUrl::NormalizePathSegments);
1774     if (url.path().isEmpty()) {
1775         sendCommand(FISH_PWD);
1776     } else {
1777         sendCommand((isFile ? FISH_DELE : FISH_RMD), E(url.path()));
1778     }
1779     return run();
1780 }
1781 /** special like background execute */
1782 KIO::WorkerResult fishProtocol::special(const QByteArray &data)
1783 {
1784     int tmp;
1785 
1786     QDataStream stream(data);
1787 
1788     stream >> tmp;
1789     switch (tmp) {
1790     case FISH_EXEC_CMD: // SSH EXEC
1791     {
1792         QUrl u;
1793         QString command;
1794         stream >> u;
1795         stream >> command;
1796         myDebug(<< "@@@@@@@@@ exec " << u << " " << command);
1797         setHostInternal(u);
1798         url = u;
1799         if (const auto result = openConnection(); !result.success()) {
1800             return result;
1801         }
1802         sendCommand(FISH_EXEC, E(command), E(url.path()));
1803         return run();
1804     }
1805     default:
1806         // Some command we don't understand.
1807         return error(ERR_UNSUPPORTED_ACTION, QString().setNum(tmp));
1808     }
1809 }
1810 /** report status */
1811 void fishProtocol::worker_status()
1812 {
1813     myDebug(<< "@@@@@@@@@ worker_status");
1814     if (childPid > 0)
1815         workerStatus(connectionHost, isLoggedIn);
1816     else
1817         workerStatus(QString(), false);
1818 }
1819 
1820 #include "fish.moc"