PATCH: support for ftp:

Marc Espie konq-e@mail.kde.org
Mon, 1 Apr 2002 14:55:49 +0200


Turns out it was mostly stealing the code from kdelibs-2.2.2 and tweaking
a few details. The following patch is somewhat large, because I have
simply commented out the parts of kio_ftp.cc I don't use instead of
removing them entirely.

To summarize, it's simply a question of telling konq we support ftp, remove
all the write stuff, and adding directory listing support to `get'.

One single issue took me some time to track down correctly: 
change `empty' paths to "/" paths.

(the Makefile.in patch is probably better propagated to Makefile.am, which
shouldn't be hard to do).

--- konq-embed/dropin/kio/Makefile.in.orig	Sat Mar 30 18:38:54 2002
+++ konq-embed/dropin/kio/Makefile.in	Sat Mar 30 18:39:50 2002
@@ -256,7 +256,7 @@ noinst_LTLIBRARIES = libkiodropin.la
 libkiodropin_la_SOURCES = slavebase.cpp job.cpp netaccess.cpp jobclasses.cpp \
 	ktrader.cpp kprotocolmanager.cpp krun.cpp authcache.cpp authcache_skel.cpp \
 	scheduler.cpp observer.cpp \
-	slave.cpp passdlg.cpp kio_file.cpp kmimemagic.cpp global.cpp \
+	slave.cpp passdlg.cpp kio_file.cpp kio_ftp.cpp kmimemagic.cpp global.cpp \
 	launcher.cpp
 
 #>- libkiodropin_la_METASOURCES = AUTO
@@ -273,7 +273,7 @@ libkiodropin_la_LIBADD =
 am_libkiodropin_la_OBJECTS = slavebase.lo job.lo netaccess.lo \
 	jobclasses.lo ktrader.lo kprotocolmanager.lo krun.lo \
 	authcache.lo authcache_skel.lo scheduler.lo observer.lo \
-	slave.lo passdlg.lo kio_file.lo kmimemagic.lo global.lo \
+	slave.lo passdlg.lo kio_file.lo kio_ftp.lo kmimemagic.lo global.lo \
 	launcher.lo
 #>- libkiodropin_la_OBJECTS = $(am_libkiodropin_la_OBJECTS)
 #>+ 8
@@ -281,7 +281,7 @@ libkiodropin_la_final_OBJECTS = libkiodr
 libkiodropin_la_nofinal_OBJECTS = slavebase.lo job.lo netaccess.lo \
 	jobclasses.lo ktrader.lo kprotocolmanager.lo krun.lo \
 	authcache.lo authcache_skel.lo scheduler.lo observer.lo \
-	slave.lo passdlg.lo kio_file.lo kmimemagic.lo global.lo \
+	slave.lo passdlg.lo kio_file.lo kio_ftp.lo kmimemagic.lo global.lo \
 	launcher.lo
 @KDE_USE_FINAL_FALSE@libkiodropin_la_OBJECTS = $(libkiodropin_la_nofinal_OBJECTS)
 @KDE_USE_FINAL_TRUE@libkiodropin_la_OBJECTS = $(libkiodropin_la_final_OBJECTS)
@@ -295,7 +295,7 @@ depcomp = $(SHELL) $(top_srcdir)/admin/d
 @AMDEP_TRUE@DEP_FILES = $(DEPDIR)/authcache.Plo \
 	@AMDEP_TRUE@	$(DEPDIR)/authcache_skel.Plo $(DEPDIR)/global.Plo \
 	@AMDEP_TRUE@	$(DEPDIR)/job.Plo $(DEPDIR)/jobclasses.Plo \
-	@AMDEP_TRUE@	$(DEPDIR)/kio_file.Plo $(DEPDIR)/kmimemagic.Plo \
+	@AMDEP_TRUE@	$(DEPDIR)/kio_file.Plo $(DEPDIR)/kio_ftp.Plo $(DEPDIR)/kmimemagic.Plo \
 	@AMDEP_TRUE@	$(DEPDIR)/kprotocolmanager.Plo $(DEPDIR)/krun.Plo \
 	@AMDEP_TRUE@	$(DEPDIR)/ktrader.Plo $(DEPDIR)/launcher.Plo \
 	@AMDEP_TRUE@	$(DEPDIR)/netaccess.Plo $(DEPDIR)/observer.Plo \
@@ -374,6 +374,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/job.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/jobclasses.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/kio_file.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/kio_ftp.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/kmimemagic.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/kprotocolmanager.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/krun.Plo@am__quote@
@@ -503,7 +504,7 @@ distclean-tags:
 
 #>- DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 #>+ 4
-KDE_DIST=kio_file.h job.h krun.h launcher.h netaccess.h kimageio.h kfileitem.h scheduler.h slave.h slavebase.h kprotocolmanager.h authcache.h jobclasses.h kmimetype.h kmimemagic.h ktrader.h passdlg.h observer.h kservice.h 
+KDE_DIST=kio_file.h kio_ftp.h job.h krun.h launcher.h netaccess.h kimageio.h kfileitem.h scheduler.h slave.h slavebase.h kprotocolmanager.h authcache.h jobclasses.h kmimetype.h kmimemagic.h ktrader.h passdlg.h observer.h kservice.h 
 
 DISTFILES= $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) $(KDE_DIST)
 
@@ -676,11 +677,11 @@ force-reedit:
 
 
 #>+ 11
-libkiodropin_la.all_cpp.cpp: $(srcdir)/Makefile.in $(srcdir)/slavebase.cpp $(srcdir)/job.cpp $(srcdir)/netaccess.cpp $(srcdir)/jobclasses.cpp $(srcdir)/ktrader.cpp $(srcdir)/kprotocolmanager.cpp $(srcdir)/krun.cpp $(srcdir)/authcache.cpp $(srcdir)/authcache_skel.cpp $(srcdir)/scheduler.cpp $(srcdir)/observer.cpp $(srcdir)/slave.cpp $(srcdir)/passdlg.cpp $(srcdir)/kio_file.cpp $(srcdir)/kmimemagic.cpp $(srcdir)/global.cpp $(srcdir)/launcher.cpp   krun.moc jobclasses.moc authcache.moc slave.moc scheduler.moc 
+libkiodropin_la.all_cpp.cpp: $(srcdir)/Makefile.in $(srcdir)/slavebase.cpp $(srcdir)/job.cpp $(srcdir)/netaccess.cpp $(srcdir)/jobclasses.cpp $(srcdir)/ktrader.cpp $(srcdir)/kprotocolmanager.cpp $(srcdir)/krun.cpp $(srcdir)/authcache.cpp $(srcdir)/authcache_skel.cpp $(srcdir)/scheduler.cpp $(srcdir)/observer.cpp $(srcdir)/slave.cpp $(srcdir)/passdlg.cpp $(srcdir)/kio_file.cpp $(srcdir)/kio_ftp.cpp $(srcdir)/kmimemagic.cpp $(srcdir)/global.cpp $(srcdir)/launcher.cpp   krun.moc jobclasses.moc authcache.moc slave.moc scheduler.moc 
 	@echo 'creating libkiodropin_la.all_cpp.cpp ...'; \
 	rm -f libkiodropin_la.all_cpp.files libkiodropin_la.all_cpp.final; \
 	echo "#define KDE_USE_FINAL 1" >> libkiodropin_la.all_cpp.final; \
-	for file in slavebase.cpp job.cpp netaccess.cpp jobclasses.cpp ktrader.cpp kprotocolmanager.cpp krun.cpp authcache.cpp authcache_skel.cpp scheduler.cpp observer.cpp slave.cpp passdlg.cpp kio_file.cpp kmimemagic.cpp global.cpp launcher.cpp ; do \
+	for file in slavebase.cpp job.cpp netaccess.cpp jobclasses.cpp ktrader.cpp kprotocolmanager.cpp krun.cpp authcache.cpp authcache_skel.cpp scheduler.cpp observer.cpp slave.cpp passdlg.cpp kio_file.cpp kio_ftp.cpp kmimemagic.cpp global.cpp launcher.cpp ; do \
 	  echo "#include \"$$file\"" >> libkiodropin_la.all_cpp.files; \
 	  test ! -f $(srcdir)/$$file || egrep '^#pragma +implementation' $(srcdir)/$$file >> libkiodropin_la.all_cpp.final; \
 	done; \
