summaryrefslogtreecommitdiffstats
path: root/kioslave/fish/fish.cpp
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit4aed2c8219774f5d797760606b8489a92ddc5163 (patch)
tree3f8c130f7d269626bf6a9447407ef6c35954426a /kioslave/fish/fish.cpp
downloadtdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz
tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kioslave/fish/fish.cpp')
-rw-r--r--kioslave/fish/fish.cpp1661
1 files changed, 1661 insertions, 0 deletions
diff --git a/kioslave/fish/fish.cpp b/kioslave/fish/fish.cpp
new file mode 100644
index 000000000..3967bcd6b
--- /dev/null
+++ b/kioslave/fish/fish.cpp
@@ -0,0 +1,1661 @@
+/***************************************************************************
+ fish.cpp - a FISH kioslave
+ -------------------
+ begin : Thu Oct 4 17:09:14 CEST 2001
+ copyright : (C) 2001-2003 by J�rg Walter
+ email : jwalt-kde@garni.ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License *
+ * *
+ ***************************************************************************/
+
+/*
+ This code contains fragments and ideas from the ftp kioslave
+ done by David Faure <faure@kde.org>.
+
+ Structure is a bit complicated, since I made the mistake to use
+ KProcess... now there is a lightweight homebrew async IO system
+ inside, but if signals/slots become available for ioslaves, switching
+ back to KProcess should be easy.
+*/
+
+#include "config.h"
+
+#include <qcstring.h>
+#include <qfile.h>
+#include <qsocket.h>
+#include <qdatetime.h>
+#include <qbitarray.h>
+#include <qregexp.h>
+
+#include <stdlib.h>
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#include <math.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/types.h>
+#ifdef HAVE_STROPTS
+#include <stropts.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kinstance.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kremoteencoding.h>
+#include <kurl.h>
+#include <ksock.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <kmimetype.h>
+#include <kmimemagic.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "fish.h"
+#include "fishcode.h"
+
+#ifndef NDEBUG
+#define myDebug(x) kdDebug(7127) << __LINE__ << ": " x
+#define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0)
+#define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0)
+#define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0)
+#define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0)
+#define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.size()<<")" << endl); statEntry(x);}while(0)
+#define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0)
+#define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0)
+#define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0)
+#define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0)
+#define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0)
+#define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0)
+#define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0)
+#define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0)
+#define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0)
+#define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0)
+#else
+#define myDebug(x)
+#define sendmimeType(x) mimeType(x)
+#endif
+
+static char *sshPath = NULL;
+static char *suPath = NULL;
+// disabled: currently not needed. Didn't work reliably.
+// static int isOpenSSH = 0;
+
+static int isNXFish = 0;
+
+#define E(x) ((const char*)remoteEncoding()->encode(x).data())
+
+using namespace KIO;
+extern "C" {
+
+static void ripper(int)
+{
+ while (waitpid(-1,0,WNOHANG) > 0) {
+ // do nothing, go on
+ }
+}
+
+int KDE_EXPORT kdemain( int argc, char **argv )
+{
+ KLocale::setMainCatalogue("kio_fish");
+ KInstance instance("fish");
+
+ myDebug( << "*** Starting fish " << endl);
+ if (argc != 4) {
+ myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl);
+ exit(-1);
+ }
+
+ setenv("TZ", "UTC", true);
+
+ struct sigaction act;
+ memset(&act,0,sizeof(act));
+ act.sa_handler = ripper;
+ act.sa_flags = 0
+#ifdef SA_NOCLDSTOP
+ | SA_NOCLDSTOP
+#endif
+#ifdef SA_RESTART
+ | SA_RESTART
+#endif
+ ;
+ sigaction(SIGCHLD,&act,NULL);
+
+ if (qstrcmp(argv[1],"nxfish")==0) {
+ // Set NXFish - Mode
+ isNXFish=1;
+ }
+
+ fishProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ myDebug( << "*** fish Done" << endl);
+ return 0;
+}
+
+}
+
+const struct fishProtocol::fish_info fishProtocol::fishInfo[] = {
+ { ("FISH"), 0,
+ ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"),
+ 1 },
+ { ("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 },
+ { ("PWD"), 0,
+ ("pwd"),
+ 1 },
+ { ("LIST"), 1,
+ ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; 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,/.*[ \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 echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
+ "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; )"),
+ 0 },
+ { ("STAT"), 1,
+ ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; 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,/.*[ \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 echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
+ "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; )"),
+ 0 },
+ { ("RETR"), 1,
+ ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"),
+ 1 },
+ { ("STOR"), 2,
+ ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;"
+ "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"),
+ 0 },
+ { ("CWD"), 1,
+ ("cd %1"),
+ 0 },
+ { ("CHMOD"), 2,
+ ("chmod %1 %2"),
+ 0 },
+ { ("DELE"), 1,
+ ("rm -f %1"),
+ 0 },
+ { ("MKD"), 1,
+ ("mkdir %1"),
+ 0 },
+ { ("RMD"), 1,
+ ("rmdir %1"),
+ 0 },
+ { ("RENAME"), 2,
+ ("mv -f %1 %2"),
+ 0 },
+ { ("LINK"), 2,
+ ("ln -f %1 %2"),
+ 0 },
+ { ("SYMLINK"), 2,
+ ("ln -sf %1 %2"),
+ 0 },
+ { ("CHOWN"), 2,
+ ("chown %1 %2"),
+ 0 },
+ { ("CHGRP"), 2,
+ ("chgrp %1 %2"),
+ 0 },
+ { ("READ"), 3,
+ ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;"
+ "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;"
+ "dd bs=%2 count=1; ) 2>/dev/null;"),
+ 0 },
+ // Yes, this is "ibs=1", since dd "count" is input blocks.
+ // On network connections, read() may not fill the buffer
+ // completely (no more data immediately available), but dd
+ // does ignore that fact by design. Sorry, writes are slow.
+ // OTOH, WRITE is not used by the current ioslave methods,
+ // we use APPEND.
+ { ("WRITE"), 3,
+ (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | "
+ "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"),
+ 0 },
+ { ("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 },
+ { ("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 },
+ { ("EXEC"), 2,
+ ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"),
+ 0 }
+};
+
+fishProtocol::fishProtocol(const QCString &pool_socket, const QCString &app_socket)
+ : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024),
+ mimeTypeSent(false)
+{
+ myDebug( << "fishProtocol::fishProtocol()" << endl);
+ if (sshPath == NULL) {
+ // disabled: currently not needed. Didn't work reliably.
+ // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null");
+ if (isNXFish)
+ sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("nxfish")));
+ else
+ sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("ssh")));
+ }
+ if (suPath == NULL) {
+ suPath = strdup(QFile::encodeName(KStandardDirs::findExe("su")));
+ }
+ childPid = 0;
+ connectionPort = 0;
+ isLoggedIn = false;
+ writeReady = true;
+ isRunning = false;
+ firstLogin = true;
+ errorCount = 0;
+ rawRead = 0;
+ rawWrite = -1;
+ recvLen = -1;
+ sendLen = -1;
+ setMultipleAuthCaching( true );
+ connectionAuth.keepPassword = true;
+ connectionAuth.url.setProtocol("fish");
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ typeAtom.m_uds = UDS_FILE_TYPE;
+ typeAtom.m_long = 0;
+ mimeAtom.m_uds = UDS_MIME_TYPE;
+ mimeAtom.m_long = 0;
+ mimeAtom.m_str = QString::null;
+
+ hasAppend = false;
+
+ isStat = false; // FIXME: just a workaround for konq deficiencies
+ redirectUser = ""; // FIXME: just a workaround for konq deficiencies
+ redirectPass = ""; // FIXME: just a workaround for konq deficiencies
+ fishCodeLen = strlen(fishCode);
+}
+/* ---------------------------------------------------------------------------------- */
+
+
+fishProtocol::~fishProtocol()
+{
+ myDebug( << "fishProtocol::~fishProtocol()" << endl);
+ shutdownConnection(true);
+}
+
+/* --------------------------------------------------------------------------- */
+
+/**
+Connects to a server and logs us in via SSH. Then starts FISH protocol.
+*/
+void fishProtocol::openConnection() {
+ if (childPid) return;
+
+ if (connectionHost.isEmpty() && !isNXFish)
+ {
+ error( KIO::ERR_UNKNOWN_HOST, QString::null );
+ return;
+ }
+
+ infoMessage(i18n("Connecting..."));
+
+ myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort << endl);
+ sendCommand(FISH_FISH);
+ sendCommand(FISH_VER);
+ if (connectionStart()) {
+ error(ERR_COULD_NOT_CONNECT,connectionHost);
+ shutdownConnection();
+ return;
+ };
+ myDebug( << "subprocess is running" << endl);
+}
+
+static int open_pty_pair(int fd[2])
+{
+#if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY)
+/** with kind regards to The GNU C Library
+Reference Manual for Version 2.2.x of the GNU C Library */
+ int master, slave;
+ char *name;
+ struct ::termios ti;
+ memset(&ti,0,sizeof(ti));
+
+ ti.c_cflag = CLOCAL|CREAD|CS8;
+ ti.c_cc[VMIN] = 1;
+
+#ifdef HAVE_GETPT
+ master = getpt();
+#else
+ master = open("/dev/ptmx", O_RDWR);
+#endif
+ if (master < 0) return 0;
+
+ if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master;
+
+ name = ptsname(master);
+ if (name == NULL) goto close_master;
+
+ slave = open(name, O_RDWR);
+ if (slave == -1) goto close_master;
+
+#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
+ if (isastream(slave) &&
+ (ioctl(slave, I_PUSH, "ptem") < 0 ||
+ ioctl(slave, I_PUSH, "ldterm") < 0))
+ goto close_slave;
+#endif
+
+ tcsetattr(slave, TCSANOW, &ti);
+ fd[0] = master;
+ fd[1] = slave;
+ return 0;
+
+#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
+close_slave:
+#endif
+ close(slave);
+
+close_master:
+ close(master);
+ return -1;
+#else
+#ifdef HAVE_OPENPTY
+ struct ::termios ti;
+ memset(&ti,0,sizeof(ti));
+
+ ti.c_cflag = CLOCAL|CREAD|CS8;
+ ti.c_cc[VMIN] = 1;
+
+ return openpty(fd,fd+1,NULL,&ti,NULL);
+#else
+#ifdef __GNUC__
+#warning "No tty support available. Password dialog won't work."
+#endif
+ return socketpair(PF_UNIX,SOCK_STREAM,0,fd);
+#endif
+#endif
+}
+/**
+creates the subprocess
+*/
+bool fishProtocol::connectionStart() {
+ int fd[2];
+ int rc, flags;
+ thisFn = QString::null;
+
+ rc = open_pty_pair(fd);
+ if (rc == -1) {
+ myDebug( << "socketpair failed, error: " << strerror(errno) << endl);
+ return true;
+ }
+
+ if (!requestNetwork()) return true;
+ myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl);
+ childPid = fork();
+ if (childPid == -1) {
+ myDebug( << "fork failed, error: " << strerror(errno) << endl);
+ close(fd[0]);
+ close(fd[1]);
+ childPid = 0;
+ dropNetwork();
+ return true;
+ }
+ if (childPid == 0) {
+ // taken from konsole, see TEPty.C for details
+ // note: if we're running on socket pairs,
+ // this will fail, but thats what we expect
+
+ for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL);
+
+ struct rlimit rlp;
+ getrlimit(RLIMIT_NOFILE, &rlp);
+ for (int i = 0; i < (int)rlp.rlim_cur; i++)
+ if (i != fd[1]) close(i);
+
+ dup2(fd[1],0);
+ dup2(fd[1],1);
+ dup2(fd[1],2);
+ if (fd[1] > 2) close(fd[1]);
+
+ setsid();
+
+#if defined(TIOCSCTTY)
+ ioctl(0, TIOCSCTTY, 0);
+#endif
+
+ int pgrp = getpid();
+#if defined( _AIX) || defined( __hpux)
+ tcsetpgrp(0, pgrp);
+#else
+ ioctl(0, TIOCSPGRP, (char *)&pgrp);
+#endif
+
+ const char *dev = ttyname(0);
+ setpgid(0,0);
+ if (dev) close(open(dev, O_WRONLY, 0));
+ setpgid(0,0);
+
+ if (local) {
+ execl(suPath, "su", "-", connectionUser.latin1(), "-c", "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= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0);
+ } else {
+ #define common_args "-l", connectionUser.latin1(), "-x", "-e", "none", \
+ "-q", connectionHost.latin1(), \
+ "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 LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0
+ // disabled: leave compression up to the client.
+ // (isOpenSSH?"-C":"+C"),
+
+ if (connectionPort)
+ execl(sshPath, "ssh", "-p", QString::number(connectionPort).latin1(), common_args);
+ else
+ execl(sshPath, "ssh", common_args);
+ #undef common_args
+ }
+ myDebug( << "could not exec! " << strerror(errno) << endl);
+ ::exit(-1);
+ }
+ close(fd[1]);
+ rc = fcntl(fd[0],F_GETFL,&flags);
+ rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK);
+ childFd = fd[0];
+
+ fd_set rfds, wfds;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ char buf[32768];
+ int offset = 0;
+ while (!isLoggedIn) {
+ FD_SET(childFd,&rfds);
+ FD_ZERO(&wfds);
+ if (outBufPos >= 0) FD_SET(childFd,&wfds);
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000;
+ rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ return true;
+ }
+ if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
+ if (outBuf) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
+ else rc = 0;
+ if (rc >= 0) outBufPos += rc;
+ else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ outBufPos = -1;
+ //return true;
+ }
+ if (outBufPos >= outBufLen) {
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ }
+ }
+ if (FD_ISSET(childFd,&rfds)) {
+ rc = read(childFd,buf+offset,32768-offset);
+ if (rc > 0) {
+ int noff = establishConnection(buf,rc+offset);
+ if (noff < 0) return false;
+ if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
+ offset = noff;
+ } else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+writes one chunk of data to stdin of child process
+*/
+void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len) {
+ if (outBufPos >= 0 && outBuf) {
+#if 0
+ QString debug;
+ debug.setLatin1(outBuf,outBufLen);
+ if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)" << endl);
+#endif
+ return;
+ }
+ outBuf = buf;
+ outBufPos = 0;
+ outBufLen = len;
+}
+
+/**
+manages initial communication setup including password queries
+*/
+int fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len) {
+ QString buf;
+ buf.setLatin1(buffer,len);
+ int pos;
+ // Strip trailing whitespace
+ while (buf.length() && (buf[buf.length()-1] == ' '))
+ buf.truncate(buf.length()-1);
+
+ myDebug( << "establishing: got " << buf << endl);
+ while (childPid && ((pos = buf.find('\n')) >= 0 ||
+ buf.endsWith(":") || buf.endsWith("?"))) {
+ pos++;
+ QString str = buf.left(pos);
+ buf = buf.mid(pos);
+ if (str == "\n")
+ continue;
+ if (str == "FISH:\n") {
+ thisFn = QString::null;
+ infoMessage(i18n("Initiating protocol..."));
+ if (!connectionAuth.password.isEmpty()) {
+ connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1);
+ cacheAuthentication(connectionAuth);
+ }
+ isLoggedIn = true;
+ return 0;
+ } else if (!str.isEmpty()) {
+ thisFn += str;
+ } else if (buf.endsWith(":")) {
+ if (!redirectUser.isEmpty() && connectionUser != redirectUser) {
+ KURL dest = url;
+ dest.setUser(redirectUser);
+ dest.setPass(redirectPass);
+ redirection(dest);
+ commandList.clear();
+ commandCodes.clear();
+ finished();
+ redirectUser = "";
+ redirectPass = "";
+ return -1;
+ } else if (!connectionPassword.isEmpty()) {
+ myDebug( << "sending cpass" << endl);
+ connectionAuth.password = connectionPassword+"\n";
+ connectionPassword = QString::null;
+ // su does not like receiving a password directly after sending
+ // the password prompt so we wait a while.
+ if (local)
+ sleep(1);
+ writeChild(connectionAuth.password.latin1(),connectionAuth.password.length());
+ } else {
+ myDebug( << "sending mpass" << endl);
+ connectionAuth.prompt = thisFn+buf;
+ if (local)
+ connectionAuth.caption = i18n("Local Login") + " - " + url.user() + "@" + url.host();
+ else
+ connectionAuth.caption = i18n("SSH Authorization") + " - " + url.user() + "@" + url.host();
+ if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) {
+ connectionAuth.password = QString::null; // don't prefill
+ if ( !openPassDlg(connectionAuth)) {
+ error(ERR_USER_CANCELED,connectionHost);
+ shutdownConnection();
+ return -1;
+ }
+ }
+ firstLogin = false;
+ connectionAuth.password += "\n";
+ if (connectionAuth.username != connectionUser) {
+ KURL dest = url;
+ dest.setUser(connectionAuth.username);
+ dest.setPass(connectionAuth.password);
+ redirection(dest);
+ if (isStat) { // FIXME: just a workaround for konq deficiencies
+ redirectUser = connectionAuth.username;
+ redirectPass = connectionAuth.password;
+ }
+ commandList.clear();
+ commandCodes.clear();
+ finished();
+ return -1;
+ }
+ myDebug( << "sending pass" << endl);
+ if (local)
+ sleep(1);
+ writeChild(connectionAuth.password.latin1(),connectionAuth.password.length());
+ }
+ thisFn = QString::null;
+ return 0;
+ } else if (buf.endsWith("?")) {
+ int rc = messageBox(QuestionYesNo,thisFn+buf);
+ if (rc == KMessageBox::Yes) {
+ writeChild("yes\n",4);
+ } else {
+ writeChild("no\n",3);
+ }
+ thisFn = QString::null;
+ return 0;
+ } else {
+ myDebug( << "unmatched case in initial handling! shouldn't happen!" << endl);
+ }
+ }
+ return buf.length();
+}
+/**
+sets connection information for subsequent commands
+*/
+void fishProtocol::setHost(const QString & host, int port, const QString & u, const QString & pass){
+ QString user(u);
+
+ if (isNXFish)
+ local = 0;
+ else
+ local = (host == "localhost" && port == 0);
+
+ if (port <= 0) port = 0;
+ if (user.isEmpty()) user = getenv("LOGNAME");
+
+ if (host == connectionHost && port == connectionPort && user == connectionUser)
+ return;
+ myDebug( << "setHost " << u << "@" << host << endl);
+
+ if (childPid) shutdownConnection();
+
+ connectionHost = host;
+ connectionAuth.url.setHost(host);
+
+ connectionUser = user;
+ connectionAuth.username = user;
+ connectionAuth.url.setUser(user);
+
+ connectionPort = port;
+ connectionPassword = pass;
+ firstLogin = true;
+}
+
+/**
+Forced close of the connection
+
+This function gets called from the application side of the universe,
+it shouldn't send any response.
+ */
+void fishProtocol::closeConnection(){
+ myDebug( << "closeConnection()" << endl);
+ shutdownConnection(true);
+}
+
+/**
+Closes the connection
+ */
+void fishProtocol::shutdownConnection(bool forced){
+ if (childPid) {
+ kill(childPid,SIGTERM); // We may not have permission...
+ childPid = 0;
+ close(childFd); // ...in which case this should do the trick
+ childFd = -1;
+ if (!forced)
+ {
+ dropNetwork();
+ infoMessage(i18n("Disconnected."));
+ }
+ }
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ qlist.clear();
+ commandList.clear();
+ commandCodes.clear();
+ isLoggedIn = false;
+ writeReady = true;
+ isRunning = false;
+ rawRead = 0;
+ rawWrite = -1;
+ recvLen = -1;
+ sendLen = -1;
+}
+/**
+builds each FISH request and sets the error counter
+*/
+bool fishProtocol::sendCommand(fish_command_type cmd, ...) {
+ const fish_info &info = fishInfo[cmd];
+ myDebug( << "queueing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines << endl);
+
+ va_list list;
+ va_start(list, cmd);
+ QString realCmd = info.command;
+ QString realAlt = info.alt;
+ static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
+ for (int i = 0; i < info.params; i++) {
+ QString arg(va_arg(list, const char *));
+ int pos = -2;
+ while ((pos = rx.search(arg,pos+2)) >= 0) {
+ arg.replace(pos,0,QString("\\"));
+ }
+ //myDebug( << "arg " << i << ": " << arg << endl);
+ realCmd.append(" ").append(arg);
+ realAlt.replace(QRegExp("%"+QString::number(i+1)),arg);
+ }
+ QString s("#");
+ s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n");
+ if (realCmd == "FISH")
+ s.prepend(" ");
+ commandList.append(s);
+ commandCodes.append(cmd);
+ return true;
+}
+
+/**
+checks response string for result code, converting 000 and 001 appropriately
+*/
+int fishProtocol::handleResponse(const QString &str){
+ myDebug( << "handling: " << str << endl);
+ if (str.startsWith("### ")) {
+ bool isOk = false;
+ int result = str.mid(4,3).toInt(&isOk);
+ if (!isOk) result = 500;
+ if (result == 0) result = (errorCount != 0?500:200);
+ if (result == 1) result = (errorCount != 0?500:100);
+ myDebug( << "result: " << result << ", errorCount: " << errorCount << endl);
+ return result;
+ } else {
+ errorCount++;
+ return 0;
+ }
+}
+
+int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr)
+{
+ QDateTime dt(QDate::currentDate(Qt::UTC));
+ int year = dt.date().year();
+ int month = dt.date().month();
+ int currentMonth = month;
+ int day = dayStr.toInt();
+
+ static const char * const monthNames[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) {
+ month = i+1;
+ break;
+ }
+
+ int pos = timeyearStr.find(':');
+ if (timeyearStr.length() == 4 && pos == -1) {
+ year = timeyearStr.toInt();
+ } else if (pos == -1) {
+ return 0;
+ } else {
+ if (month > currentMonth + 1) year--;
+ dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0);
+ }
+ dt.date().setYMD(year,month,day);
+
+ return dt.toTime_t();
+}
+
+/**
+parses response from server and acts accordingly
+*/
+void fishProtocol::manageConnection(const QString &l) {
+ QString line(l);
+ int rc = handleResponse(line);
+ UDSAtom atom;
+ QDateTime dt;
+ KIO::filesize_t fsize;
+ int pos, pos2, pos3;
+ bool isOk = false;
+ if (!rc) {
+ switch (fishCommand) {
+ case FISH_VER:
+ if (line.startsWith("VER 0.0.3")) {
+ line.append(" ");
+ hasAppend = line.contains(" append ");
+ } else {
+ error(ERR_UNSUPPORTED_PROTOCOL,line);
+ shutdownConnection();
+ }
+ break;
+ case FISH_PWD:
+ url.setPath(line);
+ redirection(url);
+ break;
+ case FISH_LIST:
+ myDebug( << "listReason: " << listReason << endl);
+ /* Fall through */
+ case FISH_STAT:
+ if (line.length() > 0) {
+ switch (line[0].cell()) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ fsize = line.toULongLong(&isOk);
+ if (fsize > 0 && isOk) errorCount--;
+ if ((fishCommand == FISH_LIST) && (listReason == LIST))
+ totalSize(fsize);
+ break;
+
+ case 'P':
+ errorCount--;
+ if (line[1] == 'd') {
+ mimeAtom.m_str = "inode/directory";
+ typeAtom.m_long = S_IFDIR;
+ } else {
+ if (line[1] == '-') {
+ typeAtom.m_long = S_IFREG;
+ } else if (line[1] == 'l') {
+ typeAtom.m_long = S_IFLNK;
+ } else if (line[1] == 'c') {
+ typeAtom.m_long = S_IFCHR;
+ } else if (line[1] == 'b') {
+ typeAtom.m_long = S_IFBLK;
+ } else if (line[1] == 's') {
+ typeAtom.m_long = S_IFSOCK;
+ } else if (line[1] == 'p') {
+ typeAtom.m_long = S_IFIFO;
+ } else {
+ myDebug( << "unknown file type: " << line[1].cell() << endl);
+ errorCount++;
+ break;
+ }
+ }
+ //myDebug( << "file type: " << atom.m_long << endl);
+ //udsEntry.append(atom);
+
+ atom.m_uds = UDS_ACCESS;
+ atom.m_long = 0;
+ if (line[2] == 'r') atom.m_long |= S_IRUSR;
+ if (line[3] == 'w') atom.m_long |= S_IWUSR;
+ if (line[4] == 'x' || line[4] == 's') atom.m_long |= S_IXUSR;
+ if (line[4] == 'S' || line[4] == 's') atom.m_long |= S_ISUID;
+ if (line[5] == 'r') atom.m_long |= S_IRGRP;
+ if (line[6] == 'w') atom.m_long |= S_IWGRP;
+ if (line[7] == 'x' || line[7] == 's') atom.m_long |= S_IXGRP;
+ if (line[7] == 'S' || line[7] == 's') atom.m_long |= S_ISGID;
+ if (line[8] == 'r') atom.m_long |= S_IROTH;
+ if (line[9] == 'w') atom.m_long |= S_IWOTH;
+ if (line[10] == 'x' || line[10] == 't') atom.m_long |= S_IXOTH;
+ if (line[10] == 'T' || line[10] == 't') atom.m_long |= S_ISVTX;
+ udsEntry.append(atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_long = 0;
+ pos = line.find('.',12);
+ if (pos < 0) {
+ errorCount++;
+ break;
+ }
+ atom.m_str = line.mid(12,pos-12);
+ udsEntry.append(atom);
+
+ atom.m_uds = UDS_GROUP;
+ atom.m_long = 0;
+ atom.m_str = line.mid(pos+1);
+ udsEntry.append(atom);
+ break;
+
+ case 'd':
+ atom.m_uds = UDS_MODIFICATION_TIME;
+ pos = line.find(' ');
+ pos2 = line.find(' ',pos+1);
+ if (pos < 0 || pos2 < 0) break;
+ errorCount--;
+ atom.m_long = makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1));
+ udsEntry.append(atom);
+ break;
+
+ case 'D':
+ atom.m_uds = UDS_MODIFICATION_TIME;
+ pos = line.find(' ');
+ pos2 = line.find(' ',pos+1);
+ pos3 = line.find(' ',pos2+1);
+ if (pos < 0 || pos2 < 0 || pos3 < 0) break;
+ 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()));
+ pos = pos3;
+ pos2 = line.find(' ',pos+1);
+ pos3 = line.find(' ',pos2+1);
+ if (pos < 0 || pos2 < 0 || pos3 < 0) break;
+ dt.setTime(QTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt()));
+ errorCount--;
+ atom.m_long = dt.toTime_t();
+ udsEntry.append(atom);
+ break;
+
+ case 'S':
+ atom.m_uds = UDS_SIZE;
+ atom.m_long = line.mid(1).toULongLong(&isOk);
+ if (!isOk) break;
+ errorCount--;
+ udsEntry.append(atom);
+ break;
+
+ case 'E':
+ errorCount--;
+ break;
+
+ case ':':
+ atom.m_uds = UDS_NAME;
+ atom.m_long = 0;
+ pos = line.findRev('/');
+ atom.m_str = thisFn = line.mid(pos < 0?1:pos+1);
+ if (fishCommand == FISH_LIST)
+ udsEntry.append(atom);
+ // By default, the mimetype comes from the extension
+ // We'll use the file(1) result only as fallback [like the rest of KDE does]
+ {
+ KURL kurl("fish://host/");
+ kurl.setFileName(thisFn); // properly encode special chars
+ KMimeType::Ptr mime = KMimeType::findByURL(kurl);
+ if ( mime->name() != KMimeType::defaultMimeType() )
+ mimeAtom.m_str = mime->name();
+ }
+ errorCount--;
+ break;
+
+ case 'M': {
+ QString type = line.mid(1);
+
+ // First thing's first. If remote says this is a directory, throw out any
+ // name-based file type guesses.
+ if (type == "inode/directory" && mimeAtom.m_str != type) {
+ mimeAtom.m_str = type;
+ typeAtom.m_long = S_IFDIR;
+ }
+ // This is getting ugly. file(1) makes some uneducated
+ // guesses, so we must try to ignore them (#51274)
+ else if (mimeAtom.m_str.isEmpty() && line.right(8) != "/unknown" &&
+ (thisFn.find('.') < 0 || (line.left(8) != "Mtext/x-"
+ && line != "Mtext/plain"))) {
+ mimeAtom.m_str = type;
+ }
+ errorCount--;
+ break;
+ }
+
+ case 'L':
+ atom.m_uds = UDS_LINK_DEST;
+ atom.m_long = 0;
+ atom.m_str = line.mid(1);
+ udsEntry.append(atom);
+ if (!typeAtom.m_long) typeAtom.m_long = S_IFLNK;
+ errorCount--;
+ break;
+ }
+ } else {
+ if (!mimeAtom.m_str.isNull())
+ udsEntry.append(mimeAtom);
+ mimeAtom.m_str = QString::null;
+
+ udsEntry.append(typeAtom);
+ typeAtom.m_long = 0;
+
+ if (fishCommand == FISH_STAT)
+ udsStatEntry = udsEntry;
+ else if (listReason == LIST) {
+ listEntry(udsEntry, false); //1
+ } else if (listReason == CHECK) checkExist = true; //0
+ errorCount--;
+ udsEntry.clear();
+ }
+ break;
+
+ case FISH_RETR:
+ if (line.length() == 0) {
+ error(ERR_IS_DIRECTORY,url.prettyURL());
+ recvLen = 0;
+ break;
+ }
+ recvLen = line.toLongLong(&isOk);
+ if (!isOk) {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ break;
+ }
+ break;
+ default : break;
+ }
+
+ } else if (rc == 100) {
+ switch (fishCommand) {
+ case FISH_FISH:
+ writeChild(fishCode, fishCodeLen);
+ break;
+ case FISH_READ:
+ recvLen = 1024;
+ /* fall through */
+ case FISH_RETR:
+ myDebug( << "reading " << recvLen << endl);
+ if (recvLen == -1) {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ } else {
+ rawRead = recvLen;
+ dataRead = 0;
+ mimeTypeSent = false;
+ if (recvLen == 0)
+ {
+ mimeType("application/x-zerosize");
+ mimeTypeSent = true;
+ }
+ }
+ break;
+ case FISH_STOR:
+ case FISH_WRITE:
+ case FISH_APPEND:
+ rawWrite = sendLen;
+ //myDebug( << "sending " << sendLen << endl);
+ writeChild(NULL,0);
+ break;
+ default : break;
+ }
+ } else if (rc/100 != 2) {
+ switch (fishCommand) {
+ case FISH_STOR:
+ case FISH_WRITE:
+ case FISH_APPEND:
+ error(ERR_COULD_NOT_WRITE,url.prettyURL());
+ shutdownConnection();
+ break;
+ case FISH_RETR:
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ break;
+ case FISH_READ:
+ if ( rc == 501 )
+ {
+ mimeType("inode/directory");
+ mimeTypeSent = true;
+ recvLen = 0;
+ finished();
+ }
+ else
+ {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ }
+ break;
+ case FISH_FISH:
+ case FISH_VER:
+ error(ERR_SLAVE_DEFINED,line);
+ shutdownConnection();
+ break;
+ case FISH_PWD:
+ case FISH_CWD:
+ error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL());
+ break;
+ case FISH_LIST:
+ myDebug( << "list error. reason: " << listReason << endl);
+ if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL());
+ else if (listReason == CHECK) {
+ checkExist = false;
+ finished();
+ }
+ break;
+ case FISH_STAT:
+ error(ERR_DOES_NOT_EXIST,url.prettyURL());
+ udsStatEntry.clear();
+ break;
+ case FISH_CHMOD:
+ error(ERR_CANNOT_CHMOD,url.prettyURL());
+ break;
+ case FISH_CHOWN:
+ case FISH_CHGRP:
+ error(ERR_ACCESS_DENIED,url.prettyURL());
+ break;
+ case FISH_MKD:
+ if ( rc == 501 )
+ error(ERR_DIR_ALREADY_EXIST,url.prettyURL());
+ else
+ error(ERR_COULD_NOT_MKDIR,url.prettyURL());
+ break;
+ case FISH_RMD:
+ error(ERR_COULD_NOT_RMDIR,url.prettyURL());
+ break;
+ case FISH_DELE:
+ error(ERR_CANNOT_DELETE,url.prettyURL());
+ break;
+ case FISH_RENAME:
+ error(ERR_CANNOT_RENAME,url.prettyURL());
+ break;
+ case FISH_COPY:
+ case FISH_LINK:
+ case FISH_SYMLINK:
+ error(ERR_COULD_NOT_WRITE,url.prettyURL());
+ break;
+ default : break;
+ }
+ } else {
+ if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE);
+ if (fishCommand == FISH_FISH) {
+ connected();
+ } else if (fishCommand == FISH_LIST) {
+ if (listReason == LIST) {
+ listEntry(UDSEntry(),true);
+ } else if (listReason == CHECK) {
+ if (!checkOverwrite && checkExist)
+ {
+ error(ERR_FILE_ALREADY_EXIST,url.prettyURL());
+ return; // Don't call finished!
+ }
+ }
+ } else if (fishCommand == FISH_STAT) {
+ UDSAtom atom;
+
+ atom.m_uds = KIO::UDS_NAME;
+ atom.m_str = url.fileName();
+ udsStatEntry.append( atom );
+ statEntry(udsStatEntry);
+ } else if (fishCommand == FISH_APPEND) {
+ dataReq();
+ if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(QString::number(rawData.size())),E(url.path()));
+ else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path()));
+ sendLen = rawData.size();
+ } else if (fishCommand == FISH_WRITE) {
+ dataReq();
+ if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path()));
+ else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path()));
+ putPos += rawData.size();
+ sendLen = rawData.size();
+ } else if (fishCommand == FISH_RETR) {
+ data(QByteArray());
+ }
+ finished();
+ }
+}
+
+void fishProtocol::writeStdin(const QString &line)
+{
+ qlist.append(line);
+
+ if (writeReady) {
+ writeReady = false;
+ //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl);
+ myDebug( << "Writing: " << qlist.first() << endl);
+ myDebug( << "---------" << endl);
+ writeChild((const char *)qlist.first().latin1(), qlist.first().length());
+ }
+}
+
+void fishProtocol::sent()
+{
+ if (rawWrite > 0) {
+ myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl);
+ writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite));
+ rawWrite -= rawData.size();
+ if (rawWrite > 0) {
+ dataReq();
+ if (readData(rawData) <= 0) {
+ shutdownConnection();
+ }
+ }
+ return;
+ } else if (rawWrite == 0) {
+ // workaround: some dd's insist in reading multiples of
+ // 8 bytes, swallowing up to seven bytes. Sending
+ // newlines is safe even when a sane dd is used
+ writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15);
+ rawWrite = -1;
+ return;
+ }
+ if (qlist.count() > 0) qlist.remove(qlist.begin());
+ if (qlist.count() == 0) {
+ writeReady = true;
+ } else {
+ //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl);
+ myDebug( << "Writing: " << qlist.first() << endl);
+ myDebug( << "---------" << endl);
+ writeChild((const char *)qlist.first().latin1(),qlist.first().length());
+ }
+}
+
+int fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen)
+{
+ int pos = 0;
+ do {
+ if (buflen <= 0) break;
+
+ if (rawRead > 0) {
+ //myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl);
+ int dataSize = (rawRead > buflen?buflen:rawRead);
+ if (!mimeTypeSent)
+ {
+ int mimeSize = QMIN(dataSize, (int)mimeBuffer.size()-dataRead);
+ memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize);
+ dataRead += mimeSize;
+ rawRead -= mimeSize;
+ buffer += mimeSize;
+ buflen -= mimeSize;
+ if (rawRead == 0) // End of data
+ mimeBuffer.resize(dataRead);
+ if (dataRead < (int)mimeBuffer.size())
+ {
+ myDebug( << "wait for more" << endl);
+ break;
+ }
+
+ // We need a KMimeType::findByNameAndContent(filename,data)
+ // For now we do: find by extension, and if not found (or extension not reliable)
+ // then find by content.
+ bool accurate = false;
+ KMimeType::Ptr mime = KMimeType::findByURL( url, 0, false, true, &accurate );
+ if ( !mime || mime->name() == KMimeType::defaultMimeType()
+ || !accurate )
+ {
+ KMimeType::Ptr p_mimeType = KMimeType::findByContent(mimeBuffer);
+ if ( p_mimeType && p_mimeType->name() != KMimeType::defaultMimeType() )
+ mime = p_mimeType;
+ }
+
+ sendmimeType(mime->name());
+
+
+ mimeTypeSent = true;
+ if (fishCommand != FISH_READ) {
+ totalSize(dataRead + rawRead);
+ data(mimeBuffer);
+ processedSize(dataRead);
+ }
+ mimeBuffer.resize(1024);
+ pos = 0;
+ continue; // Process rest of buffer/buflen
+ }
+
+ QByteArray bdata;
+ bdata.duplicate(buffer,dataSize);
+ data(bdata);
+
+ dataRead += dataSize;
+ rawRead -= dataSize;
+ processedSize(dataRead);
+ if (rawRead <= 0) {
+ buffer += dataSize;
+ buflen -= dataSize;
+ } else {
+ return 0;
+ }
+ }
+
+ if (buflen <= 0) break;
+
+ pos = 0;
+ // Find newline
+ while((pos < buflen) && (buffer[pos] != '\n'))
+ ++pos;
+
+ if (pos < buflen)
+ {
+ QString s = remoteEncoding()->decode(QCString(buffer,pos+1));
+
+ buffer += pos+1;
+ buflen -= pos+1;
+
+ manageConnection(s);
+
+ pos = 0;
+ // Find next newline
+ while((pos < buflen) && (buffer[pos] != '\n'))
+ ++pos;
+ }
+ } while (childPid && buflen && (rawRead > 0 || pos < buflen));
+ return buflen;
+}
+/** get a file */
+void fishProtocol::get(const KURL& u){
+ myDebug( << "@@@@@@@@@ get " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ recvLen = -1;
+ sendCommand(FISH_RETR,E(url.path()));
+ }
+ run();
+}
+
+/** put a file */
+void fishProtocol::put(const KURL& u, int permissions, bool overwrite, bool /*resume*/){
+ myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << overwrite << " " /* << resume */ << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ putPerm = permissions;
+ checkOverwrite = overwrite;
+ checkExist = false;
+ putPos = 0;
+ listReason = CHECK;
+ sendCommand(FISH_LIST,E(url.path()));
+ sendCommand(FISH_STOR,"0",E(url.path()));
+ }
+ run();
+}
+/** executes next command in sequence or calls finished() if all is done */
+void fishProtocol::finished() {
+ if (commandList.count() > 0) {
+ fishCommand = (fish_command_type)commandCodes.first();
+ errorCount = -fishInfo[fishCommand].lines;
+ rawRead = 0;
+ rawWrite = -1;
+ udsEntry.clear();
+ udsStatEntry.clear();
+ writeStdin(commandList.first());
+ //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().find("\n")-1))+"...");
+ commandList.remove(commandList.begin());
+ commandCodes.remove(commandCodes.begin());
+ } else {
+ myDebug( << "_______ emitting finished()" << endl);
+ SlaveBase::finished();
+ isRunning = false;
+ }
+}
+/** aborts command sequence and calls error() */
+void fishProtocol::error(int type, const QString &detail) {
+ commandList.clear();
+ commandCodes.clear();
+ myDebug( << "ERROR: " << type << " - " << detail << endl);
+ SlaveBase::error(type,detail);
+ isRunning = false;
+}
+/** executes a chain of commands */
+void fishProtocol::run() {
+ if (!isRunning) {
+ int rc;
+ isRunning = true;
+ finished();
+ fd_set rfds, wfds;
+ FD_ZERO(&rfds);
+ char buf[32768];
+ int offset = 0;
+ while (isRunning) {
+ FD_SET(childFd,&rfds);
+ FD_ZERO(&wfds);
+ if (outBufPos >= 0) FD_SET(childFd,&wfds);
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000;
+ rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
+#if 0
+ QString debug;
+ debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos);
+ myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl);
+#endif
+ if (outBufLen-outBufPos > 0) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
+ else rc = 0;
+ if (rc >= 0) outBufPos += rc;
+ else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ if (outBufPos >= outBufLen) {
+ outBufPos = -1;
+ outBuf = NULL;
+ sent();
+ }
+ }
+ else if (FD_ISSET(childFd,&rfds)) {
+ rc = read(childFd,buf+offset,32768-offset);
+ //myDebug( << "read " << rc << " bytes" << endl);
+ if (rc > 0) {
+ int noff = received(buf,rc+offset);
+ if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
+ //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset) << endl);
+ offset = noff;
+ } else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ }
+ if (wasKilled())
+ return;
+ }
+ }
+}
+/** stat a file */
+void fishProtocol::stat(const KURL& u){
+ myDebug( << "@@@@@@@@@ stat " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ isStat = true; // FIXME: just a workaround for konq deficiencies
+ openConnection();
+ isStat = false; // FIXME: just a workaround for konq deficiencies
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand(FISH_STAT,E(url.path(-1)));
+ }
+ run();
+}
+/** find mimetype for a file */
+void fishProtocol::mimetype(const KURL& u){
+ myDebug( << "@@@@@@@@@ mimetype " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ recvLen = 1024;
+ sendCommand(FISH_READ,"0","1024",E(url.path()));
+ }
+ run();
+}
+/** list a directory */
+void fishProtocol::listDir(const KURL& u){
+ myDebug( << "@@@@@@@@@ listDir " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ listReason = LIST;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ run();
+}
+/** create a directory */
+void fishProtocol::mkdir(const KURL& u, int permissions) {
+ myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand(FISH_MKD,E(url.path()));
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** rename a file */
+void fishProtocol::rename(const KURL& s, const KURL& d, bool overwrite) {
+ myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << overwrite << endl);
+ if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
+ error(ERR_UNSUPPORTED_ACTION,s.prettyURL());
+ return;
+ }
+ setHost(s.host(),s.port(),s.user(),s.pass());
+ url = d;
+ openConnection();
+ if (!isLoggedIn) return;
+ KURL src = s;
+ url.cleanPath();
+ src.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_RENAME,E(src.path()),E(url.path()));
+ }
+ run();
+}
+/** create a symlink */
+void fishProtocol::symlink(const QString& target, const KURL& u, bool overwrite) {
+ myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << overwrite << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_SYMLINK,E(target),E(url.path()));
+ }
+ run();
+}
+/** change file permissions */
+void fishProtocol::chmod(const KURL& u, int permissions){
+ myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** copies a file */
+void fishProtocol::copy(const KURL &s, const KURL &d, int permissions, bool overwrite) {
+ myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << overwrite << endl);
+ if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
+ error(ERR_UNSUPPORTED_ACTION,s.prettyURL());
+ return;
+ }
+ //myDebug( << s << endl << d << endl);
+ setHost(s.host(),s.port(),s.user(),s.pass());
+ url = d;
+ openConnection();
+ if (!isLoggedIn) return;
+ KURL src = s;
+ url.cleanPath();
+ src.cleanPath();
+ if (!src.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_COPY,E(src.path()),E(url.path()));
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** removes a file or directory */
+void fishProtocol::del(const KURL &u, bool isFile){
+ myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path()));
+ }
+ run();
+}
+/** special like background execute */
+void fishProtocol::special( const QByteArray &data ){
+ int tmp;
+
+ QDataStream stream(data, IO_ReadOnly);
+
+ stream >> tmp;
+ switch (tmp) {
+ case FISH_EXEC_CMD: // SSH EXEC
+ {
+ KURL u;
+ QString command;
+ QString tempfile;
+ stream >> u;
+ stream >> command;
+ myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ sendCommand(FISH_EXEC,E(command),E(url.path()));
+ run();
+ break;
+ }
+ default:
+ // Some command we don't understand.
+ error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp));
+ break;
+ }
+}
+/** report status */
+void fishProtocol::slave_status() {
+ myDebug( << "@@@@@@@@@ slave_status" << endl);
+ if (childPid > 0)
+ slaveStatus(connectionHost,isLoggedIn);
+ else
+ slaveStatus(QString::null,false);
+}