You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kioslave/fish/fish.cpp

1662 lines
56 KiB

/***************************************************************************
fish.cpp - a FISH kioslave
-------------------
begin : Thu Oct 4 17:09:14 CEST 2001
copyright : (C) 2001-2003 by J<EFBFBD>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 <tqcstring.h>
#include <tqfile.h>
#include <tqsocket.h>
#include <tqdatetime.h>
#include <tqbitarray.h>
#include <tqregexp.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 TQCString &pool_socket, const TQCString &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(TQFile::encodeName(KStandardDirs::findExe("nxfish")));
else
sshPath = strdup(TQFile::encodeName(KStandardDirs::findExe("ssh")));
}
if (suPath == NULL) {
suPath = strdup(TQFile::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 = TQString::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, TQString::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 = TQString::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", TQString::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
TQString 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) {
TQString 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++;
TQString str = buf.left(pos);
buf = buf.mid(pos);
if (str == "\n")
continue;
if (str == "FISH:\n") {
thisFn = TQString::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 = TQString::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 = TQString::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 = TQString::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 = TQString::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 TQString & host, int port, const TQString & u, const TQString & pass){
TQString 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);
TQString realCmd = info.command;
TQString realAlt = info.alt;
static TQRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
for (int i = 0; i < info.params; i++) {
TQString arg(va_arg(list, const char *));
int pos = -2;
while ((pos = rx.search(arg,pos+2)) >= 0) {
arg.replace(pos,0,TQString("\\"));
}
//myDebug( << "arg " << i << ": " << arg << endl);
realCmd.append(" ").append(arg);
realAlt.replace(TQRegExp("%"+TQString::number(i+1)),arg);
}
TQString 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 TQString &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 TQString &monthStr, const TQString &dayStr, const TQString &timeyearStr)
{
TQDateTime dt(TQDate::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 TQString &l) {
TQString line(l);
int rc = handleResponse(line);
UDSAtom atom;
TQDateTime 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(TQDate(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(TQTime(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': {
TQString 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 = TQString::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(TQString::number(rawData.size())),E(url.path()));
else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path()));
sendLen = rawData.size();
} else if (fishCommand == FISH_WRITE) {
dataReq();
if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(TQString::number(putPos)),E(TQString::number(rawData.size())),E(url.path()));
else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path()));
putPos += rawData.size();
sendLen = rawData.size();
} else if (fishCommand == FISH_RETR) {
data(TQByteArray());
}
finished();
}
}
void fishProtocol::writeStdin(const TQString &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 = TQMIN(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
}
TQByteArray 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)
{
TQString s = remoteEncoding()->decode(TQCString(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 TQString &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
TQString 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: " << TQString::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(TQString::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 TQString& 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(TQString::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(TQString::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 TQByteArray &data ){
int tmp;
TQDataStream stream(data, IO_ReadOnly);
stream >> tmp;
switch (tmp) {
case FISH_EXEC_CMD: // SSH EXEC
{
KURL u;
TQString command;
TQString 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,TQString().setNum(tmp));
break;
}
}
/** report status */
void fishProtocol::slave_status() {
myDebug( << "@@@@@@@@@ slave_status" << endl);
if (childPid > 0)
slaveStatus(connectionHost,isLoggedIn);
else
slaveStatus(TQString::null,false);
}