--- konq-embed/dropin/kio/kio_ftp.cpp.orig	Sun Mar 31 21:00:30 2002
+++ konq-embed/dropin/kio/kio_ftp.cpp	Mon Apr  1 02:29:07 2002
@@ -0,0 +1,2252 @@
+/*  This file is part of the KDE libraries
+    Copyright (C) 2000 David Faure <faure@kde.org>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, USA.
+*/
+
+// $Id: patch-konq-embed_dropin_kio_kio_ftp_cpp,v 1.4 2002/04/01 00:55:23 espie Exp $
+
+#include "kio_ftp.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if TIME_WITH_SYS_TIME
+#include <time.h>
+#endif
+
+#include <qdir.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kinstance.h>
+#include <kmimemagic.h>
+#include <kmimetype.h>
+#include <kextsock.h>
+#include <ksockaddr.h>
+#include <ksocks.h>
+#include <kio/ioslave_defaults.h>
+#include <kio/slaveconfig.h>
+#include <kconfig.h>
+
+#include <qtextstream.h>
+
+#define FTP_LOGIN QString::fromLatin1("anonymous")
+#define FTP_PASSWD QString::fromLatin1("kde-user@kde.org")
+
+
+using namespace KIO;
+
+Ftp::Ftp( const QCString &pool, const QCString &app )
+    : SlaveBase( "ftp", pool, app )
+{
+  dirfile = 0L;
+  m_extControl = sData = sDatal = 0;
+  sControl = -1;
+  ksControl = NULL;
+  m_bLoggedOn = false;
+  m_bFtpStarted = false;
+  setMultipleAuthCaching( true );
+  kdDebug(7102) << "Ftp::Ftp()" << endl;
+}
+
+
+Ftp::~Ftp()
+{
+  closeConnection();
+}
+
+/* memccpy appeared first in BSD4.4 */
+void *mymemccpy(void *dest, const void *src, int c, size_t n)
+{
+    char *d = (char*)dest;
+    const char *s = (const char*)src;
+
+    while (n-- > 0)
+    if ((*d++ = *s++) == c)
+      return d;
+
+  return NULL;
+}
+
+/*
+ * read a line of text
+ *
+ * return -1 on error, bytecount otherwise
+ */
+int Ftp::ftpReadline(char *buf,int max,netbuf *ctl)
+{
+  int x,retval = 0;
+  char *end;
+  int eof = 0;
+
+  if ( max == 0 )
+    return 0;
+  do
+  {
+    if (ctl->cavail > 0)
+    {
+      x = (max >= ctl->cavail) ? ctl->cavail : max-1;
+      end = (char*)mymemccpy(buf,ctl->cget,'\n',x);
+      if (end != NULL)
+        x = end - buf;
+      retval += x;
+      buf += x;
+      *buf = '\0';
+      max -= x;
+      ctl->cget += x;
+      ctl->cavail -= x;
+      if (end != NULL)
+        break;
+    }
+    if (max == 1)
+    {
+      *buf = '\0';
+      break;
+    }
+    if (ctl->cput == ctl->cget)
+    {
+      ctl->cput = ctl->cget = ctl->buf;
+      ctl->cavail = 0;
+      ctl->cleft = FTP_BUFSIZ;
+    }
+    if (eof)
+    {
+      if (retval == 0)
+        retval = -1;
+      break;
+    }
+    if ((x = KSocks::self()->read(ctl->handle,ctl->cput,ctl->cleft)) == -1)
+    {
+      kdError(7102) << "read failed: " << strerror(errno) << endl;
+      retval = -1;
+      break;
+    }
+    if (x == 0)
+      eof = 1;
+    ctl->cleft -= x;
+    ctl->cavail += x;
+    ctl->cput += x;
+  }
+  while (1);
+
+  return retval;
+}
+
+/**
+ * read a response from the server, into rspbuf
+ * @return first char of response (rspbuf[0]), '\0' if we couldn't read the response
+ */
+char Ftp::readresp()
+{
+  char match[5];
+  if ( ftpReadline( rspbuf, 256, nControl ) == -1 )
+  {
+    // This can happen after the server closed the connection (after a timeout)
+    kdWarning(7102) << "Could not read" << endl;
+    //error( ERR_COULD_NOT_READ, QString::null );
+    return '\0';
+  }
+  kdDebug(7102) << "resp> " << rspbuf << endl;
+  if ( rspbuf[3] == '-' )  {
+    strncpy( match, rspbuf, 3 );
+    match[3] = ' ';
+    match[4] = '\0';
+    do {
+      if ( ftpReadline( rspbuf, 256, nControl ) == -1 ) {
+          kdWarning(7102) << "Could not read" << endl;
+          //error( ERR_COULD_NOT_READ, QString::null );
+          return '\0';
+      }
+      kdDebug(7102) << rspbuf << endl;
+    }
+    while ( strncmp( rspbuf, match, 4 ) );
+  }
+
+  return rspbuf[0];
+}
+
+void Ftp::closeConnection()
+{
+  kdDebug(7102) << "Ftp::closeConnection() m_bLoggedOn=" << m_bLoggedOn << " m_bFtpStarted=" << m_bFtpStarted << endl;
+  if ( m_bLoggedOn || m_bFtpStarted )
+  {
+    ASSERT( m_bFtpStarted ); // can't see how it could be false is loggedon is true
+    if( sControl != 0 )
+    {
+      kdDebug(7102) << "Ftp::closeConnection() sending quit" << endl;
+      if ( !ftpSendCmd( "quit", '2' ) )
+        kdWarning(7102) << "Ftp::closeConnection() 'quit' failed with err=" << rspbuf[0] << rspbuf[1] << rspbuf[2] << endl;
+      free( nControl );
+      if (ksControl != NULL)
+	delete ksControl;
+      //      ::close( sControl );
+      sControl = 0;
+    }
+  }
+
+  m_extControl = 0;
+  m_bLoggedOn = false;
+  m_bFtpStarted = false;
+  //ready()
+}
+
+
+void Ftp::setHost( const QString& _host, int _port, const QString& _user,
+                   const QString& _pass )
+{
+  kdDebug(7102) << "Ftp::setHost " << _host << endl;
+  QString user = _user;
+  QString pass = _pass;
+  if ( !_user.isEmpty() )
+  {
+      user = _user;
+      pass = _pass.isEmpty() ? QString::null:_pass;
+  }
+  else
+  {
+      user = FTP_LOGIN;
+      pass = FTP_PASSWD;
+  }
+
+  m_proxyURL = metaData("UseProxy");
+  kdDebug(7102) << "Proxy URL: " << m_proxyURL.url() << endl;
+  m_bUseProxy = ( m_proxyURL.isValid() &&
+                  m_proxyURL.protocol() == QString::fromLatin1("ftp") );
+
+  if ( m_host != _host || m_port != _port ||
+       m_user != user || m_pass != pass )
+      closeConnection( );
+
+  m_host = _host;
+  m_port = _port;
+  m_user = user;
+  m_pass = pass;
+}
+
+void Ftp::openConnection()
+{
+  kdDebug(7102) << "openConnection " << m_host << ":" << m_port << " " << m_user << " [password hidden]" << endl;
+
+  infoMessage( i18n("Opening connection to host <b>%1</b>").arg(m_host) );
+
+  if ( m_host.isEmpty() )
+  {
+    error( ERR_UNKNOWN_HOST, QString::null );
+    return;
+  }
+
+  assert( !m_bLoggedOn );
+
+  m_initialPath = QString::null;
+
+  QString host = m_bUseProxy ? m_proxyURL.host() : m_host;
+  unsigned short int port = m_bUseProxy ? m_proxyURL.port() : m_port;
+
+  if (!connect( host, port ))
+    return; // error emitted by connect
+
+  m_bFtpStarted = true;
+
+  infoMessage( i18n("Connected to host <b>%1</b>").arg(m_host) );
+  kdDebug(7102) << "Connected ...." << endl;
+
+  m_bLoggedOn = ftpLogin();
+  if ( !m_bLoggedOn )
+    return; // error emitted by ftpLogin
+
+  connected();
+}
+
+
+/**
+ * Called by @ref openConnection. It opens the control connection to the ftp server.
+ *
+ * @return true on success.
+ */
+bool Ftp::connect( const QString &host, unsigned short int port )
+{
+  if ( port == 0 ) {
+      struct servent *pse;
+      if ( ( pse = getservbyname( "ftp", "tcp" ) ) == NULL )
+          port = 21;
+      else
+          port = ntohs(pse->s_port);
+  }
+  int on = 1;
+  // require an Internet Socket
+  ksControl = new KExtendedSocket(host, port, KExtendedSocket::inetSocket);
+  if (ksControl == NULL)
+    {
+      error( ERR_OUT_OF_MEMORY, QString::null );
+      return false;
+    }
+  if (ksControl->connect() < 0)
+    {
+      if (ksControl->status() == IO_LookupError)
+	error(ERR_UNKNOWN_HOST, host);
+      else
+	error(ERR_COULD_NOT_CONNECT, host);
+      delete ksControl;
+      ksControl = NULL;
+      return false;
+    }
+  sControl = ksControl->fd();
+
+  if ( setsockopt( sControl, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on) ) == -1 )
+  {
+    //    ::close( sControl );
+    delete ksControl;
+    ksControl = NULL;
+    error( ERR_COULD_NOT_CREATE_SOCKET, host );
+    return false;
+  }
+
+  nControl = (netbuf*)calloc(1,sizeof(netbuf));
+  if (nControl == NULL)
+  {
+    //    ::close( sControl );
+    delete ksControl;
+    ksControl = NULL;
+    error( ERR_OUT_OF_MEMORY, QString::null );
+    return false;
+  }
+  nControl->handle = sControl;
+
+  if ( readresp() != '2' )
+  {
+    //    ::close( sControl );
+    delete ksControl;
+    ksControl = NULL;
+    free( nControl );
+    error( ERR_COULD_NOT_CONNECT, host );
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Called by @ref openConnection. It logs us in.
+ * @ref m_initialPath is set to the current working directory
+ * if logging on was successfull.
+ *
+ * @return true on success.
+ */
+bool Ftp::ftpLogin()
+{
+  infoMessage( i18n("Sending login information") );
+
+  assert( !m_bLoggedOn );
+
+  QString user = m_user;
+  QString pass = m_pass;
+
+  if ( config()->readBoolEntry("EnableAutoLogin") )
+  {
+    QString au = config()->readEntry("autoLoginUser");
+    if ( !au.isEmpty() )
+    {
+        user = au;
+        pass = config()->readEntry("autoLoginPass");
+    }
+  }
+  kdDebug(7102) << "ftpLogin " << user << endl;
+
+  if ( !user.isEmpty() )
+  {
+    AuthInfo info;
+    QCString tempbuf;
+    int failedAuth = 0;
+
+    // Construct the URL to be used as key for caching.
+    info.url.setProtocol( QString::fromLatin1("ftp") );
+    info.url.setHost( m_host );
+    info.url.setPort( m_port );
+
+    while ( ++failedAuth )
+    {
+      // Ask user if we should retry after login failure.
+      if( failedAuth > 2 )
+      {
+        info.prompt = i18n("Login Failed! Do you want to retry ?");
+        if( messageBox(QuestionYesNo, info.prompt, i18n("Authorization")) != 3 )
+        {
+          kdDebug(7102) << "Login aborted by user!" << endl;
+          error( ERR_USER_CANCELED, QString::null);
+          return false;
+        }
+      }
+
+      // Check the cache and/or prompt user for password if 1st
+      // login attempt failed OR the user supplied a login name,
+      // but no password.
+      if ( failedAuth > 1 || (!user.isEmpty() && pass.isEmpty()) )
+      {
+        if ( user != FTP_LOGIN && pass != FTP_PASSWD )
+            info.username = m_user;
+        if ( failedAuth < 3 && checkCachedAuthentication( info ) )
+        {
+          user = info.username;
+          pass = info.password;
+        }
+        else
+        {
+          info.prompt = i18n("You need to supply a username and a password "
+                             "to access this site.");
+          info.commentLabel = i18n( "Site:" );
+          info.comment = i18n("<b>%1</b>").arg( m_host );
+
+          bool disablePassDlg = config()->readBoolEntry( "DisablePassDlg", false );
+          if ( disablePassDlg || !openPassDlg( info ) )
+          {
+            error( ERR_USER_CANCELED, m_host );
+            return false;
+          }
+          else
+          {
+            user = info.username;
+            pass = info.password;
+          }
+        }
+      }
+
+      tempbuf = "user ";
+      tempbuf += user.latin1();
+      if ( m_bUseProxy )
+      {
+        tempbuf += '@';
+        tempbuf += m_host.latin1();
+        if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
+        {
+          tempbuf += ':';
+          tempbuf += QString::number(m_port).latin1();
+        }
+      }
+      kdDebug(7102) << "Sending Login name: " << user << endl;
+      bool loggedIn = (ftpSendCmd( tempbuf, '2' ) &&
+                       !strncmp( rspbuf, "230", 3));
+      bool needPass = !strncmp( rspbuf, "331", 3);
+      // Prompt user for login info if we do not
+      // get back a "230" or "331".
+      if ( !loggedIn && !needPass )
+      {
+        kdDebug(7102) << "1> " << rspbuf << endl;
+        continue;  // Well we failed, prompt the user please!!
+      }
+
+      if( needPass )
+      {
+        tempbuf = "pass ";
+        tempbuf += pass.latin1();
+        kdDebug(7102) << "Sending Login password: " << "[protected]" << endl;
+        loggedIn = (ftpSendCmd( tempbuf, '2' ) && !strncmp(rspbuf, "230", 3));
+      }
+
+      if ( loggedIn )
+      {
+        // Do not cache the default login!!
+        if( user != FTP_LOGIN && pass != FTP_PASSWD )
+          cacheAuthentication( info );
+        failedAuth = -1;
+      }
+    }
+  }
+
+  kdDebug(7102) << "Login OK" << endl;
+  infoMessage( i18n("Login OK") );
+
+  // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix:
+  // Thanks to jk@soegaard.net (Jens Kristian Søgaard) for this hint
+  if( ftpSendCmd( "syst", '2' ) )
+  {
+    if( !strncmp( rspbuf, "215 Windows_NT version", 22 ) ) // should do for any version
+    {
+      (void)ftpSendCmd( "site dirstyle", '2' );
+      // Check if it was already in Unix style
+      // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk>
+      if( !strncmp( rspbuf, "200 MSDOS-like directory output is on", 37 ))
+         //It was in Unix style already!
+         (void)ftpSendCmd( "site dirstyle", '2' );
+
+    }
+  }
+  else
+    kdWarning(7102) << "syst failed" << endl;
+
+
+  QString macro = metaData( "autoLoginMacro" );
+  if ( !macro.isEmpty() && config()->readBoolEntry("EnableAutoLoginMacro") )
+  {
+      QStringList list = QStringList::split('\n', macro);
+      if ( !list.isEmpty() )
+      {
+          QStringList::Iterator it = list.begin();
+          for( ; it != list.end() ; ++it )
+          {
+              if ( (*it).find("init") == 0 )
+              {
+                  list = QStringList::split( '\\', macro);
+                  it = list.begin();
+                  ++it;  // ignore the macro name
+                  for( ; it != list.end() ; ++it )
+                  {
+                      // TODO: Add support for arbitrary commands
+                      // besides simply changing directory!!
+                      if ( (*it).startsWith( "cwd" ) )
+                          ftpSendCmd( (*it).latin1(), '2' );
+                  }
+                  break;
+              }
+          }
+      }
+  }
+
+  // Get the current working directory
+  kdDebug(7102) << "Searching for pwd" << endl;
+  if ( !ftpSendCmd( "pwd", '2' ) )
+  {
+    kdDebug(7102) << "Couldn't issue pwd command" << endl;
+    error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.").arg(m_host) ); // or anything better ?
+    return false;
+  }
+
+  kdDebug(7102) << "2> " << rspbuf << endl;
+
+  char *p = strchr( rspbuf+3, '"' ); // Look for first "
+  if ( p != 0 )
+  {
+    char *p2 = strchr( p + 1, '"' ); // Look for second "
+    if ( p2 != 0 )
+    {
+        *p2 = '\0';
+        m_initialPath = p + 1;
+        if ( *(p+1) != '/' ) // safety check, for servers that return C:/TEMP/
+          m_initialPath.prepend('/');
+        kdDebug(7102) << "Initial path set to: " << m_initialPath << endl;
+    }
+  }
+  return true;
+}
+
+
+/**
+ * ftpSendCmd - send a command (@p cmd) and read response
+ *
+ * @param expresp the expected first char. '\001' for no check
+ * @param maxretries number of time it should retry. Since it recursively
+ * calls itself if it can't read the answer (this happens especially after
+ * timeouts), we need to limit the recursiveness ;-)
+ *
+ * return true if proper response received, false on error
+ * or if @p expresp doesn't match
+ */
+bool Ftp::ftpSendCmd( const QCString& cmd, char expresp, int maxretries )
+{
+  assert( sControl > 0 );
+
+  QCString buf = cmd;
+  buf += "\r\n";
+
+  if ( cmd.left(4).lower() != "pass" ) // don't print out the password
+    kdDebug(7102) << cmd.data() << endl;
+
+  if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 )  {
+    error( ERR_COULD_NOT_WRITE, QString::null );
+    return false;
+  }
+
+  char rsp = readresp();
+  if (!rsp || ( rsp == '4' && rspbuf[1] == '2' && rspbuf[2] == '1' ))
+  {
+      kdDebug() << "got 421 -> timeout" << endl;
+    // 421 is "421 No Transfer Timeout (300 seconds): closing control connection"
+    if ( cmd=="list" && maxretries > 0 ) // Only retry for "list". retr/stor/... need to redo the whole thing
+    {
+      // It might mean a timeout occured, let's try logging in again
+      m_bLoggedOn = false;
+      kdDebug(7102) << "Couldn't read answer - perhaps timeout - trying logging in again" << endl;
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return false;
+      }
+      kdDebug(7102) << "Logged back in, reissuing command" << endl;
+      // On success, try the command again
+      return ftpSendCmd( cmd, expresp, maxretries - 1 );
+    } else
+    {
+      error( ERR_SERVER_TIMEOUT, m_host );
+      return false;
+    }
+  }
+  return (expresp == 0) || (rsp == expresp);
+}
+
+/*
+ * ftpOpenPASVDataConnection - set up data connection, using PASV mode
+ *
+ * return 1 if successful, 0 otherwise
+ * doesn't set error message, since non-pasv mode will always be tried if
+ * this one fails
+ */
+bool Ftp::ftpOpenPASVDataConnection()
+{
+  int i[6], j;
+  unsigned char n[6];
+  int on=1;
+  struct linger lng = { 1, 120 };
+  KExtendedSocket ks;
+  const KSocketAddress *sa = ksControl->peerAddress();
+  QString host;
+
+  // Check that we can do PASV
+  if (sa != NULL && sa->family() != PF_INET)
+    return false;		// no PASV for non-PF_INET connections
+
+  m_bPasv = true;
+
+  /* Let's PASsiVe*/
+  if (!(ftpSendCmd("PASV",'2')))
+  {
+    return false;
+  }
+
+  // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)'
+  // but anonftpd gives '227 =160,39,200,55,6,245'
+  char *start = strchr(rspbuf,'(');
+  if ( !start )
+    start = strchr(rspbuf,'=');
+  if ( !start ||
+       ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
+         sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
+  {
+    kdError(7102) << "parsing IP and port numbers failed. String parsed: " << start << endl;
+    return false;
+  }
+
+  for (j=0; j<6; j++)
+  {
+    n[j] = (unsigned char) (i[j] & 0xff);
+  }
+
+  // Make hostname
+  host.sprintf("%d.%d.%d.%d", i[0], i[1], i[2], i[3]);
+  // port number is given in network byte order
+  ks.setAddress(host, ntohs(i[5] << 8 | i[4]));
+  ks.setSocketFlags(KExtendedSocket::noResolve);
+
+  if (ks.connect() < 0)
+    {
+      return false;
+    }
+
+  sDatal = ks.fd();
+  if ( (setsockopt( sDatal,SOL_SOCKET,SO_REUSEADDR,(char*)&on, sizeof(on) ) == -1)
+       || (sDatal < 0) )
+  {
+    return false;
+  }
+
+  if ( setsockopt(sDatal, SOL_SOCKET,SO_KEEPALIVE, (char *) &on, (int) sizeof(on)) < 0 )
+    kdError(7102) << "Keepalive not allowed" << endl;
+  if ( setsockopt(sDatal, SOL_SOCKET,SO_LINGER, (char *) &lng,(int) sizeof (lng)) < 0 )
+    kdError(7102) << "Linger mode was not allowed." << endl;
+
+  ks.release();
+  return true;
+}
+
+/*
+ * ftpOpenEPSVDataConnection - opens a data connection via EPSV
+ */
+bool Ftp::ftpOpenEPSVDataConnection()
+{
+  // for SO_LINGER
+  int on=1;
+  struct linger lng = { 1, 120 };
+
+  KExtendedSocket ks;
+  const KSocketAddress *sa = ksControl->peerAddress();
+  int portnum;
+  // we are sure sa is a KInetSocketAddress, because we asked for KExtendedSocket::inetSocket
+  // when we connected
+  const KInetSocketAddress *sin = static_cast<const KInetSocketAddress*>(sa);
+
+  if (m_extControl & epsvUnknown || sa == NULL)
+    return false;
+
+  m_bPasv = true;
+  if (!(ftpSendCmd("EPSV", '2')))
+    {
+      // unknown command?
+      if (rspbuf[0] == '5')
+	{
+	  kdDebug(7102) << "disabling use of EPSV" << endl;
+	  m_extControl |= epsvUnknown;
+	}
+      return false;
+    }
+
+  char *start = strchr(rspbuf,'|');
+  if ( !start ||
+       sscanf(rspbuf, "|||%d|", &portnum) != 1)
+    {
+      // invalid response?
+      return false;
+    }
+
+  ks.setSocketFlags(KExtendedSocket::noResolve);
+  ks.setAddress(sin->nodeName(), portnum);
+
+  if (ks.connect() < 0)
+    {
+      return false;
+    }
+
+  sDatal = ks.fd();
+  if ( (setsockopt( sDatal,SOL_SOCKET,SO_REUSEADDR,(char*)&on, sizeof(on) ) == -1)
+       || (sDatal < 0) )
+  {
+    return false;
+  }
+
+  if ( setsockopt(sDatal, SOL_SOCKET,SO_KEEPALIVE, (char *) &on, (int) sizeof(on)) < 0 )
+    kdError(7102) << "Keepalive not allowed" << endl;
+  if ( setsockopt(sDatal, SOL_SOCKET,SO_LINGER, (char *) &lng,(int) sizeof (lng)) < 0 )
+    kdError(7102) << "Linger mode was not allowed." << endl;
+
+  ks.release();
+  return true;
+}
+
+/*
+ * ftpOpenEPRTDataConnection
+ */
+bool Ftp::ftpOpenEPRTDataConnection()
+{
+  KExtendedSocket ks;
+  // yes, we are sure this is a KInetSocketAddress
+  const KInetSocketAddress *sin = static_cast<const KInetSocketAddress*>(ksControl->localAddress());
+
+  m_bPasv = false;
+
+  if (m_extControl & eprtUnknown || sin == NULL)
+    return false;
+  ks.setHost(sin->nodeName());
+  ks.setPort(0);		// setting port to 0 will make us bind to a random, free port
+  ks.setSocketFlags(KExtendedSocket::noResolve | KExtendedSocket::passiveSocket |
+		    KExtendedSocket::inetSocket);
+
+  if (ks.listen(1) < 0)
+    {
+      error(ERR_COULD_NOT_LISTEN, m_host);
+      return false;
+    }
+
+  sin = static_cast<const KInetSocketAddress*>(ks.localAddress());
+  if (sin == NULL)
+    // error ?
+    return false;
+
+  //  QString command = QString::fromLatin1("eprt |%1|%2|%3|").arg(sin->ianaFamily())
+  //  .arg(sin->nodeName())
+  //  .arg(sin->port());
+  QCString command;
+  command.sprintf("eprt |%d|%s|%d|", sin->ianaFamily(),
+		  sin->nodeName().latin1(), sin->port());
+
+  // FIXME! Encoding for hostnames?
+  if (!ftpSendCmd(command, '2'))
+    {
+      // unknown command?
+      if (rspbuf[0] == '5')
+	{
+	  kdDebug(7102) << "disabling use of EPRT" << endl;
+	  m_extControl |= eprtUnknown;
+	}
+      return false;
+    }
+
+  sDatal = ks.fd();
+  ks.release();
+  return true;
+}
+
+
+/*
+ * ftpOpenDataConnection - set up data connection
+ *
+ * return 1 if successful, 0 otherwise
+ */
+bool Ftp::ftpOpenDataConnection()
+{
+  assert( m_bLoggedOn );
+
+  union
+  {
+    struct sockaddr sa;
+    struct sockaddr_in in;
+  } sin;
+
+  struct linger lng = { 0, 0 };
+  socklen_t l;
+  char buf[64];
+  int on = 1;
+
+  ////////////// First try passive (EPSV & PASV) modes
+  if ( config()->readBoolEntry( "DisablePassiveMode", false ) == false )
+  {
+    if (ftpOpenEPSVDataConnection())
+      return true;
+    if (ftpOpenPASVDataConnection())
+      return true;
+
+    // if we sent EPSV ALL already and it was accepted, then we can't
+    // use active connections any more
+    if (m_extControl & epsvAllSent)
+      return false;
+
+    if (ftpOpenEPRTDataConnection())
+      return true;
+  }
+  ////////////// Fallback : PORT mode
+  m_bPasv = false;
+
+  l = sizeof(sin);
+  if ( getsockname( sControl, &sin.sa, &l ) < 0 )
+    return false;
+  if (sin.sa.sa_family != PF_INET)
+    return false;		// wrong family
+
+  sDatal = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
+  if ( sDatal == 0 )
+  {
+    error( ERR_COULD_NOT_CREATE_SOCKET, QString::null );
+    return false;
+  }
+  if ( setsockopt( sDatal, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on) ) == -1 )
+  {
+    ::close( sDatal );
+    error( ERR_COULD_NOT_CREATE_SOCKET, QString::null );
+    return false;
+  }
+  if ( setsockopt( sDatal, SOL_SOCKET, SO_LINGER, (char*)&lng, sizeof(lng) ) == -1 )
+  {
+    ::close( sDatal );
+    error( ERR_COULD_NOT_CREATE_SOCKET, QString::null );
+    return false;
+  }
+
+  sin.in.sin_port = 0;
+  if ( bind( sDatal, &sin.sa, sizeof(sin) ) == -1 )
+  {
+    ::close( sDatal );
+    sDatal = 0;
+    error( ERR_COULD_NOT_BIND, m_host );
+    return false;
+  }
+
+  if ( listen( sDatal, 1 ) < 0 )
+  {
+    error( ERR_COULD_NOT_LISTEN, m_host );
+    ::close( sDatal );
+    sDatal = 0;
+    return 0;
+  }
+
+  if ( getsockname( sDatal, &sin.sa, &l ) < 0 )
+    // error ?
+    return false;
+
+  sprintf(buf,"port %d,%d,%d,%d,%d,%d",
+          (unsigned char)sin.sa.sa_data[2],(unsigned char)sin.sa.sa_data[3],
+          (unsigned char)sin.sa.sa_data[4],(unsigned char)sin.sa.sa_data[5],
+          (unsigned char)sin.sa.sa_data[0],(unsigned char)sin.sa.sa_data[1]);
+
+  return ftpSendCmd( buf, '2' );
+}
+
+
+/*
+ * ftpAcceptConnect - wait for incoming connection
+ * Used by @ref ftpOpenCommand
+ *
+ * return -2 on error or timeout
+ * otherwise returns socket descriptor
+ */
+int Ftp::ftpAcceptConnect()
+{
+  struct sockaddr addr;
+  int sData;
+  socklen_t l;
+  fd_set mask;
+
+  FD_ZERO(&mask);
+  FD_SET(sDatal,&mask);
+
+  if ( m_bPasv )
+    return sDatal;
+  if ( select( sDatal + 1, &mask, NULL, NULL, 0L ) == 0)
+  {
+    ::close( sDatal );
+    return -2;
+  }
+
+  l = sizeof(addr);
+  if ( ( sData = accept( sDatal, &addr, &l ) ) > 0 )
+    return sData;
+
+  ::close( sDatal );
+  return -2;
+}
+
+bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
+                          int errorcode, unsigned long _offset )
+{
+  QCString buf = "type ";
+  buf += _mode;
+
+  if ( !ftpSendCmd( buf, '2' ) )
+  {
+    error( ERR_COULD_NOT_CONNECT, QString::null );
+    return false;
+  }
+  if ( !ftpOpenDataConnection() )
+  {
+    error( ERR_COULD_NOT_CONNECT, QString::null );
+    return false;
+  }
+
+  if ( _offset > 0 ) {
+    // send rest command if offset > 0, this applies to retr and stor commands
+    char buf[100];
+    sprintf(buf, "rest %ld", _offset);
+    if ( !ftpSendCmd( buf, '3' ) ) {
+      if ( rspbuf[0] != '3' ) // other errors were already emitted
+        {
+          error( ERR_CANNOT_RESUME, _path ); // should never happen
+          return false;
+        }
+    }
+  }
+
+  QCString tmp = _command;
+
+  if ( !_path.isEmpty() ) {
+    tmp += " ";
+    tmp += _path.ascii();
+  }
+
+  if ( !ftpSendCmd( tmp, '1' ) ) {
+    if ( _offset > 0 && strcmp(_command, "retr") == 0 && rspbuf[0] == '4')
+    {
+      // Failed to resume
+      errorcode = ERR_CANNOT_RESUME;
+    }
+    // The error here depends on the command
+    error( errorcode, _path );
+    return false;
+  }
+
+  // Only now we know for sure that we can resume
+  if ( _offset > 0 && strcmp(_command, "retr") == 0 )
+    canResume();
+
+  if ( ( sData = ftpAcceptConnect() ) < 0 )
+  {
+    error( ERR_COULD_NOT_ACCEPT, QString::null );
+    return false;
+  }
+
+  return true;
+}
+
+
+void Ftp::closeSockets()
+{
+  if( sData != 0 )
+  {
+    shutdown( sData, 2 );
+    ::close( sData );
+    sData = 0;
+  }
+
+  if( sDatal != 0 )
+  {
+    ::close( sDatal );
+    sDatal = 0;
+  }
+}
+
+bool Ftp::ftpCloseCommand()
+{
+  kdDebug(7102) << "Ftp::ftpCloseCommand" << endl;
+  // first close data sockets (if opened), then read response that
+  // we got for whatever was used in ftpOpenCommand ( should be 226 )
+  closeSockets();
+  if ( readresp() != '2' )
+  {
+    kdDebug(7102) << "Did not get transfer complete message" << endl;
+    return false;
+  }
+  return true;
+}
+
+#if 0
+void Ftp::mkdir( const KURL & url, int permissions )
+{
+  QString path = url.path();
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  assert( m_bLoggedOn );
+
+  QCString buf = "mkd ";
+  buf += path.latin1();
+
+  if ( ! ftpSendCmd( buf, '2' ) )
+  {
+    error( ERR_COULD_NOT_MKDIR, path );
+    return;
+  }
+
+  if ( permissions != -1 )
+  {
+    // chmod the dir we just created, ignoring errors.
+    (void) ftpChmod( path, permissions );
+  }
+
+  finished();
+}
+
+void Ftp::rename( const KURL& src, const KURL& dst, bool overwrite )
+{
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  // The actual functionality is in ftpRename because put needs it
+  if ( ftpRename( src.path(), dst.path(), overwrite ) )
+    finished();
+  else
+    error( ERR_CANNOT_RENAME, src.path() );
+}
+
+bool Ftp::ftpRename( const QString & src, const QString & dst, bool /* overwrite */ )
+{
+  // TODO honor overwrite
+  assert( m_bLoggedOn );
+
+  QCString cmd;
+  cmd = "RNFR ";
+  cmd += src.ascii();
+  if ( !ftpSendCmd( cmd, '3') )
+    return false;
+  cmd = "RNTO ";
+  cmd += dst.ascii();
+  return ftpSendCmd( cmd, '2' );
+}
+
+void Ftp::del( const KURL& url, bool isfile )
+{
+  QString path = url.path();
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  assert( m_bLoggedOn );
+
+  if ( !isfile )
+  {
+    // When deleting a directory, we must exit from it first
+    // The last command probably went into it (to stat it)
+    QCString tmp = "cwd ";
+    tmp += url.directory().ascii();
+
+    (void) ftpSendCmd( tmp, '2' );
+    // ignore errors
+  }
+
+  QCString cmd = isfile ? "DELE " : "RMD ";
+  cmd += path.ascii();
+
+  if ( !ftpSendCmd( cmd, '2' ) )
+    error( ERR_CANNOT_DELETE, path );
+  else
+    finished();
+}
+
+bool Ftp::ftpChmod( const QString & path, int permissions )
+{
+  assert( m_bLoggedOn );
+
+  QCString cmd = "SITE CHMOD ";
+
+  char buf[10];
+  // we need to do bit AND 777 to get permissions, in case
+  // we were sent a full mode (unlikely)
+  sprintf(buf, "%o ", permissions & 511 );
+
+  cmd += buf;
+  cmd += path.ascii();
+
+  return ftpSendCmd( cmd, '2' );
+}
+
+void Ftp::chmod( const KURL & url, int permissions )
+{
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  if ( !ftpChmod( url.path(), permissions ) )
+    error( ERR_CANNOT_CHMOD, url.path() );
+  else
+    finished();
+}
+
+void Ftp::createUDSEntry( const QString & filename, FtpEntry * e, UDSEntry & entry, bool isDir )
+{
+  assert(entry.count() == 0); // by contract :-)
+  UDSAtom atom;
+  atom.m_uds = UDS_NAME;
+  atom.m_str = filename;
+  entry.append( atom );
+
+  atom.m_uds = UDS_SIZE;
+  atom.m_long = e->size;
+  entry.append( atom );
+
+  atom.m_uds = UDS_MODIFICATION_TIME;
+  atom.m_long = e->date;
+  entry.append( atom );
+
+  atom.m_uds = UDS_ACCESS;
+  atom.m_long = e->access;
+  entry.append( atom );
+
+  atom.m_uds = UDS_USER;
+  atom.m_str = e->owner;
+  entry.append( atom );
+
+  if ( !e->group.isEmpty() )
+  {
+    atom.m_uds = UDS_GROUP;
+    atom.m_str = e->group;
+    entry.append( atom );
+  }
+
+  if ( !e->link.isEmpty() )
+  {
+    atom.m_uds = UDS_LINK_DEST;
+    atom.m_str = e->link;
+    entry.append( atom );
+    isDir = true;
+
+#if 0
+    KMimeType::Ptr mime = KMimeType::findByURL( KURL(QString::fromLatin1("ftp://host/") + filename ) );
+    // Links on ftp sites are often links to dirs, and we have no way to check
+    // that. Let's do like Netscape : assume dirs generally.
+    // But we do this only when the mimetype can't be known from the filename.
+    // --> we do better than Netscape :-)
+    if ( mime->name() == KMimeType::defaultMimeType() )
+    {
+      kdDebug() << "Setting guessed mime type to inode/directory for " << filename << endl;
+      atom.m_uds = UDS_GUESSED_MIME_TYPE;
+      atom.m_str = "inode/directory";
+      entry.append( atom );
+      isDir = true;
+    }
+#endif
+  }
+
+  atom.m_uds = UDS_FILE_TYPE;
+  atom.m_long = isDir ? S_IFDIR : e->type;
+  entry.append( atom );
+
+  /* atom.m_uds = UDS_ACCESS_TIME;
+     atom.m_long = buff.st_atime;
+     entry.append( atom );
+
+     atom.m_uds = UDS_CREATION_TIME;
+     atom.m_long = buff.st_ctime;
+     entry.append( atom ); */
+}
+
+void Ftp::stat( const KURL &url)
+{
+  kdDebug(7102) << "Ftp::stat : path='" << url.path() << "'" << endl;
+  QString path = QDir::cleanDirPath( url.path() );
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  kdDebug(7102) << "Ftp::stat : cleaned path='" << path << "'" << endl;
+
+  // We can't stat root, but we know it's a dir.
+  if ( path.isEmpty() || path == QString::fromLatin1("/") ) {
+    UDSEntry entry;
+    UDSAtom atom;
+
+    atom.m_uds = KIO::UDS_NAME;
+    atom.m_str = QString::null;
+    entry.append( atom );
+
+    atom.m_uds = KIO::UDS_FILE_TYPE;
+    atom.m_long = S_IFDIR;
+    entry.append( atom );
+
+    atom.m_uds = KIO::UDS_ACCESS;
+    atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+    entry.append( atom );
+
+    atom.m_uds = KIO::UDS_USER;
+    atom.m_str = "root";
+    entry.append( atom );
+    atom.m_uds = KIO::UDS_GROUP;
+    entry.append( atom );
+
+    // no size
+
+    statEntry( entry );
+    finished();
+    return;
+  }
+
+  KURL tempurl( url );
+  tempurl.setPath( path ); // take the clean one
+  QString listarg; // = tempurl.directory(false /*keep trailing slash*/);
+  QString parentDir;
+  QString filename = tempurl.fileName();
+  ASSERT(!filename.isEmpty());
+  QString search = filename;
+  bool isDir = false;
+
+  // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info)
+  // if it doesn't work, it's a file (and then we'll use dir filename)
+  QCString tmp = "cwd ";
+  tmp += path.latin1();
+  if ( !ftpSendCmd( tmp, '\0' /* no builtin response check */ ) )
+  {
+    kdDebug(7102) << "stat: ftpSendCmd returned false" << endl;
+    // error already emitted, if e.g. transmission failure
+    return;
+  }
+
+  // TODO: if we're only interested in "file or directory", we should stop here
+
+  if ( rspbuf[0] == '5' )
+  {
+    // It is a file or it doesn't exist, try going to parent directory
+    parentDir = tempurl.directory(false /*keep trailing slash*/);
+    // With files we can do "LIST <filename>" to avoid listing the whole dir
+    listarg = filename;
+  }
+  else
+  {
+    // --- New implementation:
+    // Don't list the parent dir. Too slow, might not show it, etc.
+    // Just return that it's a dir.
+    UDSEntry entry;
+    UDSAtom atom;
+
+    atom.m_uds = KIO::UDS_NAME;
+    atom.m_str = filename;
+    entry.append( atom );
+
+    atom.m_uds = KIO::UDS_FILE_TYPE;
+    atom.m_long = S_IFDIR;
+    entry.append( atom );
+
+    atom.m_uds = KIO::UDS_ACCESS;
+    atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+    entry.append( atom );
+
+    // No clue about size, ownership, group, etc.
+
+    statEntry(entry);
+    finished();
+    return;
+
+    // --- Old implementation:
+#if 0
+    // It's a dir, remember that
+    // Reason: it could be a symlink to a dir, in which case ftpReadDir
+    // in the parent dir will have no idea about that. But we know better.
+    isDir = true;
+    // If the dir starts with '.', we'll need '-a' to see it in the listing.
+    if ( search[0] == '.' )
+       listarg = "-a";
+    parentDir = "..";
+#endif
+  }
+
+  // Now cwd the parent dir, to prepare for listing
+  tmp = "cwd ";
+  tmp += parentDir.latin1();
+  if ( !ftpSendCmd( tmp, '2' ) )
+  {
+    kdDebug(7102) << "stat: Could not go to parent directory" << endl;
+    // error already emitted
+    return;
+  }
+
+  if( !ftpOpenCommand( "list", listarg, 'A', ERR_DOES_NOT_EXIST ) )
+  {
+    kdError(7102) << "COULD NOT LIST" << endl;
+    return;
+  }
+
+  dirfile = fdopen( sData, "r" );
+  if( !dirfile ) {
+    error( ERR_DOES_NOT_EXIST, path );
+    return;
+  }
+
+  kdDebug(7102) << "Starting of list was ok" << endl;
+
+  ASSERT( !search.isEmpty() && search != QString::fromLatin1("/") );
+
+  FtpEntry *e;
+  bool bFound = false;
+  KURL linkURL;
+  while( ( e = ftpReadDir() ) )
+  {
+    // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at)
+    // return only the filename when doing "dir /full/path/to/file"
+    if ( !bFound )
+    {
+        if ( ( search == e->name || filename == e->name ) ) {
+            if ( !filename.isEmpty() ) {
+              bFound = true;
+              UDSEntry entry;
+              createUDSEntry( filename, e, entry, isDir );
+              statEntry( entry );
+            }
+        } else if ( isDir && ( e->name == listarg || e->name+'/' == listarg ) ) {
+            // Damn, the dir we're trying to list is in fact a symlink
+            // Follow it and try again
+            if ( e->link.isEmpty() )
+                kdWarning(7102) << "Got " << listarg << " as answer, but empty link !" << endl;
+            else
+            {
+                linkURL = url;
+                kdDebug() << "e->link=" << e->link << endl;
+                if ( e->link[0] == '/' )
+                    linkURL.setPath( e->link ); // Absolute link
+                else
+                {
+                    // Relative link (stat will take care of cleaning ../.. etc.)
+                    linkURL.setPath( listarg ); // this is what we were listing (the link)
+                    linkURL.setPath( linkURL.directory() ); // go up one dir
+                    linkURL.addPath( e->link ); // replace link by its destination
+                    kdDebug() << "linkURL now " << linkURL.prettyURL() << endl;
+                }
+                // Re-add the filename we're looking for
+                linkURL.addPath( filename );
+            }
+            bFound = true;
+        }
+    }
+
+    kdDebug(7102) << e->name << endl;
+  }
+
+  (void) ftpCloseDir();
+
+  if ( !bFound )
+  {
+    // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source")
+    // When e.g. uploading a file, we still need stat() to return "not found"
+    // when the file doesn't exist.
+    QString statSide = metaData(QString::fromLatin1("statSide"));
+    kdDebug() << "Ftp::stat statSide=" << statSide << endl;
+    if ( statSide == "source" )
+    {
+	kdDebug() << "Not found, but assuming found, because some servers don't allow listing" << endl;
+        // MS Server is incapable of handling "list <blah>" in a case insensitive way
+        // But "retr <blah>" works. So lie in stat(), to get going...
+	//
+	// There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run
+	// where listing permissions are denied, but downloading is still possible.
+        UDSEntry entry;
+        UDSAtom atom;
+
+        atom.m_uds = KIO::UDS_NAME;
+        atom.m_str = filename;
+        entry.append( atom );
+
+        atom.m_uds = KIO::UDS_FILE_TYPE;
+        atom.m_long = S_IFREG;
+        entry.append( atom );
+
+        atom.m_uds = KIO::UDS_ACCESS;
+        atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+        entry.append( atom );
+
+        // No clue about size, ownership, group, etc.
+
+        statEntry(entry);
+        finished();
+        return;
+    }
+    error( ERR_DOES_NOT_EXIST, path );
+    return;
+  }
+
+  if ( !linkURL.isEmpty() )
+  {
+      if ( linkURL == url || linkURL == tempurl )
+      {
+          error( ERR_CYCLIC_LINK, linkURL.prettyURL() );
+          return;
+      }
+      stat( linkURL );
+      return;
+  }
+
+  kdDebug(7102) << "stat : finished successfully" << endl;
+  finished();
+}
+
+
+void Ftp::listDir( const KURL &url )
+{
+  kdDebug(7102) << "Ftp::listDir " << url.prettyURL() << endl;
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  QString path = url.path();
+  // No path specified ?
+  if ( path.isEmpty() )
+  {
+    KURL realURL;
+    realURL.setProtocol( QString::fromLatin1("ftp") );
+    if ( m_user != FTP_LOGIN )
+      realURL.setUser( m_user );
+    // We set the password, so that we don't ask for it if it was given
+    if ( m_pass != FTP_PASSWD )
+      realURL.setPass( m_pass );
+    realURL.setHost( m_host );
+    realURL.setPort( m_port );
+    if ( m_initialPath.isEmpty() )
+        m_initialPath = "/";
+    realURL.setPath( m_initialPath );
+    kdDebug(7102) << "REDIRECTION to " << realURL.prettyURL() << endl;
+    redirection( realURL.url() );
+    path = m_initialPath;
+    finished();
+    return;
+  }
+
+  kdDebug(7102) << "hunting for path '" << path << "'" << endl;
+
+  if (!ftpOpenDir( path ) )
+  {
+    if ( ftpSize( path, 'I' ) ) // is it a file ?
+    {
+      error( ERR_IS_FILE, path );
+      return;
+    }
+    // not sure which to emit
+    //error( ERR_DOES_NOT_EXIST, path );
+    error( ERR_CANNOT_ENTER_DIRECTORY, path );
+    return;
+  }
+
+  UDSEntry entry;
+  FtpEntry * e;
+  while( ( e = ftpReadDir() ) )
+  {
+    kdDebug(7102) << e->name << endl;
+    ASSERT( !e->name.isEmpty() );
+    if ( !e->name.isEmpty() )
+    {
+      //if ( S_ISDIR( (mode_t)e->type ) )
+      //   kdDebug(7102) << "is a dir" << endl;
+      //if ( !e->link.isEmpty() )
+      //   kdDebug(7102) << "is a link to " << e->link << endl;
+      entry.clear();
+      createUDSEntry( e->name, e, entry, false );
+//      listEntry( entry, false );
+    }
+  }
+//  listEntry( entry, true ); // ready
+
+  (void) ftpCloseDir();
+
+  finished();
+}
+
+void Ftp::slave_status()
+{
+  kdDebug(7102) << "Got slave_status host = " << (m_host.ascii() ? m_host.ascii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]" << endl;
+  slaveStatus( m_host, m_bLoggedOn );
+}
+#endif
+
+bool Ftp::ftpOpenDir( const QString & path )
+{
+  //QString path( _url.path(-1) );
+
+  // We try to change to this directory first to see whether it really is a directory.
+  // (And also to follow symlinks)
+  QCString tmp = "cwd ";
+  tmp += ( !path.isEmpty() ) ? path.latin1() : "/";
+
+  if ( !ftpSendCmd( tmp, '2' ) )
+  {
+    // We get '550', whether it's a file or doesn't exist...
+      return false;
+  }
+
+  // Don't use the path in the list command:
+  // We changed into this directory anyway ("cwd"), so it's enough just to send "list".
+  // We use '-a' because the application MAY be interested in dot files.
+  // The only way to really know would be to have a metadata flag for this...
+  if( !ftpOpenCommand( "list -a", QString::null, 'A', ERR_CANNOT_ENTER_DIRECTORY ) )
+  {
+    kdWarning(7102) << "Can't open for listing" << endl;
+    return false;
+  }
+
+  dirfile = fdopen( sData, "r" );
+  if( !dirfile )
+    return false;
+
+  kdDebug(7102) << "Starting of list was ok" << endl;
+
+  return true;
+}
+
+FtpEntry *Ftp::ftpReadDir()
+{
+  char buffer[1024];
+
+  while( fgets( buffer, sizeof(buffer), dirfile ) != 0 ) {
+    FtpEntry* e = ftpParseDir( buffer );
+    if ( e )
+      return e;
+  }
+  return 0L;
+}
+
+
+FtpEntry* Ftp::ftpParseDir( char* buffer )
+{
+  QString tmp;
+  kdDebug() << "ftpParseDir " << buffer << endl;
+
+  static FtpEntry de;
+  const char *p_access, *p_junk, *p_owner, *p_group;
+  const char *p_size, *p_date_1, *p_date_2, *p_date_3, *p_name;
+  if ((p_access = strtok(buffer," ")) != 0)
+    if ((p_junk = strtok(NULL," ")) != 0)
+      if ((p_owner = strtok(NULL," ")) != 0)
+        if ((p_group = strtok(NULL," ")) != 0)
+          if ((p_size = strtok(NULL," ")) != 0)
+          {
+            // A special hack for "/dev". A listing may look like this:
+            // crw-rw-rw-   1 root     root       1,   5 Jun 29  1997 zero
+            // So we just ignore the number in front of the ",". Ok, its a hack :-)
+            if ( strchr( p_size, ',' ) != 0L )
+	    {
+	      //kdDebug() << "Size contains a ',' -> reading size again (/dev hack)" << endl;
+              if ((p_size = strtok(NULL," ")) == 0)
+                return 0L;
+	    }
+
+            // Check whether the size we just read was really the size
+            // or a month (this happens when the server lists no group)
+            // Test on sunsite.uio.no, for instance
+            if ( !isdigit( *p_size ) )
+            {
+              p_date_1 = p_size;
+              p_size = p_group;
+              p_group = 0;
+	      //kdDebug() << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1 << endl;
+            }
+            else
+	    {
+              p_date_1 = strtok(NULL," ");
+              //kdDebug() << "Size has a digit -> ok. p_date_1=" << p_date_1 << endl;
+	    }
+
+            if ( p_date_1 != 0 )
+              if ((p_date_2 = strtok(NULL," ")) != 0)
+                if ((p_date_3 = strtok(NULL," ")) != 0)
+                  if ((p_name = strtok(NULL,"\r\n")) != 0)
+                  {
+                    if ( p_access[0] == 'l' )
+                    {
+                      tmp = p_name;
+                      int i = tmp.findRev( QString::fromLatin1(" -> ") );
+                      if ( i != -1 ) {
+                        de.link = p_name + i + 4;
+                        tmp.truncate( i );
+                        p_name = tmp.ascii();
+                      }
+                      else
+                        de.link = QString::null;
+                    }
+                    else
+                      de.link = QString::null;
+
+                    de.access = 0;
+                    de.type = S_IFREG;
+                    switch ( p_access[0] ) {
+                        case 'd':
+                            de.type = S_IFDIR;
+                            break;
+                        case 's':
+                            de.type = S_IFSOCK;
+                            break;
+                        case 'b':
+                            de.type = S_IFBLK;
+                            break;
+                        case 'c':
+                            de.type = S_IFCHR;
+                            break;
+                        case 'l':
+                            de.type = S_IFREG;
+                            // we don't set S_IFLNK here.  de.link says it.
+                            break;
+                        default:
+                            break;
+                    }
+
+                    if ( p_access[1] == 'r' )
+                      de.access |= S_IRUSR;
+                    if ( p_access[2] == 'w' )
+                      de.access |= S_IWUSR;
+                    if ( p_access[3] == 'x' )
+                      de.access |= S_IXUSR;
+                    if ( p_access[4] == 'r' )
+                      de.access |= S_IRGRP;
+                    if ( p_access[5] == 'w' )
+                      de.access |= S_IWGRP;
+                    if ( p_access[6] == 'x' )
+                      de.access |= S_IXGRP;
+                    if ( p_access[7] == 'r' )
+                      de.access |= S_IROTH;
+                    if ( p_access[8] == 'w' )
+                      de.access |= S_IWOTH;
+                    if ( p_access[9] == 'x' )
+                      de.access |= S_IXOTH;
+
+                    // maybe fromLocal8Bit would be better in some cases,
+                    // but what proves that the ftp server is in the same encoding
+                    // than the user ??
+                    de.owner    = QString::fromLatin1(p_owner);
+                    de.group    = QString::fromLatin1(p_group);
+                    de.size     = atoi(p_size);
+                    QCString tmp( p_name );
+                    // Some sites put more than one space between the date and the name
+                    // e.g. ftp://ftp.uni-marburg.de/mirror/
+                    de.name     = QString::fromLatin1(tmp.stripWhiteSpace());
+
+                    // Parsing the date is somewhat tricky
+                    // Examples : "Oct  6 22:49", "May 13  1999"
+
+                    // First get current time - we need the current month and year
+                    time_t currentTime = time( 0L );
+                    struct tm * tmptr = gmtime( &currentTime );
+                    int currentMonth = tmptr->tm_mon;
+                    //kdDebug(7102) << "Current time :" << asctime( tmptr ) << endl;
+                    // Reset time fields
+                    tmptr->tm_sec = 0;
+                    tmptr->tm_min = 0;
+                    tmptr->tm_hour = 0;
+                    // Get day number (always second field)
+                    tmptr->tm_mday = atoi( p_date_2 );
+                    // Get month from first field
+                    // NOTE : no, we don't want to use KLocale here
+                    // It seems all FTP servers use the English way
+		    //kdDebug() << "Looking for month " << p_date_1 << endl;
+                    static const char * s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                                                       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+                    for ( int c = 0 ; c < 12 ; c ++ )
+                      if ( !strcmp( p_date_1, s_months[c]) )
+                      {
+			//kdDebug() << "Found month " << c << " for " << p_date_1 << endl;
+                        tmptr->tm_mon = c;
+                        break;
+                      }
+
+                    // Parse third field
+                    if ( strlen( p_date_3 ) == 4 ) // 4 digits, looks like a year
+                      tmptr->tm_year = atoi( p_date_3 ) - 1900;
+                    else
+                    {
+                      // otherwise, the year is implicit
+                      // according to man ls, this happens when it is between than 6 months
+                      // old and 1 hour in the future.
+                      // So the year is : current year if tm_mon <= currentMonth+1
+                      // otherwise current year minus one
+                      // (The +1 is a security for the "+1 hour" at the end of the month issue)
+                      if ( tmptr->tm_mon > currentMonth + 1 )
+                        tmptr->tm_year--;
+
+                      // and p_date_3 contains probably a time
+                      char * semicolon;
+                      if ( ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
+                      {
+                        *semicolon = '\0';
+                        tmptr->tm_min = atoi( semicolon + 1 );
+                        tmptr->tm_hour = atoi( p_date_3 );
+                      }
+                      else
+                        kdWarning(7102) << "Can't parse third field " << p_date_3 << endl;
+                    }
+
+                    //kdDebug(7102) << asctime( tmptr ) << endl;
+                    de.date = mktime( tmptr );
+                    return( &de );
+                  }
+          }
+  return 0L;
+}
+
+
+bool Ftp::ftpCloseDir()
+{
+  if( dirfile )
+  {
+    kdDebug(7102) << "... closing" << endl;
+
+    if ( ! ftpCloseCommand() )
+      return false;
+
+    fclose( dirfile );
+    dirfile = 0L;
+
+  } else
+    kdDebug(7102) << "ftpCloseDir but no dirfile ??" << endl;
+  return true;
+}
+
+//////////// get, put ////////
+
+void Ftp::get( const KURL & url )
+{
+  kdDebug(7102) << "Ftp::get " << url.url() << endl;
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+  if (url.path().isEmpty()) {
+  	getDir(url);
+	return;
+  }
+
+  // try to find the size of the file (and check that it exists at the same time)
+  // 550 is "File does not exist"/"not a plain file"
+  if ( !ftpSize( url.path(), 'I' ) && strncmp( rspbuf, "550", 3) == 0 )
+  {
+      // Not a file, or doesn't exist. We need to find out.
+      QCString tmp = "cwd ";
+      tmp += url.path().latin1();
+      if ( ftpSendCmd( tmp, '2' ) )
+      {
+          // Ok it's a dir in fact
+          kdDebug(7102) << "Ftp::get: it is a directory in fact" << endl;
+	  getDir(url);
+	  return;
+//          error( ERR_IS_DIRECTORY, url.path() );
+      }
+      else
+      {
+          kdDebug(7102) << "Ftp::get: doesn't exist" << endl;
+          error( ERR_DOES_NOT_EXIST, url.path() );
+      }
+      return;
+  }
+
+  unsigned long offset = 0;
+  QString resumeOffset = metaData(QString::fromLatin1("resume"));
+  if ( !resumeOffset.isEmpty() )
+  {
+      offset = resumeOffset.toInt();
+      kdDebug(7102) << "Ftp::get got offset from medata : " << offset << endl;
+  }
+
+  if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, offset ) ) {
+    kdWarning(7102) << "Can't open for reading" << endl;
+    return;
+  }
+
+  // Read the size from the response string
+  if ( strlen( rspbuf ) > 4 && m_size == 0 ) {
+    const char * p = strrchr( rspbuf, '(' );
+    if ( p != 0L ) m_size = atol( p + 1 );
+  }
+
+  size_t bytesLeft = m_size - offset;
+
+  kdDebug(7102) << "Ftp::get starting with offset=" << offset << endl;
+  int processed_size = offset;
+  time_t t_start = time( 0L );
+  time_t t_last = t_start;
+
+  char buffer[ 2048 ];
+  QByteArray array;
+  QByteArray mimetypeBuffer;
+
+  bool mimetypeEmitted = false;
+
+  while( bytesLeft > 0 )
+  {
+    int n = ftpRead( buffer, 2048 );
+    bytesLeft -= n;
+
+    // Buffer the first 1024 bytes for mimetype determination
+    if ( !mimetypeEmitted )
+    {
+      int oldSize = mimetypeBuffer.size();
+      mimetypeBuffer.resize(oldSize + n);
+      memcpy(mimetypeBuffer.data()+oldSize, buffer, n);
+
+      // Found enough data - or we're arriving to the end of the file -> emit mimetype
+      if (mimetypeBuffer.size() >= 1024 || bytesLeft <= 0)
+      {
+        KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( mimetypeBuffer, url.fileName() );
+        kdDebug(7102) << "Emitting mimetype " << result->mimeType() << endl;
+        mimeType( result->mimeType() );
+        mimetypeEmitted = true;
+        data( mimetypeBuffer );
+        mimetypeBuffer.resize(0);
+        // Emit total size AFTER mimetype
+        totalSize( m_size );
+      }
+    }
+    else if ( n > 0 )
+    {
+      array.setRawData(buffer, n);
+      data( array );
+      array.resetRawData(buffer, n);
+    }
+    else // unexpected eof. Happens when the daemon gets killed.
+    {
+      error( ERR_COULD_NOT_READ, url.path() );
+      return;
+    }
+
+    processed_size += n;
+    time_t t = time( 0L );
+    if ( t - t_last >= 1 ) {
+      processedSize( processed_size );
+      speed( ( processed_size - offset ) / ( t - t_start ) );
+      t_last = t;
+    }
+  }
+
+  kdDebug(7102) << "Get: done, sending empty QByteArray" << endl;
+  data( QByteArray() );
+
+  kdDebug(7102) << "Get: calling ftpCloseCommand()" << endl;
+  (void) ftpCloseCommand();
+  // proceed even on error
+
+  processedSize( m_size );
+  time_t t = time( 0L );
+  if ( t - t_start >= 1 )
+    speed( ( processed_size - offset ) / ( t - t_start ) );
+
+  kdDebug(7102) << "Get: emitting finished()" << endl;
+  finished();
+}
+
+/*
+void Ftp::mimetype( const KURL& url )
+{
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
+    kdWarning(7102) << "Can't open for reading" << endl;
+    return;
+  }
+  char buffer[ 2048 ];
+  QByteArray array;
+  // Get one chunk of data only and send it, KIO::Job will determine the
+  // mimetype from it using KMimeMagic
+  int n = ftpRead( buffer, 2048 );
+  array.setRawData(buffer, n);
+  data( array );
+  array.resetRawData(buffer, n);
+
+  kdDebug(7102) << "aborting" << endl;
+  ftpAbortTransfer();
+
+  kdDebug(7102) << "finished" << endl;
+  finished();
+  kdDebug(7102) << "after finished" << endl;
+}
+
+void Ftp::ftpAbortTransfer()
+{
+  // RFC 959, page 34-35
+  // IAC (interpret as command) = 255 ; IP (interrupt process) = 254
+  // DM = 242 (data mark)
+   char msg[4];
+   // 1. User system inserts the Telnet "Interrupt Process" (IP) signal
+   //   in the Telnet stream.
+   msg[0] = (char) 255; //IAC
+   msg[1] = (char) 254; //IP
+   (void) send(sControl, msg, 2, 0);
+   // 2. User system sends the Telnet "Sync" signal.
+   msg[0] = (char) 255; //IAC
+   msg[1] = (char) 242; //DM
+   if (send(sControl, msg, 2, MSG_OOB) != 2)
+     ; // error...
+
+   // Send ABOR
+   kdDebug(7102) << "send ABOR" << endl;
+   QCString buf = "ABOR\r\n";
+   if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 )  {
+     error( ERR_COULD_NOT_WRITE, QString::null );
+     return;
+   }
+
+   //
+   kdDebug(7102) << "read resp" << endl;
+   if ( readresp() != '2' )
+   {
+     error( ERR_COULD_NOT_READ, QString::null );
+     return;
+   }
+
+  kdDebug(7102) << "close sockets" << endl;
+  closeSockets();
+}
+*/
+
+#if 0
+void Ftp::put( const KURL& dest_url, int permissions, bool overwrite, bool resume )
+{
+  QString dest_orig = dest_url.path();
+  if (!m_bLoggedOn)
+  {
+      openConnection();
+      if (!m_bLoggedOn)
+      {
+        kdDebug(7102) << "Login failure, aborting" << endl;
+        return;
+      }
+  }
+
+  kdDebug(7102) << "Put " << dest_orig << endl;
+  QString dest_part( dest_orig );
+  dest_part += QString::fromLatin1(".part");
+
+  bool bMarkPartial = config()->readBoolEntry("MarkPartial", true);
+
+  // Don't use mark partial over anonymous FTP.
+  // My incoming dir allows put but not rename...
+  if (m_user == FTP_LOGIN)
+      bMarkPartial = false;
+
+  if ( ftpSize( dest_orig, 'I' ) )
+  {
+    if ( m_size == 0 ) {  // delete files with zero size
+      QCString cmd = "DELE ";
+      cmd += dest_orig.ascii();
+      if ( !ftpSendCmd( cmd, '2' ) )
+      {
+        error( ERR_CANNOT_DELETE_PARTIAL, dest_orig );
+        return;
+      }
+    } else if ( !overwrite && !resume ) {
+      error( ERR_FILE_ALREADY_EXIST, dest_orig );
+      return;
+    } else if ( bMarkPartial ) { // when using mark partial, append .part extension
+      if ( !ftpRename( dest_orig, dest_part, true ) )
+      {
+        error( KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig );
+        return;
+      }
+    }
+    // Don't chmod an existing file
+    permissions = -1;
+  } else if ( ftpSize( dest_part, 'I' ) ) { // file with extension .part exists
+    if ( m_size == 0 ) {  // delete files with zero size
+      QCString cmd = "DELE ";
+      cmd += dest_part.ascii();
+      if ( !ftpSendCmd( cmd, '2' ) )
+      {
+        error( ERR_CANNOT_DELETE_PARTIAL, dest_orig );
+        return;
+      }
+    } else if ( !overwrite && !resume ) {
+      error( ERR_FILE_ALREADY_EXIST, dest_orig );
+      return;
+    } else if ( !bMarkPartial ) { // when using mark partial, remove .part extension
+      if ( !ftpRename( dest_part, dest_orig, true ) )
+      {
+        error( KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig );
+        return;
+      }
+    }
+
+  }
+
+  QString dest;
+
+  // if we are using marking of partial downloads -> add .part extension
+  if ( bMarkPartial ) {
+    kdDebug(7102) << "Adding .part extension to " << dest_orig << endl;
+    dest = dest_part;
+  } else
+    dest = dest_orig;
+
+  unsigned long offset = 0;
+
+  // set the mode according to offset
+  if ( resume ) {
+    offset = m_size;
+    kdDebug(7102) << "Offset = " << (unsigned int) offset << "d" << endl;
+  }
+
+  if (! ftpOpenCommand( "stor", dest, 'I', ERR_COULD_NOT_WRITE, offset ) )
+    return;
+
+  int result;
+  // Loop until we got 'dataEnd'
+  do
+  {
+    QByteArray buffer;
+    dataReq(); // Request for data
+    result = readData( buffer );
+    if (result > 0)
+    {
+      ftpWrite( buffer.data(), buffer.size() );
+    }
+  }
+  while ( result > 0 );
+
+  if (result != 0) // error
+  {
+    (void) ftpCloseCommand(); // don't care about errors
+    kdDebug(7102) << "Error during 'put'. Aborting." << endl;
+    if (bMarkPartial)
+    {
+      // Remove if smaller than minimum size
+      if ( ftpSize( dest, 'I' ) &&
+           ( m_size < (unsigned long) config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
+      {
+        QCString cmd = "DELE ";
+        cmd += dest.ascii();
+        (void) ftpSendCmd( cmd, '\0' );
+      }
+    }
+    return;
+  }
+
+  if ( !ftpCloseCommand() )
+  {
+    error( KIO::ERR_COULD_NOT_WRITE, dest_orig);
+    return;
+  }
+
+  // after full download rename the file back to original name
+  if ( bMarkPartial )
+  {
+    kdDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")" << endl;
+    if ( !ftpRename( dest, dest_orig, true ) )
+    {
+      error( KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig );
+      return;
+    }
+  }
+
+  // set final permissions
+  if ( permissions != -1 )
+  {
+    if ( m_user == FTP_LOGIN )
+      kdDebug(7102) << "Trying to chmod over anonymous FTP ???" << endl;
+    // chmod the file we just put, ignoring errors.
+    (void) ftpChmod( dest_orig, permissions );
+  }
+
+  // We have done our job => finish
+  finished();
+}
+#endif
+
+
+/*
+  This is related to "canResume" ... not sure how
+  Old main.cc contained:
+  if ( !ftp.ftpResume( 0 ) )
+    m_bCanResume = false;
+
+bool Ftp::ftpResume( unsigned long offset )
+{
+  char buf[64];
+  sprintf(buf, "rest %ld", offset);
+  if ( !ftpSendCmd( buf, '3' ) ) {
+    error( ERR_CANNOT_RESUME, QString::null );
+    return false;
+  }
+  return true;
+}
+*/
+
+/** Use the SIZE command to get the file size.
+    Warning : the size depends on the transfer mode, hence the second arg. */
+bool Ftp::ftpSize( const QString & path, char mode )
+{
+  QCString buf;
+  buf.sprintf("type %c", mode);
+  if ( !ftpSendCmd( buf, '2' ) ) {
+      return false;
+  }
+
+  buf="SIZE ";
+  buf+=path.ascii();
+  if (!ftpSendCmd(buf,'2')) {
+    m_size = 0;
+    return false;
+  }
+
+  m_size = atol(rspbuf+4); // skip leading "213 " (response code)
+  return true;
+}
+
+
+size_t Ftp::ftpRead(void *buffer, long len)
+{
+  size_t n = KSocks::self()->read( sData, buffer, len );
+  return n;
+}
+
+#if 0
+size_t Ftp::ftpWrite(void *buffer, long len)
+{
+  return( KSocks::self()->write( sData, buffer, len ) );
+}
+#endif
+
+void Ftp::getDir( const KURL& url)
+{
+	QByteArray header_buffer;
+	QString listarg;
+	QTextStream header_stream( header_buffer, IO_WriteOnly );
+	header_stream << QString::fromLatin1( "<html><body><pre>" );
+	data( header_buffer );
+
+	if (!ftpOpenDir(url.path())) {
+		kdError(7102) << "COULD NOT LIST " << url.path() << endl;
+		return;
+	}
+
+	FtpEntry *e;
+	while ((e = ftpReadDir())) {
+		KURL f(url);
+		QByteArray buffer;
+
+		if (f.path().isEmpty())
+			f.addPath("/");
+
+		f.addPath(e->type == S_IFDIR ? e->name+"/" : e->name);
+		QTextStream stream( buffer, IO_WriteOnly );
+
+		stream << QString::fromLatin1( "<a href=\"%1\">%2</a>\n" )
+		    .arg( f.url() )
+		    .arg( e->type == S_IFDIR ? e->name+"/" : e->name );
+
+		data( buffer );
+	}
+	// the footer
+	QByteArray footer_buffer;
+	QTextStream footer_stream( header_buffer, IO_WriteOnly );
+	footer_stream << QString::fromLatin1( "</pre></body></html>" );
+	data( footer_buffer );
+
+	data( QByteArray() );
+	(void) ftpCloseDir();
+
+	finished();
+}
--- konq-embed/dropin/kio/kio_ftp.h.orig	Sat Mar 30 21:19:45 2002
+++ konq-embed/dropin/kio/kio_ftp.h	Sun Mar 31 17:42:53 2002
@@ -0,0 +1,278 @@
+/*  This file is part of the KDE libraries
+    Copyright (C) 2000 David Faure <faure@kde.org>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, USA.
+*/
+
+// $Id: patch-konq-embed_dropin_kio_kio_ftp_h,v 1.3 2002/03/31 15:57:05 espie Exp $
+
+#ifndef __ftp_h__
+#define __ftp_h__
+
+#include <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#define FTP_BUFSIZ 1024
+
+#include <qcstring.h>
+#include <qstring.h>
+
+#include <kurl.h>
+#include <kio/slavebase.h>
+
+class KExtendedSocket;
+
+struct FtpEntry
+{
+  QString name;
+  QString owner;
+  QString group;
+  QString link;
+
+  long size;
+  mode_t type;
+  mode_t access;
+  time_t date;
+};
+
+struct netbuf
+{
+  char *cput,*cget;
+  int handle;
+  int cavail,cleft;
+  char buf[FTP_BUFSIZ];
+};
+
+class Ftp : public KIO::SlaveBase
+{
+public:
+  Ftp( const QCString &pool, const QCString &app );
+  virtual ~Ftp();
+
+  virtual void setHost( const QString& host, int port, const QString& user, const QString& pass );
+
+  /**
+   * Connects to a ftp server and logs us in
+   * @ref m_bLoggedOn is set to true if logging on was successfull.
+   * It is set to false if the connection becomes closed.
+   *
+   */
+  virtual void openConnection();
+
+  /**
+   * Closes the connection
+   */
+  virtual void closeConnection();
+
+  //virtual void stat( const KURL &url );
+
+  //virtual void listDir( const KURL & url );
+  //virtual void mkdir( const KURL & url, int permissions );
+  //virtual void rename( const KURL & src, const KURL & dst, bool overwrite );
+  //virtual void del( const KURL & url, bool isfile );
+  //virtual void chmod( const KURL & url, int permissions );
+
+  virtual void get( const KURL& url );
+  //virtual void put( const KURL& url, int permissions, bool overwrite, bool resume);
+  //virtual void mimetype( const KURL& url );
+
+  //virtual void slave_status();
+
+  // unsupported, AFAIK
+  // virtual void copy( const KURL &src, const KURL &dest, int permissions, bool overwrite );
+
+private:
+
+  // All the methods named ftpXyz do NOT emit errors, they simply return true
+  // or false (they are lowlevel methods). The methods not named this way
+  // emit error on error (they are highlevel methods).
+
+  /**
+   * Called by @ref openConnection. It opens the control connection to the ftp server.
+   *
+   * @return true on success.
+   */
+  bool connect( const QString & host, unsigned short int port = 0 );
+
+  /**
+   * Called by @ref openConnection. It logs us in.
+   * @ref m_initialPath is set to the current working directory
+   * if logging on was successfull.
+   *
+   * @return true on success.
+   */
+  bool ftpLogin();
+
+  /**
+   * ftpSendCmd - send a command (@p cmd) and read response
+   *
+   * @param expresp the expected first char, if you want the builtin check
+   * @param maxretries number of time it should retry. Since it recursively
+   * calls itself if it can't read the answer (this happens especially after
+   * timeouts), we need to limit the recursiveness ;-)
+   *
+   * return true if proper response received, false on error
+   * or if @p expresp doesn't match
+   */
+  bool ftpSendCmd( const QCString& cmd, char expresp = '\0', int maxretries = 1 );
+
+  /**
+   * Use the SIZE command to get the file size.
+   * @param mode the size depends on the transfer mode, hence this arg.
+   * @return true on success
+   * Gets the size into m_size.
+   */
+  bool ftpSize( const QString & path, char mode );
+
+  /**
+   * Runs a command on the ftp server like "list" or "retr". In contrast to
+   * @ref ftpSendCmd a data connection is opened. The corresponding socket
+   * @ref sData is available for reading/writing on success.
+   * The connection must be closed afterwards with @ref ftpCloseCommand.
+   *
+   * @param mode is 'A' or 'I'. 'A' means ASCII transfer, 'I' means binary transfer.
+   * @param errorcode the command-dependent error code to emit on error
+   *
+   * @return true if the command was accepted by the server.
+   */
+  bool ftpOpenCommand( const char *command, const QString & path, char mode,
+                       int errorcode, unsigned long offset = 0 );
+
+  /**
+   * The counterpart to @ref openCommand.
+   * Closes data sockets and then reads line sent by server at
+   * end of command.
+   * @return false on error (line doesn't start with '2')
+   */
+  bool ftpCloseCommand();
+
+  void closeSockets();
+
+  //void ftpAbortTransfer();
+
+  /**
+   * Used by @ref openCommand
+   */
+  bool ftpOpenPASVDataConnection();
+  /**
+   * Used by @ref openCommand
+   */
+  bool ftpOpenEPSVDataConnection();
+  /**
+   * Used by @ref openCommand
+   */
+  bool ftpOpenEPRTDataConnection();
+  /**
+   * Used by @ref openCommand
+   */
+  bool ftpOpenDataConnection();
+  /**
+   * ftpAcceptConnect - wait for incoming connection
+   *
+   * return -2 on error or timeout
+   * otherwise returns socket descriptor
+   */
+  int ftpAcceptConnect();
+
+  size_t ftpRead( void *buffer, long len );
+  size_t ftpWrite( void *buffer, long len );
+
+  bool ftpChmod( const QString & path, int permissions );
+
+  // used by listDir
+  bool ftpOpenDir( const QString & path );
+  bool ftpCloseDir();
+  // return 0L on end
+  FtpEntry * ftpReadDir();
+  // used by ftpReadDir
+  FtpEntry * ftpParseDir( char* buffer );
+  void createUDSEntry( const QString & filename, FtpEntry * e, KIO::UDSEntry & entry, bool isDir );
+
+  bool ftpRename( const QString & src, const QString & dst, bool overwrite );
+
+  /**
+   * read a line of text
+   *
+   * return -1 on error, bytecount otherwise
+   */
+  int ftpReadline( char *buf, int max, netbuf *ctl );
+
+  /**
+   * read a response from the server, into rspbuf
+   * @return first char of response (rspbuf[0]), '\0' if we couldn't read the response
+   */
+  char readresp();
+
+  void getDir( const KURL& url);
+
+private: // data members
+
+  /**
+   * Connected to the socket from which we read a directory listing.
+   * If it is not zero, then a "list" command is in progress.
+   */
+  FILE *dirfile;
+
+  /**
+   * This is the data connection socket from which we read the data.
+   */
+  int sData;
+  /**
+   * The control stream socket
+   */
+  int sControl;
+  /**
+   * The server socket for a data connection. This is needed since the
+   * ftp server must open a connection to us.
+   */
+  int sDatal;
+
+  QString m_host;
+  int m_port;
+  QString m_user;
+  QString m_pass;
+  /**
+   * Where we end up after connecting
+   */
+  QString m_initialPath;
+  KURL m_proxyURL;
+
+  netbuf *nControl;
+  char rspbuf[256];
+
+  bool m_bLoggedOn;
+  bool m_bFtpStarted;
+  bool m_bPasv;
+  bool m_bUseProxy;
+  bool m_bPersistent;
+
+  size_t m_size;
+
+  enum
+  {
+    epsvUnknown = 0x01,
+    epsvAllUnknown = 0x02,
+    eprtUnknown = 0x04,
+    epsvAllSent = 0x10
+  };
+  int m_extControl;
+  KExtendedSocket *ksControl;
+
+};
+
+#endif
--- konq-embed/dropin/kio/slavebase.cpp.orig	Fri Feb  1 13:17:31 2002
+++ konq-embed/dropin/kio/slavebase.cpp	Sat Mar 30 19:35:08 2002
@@ -29,6 +29,7 @@
 
 #include "http.h"
 #include "kio_file.h"
+#include "kio_ftp.h"
 
 #include "slaveinterface.h"
 #include "connection.h"
@@ -154,13 +155,16 @@ SlaveBase *SlaveBase::createSlave( const
 #endif
     else if ( prot == "file" )
         res = new File();
+    else if ( prot == "ftp" )
+    	res = new Ftp("", "" );
 
     return res;
 }
 
 bool SlaveBase::knownProtocol( const QString &protocol )
 {
-    return ( protocol == "http" || protocol == "https" || protocol == "file" );
+    return ( protocol == "http" || protocol == "https" || 
+    	protocol == "ftp" || protocol == "file" );
 }
 
 SlaveBase::SlaveBase( const QCString &protocol, const QCString &, const QCString & )
--- konq-embed/dropin/kio/slaveconfig.h.orig	Sat Mar 30 21:25:50 2002
+++ konq-embed/dropin/kio/slaveconfig.h	Sat Mar 30 21:25:40 2002
@@ -0,0 +1,108 @@
+// -*- c++ -*-
+/*
+ *  This file is part of the KDE libraries
+ *  Copyright (c) 2001 Waldo Bastian <bastian@kde.org>
+ *
+ * $Id: slaveconfig.h,v 1.4 2001/06/26 21:51:23 waba Exp $
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License version 2 as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this library; see the file COPYING.LIB.  If not, write to
+ *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *  Boston, MA 02111-1307, USA.
+ **/
+
+#ifndef KIO_SLAVE_CONFIG_H
+#define KIO_SLAVE_CONFIG_H
+
+#include <qobject.h>
+#include <kio/global.h>
+
+namespace KIO {
+
+    class SlaveConfigPrivate;
+    /**
+     * SlaveConfig
+     *
+     * This class manages the configuration for io-slaves based on protocol
+     * and host. The Scheduler makes use of this class to configure the slave
+     * whenever it has to connect to a new host.
+     *
+     * You only need to use this class if you want to override specific
+     * configuration items of an io-slave when the io-slave is used by
+     * your application. 
+     *
+     * Normally io-slaves are being configured by "kio_<protocol>rc" 
+     * configuration files. Groups defined in such files are treated as host 
+     * or domain specification. Configuration items defined in a group are 
+     * only applied when the slave is connecting with a host that matches with 
+     * the host and/or domain specified by the group.
+     */
+    class SlaveConfig : public QObject
+    {
+	Q_OBJECT
+    public:
+        static SlaveConfig *self();
+        ~SlaveConfig();
+        /**
+         * Configure slaves of type @p protocol by setting @p key to @p value.
+         * If @p host is specified the configuration only applies when dealing
+         * with @p host.
+         *
+         * Changes made to the slave configuration only apply to slaves
+         * used by the current process.
+         */
+        void setConfigData(const QString &protocol, const QString &host, const QString &key, const QString &value );
+        
+        /**
+         * Configure slaves of type @p protocol with @p config.
+         * If @p host is specified the configuration only applies when dealing
+         * with @p host.
+         *
+         * Changes made to the slave configuration only apply to slaves
+         * used by the current process.
+         */
+        void setConfigData(const QString &protocol, const QString &host, const MetaData &config );
+                
+        /**
+         * Query slave configuration for slaves of type @p protocol when
+         * dealing with @p host.
+         */
+        MetaData configData(const QString &protocol, const QString &host);
+
+        /**
+         * Query a specific configuration key for slaves of type @p protocol when
+         * dealing with @p host.
+         */
+        QString configData(const QString &protocol, const QString &host, const QString &key);
+
+        /**
+         * Undo any changes made by calls to @ref setConfigData.
+         */
+        void reset();
+    signals:
+        /**
+         * This signal is raised when a slave of type @p protocol deals
+         * with @p host for the first time.
+         *
+         * Your application can use this signal to make some last minute
+         * configuration changes with @ref setConfiguration based on the
+         * host.
+         */
+        void configNeeded(const QString &protocol, const QString &host);
+    protected:
+        SlaveConfig();
+        static SlaveConfig *_self;
+        SlaveConfigPrivate *d;
+    };
+};
+
+#endif