• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdelibs API Reference
  • KDE Home
  • Contact Us
 

KIOSlave

  • sources
  • kde-4.12
  • kdelibs
  • kioslave
  • ftp
ftp.cpp
Go to the documentation of this file.
1 // -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; c-file-style: "stroustrup" -*-
2 /* This file is part of the KDE libraries
3  Copyright (C) 2000-2006 David Faure <faure@kde.org>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 /*
22  Recommended reading explaining FTP details and quirks:
23  http://cr.yp.to/ftp.html (by D.J. Bernstein)
24 
25  RFC:
26  RFC 959 "File Transfer Protocol (FTP)"
27  RFC 1635 "How to Use Anonymous FTP"
28  RFC 2428 "FTP Extensions for IPv6 and NATs" (defines EPRT and EPSV)
29 */
30 
31 
32 #define KIO_FTP_PRIVATE_INCLUDE
33 #include "ftp.h"
34 
35 #ifdef HAVE_SYS_TIME_H
36 #include <sys/time.h>
37 #endif
38 
39 #if TIME_WITH_SYS_TIME
40 #include <ctime>
41 #endif
42 
43 #include <cctype>
44 #include <cerrno>
45 #include <cstdlib>
46 #include <cstring>
47 
48 #include <QtCore/QCoreApplication>
49 #include <QtCore/QDir>
50 #include <QtNetwork/QHostAddress>
51 #include <QtNetwork/QTcpSocket>
52 #include <QtNetwork/QTcpServer>
53 #include <QtNetwork/QAuthenticator>
54 
55 #include <kdebug.h>
56 #include <kglobal.h>
57 #include <klocale.h>
58 #include <kcomponentdata.h>
59 #include <kmimetype.h>
60 #include <kio/ioslave_defaults.h>
61 #include <kio/slaveconfig.h>
62 #include <kremoteencoding.h>
63 #include <ksocketfactory.h>
64 #include <kde_file.h>
65 #include <kconfiggroup.h>
66 
67 #ifdef HAVE_STRTOLL
68  #define charToLongLong(a) strtoll(a, 0, 10)
69 #else
70  #define charToLongLong(a) strtol(a, 0, 10)
71 #endif
72 
73 #define FTP_LOGIN "anonymous"
74 #define FTP_PASSWD "anonymous@"
75 
76 //#undef kDebug
77 #define ENABLE_CAN_RESUME
78 
79 static QString ftpCleanPath(const QString& path)
80 {
81  if (path.endsWith(QLatin1String(";type=A"), Qt::CaseInsensitive) ||
82  path.endsWith(QLatin1String(";type=I"), Qt::CaseInsensitive) ||
83  path.endsWith(QLatin1String(";type=D"), Qt::CaseInsensitive)) {
84  return path.left((path.length() - qstrlen(";type=X")));
85  }
86 
87  return path;
88 }
89 
90 static char ftpModeFromPath(const QString& path, char defaultMode = '\0')
91 {
92  const int index = path.lastIndexOf(QLatin1String(";type="));
93 
94  if (index > -1 && (index+6) < path.size()) {
95  const QChar mode = path.at(index+6);
96  // kio_ftp supports only A (ASCII) and I(BINARY) modes.
97  if (mode == QLatin1Char('A') || mode == QLatin1Char('a') ||
98  mode == QLatin1Char('I') || mode == QLatin1Char('i')) {
99  return mode.toUpper().toLatin1();
100  }
101  }
102 
103  return defaultMode;
104 }
105 
106 static bool supportedProxyScheme(const QString& scheme)
107 {
108  return (scheme == QLatin1String("ftp") || scheme == QLatin1String("socks"));
109 }
110 
111 static bool isSocksProxy()
112 {
113  return (QNetworkProxy::applicationProxy().type() == QNetworkProxy::Socks5Proxy);
114 }
115 
116 
117 // JPF: somebody should find a better solution for this or move this to KIO
118 // JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
119 namespace KIO {
120  enum buffersizes
121  {
125  maximumIpcSize = 32 * 1024,
130  initialIpcSize = 2 * 1024,
134  minimumMimeSize = 1024
135  };
136 
137  // JPF: this helper was derived from write_all in file.cc (FileProtocol).
138  static // JPF: in ftp.cc we make it static
146  int WriteToFile(int fd, const char *buf, size_t len)
147  {
148  while (len > 0)
149  { // JPF: shouldn't there be a KDE_write?
150  ssize_t written = write(fd, buf, len);
151  if (written >= 0)
152  { buf += written;
153  len -= written;
154  continue;
155  }
156  switch(errno)
157  { case EINTR: continue;
158  case EPIPE: return ERR_CONNECTION_BROKEN;
159  case ENOSPC: return ERR_DISK_FULL;
160  default: return ERR_COULD_NOT_WRITE;
161  }
162  }
163  return 0;
164  }
165 }
166 
167 KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;
168 
169 using namespace KIO;
170 
171 extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
172 {
173  QCoreApplication app(argc, argv);
174  KComponentData componentData( "kio_ftp", "kdelibs4" );
175  ( void ) KGlobal::locale();
176 
177  kDebug(7102) << "Starting " << getpid();
178 
179  if (argc != 4)
180  {
181  fprintf(stderr, "Usage: kio_ftp protocol domain-socket1 domain-socket2\n");
182  exit(-1);
183  }
184 
185  Ftp slave(argv[2], argv[3]);
186  slave.dispatchLoop();
187 
188  kDebug(7102) << "Done";
189  return 0;
190 }
191 
192 //===============================================================================
193 // Ftp
194 //===============================================================================
195 
196 Ftp::Ftp( const QByteArray &pool, const QByteArray &app )
197  : SlaveBase( "ftp", pool, app )
198 {
199  // init the socket data
200  m_data = m_control = NULL;
201  m_server = NULL;
202  ftpCloseControlConnection();
203 
204  // init other members
205  m_port = 0;
206  m_socketProxyAuth = 0;
207 }
208 
209 
210 Ftp::~Ftp()
211 {
212  kDebug(7102);
213  closeConnection();
214 }
215 
219 void Ftp::ftpCloseDataConnection()
220 {
221  delete m_data;
222  m_data = NULL;
223  delete m_server;
224  m_server = NULL;
225 }
226 
231 void Ftp::ftpCloseControlConnection()
232 {
233  m_extControl = 0;
234  delete m_control;
235  m_control = NULL;
236  m_cDataMode = 0;
237  m_bLoggedOn = false; // logon needs control connction
238  m_bTextMode = false;
239  m_bBusy = false;
240 }
241 
246 const char* Ftp::ftpResponse(int iOffset)
247 {
248  Q_ASSERT(m_control != NULL); // must have control connection socket
249  const char *pTxt = m_lastControlLine.data();
250 
251  // read the next line ...
252  if(iOffset < 0)
253  {
254  int iMore = 0;
255  m_iRespCode = 0;
256 
257  if (!pTxt) return 0; // avoid using a NULL when calling atoi.
258 
259  // If the server sends a multiline response starting with
260  // "nnn-text" we loop here until a final "nnn text" line is
261  // reached. Only data from the final line will be stored.
262  do {
263  while (!m_control->canReadLine() && m_control->waitForReadyRead((readTimeout() * 1000))) {}
264  m_lastControlLine = m_control->readLine();
265  pTxt = m_lastControlLine.data();
266  int iCode = atoi(pTxt);
267  if (iMore == 0) {
268  // first line
269  kDebug(7102) << " > " << pTxt;
270  if(iCode >= 100) {
271  m_iRespCode = iCode;
272  if (pTxt[3] == '-') {
273  // marker for a multiple line response
274  iMore = iCode;
275  }
276  } else {
277  kWarning(7102) << "Cannot parse valid code from line" << pTxt;
278  }
279  } else {
280  // multi-line
281  kDebug(7102) << " > " << pTxt;
282  if (iCode >= 100 && iCode == iMore && pTxt[3] == ' ') {
283  iMore = 0;
284  }
285  }
286  } while(iMore != 0);
287  kDebug(7102) << "resp> " << pTxt;
288 
289  m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0;
290  }
291 
292  // return text with offset ...
293  while(iOffset-- > 0 && pTxt[0])
294  pTxt++;
295  return pTxt;
296 }
297 
298 
299 void Ftp::closeConnection()
300 {
301  if(m_control != NULL || m_data != NULL)
302  kDebug(7102) << "m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy;
303 
304  if(m_bBusy) // ftpCloseCommand not called
305  {
306  kWarning(7102) << "Abandoned data stream";
307  ftpCloseDataConnection();
308  }
309 
310  if(m_bLoggedOn) // send quit
311  {
312  if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) )
313  kWarning(7102) << "QUIT returned error: " << m_iRespCode;
314  }
315 
316  // close the data and control connections ...
317  ftpCloseDataConnection();
318  ftpCloseControlConnection();
319 }
320 
321 void Ftp::setHost( const QString& _host, quint16 _port, const QString& _user,
322  const QString& _pass )
323 {
324  kDebug(7102) << _host << "port=" << _port << "user=" << _user;
325 
326  m_proxyURL.clear();
327  m_proxyUrls = config()->readEntry("ProxyUrls", QStringList());
328  kDebug(7102) << "proxy urls:" << m_proxyUrls;
329 
330  if ( m_host != _host || m_port != _port ||
331  m_user != _user || m_pass != _pass )
332  closeConnection();
333 
334  m_host = _host;
335  m_port = _port;
336  m_user = _user;
337  m_pass = _pass;
338 }
339 
340 void Ftp::openConnection()
341 {
342  ftpOpenConnection(loginExplicit);
343 }
344 
345 bool Ftp::ftpOpenConnection (LoginMode loginMode)
346 {
347  // check for implicit login if we are already logged on ...
348  if(loginMode == loginImplicit && m_bLoggedOn)
349  {
350  Q_ASSERT(m_control != NULL); // must have control connection socket
351  return true;
352  }
353 
354  kDebug(7102) << "host=" << m_host << ", port=" << m_port << ", user=" << m_user << "password= [password hidden]";
355 
356  infoMessage( i18n("Opening connection to host %1", m_host) );
357 
358  if ( m_host.isEmpty() )
359  {
360  error( ERR_UNKNOWN_HOST, QString() );
361  return false;
362  }
363 
364  Q_ASSERT( !m_bLoggedOn );
365 
366  m_initialPath.clear();
367  m_currentPath.clear();
368 
369  if (!ftpOpenControlConnection() )
370  return false; // error emitted by ftpOpenControlConnection
371  infoMessage( i18n("Connected to host %1", m_host) );
372 
373  bool userNameChanged = false;
374  if(loginMode != loginDefered)
375  {
376  m_bLoggedOn = ftpLogin(&userNameChanged);
377  if( !m_bLoggedOn )
378  return false; // error emitted by ftpLogin
379  }
380 
381  m_bTextMode = config()->readEntry("textmode", false);
382  connected();
383 
384  // Redirected due to credential change...
385  if (userNameChanged && m_bLoggedOn)
386  {
387  KUrl realURL;
388  realURL.setProtocol( "ftp" );
389  if (m_user != FTP_LOGIN)
390  realURL.setUser( m_user );
391  if (m_pass != FTP_PASSWD)
392  realURL.setPass( m_pass );
393  realURL.setHost( m_host );
394  if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
395  realURL.setPort( m_port );
396  if ( m_initialPath.isEmpty() )
397  m_initialPath = '/';
398  realURL.setPath( m_initialPath );
399  kDebug(7102) << "User name changed! Redirecting to" << realURL.prettyUrl();
400  redirection( realURL );
401  finished();
402  return false;
403  }
404 
405  return true;
406 }
407 
408 
414 bool Ftp::ftpOpenControlConnection()
415 {
416  if (m_proxyUrls.isEmpty())
417  return ftpOpenControlConnection(m_host, m_port);
418 
419  int errorCode = 0;
420  QString errorMessage;
421 
422  Q_FOREACH (const QString& proxyUrl, m_proxyUrls) {
423  const KUrl url (proxyUrl);
424  const QString scheme (url.protocol());
425 
426  if (!supportedProxyScheme(scheme)) {
427  // TODO: Need a new error code to indicate unsupported URL scheme.
428  errorCode = ERR_COULD_NOT_CONNECT;
429  errorMessage = url.url();
430  continue;
431  }
432 
433  if (scheme == QLatin1String("socks")) {
434  kDebug(7102) << "Connecting to SOCKS proxy @" << url;
435  const int proxyPort = url.port();
436  QNetworkProxy proxy (QNetworkProxy::Socks5Proxy, url.host(), (proxyPort == -1 ? 0 : proxyPort));
437  QNetworkProxy::setApplicationProxy(proxy);
438  if (ftpOpenControlConnection(m_host, m_port)) {
439  return true;
440  }
441  QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
442  } else {
443  if (ftpOpenControlConnection(url.host(), url.port())) {
444  m_proxyURL = url;
445  return true;
446  }
447  }
448  }
449 
450  if (errorCode) {
451  error(errorCode, errorMessage);
452  }
453 
454  return false;
455 }
456 
457 bool Ftp::ftpOpenControlConnection( const QString &host, int port )
458 {
459  // implicitly close, then try to open a new connection ...
460  closeConnection();
461  QString sErrorMsg;
462 
463  // now connect to the server and read the login message ...
464  if (port == 0)
465  port = 21; // default FTP port
466  m_control = KSocketFactory::synchronousConnectToHost(QLatin1String("ftp"), host, port, connectTimeout() * 1000);
467  connect(m_control, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
468  this, SLOT(proxyAuthentication(QNetworkProxy,QAuthenticator*)));
469  int iErrorCode = m_control->state() == QAbstractSocket::ConnectedState ? 0 : ERR_COULD_NOT_CONNECT;
470 
471  // on connect success try to read the server message...
472  if(iErrorCode == 0)
473  {
474  const char* psz = ftpResponse(-1);
475  if(m_iRespType != 2)
476  { // login not successful, do we have an message text?
477  if(psz[0])
478  sErrorMsg = i18n("%1.\n\nReason: %2", host, psz);
479  iErrorCode = ERR_COULD_NOT_CONNECT;
480  }
481  }
482  else
483  {
484  if (m_control->error() == QAbstractSocket::HostNotFoundError)
485  iErrorCode = ERR_UNKNOWN_HOST;
486 
487  sErrorMsg = QString("%1: %2").arg(host).arg(m_control->errorString());
488  }
489 
490  // if there was a problem - report it ...
491  if(iErrorCode == 0) // OK, return success
492  return true;
493  closeConnection(); // clean-up on error
494  error(iErrorCode, sErrorMsg);
495  return false;
496 }
497 
505 bool Ftp::ftpLogin(bool* userChanged)
506 {
507  infoMessage( i18n("Sending login information") );
508 
509  Q_ASSERT( !m_bLoggedOn );
510 
511  QString user (m_user);
512  QString pass (m_pass);
513 
514  if ( config()->readEntry("EnableAutoLogin", false) )
515  {
516  QString au = config()->readEntry("autoLoginUser");
517  if ( !au.isEmpty() )
518  {
519  user = au;
520  pass = config()->readEntry("autoLoginPass");
521  }
522  }
523 
524  AuthInfo info;
525  info.url.setProtocol( "ftp" );
526  info.url.setHost( m_host );
527  if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
528  info.url.setPort( m_port );
529  if (!user.isEmpty())
530  info.url.setUser(user);
531 
532  // Check for cached authentication first and fallback to
533  // anonymous login when no stored credentials are found.
534  if (!config()->readEntry("TryAnonymousLoginFirst", false) &&
535  pass.isEmpty() && checkCachedAuthentication(info))
536  {
537  user = info.username;
538  pass = info.password;
539  }
540 
541  // Try anonymous login if both username/password
542  // information is blank.
543  if (user.isEmpty() && pass.isEmpty())
544  {
545  user = FTP_LOGIN;
546  pass = FTP_PASSWD;
547  }
548 
549  QByteArray tempbuf;
550  QString lastServerResponse;
551  int failedAuth = 0;
552  bool promptForRetry = false;
553 
554  // Give the user the option to login anonymously...
555  info.setExtraField(QLatin1String("anonymous"), false);
556 
557  do
558  {
559  // Check the cache and/or prompt user for password if 1st
560  // login attempt failed OR the user supplied a login name,
561  // but no password.
562  if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) )
563  {
564  QString errorMsg;
565  kDebug(7102) << "Prompting user for login info...";
566 
567  // Ask user if we should retry after when login fails!
568  if( failedAuth > 0 && promptForRetry)
569  {
570  errorMsg = i18n("Message sent:\nLogin using username=%1 and "
571  "password=[hidden]\n\nServer replied:\n%2\n\n"
572  , user, lastServerResponse);
573  }
574 
575  if ( user != FTP_LOGIN )
576  info.username = user;
577 
578  info.prompt = i18n("You need to supply a username and a password "
579  "to access this site.");
580  info.commentLabel = i18n( "Site:" );
581  info.comment = i18n("<b>%1</b>", m_host );
582  info.keepPassword = true; // Prompt the user for persistence as well.
583  info.setModified(false); // Default the modified flag since we reuse authinfo.
584 
585  bool disablePassDlg = config()->readEntry( "DisablePassDlg", false );
586  if ( disablePassDlg || !openPasswordDialog( info, errorMsg ) )
587  {
588  error( ERR_USER_CANCELED, m_host );
589  return false;
590  }
591  else
592  {
593  // User can decide go anonymous using checkbox
594  if( info.getExtraField( "anonymous" ).toBool() )
595  {
596  user = FTP_LOGIN;
597  pass = FTP_PASSWD;
598  }
599  else
600  {
601  user = info.username;
602  pass = info.password;
603  }
604  promptForRetry = true;
605  }
606  }
607 
608  tempbuf = "USER ";
609  tempbuf += user.toLatin1();
610  if ( m_proxyURL.isValid() )
611  {
612  tempbuf += '@';
613  tempbuf += m_host.toLatin1();
614  if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
615  {
616  tempbuf += ':';
617  tempbuf += QString::number(m_port).toLatin1();
618  }
619  }
620 
621  kDebug(7102) << "Sending Login name: " << tempbuf;
622 
623  bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
624  bool needPass = (m_iRespCode == 331);
625  // Prompt user for login info if we do not
626  // get back a "230" or "331".
627  if ( !loggedIn && !needPass )
628  {
629  lastServerResponse = ftpResponse(0);
630  kDebug(7102) << "Login failed: " << lastServerResponse;
631  ++failedAuth;
632  continue; // Well we failed, prompt the user please!!
633  }
634 
635  if( needPass )
636  {
637  tempbuf = "PASS ";
638  tempbuf += pass.toLatin1();
639  kDebug(7102) << "Sending Login password: " << "[protected]";
640  loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) );
641  }
642 
643  if ( loggedIn )
644  {
645  // Make sure the user name changed flag is properly set.
646  if (userChanged)
647  *userChanged = (!m_user.isEmpty() && (m_user != user));
648 
649  // Do not cache the default login!!
650  if( user != FTP_LOGIN && pass != FTP_PASSWD )
651  {
652  // Update the username in case it was changed during login.
653  if (!m_user.isEmpty()) {
654  info.url.setUser (user);
655  m_user = user;
656  }
657 
658  // Cache the password if the user requested it.
659  if (info.keepPassword) {
660  cacheAuthentication(info);
661  }
662  }
663  failedAuth = -1;
664  }
665  else
666  {
667  // some servers don't let you login anymore
668  // if you fail login once, so restart the connection here
669  lastServerResponse = ftpResponse(0);
670  if (!ftpOpenControlConnection())
671  {
672  return false;
673  }
674  }
675  } while( ++failedAuth );
676 
677 
678  kDebug(7102) << "Login OK";
679  infoMessage( i18n("Login OK") );
680 
681  // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix:
682  // Thanks to jk@soegaard.net (Jens Kristian Sgaard) for this hint
683  if( ftpSendCmd("SYST") && (m_iRespType == 2) )
684  {
685  if( !qstrncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version
686  {
687  ftpSendCmd( "site dirstyle" );
688  // Check if it was already in Unix style
689  // Patch from Keith Refson <Keith.Refson@earth.ox.ac.uk>
690  if( !qstrncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 ))
691  //It was in Unix style already!
692  ftpSendCmd( "site dirstyle" );
693  // windows won't support chmod before KDE konquers their desktop...
694  m_extControl |= chmodUnknown;
695  }
696  }
697  else
698  kWarning(7102) << "SYST failed";
699 
700  if ( config()->readEntry ("EnableAutoLoginMacro", false) )
701  ftpAutoLoginMacro ();
702 
703  // Get the current working directory
704  kDebug(7102) << "Searching for pwd";
705  if( !ftpSendCmd("PWD") || (m_iRespType != 2) )
706  {
707  kDebug(7102) << "Couldn't issue pwd command";
708  error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.", m_host) ); // or anything better ?
709  return false;
710  }
711 
712  QString sTmp = remoteEncoding()->decode( ftpResponse(3) );
713  int iBeg = sTmp.indexOf('"');
714  int iEnd = sTmp.lastIndexOf('"');
715  if(iBeg > 0 && iBeg < iEnd)
716  {
717  m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1);
718  if(m_initialPath[0] != '/') m_initialPath.prepend('/');
719  kDebug(7102) << "Initial path set to: " << m_initialPath;
720  m_currentPath = m_initialPath;
721  }
722  return true;
723 }
724 
725 void Ftp::ftpAutoLoginMacro ()
726 {
727  QString macro = metaData( "autoLoginMacro" );
728 
729  if ( macro.isEmpty() )
730  return;
731 
732  const QStringList list = macro.split('\n',QString::SkipEmptyParts);
733 
734  for(QStringList::const_iterator it = list.begin() ; it != list.end() ; ++it )
735  {
736  if ( (*it).startsWith(QLatin1String("init")) )
737  {
738  const QStringList list2 = macro.split( '\\',QString::SkipEmptyParts);
739  it = list2.begin();
740  ++it; // ignore the macro name
741 
742  for( ; it != list2.end() ; ++it )
743  {
744  // TODO: Add support for arbitrary commands
745  // besides simply changing directory!!
746  if ( (*it).startsWith( QLatin1String("cwd") ) )
747  (void)ftpFolder( (*it).mid(4), false );
748  }
749 
750  break;
751  }
752  }
753 }
754 
755 
765 bool Ftp::ftpSendCmd( const QByteArray& cmd, int maxretries )
766 {
767  Q_ASSERT(m_control != NULL); // must have control connection socket
768 
769  if ( cmd.indexOf( '\r' ) != -1 || cmd.indexOf( '\n' ) != -1)
770  {
771  kWarning(7102) << "Invalid command received (contains CR or LF):"
772  << cmd.data();
773  error( ERR_UNSUPPORTED_ACTION, m_host );
774  return false;
775  }
776 
777  // Don't print out the password...
778  bool isPassCmd = (cmd.left(4).toLower() == "pass");
779  if ( !isPassCmd )
780  kDebug(7102) << "send> " << cmd.data();
781  else
782  kDebug(7102) << "send> pass [protected]";
783 
784  // Send the message...
785  QByteArray buf = cmd;
786  buf += "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html
787  int num = m_control->write(buf);
788  while (m_control->bytesToWrite() && m_control->waitForBytesWritten()) {}
789 
790  // If we were able to successfully send the command, then we will
791  // attempt to read the response. Otherwise, take action to re-attempt
792  // the login based on the maximum number of retries specified...
793  if( num > 0 )
794  ftpResponse(-1);
795  else
796  {
797  m_iRespType = m_iRespCode = 0;
798  }
799 
800  // If respCh is NULL or the response is 421 (Timed-out), we try to re-send
801  // the command based on the value of maxretries.
802  if( (m_iRespType <= 0) || (m_iRespCode == 421) )
803  {
804  // We have not yet logged on...
805  if (!m_bLoggedOn)
806  {
807  // The command was sent from the ftpLogin function, i.e. we are actually
808  // attempting to login in. NOTE: If we already sent the username, we
809  // return false and let the user decide whether (s)he wants to start from
810  // the beginning...
811  if (maxretries > 0 && !isPassCmd)
812  {
813  closeConnection ();
814  if( ftpOpenConnection(loginDefered) )
815  ftpSendCmd ( cmd, maxretries - 1 );
816  }
817 
818  return false;
819  }
820  else
821  {
822  if ( maxretries < 1 )
823  return false;
824  else
825  {
826  kDebug(7102) << "Was not able to communicate with " << m_host
827  << "Attempting to re-establish connection.";
828 
829  closeConnection(); // Close the old connection...
830  openConnection(); // Attempt to re-establish a new connection...
831 
832  if (!m_bLoggedOn)
833  {
834  if (m_control != NULL) // if openConnection succeeded ...
835  {
836  kDebug(7102) << "Login failure, aborting";
837  error (ERR_COULD_NOT_LOGIN, m_host);
838  closeConnection ();
839  }
840  return false;
841  }
842 
843  kDebug(7102) << "Logged back in, re-issuing command";
844 
845  // If we were able to login, resend the command...
846  if (maxretries)
847  maxretries--;
848 
849  return ftpSendCmd( cmd, maxretries );
850  }
851  }
852  }
853 
854  return true;
855 }
856 
857 /*
858  * ftpOpenPASVDataConnection - set up data connection, using PASV mode
859  *
860  * return 0 if successful, ERR_INTERNAL otherwise
861  * doesn't set error message, since non-pasv mode will always be tried if
862  * this one fails
863  */
864 int Ftp::ftpOpenPASVDataConnection()
865 {
866  Q_ASSERT(m_control != NULL); // must have control connection socket
867  Q_ASSERT(m_data == NULL); // ... but no data connection
868 
869  // Check that we can do PASV
870  QHostAddress address = m_control->peerAddress();
871  if (address.protocol() != QAbstractSocket::IPv4Protocol)
872  return ERR_INTERNAL; // no PASV for non-PF_INET connections
873 
874  if (m_extControl & pasvUnknown)
875  return ERR_INTERNAL; // already tried and got "unknown command"
876 
877  m_bPasv = true;
878 
879  /* Let's PASsiVe*/
880  if( !ftpSendCmd("PASV") || (m_iRespType != 2) )
881  {
882  kDebug(7102) << "PASV attempt failed";
883  // unknown command?
884  if( m_iRespType == 5 )
885  {
886  kDebug(7102) << "disabling use of PASV";
887  m_extControl |= pasvUnknown;
888  }
889  return ERR_INTERNAL;
890  }
891 
892  // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)'
893  // but anonftpd gives '227 =160,39,200,55,6,245'
894  int i[6];
895  const char *start = strchr(ftpResponse(3), '(');
896  if ( !start )
897  start = strchr(ftpResponse(3), '=');
898  if ( !start ||
899  ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 &&
900  sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) )
901  {
902  kError(7102) << "parsing IP and port numbers failed. String parsed: " << start;
903  return ERR_INTERNAL;
904  }
905 
906  // we ignore the host part on purpose for two reasons
907  // a) it might be wrong anyway
908  // b) it would make us being suceptible to a port scanning attack
909 
910  // now connect the data socket ...
911  quint16 port = i[4] << 8 | i[5];
912  const QString host = (isSocksProxy() ? m_host : address.toString());
913  m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, port, connectTimeout() * 1000);
914 
915  return m_data->state() == QAbstractSocket::ConnectedState ? 0 : ERR_INTERNAL;
916 }
917 
918 /*
919  * ftpOpenEPSVDataConnection - opens a data connection via EPSV
920  */
921 int Ftp::ftpOpenEPSVDataConnection()
922 {
923  Q_ASSERT(m_control != NULL); // must have control connection socket
924  Q_ASSERT(m_data == NULL); // ... but no data connection
925 
926  QHostAddress address = m_control->peerAddress();
927  int portnum;
928 
929  if (m_extControl & epsvUnknown)
930  return ERR_INTERNAL;
931 
932  m_bPasv = true;
933  if( !ftpSendCmd("EPSV") || (m_iRespType != 2) )
934  {
935  // unknown command?
936  if( m_iRespType == 5 )
937  {
938  kDebug(7102) << "disabling use of EPSV";
939  m_extControl |= epsvUnknown;
940  }
941  return ERR_INTERNAL;
942  }
943 
944  const char *start = strchr(ftpResponse(3), '|');
945  if ( !start || sscanf(start, "|||%d|", &portnum) != 1)
946  return ERR_INTERNAL;
947 
948  const QString host = (isSocksProxy() ? m_host : address.toString());
949  m_data = KSocketFactory::synchronousConnectToHost("ftp-data", host, portnum, connectTimeout() * 1000);
950  return m_data->isOpen() ? 0 : ERR_INTERNAL;
951 }
952 
953 /*
954  * ftpOpenDataConnection - set up data connection
955  *
956  * The routine calls several ftpOpenXxxxConnection() helpers to find
957  * the best connection mode. If a helper cannot connect if returns
958  * ERR_INTERNAL - so this is not really an error! All other error
959  * codes are treated as fatal, e.g. they are passed back to the caller
960  * who is responsible for calling error(). ftpOpenPortDataConnection
961  * can be called as last try and it does never return ERR_INTERNAL.
962  *
963  * @return 0 if successful, err code otherwise
964  */
965 int Ftp::ftpOpenDataConnection()
966 {
967  // make sure that we are logged on and have no data connection...
968  Q_ASSERT( m_bLoggedOn );
969  ftpCloseDataConnection();
970 
971  int iErrCode = 0;
972  int iErrCodePASV = 0; // Remember error code from PASV
973 
974  // First try passive (EPSV & PASV) modes
975  if( !config()->readEntry("DisablePassiveMode", false) )
976  {
977  iErrCode = ftpOpenPASVDataConnection();
978  if(iErrCode == 0)
979  return 0; // success
980  iErrCodePASV = iErrCode;
981  ftpCloseDataConnection();
982 
983  if( !config()->readEntry("DisableEPSV", false) )
984  {
985  iErrCode = ftpOpenEPSVDataConnection();
986  if(iErrCode == 0)
987  return 0; // success
988  ftpCloseDataConnection();
989  }
990 
991  // if we sent EPSV ALL already and it was accepted, then we can't
992  // use active connections any more
993  if (m_extControl & epsvAllSent)
994  return iErrCodePASV;
995  }
996 
997  // fall back to port mode
998  iErrCode = ftpOpenPortDataConnection();
999  if(iErrCode == 0)
1000  return 0; // success
1001 
1002  ftpCloseDataConnection();
1003  // prefer to return the error code from PASV if any, since that's what should have worked in the first place
1004  return iErrCodePASV ? iErrCodePASV : iErrCode;
1005 }
1006 
1007 /*
1008  * ftpOpenPortDataConnection - set up data connection
1009  *
1010  * @return 0 if successful, err code otherwise (but never ERR_INTERNAL
1011  * because this is the last connection mode that is tried)
1012  */
1013 int Ftp::ftpOpenPortDataConnection()
1014 {
1015  Q_ASSERT(m_control != NULL); // must have control connection socket
1016  Q_ASSERT(m_data == NULL); // ... but no data connection
1017 
1018  m_bPasv = false;
1019  if (m_extControl & eprtUnknown)
1020  return ERR_INTERNAL;
1021 
1022  if (!m_server)
1023  m_server = KSocketFactory::listen("ftp-data");
1024 
1025  if (!m_server->isListening()) {
1026  delete m_server;
1027  m_server = NULL;
1028  return ERR_COULD_NOT_LISTEN;
1029  }
1030 
1031  m_server->setMaxPendingConnections(1);
1032 
1033  QString command;
1034  QHostAddress localAddress = m_control->localAddress();
1035  if (localAddress.protocol() == QAbstractSocket::IPv4Protocol)
1036  {
1037  struct
1038  {
1039  quint32 ip4;
1040  quint16 port;
1041  } data;
1042  data.ip4 = localAddress.toIPv4Address();
1043  data.port = m_server->serverPort();
1044 
1045  unsigned char *pData = reinterpret_cast<unsigned char*>(&data);
1046  command.sprintf("PORT %d,%d,%d,%d,%d,%d",pData[3],pData[2],pData[1],pData[0],pData[5],pData[4]);
1047  }
1048  else if (localAddress.protocol() == QAbstractSocket::IPv6Protocol)
1049  {
1050  command = QString("EPRT |2|%2|%3|").arg(localAddress.toString()).arg(m_server->serverPort());
1051  }
1052 
1053  if( ftpSendCmd(command.toLatin1()) && (m_iRespType == 2) )
1054  {
1055  return 0;
1056  }
1057 
1058  delete m_server;
1059  m_server = NULL;
1060  return ERR_INTERNAL;
1061 }
1062 
1063 bool Ftp::ftpOpenCommand( const char *_command, const QString & _path, char _mode,
1064  int errorcode, KIO::fileoffset_t _offset )
1065 {
1066  int errCode = 0;
1067  if( !ftpDataMode(ftpModeFromPath(_path, _mode)) )
1068  errCode = ERR_COULD_NOT_CONNECT;
1069  else
1070  errCode = ftpOpenDataConnection();
1071 
1072  if(errCode != 0)
1073  {
1074  error(errCode, m_host);
1075  return false;
1076  }
1077 
1078  if ( _offset > 0 ) {
1079  // send rest command if offset > 0, this applies to retr and stor commands
1080  char buf[100];
1081  sprintf(buf, "rest %lld", _offset);
1082  if ( !ftpSendCmd( buf ) )
1083  return false;
1084  if( m_iRespType != 3 )
1085  {
1086  error( ERR_CANNOT_RESUME, _path ); // should never happen
1087  return false;
1088  }
1089  }
1090 
1091  QByteArray tmp = _command;
1092  QString errormessage;
1093 
1094  if ( !_path.isEmpty() ) {
1095  tmp += ' ';
1096  tmp += remoteEncoding()->encode(ftpCleanPath(_path));
1097  }
1098 
1099  if( !ftpSendCmd( tmp ) || (m_iRespType != 1) )
1100  {
1101  if( _offset > 0 && qstrcmp(_command, "retr") == 0 && (m_iRespType == 4) )
1102  errorcode = ERR_CANNOT_RESUME;
1103  // The error here depends on the command
1104  errormessage = _path;
1105  }
1106 
1107  else
1108  {
1109  // Only now we know for sure that we can resume
1110  if ( _offset > 0 && qstrcmp(_command, "retr") == 0 )
1111  canResume();
1112 
1113  if(m_server && !m_data) {
1114  kDebug(7102) << "waiting for connection from remote.";
1115  m_server->waitForNewConnection(connectTimeout() * 1000);
1116  m_data = m_server->nextPendingConnection();
1117  }
1118 
1119  if(m_data) {
1120  kDebug(7102) << "connected with remote.";
1121  m_bBusy = true; // cleared in ftpCloseCommand
1122  return true;
1123  }
1124 
1125  kDebug(7102) << "no connection received from remote.";
1126  errorcode=ERR_COULD_NOT_ACCEPT;
1127  errormessage=m_host;
1128  return false;
1129  }
1130 
1131  error(errorcode, errormessage);
1132  return false;
1133 }
1134 
1135 
1136 bool Ftp::ftpCloseCommand()
1137 {
1138  // first close data sockets (if opened), then read response that
1139  // we got for whatever was used in ftpOpenCommand ( should be 226 )
1140  ftpCloseDataConnection();
1141 
1142  if(!m_bBusy)
1143  return true;
1144 
1145  kDebug(7102) << "ftpCloseCommand: reading command result";
1146  m_bBusy = false;
1147 
1148  if(!ftpResponse(-1) || (m_iRespType != 2) )
1149  {
1150  kDebug(7102) << "ftpCloseCommand: no transfer complete message";
1151  return false;
1152  }
1153  return true;
1154 }
1155 
1156 void Ftp::mkdir( const KUrl & url, int permissions )
1157 {
1158  if( !ftpOpenConnection(loginImplicit) )
1159  return;
1160 
1161  const QByteArray encodedPath (remoteEncoding()->encode(url));
1162  const QString path = QString::fromLatin1(encodedPath.constData(), encodedPath.size());
1163 
1164  if( !ftpSendCmd( (QByteArray ("mkd ") + encodedPath) ) || (m_iRespType != 2) )
1165  {
1166  QString currentPath( m_currentPath );
1167 
1168  // Check whether or not mkdir failed because
1169  // the directory already exists...
1170  if( ftpFolder( path, false ) )
1171  {
1172  error( ERR_DIR_ALREADY_EXIST, path );
1173  // Change the directory back to what it was...
1174  (void) ftpFolder( currentPath, false );
1175  return;
1176  }
1177 
1178  error( ERR_COULD_NOT_MKDIR, path );
1179  return;
1180  }
1181 
1182  if ( permissions != -1 )
1183  {
1184  // chmod the dir we just created, ignoring errors.
1185  (void) ftpChmod( path, permissions );
1186  }
1187 
1188  finished();
1189 }
1190 
1191 void Ftp::rename( const KUrl& src, const KUrl& dst, KIO::JobFlags flags )
1192 {
1193  if( !ftpOpenConnection(loginImplicit) )
1194  return;
1195 
1196  // The actual functionality is in ftpRename because put needs it
1197  if ( ftpRename( src.path(), dst.path(), flags ) )
1198  finished();
1199 }
1200 
1201 bool Ftp::ftpRename(const QString & src, const QString & dst, KIO::JobFlags jobFlags)
1202 {
1203  Q_ASSERT(m_bLoggedOn);
1204 
1205  // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793).
1206  if (!(jobFlags & KIO::Overwrite)) {
1207  if (ftpFileExists(dst)) {
1208  error(ERR_FILE_ALREADY_EXIST, dst);
1209  return false;
1210  }
1211  }
1212 
1213  if (ftpFolder(dst, false)) {
1214  error(ERR_DIR_ALREADY_EXIST, dst);
1215  return false;
1216  }
1217 
1218  // CD into parent folder
1219  const int pos = src.lastIndexOf('/');
1220  if (pos >= 0) {
1221  if(!ftpFolder(src.left(pos+1), false))
1222  return false;
1223  }
1224 
1225  QByteArray from_cmd = "RNFR ";
1226  from_cmd += remoteEncoding()->encode(src.mid(pos+1));
1227  if (!ftpSendCmd(from_cmd) || (m_iRespType != 3)) {
1228  error( ERR_CANNOT_RENAME, src );
1229  return false;
1230  }
1231 
1232  QByteArray to_cmd = "RNTO ";
1233  to_cmd += remoteEncoding()->encode(dst);
1234  if (!ftpSendCmd(to_cmd) || (m_iRespType != 2)) {
1235  error( ERR_CANNOT_RENAME, src );
1236  return false;
1237  }
1238 
1239  return true;
1240 }
1241 
1242 void Ftp::del( const KUrl& url, bool isfile )
1243 {
1244  if( !ftpOpenConnection(loginImplicit) )
1245  return;
1246 
1247  // When deleting a directory, we must exit from it first
1248  // The last command probably went into it (to stat it)
1249  if ( !isfile )
1250  ftpFolder(remoteEncoding()->directory(url), false); // ignore errors
1251 
1252  QByteArray cmd = isfile ? "DELE " : "RMD ";
1253  cmd += remoteEncoding()->encode(url);
1254 
1255  if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
1256  error( ERR_CANNOT_DELETE, url.path() );
1257  else
1258  finished();
1259 }
1260 
1261 bool Ftp::ftpChmod( const QString & path, int permissions )
1262 {
1263  Q_ASSERT( m_bLoggedOn );
1264 
1265  if(m_extControl & chmodUnknown) // previous errors?
1266  return false;
1267 
1268  // we need to do bit AND 777 to get permissions, in case
1269  // we were sent a full mode (unlikely)
1270  QString cmd = QString::fromLatin1("SITE CHMOD ") + QString::number( permissions & 511, 8 /*octal*/ ) + ' ';
1271  cmd += path;
1272 
1273  ftpSendCmd(remoteEncoding()->encode(cmd));
1274  if(m_iRespType == 2)
1275  return true;
1276 
1277  if(m_iRespCode == 500)
1278  {
1279  m_extControl |= chmodUnknown;
1280  kDebug(7102) << "ftpChmod: CHMOD not supported - disabling";
1281  }
1282  return false;
1283 }
1284 
1285 void Ftp::chmod( const KUrl & url, int permissions )
1286 {
1287  if( !ftpOpenConnection(loginImplicit) )
1288  return;
1289 
1290  if ( !ftpChmod( url.path(), permissions ) )
1291  error( ERR_CANNOT_CHMOD, url.path() );
1292  else
1293  finished();
1294 }
1295 
1296 void Ftp::ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, UDSEntry& entry, bool isDir )
1297 {
1298  Q_ASSERT(entry.count() == 0); // by contract :-)
1299 
1300  entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1301  entry.insert( KIO::UDSEntry::UDS_SIZE, ftpEnt.size );
1302  entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date );
1303  entry.insert( KIO::UDSEntry::UDS_ACCESS, ftpEnt.access );
1304  entry.insert( KIO::UDSEntry::UDS_USER, ftpEnt.owner );
1305  if ( !ftpEnt.group.isEmpty() )
1306  {
1307  entry.insert( KIO::UDSEntry::UDS_GROUP, ftpEnt.group );
1308  }
1309 
1310  if ( !ftpEnt.link.isEmpty() )
1311  {
1312  entry.insert( KIO::UDSEntry::UDS_LINK_DEST, ftpEnt.link );
1313 
1314  KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("ftp://host/" + filename ) );
1315  // Links on ftp sites are often links to dirs, and we have no way to check
1316  // that. Let's do like Netscape : assume dirs generally.
1317  // But we do this only when the mimetype can't be known from the filename.
1318  // --> we do better than Netscape :-)
1319  if ( mime->name() == KMimeType::defaultMimeType() )
1320  {
1321  kDebug(7102) << "Setting guessed mime type to inode/directory for " << filename;
1322  entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, QString::fromLatin1( "inode/directory" ) );
1323  isDir = true;
1324  }
1325  }
1326 
1327  entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : ftpEnt.type );
1328  // entry.insert KIO::UDSEntry::UDS_ACCESS_TIME,buff.st_atime);
1329  // entry.insert KIO::UDSEntry::UDS_CREATION_TIME,buff.st_ctime);
1330 }
1331 
1332 
1333 void Ftp::ftpShortStatAnswer( const QString& filename, bool isDir )
1334 {
1335  UDSEntry entry;
1336 
1337 
1338  entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1339  entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG );
1340  entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1341  if (isDir) {
1342  entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
1343  }
1344  // No details about size, ownership, group, etc.
1345 
1346  statEntry(entry);
1347  finished();
1348 }
1349 
1350 void Ftp::ftpStatAnswerNotFound( const QString & path, const QString & filename )
1351 {
1352  // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source")
1353  // When e.g. uploading a file, we still need stat() to return "not found"
1354  // when the file doesn't exist.
1355  QString statSide = metaData("statSide");
1356  kDebug(7102) << "statSide=" << statSide;
1357  if ( statSide == "source" )
1358  {
1359  kDebug(7102) << "Not found, but assuming found, because some servers don't allow listing";
1360  // MS Server is incapable of handling "list <blah>" in a case insensitive way
1361  // But "retr <blah>" works. So lie in stat(), to get going...
1362  //
1363  // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run
1364  // where listing permissions are denied, but downloading is still possible.
1365  ftpShortStatAnswer( filename, false /*file, not dir*/ );
1366 
1367  return;
1368  }
1369 
1370  error( ERR_DOES_NOT_EXIST, path );
1371 }
1372 
1373 void Ftp::stat(const KUrl &url)
1374 {
1375  kDebug(7102) << "path=" << url.path();
1376  if( !ftpOpenConnection(loginImplicit) )
1377  return;
1378 
1379  const QString path = ftpCleanPath( QDir::cleanPath( url.path() ) );
1380  kDebug(7102) << "cleaned path=" << path;
1381 
1382  // We can't stat root, but we know it's a dir.
1383  if( path.isEmpty() || path == "/" )
1384  {
1385  UDSEntry entry;
1386  //entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) );
1387  entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1( "." ) );
1388  entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
1389  entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory"));
1390  entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1391  entry.insert( KIO::UDSEntry::UDS_USER, QString::fromLatin1( "root" ) );
1392  entry.insert( KIO::UDSEntry::UDS_GROUP, QString::fromLatin1( "root" ) );
1393  // no size
1394 
1395  statEntry( entry );
1396  finished();
1397  return;
1398  }
1399 
1400  KUrl tempurl( url );
1401  tempurl.setPath( path ); // take the clean one
1402  QString listarg; // = tempurl.directory(KUrl::ObeyTrailingSlash);
1403  QString parentDir;
1404  QString filename = tempurl.fileName();
1405  Q_ASSERT(!filename.isEmpty());
1406  QString search = filename;
1407 
1408  // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info)
1409  // if it doesn't work, it's a file (and then we'll use dir filename)
1410  bool isDir = ftpFolder(path, false);
1411 
1412  // if we're only interested in "file or directory", we should stop here
1413  QString sDetails = metaData("details");
1414  int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
1415  kDebug(7102) << "details=" << details;
1416  if ( details == 0 )
1417  {
1418  if ( !isDir && !ftpFileExists(path) ) // ok, not a dir -> is it a file ?
1419  { // no -> it doesn't exist at all
1420  ftpStatAnswerNotFound( path, filename );
1421  return;
1422  }
1423  ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done
1424  return;
1425  }
1426 
1427  if (!isDir)
1428  {
1429  // It is a file or it doesn't exist, try going to parent directory
1430  parentDir = tempurl.directory(KUrl::AppendTrailingSlash);
1431  // With files we can do "LIST <filename>" to avoid listing the whole dir
1432  listarg = filename;
1433  }
1434  else
1435  {
1436  // --- New implementation:
1437  // Don't list the parent dir. Too slow, might not show it, etc.
1438  // Just return that it's a dir.
1439  UDSEntry entry;
1440  entry.insert( KIO::UDSEntry::UDS_NAME, filename );
1441  entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
1442  entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH );
1443  // No clue about size, ownership, group, etc.
1444 
1445  statEntry(entry);
1446  finished();
1447  return;
1448  }
1449 
1450  // Now cwd the parent dir, to prepare for listing
1451  if( !ftpFolder(parentDir, true) )
1452  return;
1453 
1454  if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) )
1455  {
1456  kError(7102) << "COULD NOT LIST";
1457  return;
1458  }
1459  kDebug(7102) << "Starting of list was ok";
1460 
1461  Q_ASSERT( !search.isEmpty() && search != "/" );
1462 
1463  bool bFound = false;
1464  KUrl linkURL;
1465  FtpEntry ftpEnt;
1466  QList<FtpEntry> ftpValidateEntList;
1467  while (ftpReadDir(ftpEnt)) {
1468  if (!ftpEnt.name.isEmpty() && ftpEnt.name.at(0).isSpace()) {
1469  ftpValidateEntList.append(ftpEnt);
1470  continue;
1471  }
1472 
1473  // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at)
1474  // return only the filename when doing "dir /full/path/to/file"
1475  if (!bFound) {
1476  bFound = maybeEmitStatEntry(ftpEnt, search, filename, isDir);
1477  }
1478  // kDebug(7102) << ftpEnt.name;
1479  }
1480 
1481  for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
1482  FtpEntry& ftpEnt = ftpValidateEntList[i];
1483  fixupEntryName(&ftpEnt);
1484  if (maybeEmitStatEntry(ftpEnt, search, filename, isDir)) {
1485  break;
1486  }
1487  }
1488 
1489  ftpCloseCommand(); // closes the data connection only
1490 
1491  if ( !bFound )
1492  {
1493  ftpStatAnswerNotFound( path, filename );
1494  return;
1495  }
1496 
1497  if ( !linkURL.isEmpty() )
1498  {
1499  if ( linkURL == url || linkURL == tempurl )
1500  {
1501  error( ERR_CYCLIC_LINK, linkURL.prettyUrl() );
1502  return;
1503  }
1504  Ftp::stat( linkURL );
1505  return;
1506  }
1507 
1508  kDebug(7102) << "stat : finished successfully";
1509  finished();
1510 }
1511 
1512 bool Ftp::maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir)
1513 {
1514  if ((search == ftpEnt.name || filename == ftpEnt.name) && !filename.isEmpty()) {
1515  UDSEntry entry;
1516  ftpCreateUDSEntry( filename, ftpEnt, entry, isDir );
1517  statEntry( entry );
1518  return true;
1519  }
1520 
1521  return false;
1522 }
1523 
1524 void Ftp::listDir( const KUrl &url )
1525 {
1526  kDebug(7102) << url;
1527  if( !ftpOpenConnection(loginImplicit) )
1528  return;
1529 
1530  // No path specified ?
1531  QString path = url.path();
1532  if ( path.isEmpty() )
1533  {
1534  KUrl realURL;
1535  realURL.setProtocol( "ftp" );
1536  realURL.setUser( m_user );
1537  realURL.setPass( m_pass );
1538  realURL.setHost( m_host );
1539  if ( m_port > 0 && m_port != DEFAULT_FTP_PORT )
1540  realURL.setPort( m_port );
1541  if ( m_initialPath.isEmpty() )
1542  m_initialPath = '/';
1543  realURL.setPath( m_initialPath );
1544  kDebug(7102) << "REDIRECTION to " << realURL.prettyUrl();
1545  redirection( realURL );
1546  finished();
1547  return;
1548  }
1549 
1550  kDebug(7102) << "hunting for path" << path;
1551 
1552  if (!ftpOpenDir(path)) {
1553  if (ftpFileExists(path)) {
1554  error(ERR_IS_FILE, path);
1555  } else {
1556  // not sure which to emit
1557  //error( ERR_DOES_NOT_EXIST, path );
1558  error( ERR_CANNOT_ENTER_DIRECTORY, path );
1559  }
1560  return;
1561  }
1562 
1563  UDSEntry entry;
1564  FtpEntry ftpEnt;
1565  QList<FtpEntry> ftpValidateEntList;
1566  while( ftpReadDir(ftpEnt) )
1567  {
1568  //kDebug(7102) << ftpEnt.name;
1569  //Q_ASSERT( !ftpEnt.name.isEmpty() );
1570  if (!ftpEnt.name.isEmpty()) {
1571  if (ftpEnt.name.at(0).isSpace()) {
1572  ftpValidateEntList.append(ftpEnt);
1573  continue;
1574  }
1575 
1576  //if ( S_ISDIR( (mode_t)ftpEnt.type ) )
1577  // kDebug(7102) << "is a dir";
1578  //if ( !ftpEnt.link.isEmpty() )
1579  // kDebug(7102) << "is a link to " << ftpEnt.link;
1580  ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
1581  listEntry( entry, false );
1582  entry.clear();
1583  }
1584  }
1585 
1586  for (int i = 0, count = ftpValidateEntList.count(); i < count; ++i) {
1587  FtpEntry& ftpEnt = ftpValidateEntList[i];
1588  fixupEntryName(&ftpEnt);
1589  ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false );
1590  listEntry( entry, false );
1591  entry.clear();
1592  }
1593 
1594  listEntry( entry, true ); // ready
1595  ftpCloseCommand(); // closes the data connection only
1596  finished();
1597 }
1598 
1599 void Ftp::slave_status()
1600 {
1601  kDebug(7102) << "Got slave_status host = " << (!m_host.toLatin1().isEmpty() ? m_host.toAscii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]";
1602  slaveStatus( m_host, m_bLoggedOn );
1603 }
1604 
1605 bool Ftp::ftpOpenDir( const QString & path )
1606 {
1607  //QString path( _url.path(KUrl::RemoveTrailingSlash) );
1608 
1609  // We try to change to this directory first to see whether it really is a directory.
1610  // (And also to follow symlinks)
1611  QString tmp = path.isEmpty() ? QString("/") : path;
1612 
1613  // We get '550', whether it's a file or doesn't exist...
1614  if( !ftpFolder(tmp, false) )
1615  return false;
1616 
1617  // Don't use the path in the list command:
1618  // We changed into this directory anyway - so it's enough just to send "list".
1619  // We use '-a' because the application MAY be interested in dot files.
1620  // The only way to really know would be to have a metadata flag for this...
1621  // Since some windows ftp server seems not to support the -a argument, we use a fallback here.
1622  // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com)
1623  if( !ftpOpenCommand( "list -la", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
1624  {
1625  if ( !ftpOpenCommand( "list", QString(), 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
1626  {
1627  kWarning(7102) << "Can't open for listing";
1628  return false;
1629  }
1630  }
1631  kDebug(7102) << "Starting of list was ok";
1632  return true;
1633 }
1634 
1635 bool Ftp::ftpReadDir(FtpEntry& de)
1636 {
1637  Q_ASSERT(m_data != NULL);
1638 
1639  // get a line from the data connecetion ...
1640  while( true )
1641  {
1642  while (!m_data->canReadLine() && m_data->waitForReadyRead((readTimeout() * 1000))) {}
1643  QByteArray data = m_data->readLine();
1644  if (data.size() == 0)
1645  break;
1646 
1647  const char* buffer = data.data();
1648  kDebug(7102) << "dir > " << buffer;
1649 
1650  //Normally the listing looks like
1651  // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log
1652  // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442)
1653  // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI
1654 
1655  // we should always get the following 5 fields ...
1656  const char *p_access, *p_junk, *p_owner, *p_group, *p_size;
1657  if( (p_access = strtok((char*)buffer," ")) == 0) continue;
1658  if( (p_junk = strtok(NULL," ")) == 0) continue;
1659  if( (p_owner = strtok(NULL," ")) == 0) continue;
1660  if( (p_group = strtok(NULL," ")) == 0) continue;
1661  if( (p_size = strtok(NULL," ")) == 0) continue;
1662 
1663  //kDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size;
1664 
1665  de.access = 0;
1666  if ( qstrlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware
1667  de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions
1668  }
1669 
1670  const char *p_date_1, *p_date_2, *p_date_3, *p_name;
1671 
1672  // A special hack for "/dev". A listing may look like this:
1673  // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero
1674  // So we just ignore the number in front of the ",". Ok, it is a hack :-)
1675  if ( strchr( p_size, ',' ) != 0L )
1676  {
1677  //kDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)";
1678  if ((p_size = strtok(NULL," ")) == 0)
1679  continue;
1680  }
1681 
1682  // Check whether the size we just read was really the size
1683  // or a month (this happens when the server lists no group)
1684  // Used to be the case on sunsite.uio.no, but not anymore
1685  // This is needed for the Netware case, too.
1686  if ( !isdigit( *p_size ) )
1687  {
1688  p_date_1 = p_size;
1689  p_size = p_group;
1690  p_group = 0;
1691  //kDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1;
1692  }
1693  else
1694  {
1695  p_date_1 = strtok(NULL," ");
1696  //kDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1;
1697  }
1698 
1699  if ( p_date_1 != 0 &&
1700  (p_date_2 = strtok(NULL," ")) != 0 &&
1701  (p_date_3 = strtok(NULL," ")) != 0 &&
1702  (p_name = strtok(NULL,"\r\n")) != 0 )
1703  {
1704  {
1705  QByteArray tmp( p_name );
1706  if ( p_access[0] == 'l' )
1707  {
1708  int i = tmp.lastIndexOf( " -> " );
1709  if ( i != -1 ) {
1710  de.link = remoteEncoding()->decode(p_name + i + 4);
1711  tmp.truncate( i );
1712  }
1713  else
1714  de.link.clear();
1715  }
1716  else
1717  de.link.clear();
1718 
1719  if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/'
1720  tmp.remove( 0, 1 );
1721 
1722  if (tmp.indexOf('/') != -1)
1723  continue; // Don't trick us!
1724 
1725  de.name = remoteEncoding()->decode(tmp);
1726  }
1727 
1728  de.type = S_IFREG;
1729  switch ( p_access[0] ) {
1730  case 'd':
1731  de.type = S_IFDIR;
1732  break;
1733  case 's':
1734  de.type = S_IFSOCK;
1735  break;
1736  case 'b':
1737  de.type = S_IFBLK;
1738  break;
1739  case 'c':
1740  de.type = S_IFCHR;
1741  break;
1742  case 'l':
1743  de.type = S_IFREG;
1744  // we don't set S_IFLNK here. de.link says it.
1745  break;
1746  default:
1747  break;
1748  }
1749 
1750  if ( p_access[1] == 'r' )
1751  de.access |= S_IRUSR;
1752  if ( p_access[2] == 'w' )
1753  de.access |= S_IWUSR;
1754  if ( p_access[3] == 'x' || p_access[3] == 's' )
1755  de.access |= S_IXUSR;
1756  if ( p_access[4] == 'r' )
1757  de.access |= S_IRGRP;
1758  if ( p_access[5] == 'w' )
1759  de.access |= S_IWGRP;
1760  if ( p_access[6] == 'x' || p_access[6] == 's' )
1761  de.access |= S_IXGRP;
1762  if ( p_access[7] == 'r' )
1763  de.access |= S_IROTH;
1764  if ( p_access[8] == 'w' )
1765  de.access |= S_IWOTH;
1766  if ( p_access[9] == 'x' || p_access[9] == 't' )
1767  de.access |= S_IXOTH;
1768  if ( p_access[3] == 's' || p_access[3] == 'S' )
1769  de.access |= S_ISUID;
1770  if ( p_access[6] == 's' || p_access[6] == 'S' )
1771  de.access |= S_ISGID;
1772  if ( p_access[9] == 't' || p_access[9] == 'T' )
1773  de.access |= S_ISVTX;
1774 
1775  de.owner = remoteEncoding()->decode(p_owner);
1776  de.group = remoteEncoding()->decode(p_group);
1777  de.size = charToLongLong(p_size);
1778 
1779  // Parsing the date is somewhat tricky
1780  // Examples : "Oct 6 22:49", "May 13 1999"
1781 
1782  // First get current time - we need the current month and year
1783  time_t currentTime = time( 0L );
1784  struct tm * tmptr = gmtime( &currentTime );
1785  int currentMonth = tmptr->tm_mon;
1786  //kDebug(7102) << "Current time :" << asctime( tmptr );
1787  // Reset time fields
1788  tmptr->tm_isdst = -1; // We do not anything about day saving time
1789  tmptr->tm_sec = 0;
1790  tmptr->tm_min = 0;
1791  tmptr->tm_hour = 0;
1792  // Get day number (always second field)
1793  if (p_date_2)
1794  tmptr->tm_mday = atoi( p_date_2 );
1795  // Get month from first field
1796  // NOTE : no, we don't want to use KLocale here
1797  // It seems all FTP servers use the English way
1798  //kDebug(7102) << "Looking for month " << p_date_1;
1799  static const char * const s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1800  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1801  for ( int c = 0 ; c < 12 ; c ++ )
1802  if ( !qstrcmp( p_date_1, s_months[c]) )
1803  {
1804  //kDebug(7102) << "Found month " << c << " for " << p_date_1;
1805  tmptr->tm_mon = c;
1806  break;
1807  }
1808 
1809  // Parse third field
1810  if ( qstrlen( p_date_3 ) == 4 ) // 4 digits, looks like a year
1811  tmptr->tm_year = atoi( p_date_3 ) - 1900;
1812  else
1813  {
1814  // otherwise, the year is implicit
1815  // according to man ls, this happens when it is between than 6 months
1816  // old and 1 hour in the future.
1817  // So the year is : current year if tm_mon <= currentMonth+1
1818  // otherwise current year minus one
1819  // (The +1 is a security for the "+1 hour" at the end of the month issue)
1820  if ( tmptr->tm_mon > currentMonth + 1 )
1821  tmptr->tm_year--;
1822 
1823  // and p_date_3 contains probably a time
1824  char * semicolon;
1825  if ( p_date_3 && ( semicolon = (char*)strchr( p_date_3, ':' ) ) )
1826  {
1827  *semicolon = '\0';
1828  tmptr->tm_min = atoi( semicolon + 1 );
1829  tmptr->tm_hour = atoi( p_date_3 );
1830  }
1831  else
1832  kWarning(7102) << "Can't parse third field " << p_date_3;
1833  }
1834 
1835  //kDebug(7102) << asctime( tmptr );
1836  de.date = mktime( tmptr );
1837  return true;
1838  }
1839  } // line invalid, loop to get another line
1840  return false;
1841 }
1842 
1843 //===============================================================================
1844 // public: get download file from server
1845 // helper: ftpGet called from get() and copy()
1846 //===============================================================================
1847 void Ftp::get( const KUrl & url )
1848 {
1849  kDebug(7102) << url;
1850 
1851  int iError = 0;
1852  const StatusCode cs = ftpGet(iError, -1, url, 0);
1853  ftpCloseCommand(); // must close command!
1854 
1855  if (cs == statusSuccess) {
1856  finished();
1857  return;
1858  }
1859 
1860  if (iError) { // can have only server side errs
1861  error(iError, url.path());
1862  }
1863 }
1864 
1865 Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t llOffset)
1866 {
1867  // Calls error() by itself!
1868  if( !ftpOpenConnection(loginImplicit) )
1869  return statusServerError;
1870 
1871  // Try to find the size of the file (and check that it exists at
1872  // the same time). If we get back a 550, "File does not exist"
1873  // or "not a plain file", check if it is a directory. If it is a
1874  // directory, return an error; otherwise simply try to retrieve
1875  // the request...
1876  if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) &&
1877  ftpFolder(url.path(), false) )
1878  {
1879  // Ok it's a dir in fact
1880  kDebug(7102) << "it is a directory in fact";
1881  iError = ERR_IS_DIRECTORY;
1882  return statusServerError;
1883  }
1884 
1885  QString resumeOffset = metaData("resume");
1886  if ( !resumeOffset.isEmpty() )
1887  {
1888  llOffset = resumeOffset.toLongLong();
1889  kDebug(7102) << "got offset from metadata : " << llOffset;
1890  }
1891 
1892  if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) )
1893  {
1894  kWarning(7102) << "Can't open for reading";
1895  return statusServerError;
1896  }
1897 
1898  // Read the size from the response string
1899  if(m_size == UnknownSize)
1900  {
1901  const char* psz = strrchr( ftpResponse(4), '(' );
1902  if(psz) m_size = charToLongLong(psz+1);
1903  if (!m_size) m_size = UnknownSize;
1904  }
1905 
1906  // Send the mime-type...
1907  if (iCopyFile == -1) {
1908  StatusCode status = ftpSendMimeType(iError, url);
1909  if (status != statusSuccess) {
1910  return status;
1911  }
1912  }
1913 
1914  KIO::filesize_t bytesLeft = 0;
1915  if ( m_size != UnknownSize ) {
1916  bytesLeft = m_size - llOffset;
1917  totalSize( m_size ); // emit the total size...
1918  }
1919 
1920  kDebug(7102) << "starting with offset=" << llOffset;
1921  KIO::fileoffset_t processed_size = llOffset;
1922 
1923  QByteArray array;
1924  char buffer[maximumIpcSize];
1925  // start with small data chunks in case of a slow data source (modem)
1926  // - unfortunately this has a negative impact on performance for large
1927  // - files - so we will increase the block size after a while ...
1928  int iBlockSize = initialIpcSize;
1929  int iBufferCur = 0;
1930 
1931  while(m_size == UnknownSize || bytesLeft > 0)
1932  { // let the buffer size grow if the file is larger 64kByte ...
1933  if(processed_size-llOffset > 1024 * 64)
1934  iBlockSize = maximumIpcSize;
1935 
1936  // read the data and detect EOF or error ...
1937  if(iBlockSize+iBufferCur > (int)sizeof(buffer))
1938  iBlockSize = sizeof(buffer) - iBufferCur;
1939  if (m_data->bytesAvailable() == 0)
1940  m_data->waitForReadyRead((readTimeout() * 1000));
1941  int n = m_data->read( buffer+iBufferCur, iBlockSize );
1942  if(n <= 0)
1943  { // this is how we detect EOF in case of unknown size
1944  if( m_size == UnknownSize && n == 0 )
1945  break;
1946  // unexpected eof. Happens when the daemon gets killed.
1947  iError = ERR_COULD_NOT_READ;
1948  return statusServerError;
1949  }
1950  processed_size += n;
1951 
1952  // collect very small data chunks in buffer before processing ...
1953  if(m_size != UnknownSize)
1954  {
1955  bytesLeft -= n;
1956  iBufferCur += n;
1957  if(iBufferCur < minimumMimeSize && bytesLeft > 0)
1958  {
1959  processedSize( processed_size );
1960  continue;
1961  }
1962  n = iBufferCur;
1963  iBufferCur = 0;
1964  }
1965 
1966  // write output file or pass to data pump ...
1967  if(iCopyFile == -1)
1968  {
1969  array = QByteArray::fromRawData(buffer, n);
1970  data( array );
1971  array.clear();
1972  }
1973  else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0)
1974  return statusClientError; // client side error
1975  processedSize( processed_size );
1976  }
1977 
1978  kDebug(7102) << "done";
1979  if(iCopyFile == -1) // must signal EOF to data pump ...
1980  data(array); // array is empty and must be empty!
1981 
1982  processedSize( m_size == UnknownSize ? processed_size : m_size );
1983  return statusSuccess;
1984 }
1985 
1986 #if 0
1987  void Ftp::mimetype( const KUrl& url )
1988  {
1989  if( !ftpOpenConnection(loginImplicit) )
1990  return;
1991 
1992  if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) {
1993  kWarning(7102) << "Can't open for reading";
1994  return;
1995  }
1996  char buffer[ 2048 ];
1997  QByteArray array;
1998  // Get one chunk of data only and send it, KIO::Job will determine the
1999  // mimetype from it using KMimeMagic
2000  int n = m_data->read( buffer, 2048 );
2001  array.setRawData(buffer, n);
2002  data( array );
2003  array.resetRawData(buffer, n);
2004 
2005  kDebug(7102) << "aborting";
2006  ftpAbortTransfer();
2007 
2008  kDebug(7102) << "finished";
2009  finished();
2010  kDebug(7102) << "after finished";
2011  }
2012 
2013  void Ftp::ftpAbortTransfer()
2014  {
2015  // RFC 959, page 34-35
2016  // IAC (interpret as command) = 255 ; IP (interrupt process) = 254
2017  // DM = 242 (data mark)
2018  char msg[4];
2019  // 1. User system inserts the Telnet "Interrupt Process" (IP) signal
2020  // in the Telnet stream.
2021  msg[0] = (char) 255; //IAC
2022  msg[1] = (char) 254; //IP
2023  (void) send(sControl, msg, 2, 0);
2024  // 2. User system sends the Telnet "Sync" signal.
2025  msg[0] = (char) 255; //IAC
2026  msg[1] = (char) 242; //DM
2027  if (send(sControl, msg, 2, MSG_OOB) != 2)
2028  ; // error...
2029 
2030  // Send ABOR
2031  kDebug(7102) << "send ABOR";
2032  QCString buf = "ABOR\r\n";
2033  if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) {
2034  error( ERR_COULD_NOT_WRITE, QString() );
2035  return;
2036  }
2037 
2038  //
2039  kDebug(7102) << "read resp";
2040  if ( readresp() != '2' )
2041  {
2042  error( ERR_COULD_NOT_READ, QString() );
2043  return;
2044  }
2045 
2046  kDebug(7102) << "close sockets";
2047  closeSockets();
2048  }
2049 #endif
2050 
2051 //===============================================================================
2052 // public: put upload file to server
2053 // helper: ftpPut called from put() and copy()
2054 //===============================================================================
2055 void Ftp::put(const KUrl& url, int permissions, KIO::JobFlags flags)
2056 {
2057  kDebug(7102) << url;
2058 
2059  int iError = 0; // iError gets status
2060  const StatusCode cs = ftpPut(iError, -1, url, permissions, flags);
2061  ftpCloseCommand(); // must close command!
2062 
2063  if (cs == statusSuccess) {
2064  finished();
2065  return;
2066  }
2067 
2068  if (iError) { // can have only server side errs
2069  error(iError, url.path());
2070  }
2071 }
2072 
2073 Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KUrl& dest_url,
2074  int permissions, KIO::JobFlags flags)
2075 {
2076  if( !ftpOpenConnection(loginImplicit) )
2077  return statusServerError;
2078 
2079  // Don't use mark partial over anonymous FTP.
2080  // My incoming dir allows put but not rename...
2081  bool bMarkPartial;
2082  if (m_user.isEmpty () || m_user == FTP_LOGIN)
2083  bMarkPartial = false;
2084  else
2085  bMarkPartial = config()->readEntry("MarkPartial", true);
2086 
2087  QString dest_orig = dest_url.path();
2088  QString dest_part( dest_orig );
2089  dest_part += ".part";
2090 
2091  if ( ftpSize( dest_orig, 'I' ) )
2092  {
2093  if ( m_size == 0 )
2094  { // delete files with zero size
2095  QByteArray cmd = "DELE ";
2096  cmd += remoteEncoding()->encode(dest_orig);
2097  if( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
2098  {
2099  iError = ERR_CANNOT_DELETE_PARTIAL;
2100  return statusServerError;
2101  }
2102  }
2103  else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
2104  {
2105  iError = ERR_FILE_ALREADY_EXIST;
2106  return statusServerError;
2107  }
2108  else if ( bMarkPartial )
2109  { // when using mark partial, append .part extension
2110  if ( !ftpRename( dest_orig, dest_part, KIO::Overwrite ) )
2111  {
2112  iError = ERR_CANNOT_RENAME_PARTIAL;
2113  return statusServerError;
2114  }
2115  }
2116  // Don't chmod an existing file
2117  permissions = -1;
2118  }
2119  else if ( bMarkPartial && ftpSize( dest_part, 'I' ) )
2120  { // file with extension .part exists
2121  if ( m_size == 0 )
2122  { // delete files with zero size
2123  QByteArray cmd = "DELE ";
2124  cmd += remoteEncoding()->encode(dest_part);
2125  if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) )
2126  {
2127  iError = ERR_CANNOT_DELETE_PARTIAL;
2128  return statusServerError;
2129  }
2130  }
2131  else if ( !(flags & KIO::Overwrite) && !(flags & KIO::Resume) )
2132  {
2133  flags |= canResume (m_size) ? KIO::Resume : KIO::DefaultFlags;
2134  if (!(flags & KIO::Resume))
2135  {
2136  iError = ERR_FILE_ALREADY_EXIST;
2137  return statusServerError;
2138  }
2139  }
2140  }
2141  else
2142  m_size = 0;
2143 
2144  QString dest;
2145 
2146  // if we are using marking of partial downloads -> add .part extension
2147  if ( bMarkPartial ) {
2148  kDebug(7102) << "Adding .part extension to " << dest_orig;
2149  dest = dest_part;
2150  } else
2151  dest = dest_orig;
2152 
2153  KIO::fileoffset_t offset = 0;
2154 
2155  // set the mode according to offset
2156  if( (flags & KIO::Resume) && m_size > 0 )
2157  {
2158  offset = m_size;
2159  if(iCopyFile != -1)
2160  {
2161  if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 )
2162  {
2163  iError = ERR_CANNOT_RESUME;
2164  return statusClientError;
2165  }
2166  }
2167  }
2168 
2169  if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) )
2170  return statusServerError;
2171 
2172  kDebug(7102) << "ftpPut: starting with offset=" << offset;
2173  KIO::fileoffset_t processed_size = offset;
2174 
2175  QByteArray buffer;
2176  int result;
2177  int iBlockSize = initialIpcSize;
2178  // Loop until we got 'dataEnd'
2179  do
2180  {
2181  if(iCopyFile == -1)
2182  {
2183  dataReq(); // Request for data
2184  result = readData( buffer );
2185  }
2186  else
2187  { // let the buffer size grow if the file is larger 64kByte ...
2188  if(processed_size-offset > 1024 * 64)
2189  iBlockSize = maximumIpcSize;
2190  buffer.resize(iBlockSize);
2191  result = ::read(iCopyFile, buffer.data(), buffer.size());
2192  if(result < 0)
2193  iError = ERR_COULD_NOT_WRITE;
2194  else
2195  buffer.resize(result);
2196  }
2197 
2198  if (result > 0)
2199  {
2200  m_data->write( buffer );
2201  while (m_data->bytesToWrite() && m_data->waitForBytesWritten()) {}
2202  processed_size += result;
2203  processedSize (processed_size);
2204  }
2205  }
2206  while ( result > 0 );
2207 
2208  if (result != 0) // error
2209  {
2210  ftpCloseCommand(); // don't care about errors
2211  kDebug(7102) << "Error during 'put'. Aborting.";
2212  if (bMarkPartial)
2213  {
2214  // Remove if smaller than minimum size
2215  if ( ftpSize( dest, 'I' ) &&
2216  ( processed_size < config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) )
2217  {
2218  QByteArray cmd = "DELE ";
2219  cmd += remoteEncoding()->encode(dest);
2220  (void) ftpSendCmd( cmd );
2221  }
2222  }
2223  return statusServerError;
2224  }
2225 
2226  if ( !ftpCloseCommand() )
2227  {
2228  iError = ERR_COULD_NOT_WRITE;
2229  return statusServerError;
2230  }
2231 
2232  // after full download rename the file back to original name
2233  if ( bMarkPartial )
2234  {
2235  kDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")";
2236  if ( !ftpRename( dest, dest_orig, KIO::Overwrite ) )
2237  {
2238  iError = ERR_CANNOT_RENAME_PARTIAL;
2239  return statusServerError;
2240  }
2241  }
2242 
2243  // set final permissions
2244  if ( permissions != -1 )
2245  {
2246  if ( m_user == FTP_LOGIN )
2247  kDebug(7102) << "Trying to chmod over anonymous FTP ???";
2248  // chmod the file we just put
2249  if ( ! ftpChmod( dest_orig, permissions ) )
2250  {
2251  // To be tested
2252  //if ( m_user != FTP_LOGIN )
2253  // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) );
2254  }
2255  }
2256 
2257  return statusSuccess;
2258 }
2259 
2260 const char* Ftp::ftpSendSizeCmd(const QString& path)
2261 {
2262  // Some servers do not allow absolute path for SIZE; so we use
2263  // relative paths whenever possible. #326292
2264  QString currentPath(m_currentPath);
2265  if (!currentPath.endsWith(QLatin1Char('/'))) {
2266  currentPath += QLatin1Char('/');
2267  }
2268 
2269  QByteArray buf;
2270  buf = "SIZE ";
2271  if (path.startsWith(currentPath)) {
2272  buf += remoteEncoding()->encode(path.mid(currentPath.length()));
2273  } else {
2274  buf += remoteEncoding()->encode(path);
2275  }
2276 
2277  if (!ftpSendCmd(buf) || m_iRespType != 2) {
2278  return 0;
2279  }
2280 
2281  // skip leading "213 " (response code)
2282  return ftpResponse(4);
2283 }
2284 
2285 
2288 bool Ftp::ftpSize(const QString & path, char mode)
2289 {
2290  m_size = UnknownSize;
2291  if (!ftpDataMode(mode)) {
2292  return false;
2293  }
2294 
2295  const QByteArray psz(ftpSendSizeCmd(path));
2296  if (psz.isEmpty()) {
2297  return false;
2298  }
2299 
2300  bool ok = false;
2301  m_size = psz.trimmed().toLongLong(&ok);
2302  if (!ok) {
2303  m_size = UnknownSize;
2304  }
2305 
2306  return true;
2307 }
2308 
2309 bool Ftp::ftpFileExists(const QString& path)
2310 {
2311  return ftpSendSizeCmd(path) != 0;
2312 }
2313 
2314 // Today the differences between ASCII and BINARY are limited to
2315 // CR or CR/LF line terminators. Many servers ignore ASCII (like
2316 // win2003 -or- vsftp with default config). In the early days of
2317 // computing, when even text-files had structure, this stuff was
2318 // more important.
2319 // Theoretically "list" could return different results in ASCII
2320 // and BINARY mode. But again, most servers ignore ASCII here.
2321 bool Ftp::ftpDataMode(char cMode)
2322 {
2323  if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I';
2324  else if(cMode == 'a') cMode = 'A';
2325  else if(cMode != 'A') cMode = 'I';
2326 
2327  kDebug(7102) << "want" << cMode << "has" << m_cDataMode;
2328  if(m_cDataMode == cMode)
2329  return true;
2330 
2331  QByteArray buf = "TYPE ";
2332  buf += cMode;
2333  if( !ftpSendCmd(buf) || (m_iRespType != 2) )
2334  return false;
2335  m_cDataMode = cMode;
2336  return true;
2337 }
2338 
2339 
2340 bool Ftp::ftpFolder(const QString& path, bool bReportError)
2341 {
2342  QString newPath = path;
2343  int iLen = newPath.length();
2344  if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1);
2345 
2346  //kDebug(7102) << "want" << newPath << "has" << m_currentPath;
2347  if(m_currentPath == newPath)
2348  return true;
2349 
2350  QByteArray tmp = "cwd ";
2351  tmp += remoteEncoding()->encode(newPath);
2352  if( !ftpSendCmd(tmp) )
2353  return false; // connection failure
2354  if(m_iRespType != 2)
2355  {
2356  if(bReportError)
2357  error(ERR_CANNOT_ENTER_DIRECTORY, path);
2358  return false; // not a folder
2359  }
2360  m_currentPath = newPath;
2361  return true;
2362 }
2363 
2364 
2365 //===============================================================================
2366 // public: copy don't use kio data pump if one side is a local file
2367 // helper: ftpCopyPut called from copy() on upload
2368 // helper: ftpCopyGet called from copy() on download
2369 //===============================================================================
2370 void Ftp::copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags )
2371 {
2372  int iError = 0;
2373  int iCopyFile = -1;
2374  StatusCode cs = statusSuccess;
2375  bool bSrcLocal = src.isLocalFile();
2376  bool bDestLocal = dest.isLocalFile();
2377  QString sCopyFile;
2378 
2379  if(bSrcLocal && !bDestLocal) // File -> Ftp
2380  {
2381  sCopyFile = src.toLocalFile();
2382  kDebug(7102) << "local file" << sCopyFile << "-> ftp" << dest.path();
2383  cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, flags);
2384  if( cs == statusServerError) sCopyFile = dest.url();
2385  }
2386  else if(!bSrcLocal && bDestLocal) // Ftp -> File
2387  {
2388  sCopyFile = dest.toLocalFile();
2389  kDebug(7102) << "ftp" << src.path() << "-> local file" << sCopyFile;
2390  cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, flags);
2391  if( cs == statusServerError ) sCopyFile = src.url();
2392  }
2393  else {
2394  error( ERR_UNSUPPORTED_ACTION, QString() );
2395  return;
2396  }
2397 
2398  // perform clean-ups and report error (if any)
2399  if(iCopyFile != -1)
2400  ::close(iCopyFile);
2401  ftpCloseCommand(); // must close command!
2402  if(iError)
2403  error(iError, sCopyFile);
2404  else
2405  finished();
2406 }
2407 
2408 
2409 Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile,
2410  const KUrl& url, int permissions, KIO::JobFlags flags)
2411 {
2412  // check if source is ok ...
2413  KDE_struct_stat buff;
2414  bool bSrcExists = (KDE::stat( sCopyFile, &buff ) != -1);
2415  if(bSrcExists)
2416  { if(S_ISDIR(buff.st_mode))
2417  {
2418  iError = ERR_IS_DIRECTORY;
2419  return statusClientError;
2420  }
2421  }
2422  else
2423  {
2424  iError = ERR_DOES_NOT_EXIST;
2425  return statusClientError;
2426  }
2427 
2428  iCopyFile = KDE::open( sCopyFile, O_RDONLY );
2429  if(iCopyFile == -1)
2430  {
2431  iError = ERR_CANNOT_OPEN_FOR_READING;
2432  return statusClientError;
2433  }
2434 
2435  // delegate the real work (iError gets status) ...
2436  totalSize(buff.st_size);
2437 #ifdef ENABLE_CAN_RESUME
2438  return ftpPut(iError, iCopyFile, url, permissions, flags & ~KIO::Resume);
2439 #else
2440  return ftpPut(iError, iCopyFile, url, permissions, flags | KIO::Resume);
2441 #endif
2442 }
2443 
2444 
2445 Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile,
2446  const KUrl& url, int permissions, KIO::JobFlags flags)
2447 {
2448  // check if destination is ok ...
2449  KDE_struct_stat buff;
2450  const bool bDestExists = (KDE::stat( sCopyFile, &buff ) != -1);
2451  if(bDestExists)
2452  { if(S_ISDIR(buff.st_mode))
2453  {
2454  iError = ERR_IS_DIRECTORY;
2455  return statusClientError;
2456  }
2457  if(!(flags & KIO::Overwrite))
2458  {
2459  iError = ERR_FILE_ALREADY_EXIST;
2460  return statusClientError;
2461  }
2462  }
2463 
2464  // do we have a ".part" file?
2465  const QString sPart = sCopyFile + QLatin1String(".part");
2466  bool bResume = false;
2467  const bool bPartExists = (KDE::stat( sPart, &buff ) != -1);
2468  const bool bMarkPartial = config()->readEntry("MarkPartial", true);
2469  const QString dest = bMarkPartial ? sPart : sCopyFile;
2470  if (bMarkPartial && bPartExists && buff.st_size > 0)
2471  { // must not be a folder! please fix a similar bug in kio_file!!
2472  if(S_ISDIR(buff.st_mode))
2473  {
2474  iError = ERR_DIR_ALREADY_EXIST;
2475  return statusClientError; // client side error
2476  }
2477  //doesn't work for copy? -> design flaw?
2478 #ifdef ENABLE_CAN_RESUME
2479  bResume = canResume( buff.st_size );
2480 #else
2481  bResume = true;
2482 #endif
2483  }
2484 
2485  if (bPartExists && !bResume) // get rid of an unwanted ".part" file
2486  QFile::remove(sPart);
2487 
2488  // WABA: Make sure that we keep writing permissions ourselves,
2489  // otherwise we can be in for a surprise on NFS.
2490  mode_t initialMode;
2491  if (permissions != -1)
2492  initialMode = permissions | S_IWUSR;
2493  else
2494  initialMode = 0666;
2495 
2496  // open the output file ...
2497  KIO::fileoffset_t hCopyOffset = 0;
2498  if (bResume) {
2499  iCopyFile = KDE::open( sPart, O_RDWR ); // append if resuming
2500  hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END);
2501  if(hCopyOffset < 0)
2502  {
2503  iError = ERR_CANNOT_RESUME;
2504  return statusClientError; // client side error
2505  }
2506  kDebug(7102) << "resuming at " << hCopyOffset;
2507  }
2508  else {
2509  iCopyFile = KDE::open(dest, O_CREAT | O_TRUNC | O_WRONLY, initialMode);
2510  }
2511 
2512  if(iCopyFile == -1)
2513  {
2514  kDebug(7102) << "### COULD NOT WRITE " << sCopyFile;
2515  iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED
2516  : ERR_CANNOT_OPEN_FOR_WRITING;
2517  return statusClientError;
2518  }
2519 
2520  // delegate the real work (iError gets status) ...
2521  StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset);
2522  if( ::close(iCopyFile) && iRes == statusSuccess )
2523  {
2524  iError = ERR_COULD_NOT_WRITE;
2525  iRes = statusClientError;
2526  }
2527  iCopyFile = -1;
2528 
2529  // handle renaming or deletion of a partial file ...
2530  if(bMarkPartial)
2531  {
2532  if(iRes == statusSuccess)
2533  { // rename ".part" on success
2534  if ( KDE::rename( sPart, sCopyFile ) )
2535  {
2536  // If rename fails, try removing the destination first if it exists.
2537  if (!bDestExists || !(QFile::remove(sCopyFile) && KDE::rename(sPart, sCopyFile) == 0)) {
2538  kDebug(7102) << "cannot rename " << sPart << " to " << sCopyFile;
2539  iError = ERR_CANNOT_RENAME_PARTIAL;
2540  iRes = statusClientError;
2541  }
2542  }
2543  }
2544  else if(KDE::stat( sPart, &buff ) == 0)
2545  { // should a very small ".part" be deleted?
2546  int size = config()->readEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE);
2547  if (buff.st_size < size)
2548  QFile::remove(sPart);
2549  }
2550  }
2551 
2552  if (iRes == statusSuccess) {
2553  const QString mtimeStr = metaData("modified");
2554  if (!mtimeStr.isEmpty()) {
2555  QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate);
2556  if (dt.isValid()) {
2557  kDebug(7102) << "Updating modified timestamp to" << mtimeStr;
2558  struct utimbuf utbuf;
2559  utbuf.actime = buff.st_atime; // access time, unchanged
2560  utbuf.modtime = dt.toTime_t(); // modification time
2561  KDE::utime(sCopyFile, &utbuf);
2562  }
2563  }
2564  }
2565 
2566  return iRes;
2567 }
2568 
2569 Ftp::StatusCode Ftp::ftpSendMimeType(int& iError, const KUrl& url)
2570 {
2571  // Emit proper mimetype for zero sized files. #323491
2572  if (m_size == 0) {
2573  mimeType(QLatin1String("application/x-zerosize"));
2574  return statusSuccess;
2575  }
2576 
2577  const int totalSize = ((m_size == UnknownSize || m_size > 1024) ? 1024 : m_size);
2578  QByteArray buffer(totalSize, '\0');
2579 
2580  while (true) {
2581  // Wait for content to be available...
2582  if (m_data->bytesAvailable() == 0 && !m_data->waitForReadyRead((readTimeout() * 1000))) {
2583  iError = ERR_COULD_NOT_READ;
2584  return statusServerError;
2585  }
2586 
2587  const int bytesRead = m_data->peek(buffer.data(), totalSize);
2588 
2589  // If we got a -1, it must be an error so return an error.
2590  if (bytesRead == -1) {
2591  iError = ERR_COULD_NOT_READ;
2592  return statusServerError;
2593  }
2594 
2595  // If m_size is unknown, peek returns 0 (0 sized file ??), or peek returns size
2596  // equal to the size we want, then break.
2597  if (bytesRead == 0 || bytesRead == totalSize || m_size == UnknownSize) {
2598  break;
2599  }
2600  }
2601 
2602  if (!buffer.isEmpty()) {
2603  KMimeType::Ptr mime = KMimeType::findByNameAndContent(url.fileName(), buffer);
2604  kDebug(7102) << "Emitting mimetype" << mime->name();
2605  mimeType( mime->name() ); // emit the mime type...
2606  }
2607 
2608  return statusSuccess;
2609 }
2610 
2611 void Ftp::proxyAuthentication(const QNetworkProxy& proxy, QAuthenticator* authenticator)
2612 {
2613  Q_UNUSED(proxy);
2614  kDebug(7102) << "Authenticator received -- realm:" << authenticator->realm() << "user:"
2615  << authenticator->user();
2616 
2617  AuthInfo info;
2618  info.url = m_proxyURL;
2619  info.realmValue = authenticator->realm();
2620  info.verifyPath = true; //### whatever
2621  info.username = authenticator->user();
2622 
2623  const bool haveCachedCredentials = checkCachedAuthentication(info);
2624 
2625  // if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
2626  // and it was not successful. see below and saveProxyAuthenticationForSocket().
2627  if (!haveCachedCredentials || m_socketProxyAuth) {
2628  // Save authentication info if the connection succeeds. We need to disconnect
2629  // this after saving the auth data (or an error) so we won't save garbage afterwards!
2630  connect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
2631  //### fillPromptInfo(&info);
2632  info.prompt = i18n("You need to supply a username and a password for "
2633  "the proxy server listed below before you are allowed "
2634  "to access any sites.");
2635  info.keepPassword = true;
2636  info.commentLabel = i18n("Proxy:");
2637  info.comment = i18n("<b>%1</b> at <b>%2</b>", info.realmValue, m_proxyURL.host());
2638  const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
2639  if (!dataEntered) {
2640  kDebug(7102) << "looks like the user canceled proxy authentication.";
2641  error(ERR_USER_CANCELED, m_proxyURL.host());
2642  return;
2643  }
2644  }
2645  authenticator->setUser(info.username);
2646  authenticator->setPassword(info.password);
2647  authenticator->setOption(QLatin1String("keepalive"), info.keepPassword);
2648 
2649  if (m_socketProxyAuth) {
2650  *m_socketProxyAuth = *authenticator;
2651  } else {
2652  m_socketProxyAuth = new QAuthenticator(*authenticator);
2653  }
2654 
2655  m_proxyURL.setUser(info.username);
2656  m_proxyURL.setPassword(info.password);
2657 }
2658 
2659 void Ftp::saveProxyAuthentication()
2660 {
2661  kDebug(7102);
2662  disconnect(m_control, SIGNAL(connected()), this, SLOT(saveProxyAuthentication()));
2663  Q_ASSERT(m_socketProxyAuth);
2664  if (m_socketProxyAuth) {
2665  kDebug(7102) << "-- realm:" << m_socketProxyAuth->realm() << "user:" << m_socketProxyAuth->user();
2666  KIO::AuthInfo a;
2667  a.verifyPath = true;
2668  a.url = m_proxyURL;
2669  a.realmValue = m_socketProxyAuth->realm();
2670  a.username = m_socketProxyAuth->user();
2671  a.password = m_socketProxyAuth->password();
2672  a.keepPassword = m_socketProxyAuth->option(QLatin1String("keepalive")).toBool();
2673  cacheAuthentication(a);
2674  }
2675  delete m_socketProxyAuth;
2676  m_socketProxyAuth = 0;
2677 }
2678 
2679 void Ftp::fixupEntryName(FtpEntry* e)
2680 {
2681  Q_ASSERT(e);
2682  if (e->type == S_IFDIR) {
2683  if (!ftpFolder(e->name, false)) {
2684  QString name (e->name.trimmed());
2685  if (ftpFolder(name, false)) {
2686  e->name = name;
2687  kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
2688  } else {
2689  int index = 0;
2690  while (e->name.at(index).isSpace()) {
2691  index++;
2692  name = e->name.mid(index);
2693  if (ftpFolder(name, false)) {
2694  kDebug(7102) << "fixing up directory name from" << e->name << "to" << name;
2695  e->name = name;
2696  break;
2697  }
2698  }
2699  }
2700  }
2701  } else {
2702  if (!ftpFileExists(e->name)) {
2703  QString name (e->name.trimmed());
2704  if (ftpFileExists(name)) {
2705  e->name = name;
2706  kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
2707  } else {
2708  int index = 0;
2709  while (e->name.at(index).isSpace()) {
2710  index++;
2711  name = e->name.mid(index);
2712  if (ftpFileExists(name)) {
2713  kDebug(7102) << "fixing up filename from" << e->name << "to" << name;
2714  e->name = name;
2715  break;
2716  }
2717  }
2718  }
2719  }
2720  }
2721 }
KIO::AuthInfo::comment
QString comment
i18n
QString i18n(const char *text)
KIO::Overwrite
ftpCleanPath
static QString ftpCleanPath(const QString &path)
Definition: ftp.cpp:79
Ftp::chmod
virtual void chmod(const KUrl &url, int permissions)
Definition: ftp.cpp:1285
KIO::filesize_t
qulonglong filesize_t
readEntry
KAutostart::StartPhase readEntry(const KConfigGroup &group, const char *key, const KAutostart::StartPhase &aDefault)
KUrl::directory
QString directory(const DirectoryOptions &options=IgnoreTrailingSlash) const
KIO::ERR_DISK_FULL
Ftp::mkdir
virtual void mkdir(const KUrl &url, int permissions)
Definition: ftp.cpp:1156
KIO::AuthInfo::url
KUrl url
kdebug.h
ioslave_defaults.h
kmimetype.h
KIO::UDSEntry::clear
void clear()
KIO::UDSEntry
KIO::AuthInfo::keepPassword
bool keepPassword
KIO::AuthInfo::setExtraField
void setExtraField(const QString &fieldName, const QVariant &value)
KIO::UDSEntry::insert
void insert(uint field, const QString &value)
FtpEntry::name
QString name
Definition: ftp.h:39
ERR_CANNOT_DELETE
ERR_COULD_NOT_MKDIR
KIO::UDSEntry::UDS_FILE_TYPE
ERR_FILE_ALREADY_EXIST
KDE::stat
int stat(const QString &path, KDE_struct_stat *buf)
KDE::rename
int rename(const QString &in, const QString &out)
name
const char * name(StandardAction id)
KIO::AuthInfo
KIO::AuthInfo::getExtraField
QVariant getExtraField(const QString &fieldName) const
kError
static QDebug kError(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
KSocketFactory::synchronousConnectToHost
QTcpSocket * synchronousConnectToHost(const QString &protocol, const QString &host, quint16 port, int msecs=30000, QObject *parent=0)
Ftp::get
virtual void get(const KUrl &url)
Definition: ftp.cpp:1847
KSocketFactory::listen
QTcpServer * listen(const QString &protocol, const QHostAddress &address=QHostAddress::Any, quint16 port=0, QObject *parent=0)
KUrl::toLocalFile
QString toLocalFile(AdjustPathOption trailing=LeaveTrailingSlash) const
quint32
ERR_CANNOT_RENAME_PARTIAL
charToLongLong
#define charToLongLong(a)
Definition: ftp.cpp:70
QString
kDebug
static QDebug kDebug(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
klocale.h
KIO::SlaveBase::mimetype
virtual void mimetype(const KUrl &url)
KUrl
ERR_COULD_NOT_LISTEN
Ftp::del
virtual void del(const KUrl &url, bool isfile)
Definition: ftp.cpp:1242
config
KSharedConfigPtr config()
KUrl::setPath
void setPath(const QString &path)
KIO::UDSEntry::UDS_GUESSED_MIME_TYPE
ERR_COULD_NOT_CONNECT
KUrl::setUser
void setUser(const QString &user)
KDE::open
int open(const QString &pathname, int flags, mode_t mode)
KIO::DefaultFlags
Ftp
Definition: ftp.h:53
FtpEntry::link
QString link
Definition: ftp.h:42
FtpEntry::size
KIO::filesize_t size
Definition: ftp.h:44
kglobal.h
KIO::UDSEntry::UDS_USER
KUrl::setProtocol
void setProtocol(const QString &proto)
KIO::UDSEntry::count
int count() const
Ftp::listDir
virtual void listDir(const KUrl &url)
Definition: ftp.cpp:1524
KIO::AuthInfo::realmValue
QString realmValue
Ftp::copy
virtual void copy(const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags)
Handles the case that one side of the job is a local file.
Definition: ftp.cpp:2370
ERR_INTERNAL
kremoteencoding.h
KIO::AuthInfo::verifyPath
bool verifyPath
ERR_COULD_NOT_READ
KIO::SlaveBase
KIO::Resume
FtpEntry::access
mode_t access
Definition: ftp.h:46
ftp.h
QStringList
ksocketfactory.h
ERR_IS_DIRECTORY
KUrl::setPass
void setPass(const QString &pass)
FtpEntry::owner
QString owner
Definition: ftp.h:40
FtpEntry::type
mode_t type
Definition: ftp.h:45
Ftp::Ftp
Ftp(const QByteArray &pool, const QByteArray &app)
Definition: ftp.cpp:196
ftpModeFromPath
static char ftpModeFromPath(const QString &path, char defaultMode= '\0')
Definition: ftp.cpp:90
KUrl::path
QString path(AdjustPathOption trailing=LeaveTrailingSlash) const
ERR_DIR_ALREADY_EXIST
KIO::UDSEntry::UDS_MODIFICATION_TIME
kdemain
int kdemain(int argc, char **argv)
Definition: ftp.cpp:171
KIO::fileoffset_t
qlonglong fileoffset_t
KIO::ERR_CONNECTION_BROKEN
ERR_COULD_NOT_ACCEPT
ok
KGuiItem ok()
KGlobal::locale
KLocale * locale()
QDateTime
FTP_LOGIN
#define FTP_LOGIN
Definition: ftp.cpp:73
ERR_IS_FILE
Ftp::slave_status
virtual void slave_status()
Definition: ftp.cpp:1599
DEFAULT_FTP_PORT
#define DEFAULT_FTP_PORT
Ftp::put
virtual void put(const KUrl &url, int permissions, KIO::JobFlags flags)
Definition: ftp.cpp:2055
Ftp::stat
virtual void stat(const KUrl &url)
Definition: ftp.cpp:1373
KIO::AuthInfo::setModified
void setModified(bool flag)
ERR_CANNOT_DELETE_PARTIAL
KIO::AuthInfo::password
QString password
Ftp::rename
virtual void rename(const KUrl &src, const KUrl &dst, KIO::JobFlags flags)
Definition: ftp.cpp:1191
FtpEntry
Definition: ftp.h:37
KUrl::fileName
QString fileName(const DirectoryOptions &options=IgnoreTrailingSlash) const
KIO::UDSEntry::UDS_ACCESS
KIO::UDSEntry::UDS_NAME
KIO::UDSEntry::UDS_LINK_DEST
ERR_UNSUPPORTED_ACTION
FtpEntry::date
time_t date
Definition: ftp.h:47
KIO::AuthInfo::username
QString username
ERR_CANNOT_RESUME
KUrl::AppendTrailingSlash
KIO::UDSEntry::UDS_GROUP
ERR_CYCLIC_LINK
Ftp::setHost
virtual void setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
Definition: ftp.cpp:321
kWarning
static QDebug kWarning(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
FtpEntry::group
QString group
Definition: ftp.h:41
Ftp::closeConnection
virtual void closeConnection()
Closes the connection.
Definition: ftp.cpp:299
ERR_CANNOT_OPEN_FOR_READING
supportedProxyScheme
static bool supportedProxyScheme(const QString &scheme)
Definition: ftp.cpp:106
KUrl::url
QString url(AdjustPathOption trailing=LeaveTrailingSlash) const
isdigit
#define isdigit(c)
KIO::UDSEntry::UDS_MIME_TYPE
slaveconfig.h
ERR_DOES_NOT_EXIST
Ftp::openConnection
virtual void openConnection()
Connects to a ftp server and logs us in m_bLoggedOn is set to true if logging on was successful...
Definition: ftp.cpp:340
FTP_PASSWD
#define FTP_PASSWD
Definition: ftp.cpp:74
ERR_CANNOT_ENTER_DIRECTORY
kcomponentdata.h
Ftp::~Ftp
virtual ~Ftp()
Definition: ftp.cpp:210
KUrl::isLocalFile
bool isLocalFile() const
isSocksProxy
static bool isSocksProxy()
Definition: ftp.cpp:111
KDE::utime
int utime(const QString &filename, struct utimbuf *buf)
KIO::UDSEntry::UDS_SIZE
KUrl::prettyUrl
QString prettyUrl(AdjustPathOption trailing=LeaveTrailingSlash) const
KComponentData
KIO::ERR_COULD_NOT_WRITE
ERR_CANNOT_CHMOD
KIO::AuthInfo::commentLabel
QString commentLabel
close
KAction * close(const QObject *recvr, const char *slot, QObject *parent)
KIO::AuthInfo::prompt
QString prompt
kconfiggroup.h
QList
KDE_EXPORT
#define KDE_EXPORT
list
QStringList list(const QString &fileClass)
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:50:58 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs API Reference

Skip menu "kdelibs API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  • kjsembed
  •   WTF
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Nepomuk-Core
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal