/*--------------------------------------------------------------------------
 Copyright 1999, Dan Kegel http://www.kegel.com/
 See the file COPYING

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program 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 General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--------------------------------------------------------------------------*/

/* FTP Client Protocol Handler */
#ifndef ftp_client_proto_h
#define ftp_client_proto_h

/* pick up size_t */
#include <stddef.h>

/* pick up sockaddr_in */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define ftp_client_proto_LINELEN 512
#define ftp_client_proto_MAXPASSWORD 32
#define ftp_client_proto_MAXFNAME 256
#define ftp_client_proto_STACKLEN 3

/**--------------------------------------------------------------------------
 Module to implement the protocol part of the client side of the 
 File Transfer Protocol, RFC 959.
 Responsible only for parsing and generating bytes on the FTP control
 connection.  Doesn't actually do any networking calls; that is left to the 
 bfb_pipe module.  This separation of duties makes writing self-test code
 easier.

 When the caller requests a file transfer or directory listing and
 doesn't specify a local port number, this code will issue PASV, and 
 getStatus() will indicate an address.
 The caller should connect to that address immediately, and begin using
 that connection for the file or directory transfer.

 Example:
  After calling the login method, calling getOutput() will return a buffer
  containing 'USER username\r\n' to be sent to the server. When the result line
  comes back from the server, it should be passed to the giveInput method.
  Calling getOutput() at that point will return a buffer containing
  'PASS password\r\n' to be sent to the server, etc. 
  The success or failure of the login call is then available by calling
  getStatus().
--------------------------------------------------------------------------*/

class ftp_client_proto_t {
	// RX state variables
	// At any point in time, we're either UNINIT (haven't started),
	// CONNECTED (waiting for user to log in),
	// IDLE (waiting from user to do something), or we're waiting
	// for the server's response to something we've sent.
	/// m_state says what our parser is awaiting a response to.
	enum state_t {
		FCPS_UNINIT,	// waiting for user to call init()
		FCPS_INIT,		// waiting for response to connection
		FCPS_CONNECTED,	// got server hello; waiting for user to call login()
		FCPS_USER,		// waiting for response to USER
		FCPS_PASS,		// waiting for response to PASS
		FCPS_IDLE,		// central logged-in state, waiting for user
		FCPS_CWD,		// waiting for response to CWD or CDUP
		FCPS_TYPE,		// waiting for response to TYPE
		FCPS_SIZE,		// waiting for response to SIZE
		FCPS_PORT,		// waiting for response to PORT
		FCPS_PASV,		// waiting for response to PASV
		FCPS_RETR,		// waiting for response to RETR
		FCPS_QUIT		// waiting for response to QUIT
	};
///
	state_t m_state;
///
	state_t m_stack[ftp_client_proto_STACKLEN];
///
	int m_stack_ptr;

	/// TX state variables
	bool m_sent_user;	// only place we allow sendahead is USER

	/// Current output line
	char m_obuf[ftp_client_proto_LINELEN+1];          /// +1 for NUL
	int m_obuflen;

	/// Current reply from server
	bool m_multiline;				/// keep reading 'til 2nd line with status
	int m_status;									/// numerical value of ...
	char m_response[ftp_client_proto_LINELEN+1];

	/// Info stored temporarily from methods for use during next state
	char m_password[ftp_client_proto_MAXPASSWORD];
///
	char m_fname[ftp_client_proto_MAXFNAME];

///
	bool m_address_ready;			/// when PASV result comes in, these are set
	struct sockaddr_in m_address;

	/*---------------------------------------------------------------------
	 Jump to the specified state.  
	 2nd argument is source line number, for debugging.  See SETSTATE macro.
	---------------------------------------------------------------------*/
	void setState(state_t newstate, int linenum);

	/*----------------------------------------------------------------------
	 save the given state on the stack, then call setState(jump_to).
	 Normally called with 2nd parameter of m_state, so returnState()
	 returns to calling state.
	 3nd argument is source line number, for debugging.  See CALLSTATE macro.
	----------------------------------------------------------------------*/
	void callState(state_t jump_to, state_t return_to, int linenum);
	
	/*----------------------------------------------------------------------
	 Pop a state off the stack and return it
	----------------------------------------------------------------------*/
	state_t popState();

	/*----------------------------------------------------------------------
	 return to the state saved in callState()
	----------------------------------------------------------------------*/
	void returnState();
		
public:

	/* Network control channel interface */
	/*---------------------------------------------------------------------
	 Returns whether giveInput would accept a line of input.
	---------------------------------------------------------------------*/
	bool isInputReady() { 
		// This module sometimes sends several commands ahead of
		// the server's responses, so it's possible to have both
		// isInputReady() and isOutputReady()
		// The only useful state in which we aren't waiting for server
		/// input is the idle state.
		return (m_state != FCPS_IDLE);
	}

	/**---------------------------------------------------------------------
	 Returns whether getOutput would have a line of output for us.
	---------------------------------------------------------------------*/
	bool isOutputReady() { return (m_obuflen != 0); }

	/**---------------------------------------------------------------------
	 If a GET, PUT, or LS is in progress, and the server needs to tell us 
	 the port number to connect to, this will return TRUE and give us the 
	 address and port.

	 i.e. if the server sends status 227, the result of the PASV command is 
	 parsed into the given struct sockaddr_in *, and the caller should 
	 arrange to transfer data.
	---------------------------------------------------------------------*/
	bool isPortReady(struct sockaddr_in *address) { if (!m_address_ready) return false; m_address_ready = false; *address = m_address; return true; }

	/**---------------------------------------------------------------------
	 Returns whether QUIT command has finished.
	 This should trigger a disconnect.
	---------------------------------------------------------------------*/
	bool isQuit() { return (m_state == FCPS_QUIT); }

	/**---------------------------------------------------------------------
	 Offer a null-terminated input line from the server to this session.
	 Returns 0 if accepted, Unix error code otherwise.
	 In particular, returns EWOULDBLOCK if not ready for the input.
	---------------------------------------------------------------------*/
	int giveInput(const char *ibuf);

	/**---------------------------------------------------------------------
	 Get the current null-terminated output line to the server
	 and its length in bytes.
	 The line is terminated by CR LF and a NUL; the length includes
	 the CR LF but not the NUL.
	 You may call it as many times as you like; it will return the
	 same data.  This lets you retry a send.  
	 After the send succeeds, call advanceOutput() to advance to the next 
	 line of output.
	 Returns NULL if no output is ready yet.
	---------------------------------------------------------------------*/
	const char *getOutput(int *plen = 0);

	/**---------------------------------------------------------------------
	 Advances to next line of output; then call getOutput() to get it.
	 Returns 0 on success, Unix error code on error.
	 In particular, returns EWOULDBLOCK if no more output is ready.
	---------------------------------------------------------------------*/
	int advanceOutput();

	/* user-callable interface */

	/**---------------------------------------------------------------------
	 Set up the initial state of this protocol session.
	---------------------------------------------------------------------*/
	void init();

	/**---------------------------------------------------------------------
	 Get the status of the last login, quit, cd, ls, or get call.
	 If the operation is still in progress, returns 0.
	 If the operation has succeeded, returns a value between 200 and 299.
	 ASCII version of result is returned in given buffer, if buf != NULL.
	---------------------------------------------------------------------*/
	int getStatus(char *buf, size_t buflen);

	/**---------------------------------------------------------------------
	 Log in to the server.  
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded.
	---------------------------------------------------------------------*/
	int login(const char *username, const char *password);

	/**---------------------------------------------------------------------
	 Log out from the server.  This triggers the QUIT command.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded.
	---------------------------------------------------------------------*/
	int quit();

	/**---------------------------------------------------------------------
	 Change directories.
	 If dir is "..", the CDUP command is used instead of CD.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded.
	---------------------------------------------------------------------*/
	int cd(const char *dir);

	/**---------------------------------------------------------------------
	 Retrieve file size.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded, then parse the results out of the status 
	 buffer.
	---------------------------------------------------------------------*/
	int size(const char *fname);

	/**---------------------------------------------------------------------
	 Set the transfer type.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded, then parse the results out of the status 
	 buffer.
	---------------------------------------------------------------------*/
	int type(const char *ttype);

	/**---------------------------------------------------------------------
	 List the given directory's contents.   dirname may be NULL.
	 Result comes back on a data channel.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded.  

	 If address is 0, PASV mode is used, and isPortReady() should be called
	 periodically until it returns true and indicates where to connect to 
	 to retrieve the directory listing.
	 After retrieving the data from that address, continue calling
	 getStatus() to get the success status message.

	 If address is nonzero, PORT mode is used; it is assumed that a
	 port is already listening on the given address for a connection.
	---------------------------------------------------------------------*/
	int ls(const char *dirname, struct sockaddr_in *address);

	/**---------------------------------------------------------------------
	 Retrieve the given file's contents.  Result comes back on a data
	 channel.
	 Call getStatus() periodically until it returns nonzero to get whether
	 this command succeeded.  

	 If address is NULL, PASV mode is used, and isPortReady() should be called
	 periodically until it returns true and indicates where to connect to 
	 to retrieve the file.
	 After retrieving the data from that address, continue calling
	 getStatus() to get the success status message.

	 If address is not NULL, PORT mode is used; it is assumed that a
	 port is already listening on the given address for a connection.
	---------------------------------------------------------------------*/
	int get(const char *fname, struct sockaddr_in *address);

};

#endif


syntax highlighted by Code2HTML, v. 0.9.1