// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*-
/*  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., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#ifndef KDELIBS_FTP_H
#define KDELIBS_FTP_H

#include <config.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <kurl.h>
#include <kio/slavebase.h>

#include <QTcpServer>
#include <QTcpSocket>

struct FtpEntry
{
  QString name;
  QString owner;
  QString group;
  QString link;

  KIO::filesize_t size;
  mode_t type;
  mode_t access;
  time_t date;
};

//===============================================================================
// Ftp
//===============================================================================
class Ftp : public QObject, public KIO::SlaveBase
{
  Q_OBJECT

public:
  Ftp( const QByteArray &pool, const QByteArray &app );
  virtual ~Ftp();

  virtual void setHost( const QString& host, quint16 port, const QString& user, const QString& pass );

  /**
   * Connects to a ftp server and logs us in
   * m_bLoggedOn is set to true if logging on was successful.
   * 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, KIO::JobFlags flags );
  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, KIO::JobFlags flags );
  //virtual void mimetype( const KUrl& url );

  virtual void slave_status();

  /**
   * Handles the case that one side of the job is a local file
   */
  virtual void copy( const KUrl &src, const KUrl &dest, int permissions, KIO::JobFlags flags );

private:
  // ------------------------------------------------------------------------
  // All the methods named ftpXyz are lowlevel methods that are not exported.
  // The implement functionality used by the public high-level methods. Some
  // low-level methods still use error() to emit errors. This behaviour is not
  // recommended - please return a boolean status or an error code instead!
  // ------------------------------------------------------------------------

  /**
   * Status Code returned from ftpPut() and ftpGet(), used to select
   * source or destination url for error messages
   */
  typedef enum {
    statusSuccess,
    statusClientError,
    statusServerError
  } StatusCode;

  /**
   * Login Mode for ftpOpenConnection
   */
  typedef enum {
    loginDefered,
    loginExplicit,
    loginImplicit
  } LoginMode;

  /**
   * Connect and login to the FTP server.
   *
   * @param loginMode controls if login info should be sent<br>
   *  loginDefered  - must not be logged on, no login info is sent<br>
   *  loginExplicit - must not be logged on, login info is sent<br>
   *  loginImplicit - login info is sent if not logged on
   *
   * @return true on success (a login failure would return false).
   */
  bool ftpOpenConnection (LoginMode loginMode);

  /**
   * Executes any auto login macro's as specified in a .netrc file.
   */
  void ftpAutoLoginMacro ();

  /**
   * Called by openConnection. It logs us in.
   * m_initialPath is set to the current working directory
   * if logging on was successful.
   *
   * @param userChanged if not NULL, will be set to true if the user name
   *                    was changed during login.
   * @return true on success.
   */
  bool ftpLogin(bool* userChanged = 0);

  /**
   * ftpSendCmd - send a command (@p cmd) and read response
   *
   * @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 any response received, false on error
   */
  bool ftpSendCmd( const QByteArray& cmd, 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 );

  /**
   * Sends the SIZE command to the server and returns the response.
   * @param path the path to file whose size is to be retrieved.
   * @return response from the server
   */
  const char* ftpSendSizeCmd(const QString& path);

  /**
   * Returns true if the file exists.
   * Implemented using the SIZE command.
   */
  bool ftpFileExists(const QString& path);

  /**
   * Set the current working directory, but only if not yet current
   */
  bool ftpFolder(const QString& path, bool bReportError);

  /**
   * Runs a command on the ftp server like "list" or "retr". In contrast to
   * ftpSendCmd a data connection is opened. The corresponding socket
   * sData is available for reading/writing on success.
   * The connection must be closed afterwards with 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, KIO::fileoffset_t offset = 0 );

  /**
   * The counterpart to 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();

  /**
   * Send "TYPE I" or "TYPE A" only if required, see m_cDataMode.
   *
   * Use 'A' to select ASCII and 'I' to select BINARY mode.  If
   * cMode is '?' the m_bTextMode flag is used to choose a mode.
   */
  bool ftpDataMode(char cMode);

  //void ftpAbortTransfer();

  /**
   * Used by ftpOpenCommand, return 0 on success or an error code
   */
  int ftpOpenDataConnection();

  /**
   * closes a data connection, see ftpOpenDataConnection()
   */
  void ftpCloseDataConnection();

  /**
   * Helper for ftpOpenDataConnection
   */
  int ftpOpenPASVDataConnection();
  /**
   * Helper for ftpOpenDataConnection
   */
  int ftpOpenEPSVDataConnection();
  /**
   * Helper for ftpOpenDataConnection
   */
  int ftpOpenPortDataConnection();

  bool ftpChmod( const QString & path, int permissions );

  // used by listDir
  bool ftpOpenDir( const QString & path );
  /**
    * Called to parse directory listings, call this until it returns false
    */
  bool ftpReadDir(FtpEntry& ftpEnt);

  /**
    * Helper to fill an UDSEntry
    */
  void ftpCreateUDSEntry( const QString & filename, const FtpEntry& ftpEnt, KIO::UDSEntry& entry, bool isDir );

  void ftpShortStatAnswer( const QString& filename, bool isDir );

  void ftpStatAnswerNotFound( const QString & path, const QString & filename );

  /**
   * This is the internal implementation of rename() - set put().
   *
   * @return true on success.
   */
  bool ftpRename( const QString & src, const QString & dst, KIO::JobFlags flags );

  /**
   * Called by openConnection. It opens the control connection to the ftp server.
   *
   * @return true on success.
   */
  bool ftpOpenControlConnection();
  bool ftpOpenControlConnection( const QString & host, int port );

  /**
   * closes the socket holding the control connection (see ftpOpenControlConnection)
   */
  void ftpCloseControlConnection();

  /**
   * read a response from the server (a trailing CR gets stripped)
   * @param iOffset -1 to read a new line from the server<br>
   *                 0 to return the whole response string
   *                >0 to return the response with iOffset chars skipped
   * @return the reponse message with iOffset chars skipped (or "" if iOffset points
   *         behind the available data)
   */
  const char* ftpResponse(int iOffset);

  /**
   * This is the internal implementation of get() - see copy().
   *
   * IMPORTANT: the caller should call ftpCloseCommand() on return.
   * The function does not call error(), the caller should do this.
   *
   * @param iError      set to an ERR_xxxx code on error
   * @param iCopyFile   -1 -or- handle of a local destination file
   * @param hCopyOffset local file only: non-zero for resume
   * @return 0 for success, -1 for server error, -2 for client error
   */
  StatusCode ftpGet(int& iError, int iCopyFile, const KUrl& url, KIO::fileoffset_t hCopyOffset);

  /**
   * This is the internal implementation of put() - see copy().
   *
   * IMPORTANT: the caller should call ftpCloseCommand() on return.
   * The function does not call error(), the caller should do this.
   *
   * @param iError      set to an ERR_xxxx code on error
   * @param iCopyFile   -1 -or- handle of a local source file
   * @return 0 for success, -1 for server error, -2 for client error
   */
  StatusCode ftpPut(int& iError, int iCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags);

  /**
   * helper called from copy() to implement FILE -> FTP transfers
   *
   * @param iError      set to an ERR_xxxx code on error
   * @param iCopyFile   [out] handle of a local source file
   * @param sCopyFile   path of the local source file
   * @return 0 for success, -1 for server error, -2 for client error
   */
  StatusCode ftpCopyPut(int& iError, int& iCopyFile, const QString &sCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags);

  /**
   * helper called from copy() to implement FTP -> FILE transfers
   *
   * @param iError      set to an ERR_xxxx code on error
   * @param iCopyFile   [out] handle of a local source file
   * @param sCopyFile   path of the local destination file
   * @return 0 for success, -1 for server error, -2 for client error
   */
  StatusCode ftpCopyGet(int& iError, int& iCopyFile, const QString &sCopyFile, const KUrl& url, int permissions, KIO::JobFlags flags);

  /**
   * Sends the mime type of the content to retrieved.
   *
   * @param iError      set to an ERR_xxxx code on error
   * @return 0 for success, -1 for server error, -2 for client error
   */
  StatusCode ftpSendMimeType(int& iError, const KUrl& url);

  /**
   * Fixes up an entry name so that extraneous whitespaces do not cause
   * problems. See bug# 88575 and bug# 300988.
   */
  void fixupEntryName(FtpEntry* ftpEnt);

  /**
   * Calls @ref statEntry.
   */
  bool maybeEmitStatEntry(FtpEntry& ftpEnt, const QString& search, const QString& filename, bool isDir);

private: // data members

  QString m_host;
  int m_port;
  QString m_user;
  QString m_pass;
  /**
   * Where we end up after connecting
   */
  QString m_initialPath;

 /**
   * the current working directory - see ftpFolder
   */
  QString m_currentPath;

  /**
   * the status returned by the FTP protocol, set in ftpResponse()
   */
  int  m_iRespCode;

  /**
   * the status/100 returned by the FTP protocol, set in ftpResponse()
   */
  int  m_iRespType;

  /**
   * This flag is maintained by ftpDataMode() and contains I or A after
   * ftpDataMode() has successfully set the mode.
   */
  char m_cDataMode;

  /**
   * true if logged on (m_control should also be non-NULL)
   */
  bool m_bLoggedOn;

  /**
   * true if a "textmode" metadata key was found by ftpLogin(). This
   * switches the ftp data transfer mode from binary to ASCII.
   */
  bool m_bTextMode;

  /**
   * true if a data stream is open, used in closeConnection().
   *
   * When the user cancels a get or put command the Ftp dtor will be called,
   * which in turn calls closeConnection(). The later would try to send QUIT
   * which won't work until timeout. ftpOpenCommand sets the m_bBusy flag so
   * that the sockets will be closed immedeately - the server should be
   * capable of handling this and return an error code on thru the control
   * connection. The m_bBusy gets cleared by the ftpCloseCommand() routine.
   */
  bool m_bBusy;

  bool m_bPasv;

  KIO::filesize_t m_size;
  static KIO::filesize_t UnknownSize;

  enum
  {
    epsvUnknown = 0x01,
    epsvAllUnknown = 0x02,
    eprtUnknown = 0x04,
    epsvAllSent = 0x10,
    pasvUnknown = 0x20,
    chmodUnknown = 0x100
  };
  int m_extControl;

  /**
   * control connection socket, only set if openControl() succeeded
   */
  QTcpSocket  *m_control;
  QByteArray m_lastControlLine;

  /**
   * data connection socket
   */
  QTcpSocket  *m_data;

  /**
   * active mode server socket
   */
  QTcpServer *m_server;
};

#endif // KDELIBS_FTP_H

