//
// FTPClientSession.h
//
// Library: Net
// Package: FTP
// Module:  FTPClientSession
//
// Definition of the FTPClientSession class.
//
// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#ifndef Net_FTPClientSession_INCLUDED
#define Net_FTPClientSession_INCLUDED


#include "Poco/Net/Net.h"
#include "Poco/Net/DialogSocket.h"
#include "Poco/Timespan.h"
#include <istream>
#include <ostream>


namespace Poco {
namespace Net {


class SocketStream;


class Net_API FTPClientSession
	/// This class implements an File Transfer Protocol
	/// (FTP, RFC 959) client.
	///
	/// Most of the features of the FTP protocol, as specified
	/// in RFC 959, are supported. Not supported are EBCDIC and
	/// LOCAL data types and format control and structured files.
	///
	/// Also supported are the EPRT and EPSV commands from
	/// RFC 1738 (FTP Extensions for IPv6 and NAT).
	/// The client will first attempt to use the EPRT and EPSV
	/// commands. If the server does not supports these commands,
	/// the client will fall back to PORT and PASV.
{
public:
	enum
	{
		FTP_PORT = 21
	};

	enum FileType
	{
		TYPE_TEXT,  /// TYPE A (ASCII)
		TYPE_BINARY /// TYPE I (Image/binary data)
	};

	FTPClientSession();
		/// Creates an FTPClientSession.
		///
		/// Passive mode will be used for data transfers.

	FTPClientSession(const StreamSocket& socket, bool readWelcomeMessage = true);
		/// Creates an FTPClientSession using the given
		/// connected socket for the control connection.
		///
		/// Passive mode will be used for data transfers.

	FTPClientSession(const std::string& host, Poco::UInt16 port = FTP_PORT, const std::string& username = "", const std::string& password = "");
		/// Creates an FTPClientSession using a socket connected
		/// to the given host and port. If username is supplied,
		/// login is attempted.
		///
		/// Passive mode will be used for data transfers.

	virtual ~FTPClientSession();
		/// Destroys the FTPClientSession.

	void setTimeout(const Poco::Timespan& timeout);
		/// Sets the timeout for socket operations.

	Poco::Timespan getTimeout() const;
		/// Returns the timeout for socket operations.

	void setPassive(bool flag, bool useRFC1738 = true);
		/// Enables (default) or disables FTP passive mode for this session.
		///
		/// If useRFC1738 is true (the default), the RFC 1738
		/// EPSV command is used (with a fallback to PASV if EPSV fails)
		/// for switching to passive mode. The same applies to
		/// EPRT and PORT for active connections.

	bool getPassive() const;
		/// Returns true iff passive mode is enabled for this connection.

	virtual void open(const std::string& host, Poco::UInt16 port, const std::string& username = "", const std::string& password = "");
		/// Opens the FTP connection to the given host and port.
		/// If username is supplied, login is attempted.

	virtual void login(const std::string& username, const std::string& password);
		/// Authenticates the user against the FTP server. Must be
		/// called before any other commands (except QUIT) can be sent.
		///
		/// Sends a USER command followed by a PASS command with the
		/// respective arguments to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void logout();
		/// Logs out from the server by sending a QUIT command. Any transfer
		/// that's in progress is ended. The control connection is kept
		/// open.

	void close();
		/// Sends a QUIT command and closes the connection to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	std::string systemType();
		/// Returns the system type of the FTP server.
		///
		/// Sends a SYST command to the server and returns the result.

	void setFileType(FileType type);
		/// Sets the file type for transferring files.
		///
		/// Sends a TYPE command with a corresponding argument to the
		/// server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	FileType getFileType() const;
		/// Returns the file type for transferring files.

	void setWorkingDirectory(const std::string& path);
		/// Changes the current working directory on the server.
		///
		/// Sends a CWD command with the given path as argument to the
		/// server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	std::string getWorkingDirectory();
		/// Returns the current working directory on the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void cdup();
		/// Moves one directory up from the current working directory
		/// on the server.
		///
		/// Sends a CDUP command to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void rename(const std::string& oldName, const std::string& newName);
		/// Renames the file on the server given by oldName to newName.
		///
		/// Sends a RNFR command, followed by a RNTO command to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void remove(const std::string& path);
		/// Deletes the file specified by path on the server.
		///
		/// Sends a DELE command with path as argument to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void createDirectory(const std::string& path);
		/// Creates a new directory with the given path on the server.
		///
		/// Sends a MKD command with path as argument to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	void removeDirectory(const std::string& path);
		/// Removes the directory specified by path from the server.
		///
		/// Sends a RMD command with path as argument to the server.
		///
		/// Throws a FTPException in case of a FTP-specific error, or a
		/// NetException in case of a general network communication failure.

	std::istream& beginDownload(const std::string& path);
		/// Starts downloading the file with the given name.
		/// After all data has been read from the returned stream,
		/// endDownload() must be called to finish the download.
		///
		/// A stream for reading the file's content is returned.
		/// The stream is valid until endDownload() is called.
		///
		/// Creates a data connection between the client and the
		/// server. If passive mode is on, then the server waits for
		/// a connection request from the client. Otherwise, the
		/// client waits for a connection request from the server.
		/// After establishing the data connection, a SocketStream
		/// for transferring the data is created.
		///
		/// If ASCII transfer mode is selected, the caller is
		/// responsible for converting the received data to
		/// the native text file format.
		/// The InputLineEndingConverter class from the Foundation
		/// library can be used for that purpose.

	void endDownload();
		/// Must be called to complete a download initiated with
		/// beginDownload().

	std::ostream& beginUpload(const std::string& path);
		/// Starts uploading the file with the given name.
		/// After all data has been written to the returned stream,
		/// endUpload() must be called to finish the upload.
		///
		/// A stream for reading the file's content is returned.
		/// The stream is valid until endUpload() is called.
		///
		/// Creates a data connection between the client and the
		/// server. If passive mode is on, then the server waits for
		/// a connection request from the client. Otherwise, the
		/// client waits for a connection request from the server.
		/// After establishing the data connection, a SocketStream
		/// for transferring the data is created.
		///
		/// If ASCII transfer mode is selected, the caller is
		/// responsible for converting the data to be sent
		/// into network (CR-LF line endings) format.
		/// The OutputLineEndingConverter class from the Foundation
		/// library can be used for that purpose.

	void endUpload();
		/// Must be called to complete an upload initiated with
		/// beginUpload().

	std::istream& beginList(const std::string& path = "", bool extended = false);
		/// Starts downloading a directory listing.
		/// After all data has been read from the returned stream,
		/// endList() must be called to finish the download.
		///
		/// A stream for reading the directory data is returned.
		/// The stream is valid until endList() is called.
		///
		/// Optionally, a path to a directory or file can be specified.
		/// According to the FTP protocol, if a path to a filename is
		/// given, only information for the specific file is returned.
		/// If a path to a directory is given, a listing of that directory
		/// is returned. If no path is given, a listing of the current
		/// working directory is returned.
		///
		/// If extended is false, only a filenames (one per line) are
		/// returned. Otherwise, a full directory listing including
		/// file attributes is returned. The format of this listing
		/// depends on the FTP server. No attempt is made to interpret
		/// this data.
		///
		/// Creates a data connection between the client and the
		/// server. If passive mode is on, then the server waits for
		/// a connection request from the client. Otherwise, the
		/// client waits for a connection request from the server.
		/// After establishing the data connection, a SocketStream
		/// for transferring the data is created.

	void endList();
		/// Must be called to complete a directory listing download
		/// initiated with beginList().

	void abort();
		/// Aborts the download or upload currently in progress.
		///
		/// Sends a TELNET IP/SYNCH sequence, followed by an ABOR
		/// command to the server.
		///
		/// A separate call to endDownload() or endUpload() is
		/// not necessary.

	int sendCommand(const std::string& command, std::string& response);
		/// Sends the given command verbatim to the server
		/// and waits for a response.

	int sendCommand(const std::string& command, const std::string& arg, std::string& response);
		/// Sends the given command verbatim to the server
		/// and waits for a response.

	bool isOpen() const;
		/// Returns true if the connection with FTP server is opened.

	bool isLoggedIn() const;
		/// Returns true if the session is logged in.

	bool isSecure() const;
		/// Returns true if the session is FTPS.

	const std::string& welcomeMessage();
		/// Returns the welcome message.

protected:
	virtual void receiveServerReadyReply();

	enum StatusClass
	{
		FTP_POSITIVE_PRELIMINARY  = 1,
		FTP_POSITIVE_COMPLETION   = 2,
		FTP_POSITIVE_INTERMEDIATE = 3,
		FTP_TRANSIENT_NEGATIVE    = 4,
		FTP_PERMANENT_NEGATIVE    = 5
	};

	enum
	{
		DEFAULT_TIMEOUT = 30000000 // 30 seconds default timeout for socket operations
	};

	static bool isPositivePreliminary(int status);
	static bool isPositiveCompletion(int status);
	static bool isPositiveIntermediate(int status);
	static bool isTransientNegative(int status);
	static bool isPermanentNegative(int status);
	std::string extractPath(const std::string& response);
	virtual StreamSocket establishDataConnection(const std::string& command, const std::string& arg);
	StreamSocket activeDataConnection(const std::string& command, const std::string& arg);
	StreamSocket passiveDataConnection(const std::string& command, const std::string& arg);
	void sendPortCommand(const SocketAddress& addr);
	SocketAddress sendPassiveCommand();
	bool sendEPRT(const SocketAddress& addr);
	void sendPORT(const SocketAddress& addr);
	bool sendEPSV(SocketAddress& addr);
	void sendPASV(SocketAddress& addr);
	void parseAddress(const std::string& str, SocketAddress& addr);
	void parseExtAddress(const std::string& str, SocketAddress& addr);
	void endTransfer();

	DialogSocket*  _pControlSocket = nullptr;
	SocketStream*  _pDataStream = nullptr;

private:
	FTPClientSession(const FTPClientSession&);
	FTPClientSession& operator = (const FTPClientSession&);

	std::string _host;
	Poco::UInt16 _port = FTP_PORT;
	bool _passiveMode = true;
	FileType _fileType = TYPE_BINARY;
	bool _supports1738 = true;
	bool _serverReady = false;
	bool _isLoggedIn = false;
	Poco::Timespan _timeout = DEFAULT_TIMEOUT;
	std::string _welcomeMessage;
	Poco::FastMutex _wmMutex;
};


//
// inlines
//
inline bool FTPClientSession::isPositivePreliminary(int status)
{
	return status/100 == FTP_POSITIVE_PRELIMINARY;
}


inline bool FTPClientSession::isPositiveCompletion(int status)
{
	return status/100 == FTP_POSITIVE_COMPLETION;
}


inline bool FTPClientSession::isPositiveIntermediate(int status)
{
	return status/100 == FTP_POSITIVE_INTERMEDIATE;
}


inline bool FTPClientSession::isTransientNegative(int status)
{
	return status/100 == FTP_TRANSIENT_NEGATIVE;
}


inline bool FTPClientSession::isPermanentNegative(int status)
{
	return status/100 == FTP_PERMANENT_NEGATIVE;
}


inline bool FTPClientSession::isOpen() const
{
	return _pControlSocket != 0;
}


inline bool FTPClientSession::isLoggedIn() const
{
	return _isLoggedIn;
}


inline bool FTPClientSession::isSecure() const
{
	return false;
}


inline const std::string& FTPClientSession::welcomeMessage()
{
	Poco::FastMutex::ScopedLock lock(_wmMutex);
	return _welcomeMessage;
}


} } // namespace Poco::Net


#endif // Net_FTPClientSession_INCLUDED