/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* state machine to read FTP sites
 *
 * Designed and originally implemented by Lou Montulli '94
 *
 * State machine to implement File Transfer Protocol,
 * all the protocol state machines work basically the same way.
 */

#include "rosetta.h"
#include "mkutils.h"

#define FTP_PORT 21

#define FTP_MODE_UNKNOWN 0
#define FTP_MODE_ASCII   1
#define FTP_MODE_BINARY  2

#include "mkftp.h"  
#include "glhist.h"
#include "mkgeturl.h"
#include "mkparse.h"
#include "mktcp.h"
#include "mksort.h"
#include "mkfsort.h"   /* file sorting */
#include "mkformat.h"
#include "mkfile.h"  
#include "merrors.h"
#include "shist.h"
#include "ssl.h"
#include "prtime.h"
#include "pwcacapi.h"
#include "secnav.h"

#ifdef XP_OS2 /* DSR072196 */
#include "os2sock.h"
#endif /* XP_OS2 */

#ifdef XP_UNIX
#if !defined(__osf__) && !defined(AIXV3) && !defined(_HPUX_SOURCE) && !defined(__386BSD__) && !defined(__linux) && !defined(SCO_SV)
#include <sys/filio.h>
#endif
#endif /* XP_UNIX */ 

#include "xp_error.h"
#include "prefapi.h"

/* for XP_GetString() */
#include "xpgetstr.h"
extern int XP_HTML_FTPERROR_NOLOGIN;
extern int XP_HTML_FTPERROR_TRANSFER;
extern int XP_PROGRESS_RECEIVE_FTPDIR;
extern int XP_PROGRESS_RECEIVE_FTPFILE;
extern int XP_PROGRESS_READFILE;
extern int XP_PROGRESS_FILEDONE;
extern int XP_PROMPT_ENTER_PASSWORD;
extern int MK_COULD_NOT_PUT_FILE;
extern int MK_TCP_READ_ERROR;
extern int MK_TCP_WRITE_ERROR;
extern int MK_UNABLE_TO_ACCEPT_SOCKET;
extern int MK_UNABLE_TO_CHANGE_FTP_MODE;
extern int MK_UNABLE_TO_CREATE_SOCKET;
extern int MK_UNABLE_TO_LOCATE_FILE;
extern int MK_UNABLE_TO_OPEN_FILE;
extern int MK_UNABLE_TO_SEND_PORT_COMMAND;
extern int MK_UNABLE_TO_DELETE_DIRECTORY;
extern int MK_COULD_NOT_CREATE_DIRECTORY;
extern int XP_ERRNO_EWOULDBLOCK;
extern int MK_OUT_OF_MEMORY;
extern int MK_DISK_FULL;
extern int XP_COULD_NOT_LOGIN_TO_FTP_SERVER;
extern int XP_ERROR_COULD_NOT_MAKE_CONNECTION_NON_BLOCKING;
extern int XP_POSTING_FILE;
extern int XP_TITLE_DIRECTORY_OF_ETC;
extern int XP_UPTO_HIGHER_LEVEL_DIRECTORY ;


#define PUTSTRING(s)           (*cd->stream->put_block) \
                                 (cd->stream, s, XP_STRLEN(s))
#define PUTBLOCK(b,l)         (*cd->stream->put_block) \
                                (cd->stream, b, l)
#define COMPLETE_STREAM   (*cd->stream->complete) \
                                 (cd->stream)
#define ABORT_STREAM(s)   (*cd->stream->abort) \
                                 (cd->stream, s)


#define FTP_GENERIC_TYPE     0
#define FTP_UNIX_TYPE        1
#define FTP_DCTS_TYPE        2
#define FTP_NCSA_TYPE        3
#define FTP_PETER_LEWIS_TYPE 4
#define FTP_MACHTEN_TYPE     5
#define FTP_CMS_TYPE         6
#define FTP_TCPC_TYPE        7
#define FTP_VMS_TYPE         8
#define FTP_NT_TYPE          9

#define OUTPUT_BUFFER_SIZE 2048

PRIVATE XP_List * connection_list=0;  /* a pointer to a list of open connections */

PRIVATE Bool   	net_use_pasv=TRUE;
PRIVATE char *	ftp_last_password=0;
PRIVATE char *	ftp_last_password_host=0;
PRIVATE XP_Bool	net_send_email_address_as_password=FALSE;

#define PC_FTP_PASSWORD_KEY "pass"
#define PC_FTP_MODULE_KEY   "ftp"

typedef struct _FTPConnection {
    char   *hostname;       /* hostname string (may contain :port) */
    PRFileDesc *csock;          /* control socket */
    int     server_type;    /* type of server */
    int     cur_mode;       /* ascii, binary, unknown */
    Bool use_list;       /* use LIST? (or NLST) */
    Bool busy;           /* is the connection in use already? */
    Bool no_pasv;        /* should we NOT use pasv mode since is doesn't work? */
    Bool prev_cache;     /* did this connection come from the cache? */
    Bool no_restart;     /* do we know that restarts don't work? */
} FTPConnection;

/* define the states of the machine 
 */
typedef enum _FTPStates {
FTP_WAIT_FOR_RESPONSE,
FTP_CONTROL_CONNECT,
FTP_CONTROL_CONNECT_WAIT,
FTP_SEND_USERNAME,
FTP_SEND_USERNAME_RESPONSE,
FTP_GET_PASSWORD,
FTP_GET_PASSWORD_RESPONSE,
FTP_SEND_PASSWORD,
FTP_SEND_PASSWORD_RESPONSE,
FTP_SEND_ACCT,
FTP_SEND_ACCT_RESPONSE,
FTP_SEND_REST_ZERO,
FTP_SEND_REST_ZERO_RESPONSE,
FTP_SEND_SYST,
FTP_SEND_SYST_RESPONSE,
FTP_SEND_MAC_BIN,
FTP_SEND_MAC_BIN_RESPONSE,
FTP_SEND_PWD,
FTP_SEND_PWD_RESPONSE,
FTP_SEND_PASV,
FTP_SEND_PASV_RESPONSE,
FTP_PASV_DATA_CONNECT,
FTP_PASV_DATA_CONNECT_WAIT,
FTP_FIGURE_OUT_WHAT_TO_DO,
FTP_SEND_DELETE_FILE,
FTP_SEND_DELETE_FILE_RESPONSE,
FTP_SEND_DELETE_DIR,
FTP_SEND_DELETE_DIR_RESPONSE,
FTP_SEND_MKDIR,
FTP_SEND_MKDIR_RESPONSE,
FTP_SEND_PORT,
FTP_SEND_PORT_RESPONSE,
FTP_GET_FILE_TYPE,
FTP_SEND_BIN,
FTP_SEND_BIN_RESPONSE,
FTP_SEND_ASCII,
FTP_SEND_ASCII_RESPONSE,
FTP_GET_FILE_SIZE,
FTP_GET_FILE_SIZE_RESPONSE,
FTP_GET_FILE_MDTM,
FTP_GET_FILE_MDTM_RESPONSE,
FTP_GET_FILE,
FTP_BEGIN_PUT,
FTP_BEGIN_PUT_RESPONSE,
FTP_DO_PUT,
FTP_SEND_REST,
FTP_SEND_REST_RESPONSE,
FTP_SEND_RETR,
FTP_SEND_RETR_RESPONSE,
FTP_SEND_CWD,
FTP_SEND_CWD_RESPONSE,
FTP_SEND_LIST_OR_NLST,
FTP_SEND_LIST_OR_NLST_RESPONSE,
FTP_SETUP_STREAM,
FTP_WAIT_FOR_ACCEPT,
FTP_START_READ_FILE,
FTP_READ_CACHE,
FTP_READ_FILE,
FTP_START_READ_DIRECTORY,
FTP_READ_DIRECTORY,
FTP_PRINT_DIR,
FTP_DONE,
FTP_ERROR_DONE,
FTP_FREE_DATA
} FTPStates;

typedef struct _FTPConData {
    FTPStates     next_state;        /* the next state of the machine */
    Bool          pause_for_read;    /* should we pause (return) for read? */

    NET_StreamClass *stream;         /* the outgoing data stream */

    FTPConnection   *cc;  /* information about the control connection */

    PRFileDesc *dsock;                /* data socket */
    PRFileDesc *listen_sock;          /* socket to listen on */
    Bool    restart;              /* is this a restarted fetch? */
    Bool    use_list;             /* use LIST or NLST */
    Bool    pasv_mode;            /* using pasv mode? */
    Bool    is_directory;         /* is the url a directory? */
    Bool    retr_already_failed;  /* did RETR fail? */
	Bool	store_password;		  /* cache password? */
    char   *data_buf;             /* unprocessed data from the socket */
    int32   data_buf_size;        /* size of the unprocesed data */

	Bool use_default_path;        /* set if we should use PWD to figure out
							       * current path and use that 
							       */

    /* used by response */
    FTPStates   next_state_after_response;  /* the next state after 
											 * full response recieved */
    int         response_code;              /* code returned in response */
    char       *return_msg;                 /* message from response */
    int         cont_response;              /* a continuation code */

    char   *data_con_address;     /* address and port for PASV data connection*/
    char   *filename;             /* name of the file we are getting */ 
    char   *file_to_post;         /* local filename to put */
    XP_File file_to_post_fp;      /* filepointer */
    char   *post_file_to;         /* If non-NULL, specifies the destination of
                                     the source file, file_to_post.  If NULL, 
                                     generate the destination name automatically
                                     from file_to_post. */
	int32   total_size_of_files_to_post;   /* total bytes */
    char   *path;                 /* path as returned by MKParse */
    int     type;                 /* a type indicator for the file */
    char   *username;             /* the username if not anonymous */
    char   *password;             /* the password if not anonymous */

    char   *cwd_message_buffer;   /* used to print out server help messages */
    char   *login_message_buffer; /* used to print out server help messages */

	char   *output_buffer;		  /* buffer before sending to PUTSTRING */

    SortStruct *sort_base;        /* base pointer to Qsorting list */

    Bool destroy_graph_progress;  /* do we need to destroy graph progress? */    
    Bool destroy_file_upload_progress_dialog;  /* do we need to destroy the dialog? */
	Bool calling_netlib_all_the_time;  /* is SetCallNetlibAllTheTime set? */
	int32   original_content_length; /* the content length at the time of
                                      * calling graph progress
                                      */

    TCP_ConData *tcp_con_data;     /* Data pointer for tcp connect 
									* state machine */

	int32   buff_size;             /* used by ftp_send_file */
	int32   amt_of_buff_wrt;       /* amount of data already written
									* used by ftp_send_file()
									*/
	void   *write_post_data_data;
    XP_File partial_cache_fp;      /* pointer to partial cache file */
} FTPConData;

/* forward decls */
static const char *pref_email_as_ftp_password = "security.email_as_ftp_password";
PRIVATE int32 net_ProcessFTP(ActiveEntry * ce);

/* function prototypes
 */
PRIVATE NET_FileEntryInfo * net_parse_dir_entry (char *entry, int server_type);

/* call this to enable/disable FTP PASV mode.
 * Note: PASV mode is on by default
 */
PUBLIC void
NET_UsePASV(Bool do_it)
{
	net_use_pasv = do_it;
}

/* callback routine invoked by prefapi when the pref value changes */
/* fix Mac missing prototype warnings */
MODULE_PRIVATE int PR_CALLBACK
net_ftp_pref_changed(const char * newpref, void * data);

MODULE_PRIVATE int PR_CALLBACK
net_ftp_pref_changed(const char * newpref, void * data)
{
	PREF_GetBoolPref(pref_email_as_ftp_password, &net_send_email_address_as_password);
	return PREF_NOERROR;
}

PRIVATE XP_Bool
net_get_send_email_address_as_password(void)
{
#ifdef XP_WIN
	static XP_Bool	net_read_FTP_password_pref = FALSE;

	if (!net_read_FTP_password_pref) {
		PREF_GetBoolPref(pref_email_as_ftp_password, &net_send_email_address_as_password);
		PREF_RegisterCallback(pref_email_as_ftp_password, net_ftp_pref_changed, NULL);
		net_read_FTP_password_pref = TRUE;
	}
#endif

	return net_send_email_address_as_password;
}

#if defined(XP_MAC) || defined(XP_UNIX)
/* call this with TRUE to enable the sending of the email
 * address as the default user "anonymous" password.
 * The default is OFF.  
 */
PUBLIC void
NET_SendEmailAddressAsFTPPassword(Bool do_it)
{
	net_send_email_address_as_password = (XP_Bool)do_it;
}
#endif

PRIVATE NET_StreamClass *
net_ftp_make_stream(FO_Present_Types format_out, 
					URL_Struct *URL_s, 
					MWContext *window_id)
{
	MWContext *stream_context;

#ifdef MOZILLA_CLIENT
	/* if the context can't handle HTML then we
	 * need to generate an HTML dialog to handle
	 * the message
	 */
	if(URL_s->files_to_post && EDT_IS_EDITOR(window_id))
	  {
		Chrome chrome_struct;
		XP_MEMSET(&chrome_struct, 0, sizeof(Chrome));
		chrome_struct.is_modal = TRUE;
		chrome_struct.allow_close = TRUE;
		chrome_struct.allow_resize = TRUE;
		chrome_struct.show_scrollbar = TRUE;
		chrome_struct.w_hint = 400;
		chrome_struct.h_hint = 300;
#ifdef XP_MAC
		/* on Mac, topmost windows are floating windows not dialogs */
		chrome_struct.topmost = FALSE;
		/* disable commands to change to minimal menu bar; */
		/* avoids confusion about which commands are present */
		chrome_struct.disable_commands = TRUE;
#else
		chrome_struct.topmost = TRUE;
#endif

		stream_context = FE_MakeNewWindow(window_id,
		NULL,
		NULL,
		&chrome_struct);
		if(!stream_context)
			return (NULL);
	  }
	else
#endif /* MOZILLA_CLIENT */
	  {
		stream_context = window_id;
	  }

	return(NET_StreamBuilder(format_out, URL_s, stream_context));

}

PRIVATE int
net_ftp_show_error(ActiveEntry *ce, char *line)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

	/* show the error
	 */
	StrAllocCopy(ce->URL_s->content_type, TEXT_HTML);
	ce->URL_s->expires = 1; /* immediate expire date */
	cd->stream = net_ftp_make_stream(ce->format_out, ce->URL_s, ce->window_id);

	if(cd->stream)
	  {
		XP_STRCPY(cd->output_buffer, XP_GetString(XP_HTML_FTPERROR_TRANSFER));
		if(ce->status > -1)
			ce->status = PUTSTRING(cd->output_buffer);
		if(line && ce->status > -1)
			ce->status = PUTSTRING(cd->return_msg);
		XP_STRCPY(cd->output_buffer, "</PRE>");
		if(ce->status > -1)
			ce->status = PUTSTRING(cd->output_buffer);
	  }
	  
	cd->next_state = FTP_ERROR_DONE;
	return MK_INTERRUPTED;
}

/* get a reply from the server
 */
PRIVATE int 
net_ftp_response (ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;
    int result_code;        /* numerical result code from server */
    int status;
    char cont_char;
    char * line;

    status = NET_BufferedReadLine(cd->cc->csock, &line, &cd->data_buf, 
                        &cd->data_buf_size, &cd->pause_for_read);

	if(status == 0)
	  {
		/* this shouldn't really happen, but...
		 */
     	cd->next_state = cd->next_state_after_response;
     	cd->pause_for_read = FALSE; /* don't pause */
		return(0);
	  }
    else if(status < 0)
      {
		int rv = PR_GetError();

        if (rv == PR_WOULD_BLOCK_ERROR)
          {
            cd->pause_for_read = TRUE;
            return(0);
          }

		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, rv);

    	/* return TCP error
         */
		return MK_TCP_READ_ERROR;
      }
	else if(!line)
	  {
         return status; /* wait for line */
	  }

    TRACEMSG(("    Rx: %s", line));

	/* Login messages or CWD messages 
     * Their so handy, those help messages are :)
     */
    if(cd->cc->server_type == FTP_UNIX_TYPE && !XP_STRNCMP(line, "250-",4))
      {
        StrAllocCat(cd->cwd_message_buffer, line+4);
        StrAllocCat(cd->cwd_message_buffer, "\n");  /* nasty */
      }
    else if(!XP_STRNCMP(line,"230-",4) || !XP_STRNCMP(line,"220-",4))
      {
        StrAllocCat(cd->login_message_buffer, line+4);
        StrAllocCat(cd->login_message_buffer, "\n");  /* nasty */
      }
    else if(cd->cont_response == 230)
      {
	 	/* this does the same thing as the one directly above it
		 * but it checks for 230 response code instead
	  	 * this is to fix buggy ftp servers that don't follow specs
		 */
		if(XP_STRNCMP(line, "230", 3))
		  {
			/* make sure it's not  "230 Guest login ok...
			 */
            StrAllocCat(cd->login_message_buffer, line);
            StrAllocCat(cd->login_message_buffer, "\n");  /* nasty */
		  }
      }

	 result_code = 0;          /* defualts */
	 cont_char = ' ';  /* defualts */
     sscanf(line, "%d%c", &result_code, &cont_char);

     if(cd->cont_response == -1) 
       {
         if (cont_char == '-')  /* start continuation */
             cd->cont_response = result_code;

         StrAllocCopy(cd->return_msg, line+4);
       } 
     else 
       {    /* continuing */
      	 if (cd->cont_response == result_code && cont_char == ' ')
             cd->cont_response = -1;    /* ended */

         StrAllocCat(cd->return_msg, "\n");
		 if(XP_STRLEN(line) > 3)
		   {
			 if(isdigit(line[0]))
                 StrAllocCat(cd->return_msg, line+4);
			 else
                 StrAllocCat(cd->return_msg, line);
		   }
		 else
		   {
             StrAllocCat(cd->return_msg, line);
		   }
       }    

     if(cd->cont_response == -1)  /* all done with this response? */
       {
     	 cd->next_state = cd->next_state_after_response;
     	 cd->pause_for_read = FALSE; /* don't pause */
       }
	 if(result_code == 551)
	   {
		 return net_ftp_show_error(ce, line + 4);
	   }
        
    cd->response_code = result_code/100;

    return(1);  /* everything ok */
}

PRIVATE int
net_send_username(FTPConData * cd)
{

	/* look for the word UNIX in the hello message
	 * if it is there assume a unix server that can
	 * use the LIST command
	 */	
	if(cd->return_msg && strcasestr(cd->return_msg, "UNIX"))
	  {
        cd->cc->server_type = FTP_UNIX_TYPE;
		cd->cc->use_list = TRUE;
	  }

    if (cd->username)
      {
        PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "USER %.256s%c%c", cd->username, CR, LF);
      }
    else
      {
        PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "USER anonymous%c%c", CR, LF);
      }
	
	TRACEMSG(("FTP Rx: %.256s", cd->output_buffer));

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_USERNAME_RESPONSE;
    cd->pause_for_read = TRUE;

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_username_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if (cd->response_code == 3)  /* Send password */
      {
		if(!cd->password)
            cd->next_state = FTP_GET_PASSWORD;
		else 
			cd->next_state = FTP_SEND_PASSWORD;
      }
    else if (cd->response_code == 2) /* Logged in */
      {
          if( cd->cc->no_restart )
              cd->next_state = FTP_SEND_SYST;
          else
              cd->next_state = FTP_SEND_REST_ZERO;
      }
    else
      {
		/* show the error
		 */
	 	StrAllocCopy(ce->URL_s->content_type, TEXT_HTML);
		ce->URL_s->expires = 1; /* immediate expire date */
        cd->stream = net_ftp_make_stream(ce->format_out, ce->URL_s, ce->window_id);

		if(cd->stream)
		  {
			PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, XP_GetString(XP_HTML_FTPERROR_NOLOGIN)) ;
			if(ce->status > -1)
    			ce->status = PUTSTRING(cd->output_buffer);
			if(cd->return_msg && ce->status > -1)
    		    ce->status = PUTSTRING(cd->return_msg);
			PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "</PRE>");
			if(ce->status > -1)
    			ce->status = PUTSTRING(cd->output_buffer);

			if(ce->status > -1)
           		COMPLETE_STREAM;
			else
                ABORT_STREAM(ce->status);

			FREE(cd->stream);
			cd->stream = NULL;
		  }
		
        cd->next_state = FTP_ERROR_DONE;

		/* make sure error_msg is empty */
		FREE_AND_CLEAR(ce->URL_s->error_msg);

        return MK_UNABLE_TO_LOGIN;

      }

    return(0); /* good */
}


PRIVATE int
net_send_password(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, 
				"PASS %.256s%c%c", 
				NET_UnEscape(cd->password), 
				CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_PASSWORD_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE char *
gen_ftp_password_key(char *address, char *username)
{
	char *rv=NULL;

	if(!address || !username)
		return NULL;

	StrAllocCopy(rv, address);
	StrAllocCat(rv, "\t");
	StrAllocCat(rv, username);

	return rv;
}

PRIVATE int
net_send_password_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if (cd->response_code == 3)
      {
        cd->next_state = FTP_SEND_ACCT;
      }
    else if (cd->response_code == 2) /* logged in */
      {
		  if(cd->store_password && cd->username)
		  {
			/* store the password in the cache */
			char *key = gen_ftp_password_key(ce->URL_s->address, cd->username);	

			if(key)
			{
				PCNameValueArray * array = PC_NewNameValueArray();
		
				PC_AddToNameValueArray(array, PC_FTP_PASSWORD_KEY, cd->password);
				
				PC_StorePasswordNameValueArray(PC_FTP_MODULE_KEY, key, array);

				XP_FREE(key);

				PC_FreeNameValueArray(array);
			}
		  }

          if( cd->cc->no_restart )
              cd->next_state = FTP_SEND_SYST;
          else
              cd->next_state = FTP_SEND_REST_ZERO;
      }
    else
      {
		FREEIF(ftp_last_password_host);
		FREEIF(ftp_last_password);

        cd->next_state = FTP_ERROR_DONE;
        FE_Alert(ce->window_id, cd->return_msg ? cd->return_msg :
								 XP_GetString( XP_COULD_NOT_LOGIN_TO_FTP_SERVER ) );
		/* no error status message in this case 
		 */
        return MK_UNABLE_TO_LOGIN;  /* negative return code */
      }

    return(0); /* good */
}
    

PRIVATE int
net_send_acct(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "ACCT noaccount%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_ACCT_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_acct_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if (cd->response_code == 2)
      {
          if( cd->cc->no_restart )
              cd->next_state = FTP_SEND_SYST;
          else
              cd->next_state = FTP_SEND_REST_ZERO;
      }
    else
      {
        cd->next_state = FTP_ERROR_DONE;
        FE_Alert(ce->window_id, cd->return_msg ? cd->return_msg : 
								 XP_GetString( XP_COULD_NOT_LOGIN_TO_FTP_SERVER ) );
		/* no error status message in this case 
		 */
        return MK_UNABLE_TO_LOGIN;  /* negative return code */
      }

    return(0); /* good */
}

PRIVATE int
net_send_rest_zero(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;
    
    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "REST 0%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_REST_ZERO_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_rest_zero_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if( cd->response_code == 3 )
    {
        TRACEMSG(("Server can do restarts!"));
        ce->URL_s->server_can_do_restart = TRUE;
        cd->cc->no_restart = FALSE;
    }
    else
    {
        TRACEMSG(("Server can NOT do restarts!"));
        ce->URL_s->server_can_do_restart = FALSE;
        cd->cc->no_restart = TRUE;
    }

    cd->next_state = FTP_SEND_SYST;
    return 0;
}

PRIVATE int
net_send_syst(FTPConData * cd)
{
    

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "SYST%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_SYST_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_send_syst_response(FTPConData * cd)
{
    if (cd->response_code == 2)
      {
        TRACEMSG(("SYST response: %s",cd->return_msg));

        /* default next state will be to try PASV mode
		 *
		 * TEST: set this default next state to FTP_SEND_PORT
		 * to test the non-pasv mode ftp
         */
		if(cd->use_default_path)
			cd->next_state = FTP_SEND_PWD;
    	else
        	cd->next_state = FTP_FIGURE_OUT_WHAT_TO_DO;

                /* we got a line -- what kind of server are we talking to? */
        if (!XP_STRNCMP(cd->return_msg, "UNIX Type: L8 MAC-OS MachTen", 28))
          {
            cd->cc->server_type = FTP_MACHTEN_TYPE;
            cd->cc->use_list = TRUE;
          }
        else if (XP_STRSTR(cd->return_msg, "UNIX") != NULL)
          {
             cd->cc->server_type = FTP_UNIX_TYPE;
             cd->cc->use_list = TRUE;
          }
        else if (XP_STRSTR(cd->return_msg, "Windows_NT") != NULL)
          {
             cd->cc->server_type = FTP_NT_TYPE;
             cd->cc->use_list = TRUE;
          }
        else if (XP_STRNCMP(cd->return_msg, "VMS", 3) == 0)
          {
             cd->cc->server_type = FTP_VMS_TYPE;
             cd->cc->use_list = TRUE;
          }
        else if ((XP_STRNCMP(cd->return_msg, "VM/CMS", 6) == 0)
                                 || (XP_STRNCMP(cd->return_msg, "VM ", 3) == 0))
          {
             cd->cc->server_type = FTP_CMS_TYPE;
          }
        else if (XP_STRNCMP(cd->return_msg, "DCTS", 4) == 0)
          {
             cd->cc->server_type = FTP_DCTS_TYPE;
          }
        else if (XP_STRSTR(cd->return_msg, "MAC-OS TCP/Connect II") != NULL)
          {
             cd->cc->server_type = FTP_TCPC_TYPE;
         	 cd->next_state = FTP_SEND_PWD;
          }
        else if (XP_STRNCMP(cd->return_msg, "MACOS Peter's Server", 20) == 0)
          {
             cd->cc->server_type = FTP_PETER_LEWIS_TYPE;
             cd->cc->use_list = TRUE;
             cd->next_state = FTP_SEND_MAC_BIN; 
          }
        else if(cd->cc->server_type == FTP_GENERIC_TYPE)
          {
              cd->next_state = FTP_SEND_PWD; 
          }
      }
    else
      {
        cd->next_state = FTP_SEND_PWD;
      }

    TRACEMSG(("Set server type to: %d\n",cd->cc->server_type));

    return(0);  /* good */

} 

PRIVATE int
net_send_mac_bin(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "MACB%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_MAC_BIN_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_send_mac_bin_response(FTPConData * cd)
{
    if(cd->response_code == 2)
      {  /* succesfully set mac binary */
        if(cd->cc->server_type == FTP_UNIX_TYPE)
            cd->cc->server_type = FTP_NCSA_TYPE;  /* we were unsure here */
      }

	cd->next_state = FTP_FIGURE_OUT_WHAT_TO_DO;
    
    return(0); /* continue */
}

PRIVATE
int
net_figure_out_what_to_do(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

	switch(ce->URL_s->method)
	  {
		case URL_MKDIR_METHOD:
			cd->next_state = FTP_SEND_MKDIR;
			break;

		case URL_DELETE_METHOD:
			cd->next_state = FTP_SEND_DELETE_FILE;
			break;

		case URL_PUT_METHOD:
			/* trim the path to the last slash */
			{
				char * slash = XP_STRRCHR(cd->path, '/');
				if(slash)
					*slash = '\0';
			}
			/* fall through */
			goto get_method_case;
					
		case URL_POST_METHOD:
			/* we support POST if files_to_post
			 * is filed in
			 */
			XP_ASSERT(ce->URL_s->files_to_post);
			/* fall through */

		case URL_GET_METHOD:
get_method_case:  /* ack! goto alert */

			/* don't send PASV if the server cant do it */
			if(cd->cc->no_pasv || !net_use_pasv)
				cd->next_state = FTP_SEND_PORT;
			else
				cd->next_state = FTP_SEND_PASV;
			break;

		default:
			XP_ASSERT(0);
	  }

	return(0);
}

PRIVATE int
net_send_mkdir(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    PR_snprintf(cd->output_buffer, 
				OUTPUT_BUFFER_SIZE, 
				"MKD %s"CRLF, 
				cd->path);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_MKDIR_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_mkdir_response(ActiveEntry *ce)
{
	FTPConData * cd = (FTPConData *)ce->con_data;

	if (cd->response_code !=2) 
      {
		/* error */
		ce->URL_s->error_msg = NET_ExplainErrorDetails(
												MK_COULD_NOT_CREATE_DIRECTORY,
												cd->path);
		return(MK_COULD_NOT_CREATE_DIRECTORY);
	  }
	
	/* success */

	cd->next_state = FTP_DONE;

	return(0);
}


PRIVATE int
net_send_delete_file(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    PR_snprintf(cd->output_buffer, 
				OUTPUT_BUFFER_SIZE, 
				"DELE %s"CRLF, 
				cd->path);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_DELETE_FILE_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_delete_file_response(ActiveEntry *ce)
{
	FTPConData * cd = (FTPConData *)ce->con_data;

	if (cd->response_code !=2) 
      {
		/* error */
		/* try and delete it as a file */
		cd->next_state = FTP_SEND_DELETE_DIR;
		return(0);
	  }
	
	/* success */

	cd->next_state = FTP_DONE;

	return(0);
}

PRIVATE int
net_send_delete_dir(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    PR_snprintf(cd->output_buffer, 
				OUTPUT_BUFFER_SIZE, 
				"RMD %s"CRLF, 
				cd->path);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_DELETE_DIR_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_delete_dir_response(ActiveEntry *ce)
{
	FTPConData * cd = (FTPConData *)ce->con_data;

	if (cd->response_code !=2) 
      {
		/* error */
		ce->URL_s->error_msg = NET_ExplainErrorDetails(
												MK_UNABLE_TO_DELETE_DIRECTORY,
												cd->path);
		return(MK_UNABLE_TO_DELETE_DIRECTORY);
	  }
	
	/* success */

	cd->next_state = FTP_DONE;

	return(0);
}

PRIVATE int
net_send_pwd(FTPConData * cd)
{
    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "PWD%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_PWD_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_pwd_response(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;
    char *cp = XP_STRCHR(cd->return_msg+5,'"');

    if(cp) 
        *cp = '\0';

    /* default next state */
    /* don't send PASV if the server cant do it */
    cd->next_state = FTP_FIGURE_OUT_WHAT_TO_DO;

    if (cd->cc->server_type == FTP_TCPC_TYPE)
      {
        cd->cc->server_type = (cd->return_msg[1] == '/' ? 
								FTP_NCSA_TYPE : FTP_TCPC_TYPE);
      }
	else if(cd->cc->server_type == FTP_GENERIC_TYPE)
	  {
    	if (cd->return_msg[1] == '/')
          {
            /* path names beginning with / imply Unix,
             * right?
             */
              cd->cc->server_type = FTP_UNIX_TYPE;
              cd->cc->use_list = TRUE;
              cd->next_state = FTP_SEND_MAC_BIN;  /* see if it's a mac */
           }
         else if (cd->return_msg[XP_STRLEN(cd->return_msg)-1] == ']')
           {
             /* path names ending with ] imply VMS, right? */
             cd->cc->server_type = FTP_VMS_TYPE;
             cd->cc->use_list = TRUE;
           }
	  }

	/* use the path returned by PWD if use_default_path is set
	 * don't do this for vms hosts
	 */
	if(cd->use_default_path && cd->cc->server_type != FTP_VMS_TYPE)
	  {
		char *path = XP_STRTOK(&cd->return_msg[1], "\"");
		char *ptr;
		char *existing_path = cd->path;

		/* NT can return a drive letter.  We can't
		 * deal with a drive letter right now
		 * so we need to just strip the drive letter
		 */
		if(*path != '/') {
		  ptr = XP_STRCHR(path, '/');
          /* If that didn't work, look for a backslash (e.g., OS/2) */
          if( ptr == (char *)0 ) {
              ptr = XP_STRCHR(path, '\\');
              /* If that worked, then go flip the slashes so the URL is legal */
              if( ptr != (char *)0 ) {
                  char *p;
                  for( p = ptr; *p != '\0'; p++ ){
                      if( *p == '\\' ) {
                          *p = '/';
                      }
                  }
              }
          }
        }
		else
		  ptr = path;

		if(ptr)
		  {
			char * new_url;

			cd->path = NULL;
			StrAllocCopy(cd->path, ptr);

			if(existing_path && existing_path[0] != '\0')
		 	  {
			  	/* if the new path has a / on the
				 * end remove it since the
				 * existing path already has
				 * a slash at the beginning
				 */
				if(cd->path[0] != '\0')
				  {
					char *end = &cd->path[XP_STRLEN(cd->path)-1];
					if(*end == '/')
						*end = '\0';
				  }
				StrAllocCat(cd->path, existing_path);
		 	  }

			/* strip the path part from the URL
			 * and add the new path
			 */
			new_url = NET_ParseURL(ce->URL_s->address, 
								   GET_PROTOCOL_PART | GET_USERNAME_PART | GET_HOST_PART);

			StrAllocCat(new_url, cd->path);
			
			FREE(ce->URL_s->address);

			ce->URL_s->address = new_url;
		  }


		/* make sure we don't do this again for some reason */
		cd->use_default_path = FALSE;
	  }

     if (cd->cc->server_type == FTP_NCSA_TYPE ||
                        cd->cc->server_type == FTP_TCPC_TYPE ||
                        cd->cc->server_type == FTP_PETER_LEWIS_TYPE)
         cd->next_state = FTP_SEND_MAC_BIN;

     return(0); /* good */
}


PRIVATE int
net_send_pasv(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "PASV%c%c", CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_PASV_RESPONSE;

	TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_send_pasv_response(FTPConData * cd)
{
    int    h0, h1, h2, h3; 
    int32  p0, p1;
    int32  pasv_port;
    char  *cp;
    int    status;

    if (cd->response_code !=2) 
      {
        cd->next_state = FTP_SEND_PORT; /* default next state */
        cd->pasv_mode = FALSE;
        cd->cc->no_pasv = TRUE;  /* pasv doesn't work don't try it again */
        return(0); /* continue */
      }

    cd->cc->no_pasv = FALSE;  /* pasv does work */

    TRACEMSG(("PASV response: %s\n",cd->return_msg));

    /* find first comma or end of line */
    for(cp=cd->return_msg; *cp && *cp != ','; cp++)
        ; /* null body */

    /* find the begining of digits */
    while (--cp > cd->return_msg && '0' <= *cp && *cp <= '9')
        ; /* null body */   

	/* go past the '(' when the return is of the
	 * form (128,234,563,356,356)
	 */
	if(*cp < '0' || *cp > '9')
		cp++;

    status = sscanf(cp,
#ifdef __alpha
		"%d,%d,%d,%d,%d,%d",
#else
		"%d,%d,%d,%d,%ld,%ld",
#endif
		&h0, &h1, &h2, &h3, &p0, &p1);
    if (status<6)
      {
        TRACEMSG(("FTP: PASV reply has no inet address!\n"));
        cd->next_state = FTP_SEND_PORT; /* default next state */
        cd->pasv_mode = FALSE;
        return(0); /* continue */
      }

    pasv_port = ((int32) (p0<<8)) + p1;
    TRACEMSG(("FTP: Server is listening on port %d\n", pasv_port));

    /* Open connection for data:
     */
    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "%d.%d.%d.%d:%ld",h0,h1,h2,h3,pasv_port);
    StrAllocCopy(cd->data_con_address, cd->output_buffer);

    cd->next_state = FTP_PASV_DATA_CONNECT;

    return(0); /* OK */
}


PRIVATE int
net_send_port(ActiveEntry * ce)
{
	FTPConData * cd = (FTPConData *) ce->con_data;
    int    status;
	PRNetAddr pr_addr;
    PRSocketOptionData opt;
    PRStatus sock_status;
    
    TRACEMSG(("Entering get_listen_socket with control socket: %d\n", cd->cc->csock));

	cd->pasv_mode = FALSE;  /* not using pasv mode if we get here */

    /*  Create internet socket
    */
    cd->listen_sock = PR_NewTCPSocket();
	
    if(cd->listen_sock == NULL)
	  {
		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CREATE_SOCKET);
        return MK_UNABLE_TO_CREATE_SOCKET;
	  }

#if defined(XP_WIN16) || (defined(XP_OS2) && !defined(XP_OS2_DOUGSOCK))
    opt.option = PR_SockOpt_Linger;
    opt.value.linger.polarity = PR_TRUE;
    opt.value.linger.linger = PR_INTERVAL_NO_WAIT;
    PR_SetSocketOption(cd->listen_sock, &opt);
#endif
      /* lets try to set the socket non blocking
       * so that we can do multi threading 
       */
    opt.option = PR_SockOpt_Nonblocking;
    opt.value.non_blocking = PR_TRUE;
    sock_status = PR_SetSocketOption(cd->listen_sock, &opt);
    if (sock_status != PR_SUCCESS)
        NET_Progress(ce->window_id, 
                     XP_GetString( XP_ERROR_COULD_NOT_MAKE_CONNECTION_NON_BLOCKING ) );

HG31456

	/* init the addr, leave port zero to let bind allocate one */
	PR_InitializeNetAddr(PR_IpAddrAny, 0, &pr_addr);

    status = PR_GetSockName(cd->cc->csock, &pr_addr);
    if (status != PR_SUCCESS) 
      {
        TRACEMSG(("Failure in getsockname for control connection: %d\n", 
                                cd->cc->csock));
        return -1;
      }
    
    status=PR_Bind(cd->listen_sock, &pr_addr);
    if (status != PR_SUCCESS) 
    {
        TRACEMSG(("Failure in bind for listen socket: %d\n", cd->listen_sock));
       	return -1;
    }

    /* TRACEMSG(("FTP: This host is %s\n", PR_NetAddrToString(&pr_addr))); */
    
	/* get the name again to find out the port */
    status = PR_GetSockName(cd->listen_sock, &pr_addr);
    if (status != PR_SUCCESS)   
        return -1;

    /* TRACEMSG(("FTP: bound to %s\n",PR_NetAddrToString(&pr_addr))); */

    /*  Now we must tell the other side where to connect
     */
    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "PORT %d,%d,%d,%d,%d,%d%c%c",
            (int)* ((unsigned char*) (&pr_addr.inet.ip)+0),
            (int)* ((unsigned char*) (&pr_addr.inet.ip)+1),
            (int)* ((unsigned char*) (&pr_addr.inet.ip)+2),
            (int)* ((unsigned char*) (&pr_addr.inet.ip)+3),
            (int)* ((unsigned char*) (&pr_addr.inet.port)+0),
            (int)* ((unsigned char*) (&pr_addr.inet.port)+1),
            CR, LF);

    /*  Inform TCP that we will accept connections
     */
    if (PR_Listen(cd->listen_sock, 1) < 0) 
      {
		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LISTEN_ON_SOCKET);
        return MK_UNABLE_TO_LISTEN_ON_SOCKET;
      }

    TRACEMSG(("TCP: listen socket(), bind() and listen() all OK.\n"));

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_PORT_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    /* Inform the server of the port number we will listen on
     * The port_command came from above
     */
    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));

} /* get_listen */

PRIVATE int
net_send_port_response(ActiveEntry * ce)
{
	FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->response_code !=2)
      {
        TRACEMSG(("FTP: Bad response (port_command: %s)\n",cd->return_msg));
		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_SEND_PORT_COMMAND);

        return MK_UNABLE_TO_SEND_PORT_COMMAND;
      }

    /* else */

    cd->next_state = FTP_GET_FILE_TYPE;
    return(0); /* good */
}

/* figure out what file transfer type to use
 * and set the next state to set it if it needs
 * set or else begin the transfer if it's already
 * set correctly
 */
PRIVATE int
net_get_ftp_file_type(ActiveEntry *ce)
{
	FTPConData * cd = (FTPConData *)ce->con_data;

    NET_cinfo * cinfo_struct;

	/* figure out if we are puttting a file
	 * and if we are figure out which
	 * file we are doing now 
	 */
	if(ce->URL_s->files_to_post &&
		ce->URL_s->files_to_post[0] != 0)
	  {
		/* files_to_post is an array of filename pointers
		 * find the last one in the list and
		 * remove it once done.
		 */
		char **array = ce->URL_s->files_to_post;
		int i;
		
		if(!cd->total_size_of_files_to_post)
		  {
		  	/* If we don't know the total size
			 * of all the files we are posting
			 * figure it out now
			 */
			XP_StatStruct stat_entry; 
			
			for(i=0; array[i]; i++)
			  {
				if(XP_Stat(array[i], 
						   &stat_entry, xpFileToPost) != -1)
					cd->total_size_of_files_to_post += stat_entry.st_size;
			  }

			/* start the graph progress now since we know the size
		 	 */
			ce->URL_s->content_length = cd->total_size_of_files_to_post; 
	    	FE_GraphProgressInit(ce->window_id, ce->URL_s, ce->URL_s->content_length);
			cd->destroy_graph_progress = TRUE;
			cd->original_content_length = ce->URL_s->content_length;

#ifdef EDITOR
			/* Don't show the dialog if the data is being delivered to a plug-in */
			if (CLEAR_CACHE_BIT(ce->format_out) != FO_PLUGIN)
			{
	            FE_SaveDialogCreate(ce->window_id, i, ED_SAVE_DLG_PUBLISH );
	            cd->destroy_file_upload_progress_dialog = TRUE;
	        }
#endif /* EDITOR */
		  }  
		   		      
		/* posting files is started as a POST METHOD
		 * eventually though, after posting the files
		 * we end up with a directory listing which
		 * should be treated as a GET METHOD.  Change
		 * the method now so that the cache doesn't
		 * get confused about the method as it caches
		 * the directory listing at the end
		 */
		if(ce->URL_s->method == URL_POST_METHOD)
			ce->URL_s->method = URL_GET_METHOD;

		for(i=0; array[i] != 0; i++)
		    ; /* null body */
		
	    if(i < 1)
		  {
			/* no files left to post
			 * quit
			 */
			cd->next_state = FTP_DONE;
			return(0);
		  }

		FREEIF(cd->file_to_post);
		cd->file_to_post = array[i-1];
		array[i-1] = 0;

    /* Use post_to data if specified in the URL struct. */
    if (ce->URL_s->post_to && ce->URL_s->post_to[i-1]) {
      FREEIF(cd->post_file_to);
      cd->post_file_to = ce->URL_s->post_to[i-1];
      ce->URL_s->post_to[i-1] = 0;
    }


        cinfo_struct = NET_cinfo_find_type(cd->file_to_post);

        /* use binary mode always except for "text" types
		 * don't use ASCII for default types
         */
        if(!cinfo_struct->is_default 
			&& !strncasecomp(cinfo_struct->type, "text",4))
          {
            if(cd->cc->cur_mode != FTP_MODE_ASCII)
                cd->next_state = FTP_SEND_ASCII;
            else
                cd->next_state = FTP_SEND_CWD;
          }
        else
          {
            if(cd->cc->cur_mode != FTP_MODE_BINARY)
                cd->next_state = FTP_SEND_BIN;
            else
                cd->next_state = FTP_SEND_CWD;
          }

        return(0);  /* continue */
	  }
	else if(ce->URL_s->method == URL_PUT_METHOD)
	  {
		cd->next_state = FTP_SEND_BIN;
		return(0);
	  }

    if(cd->type == FTP_MODE_UNKNOWN)
      {  /* use extensions to determine the type */

		NET_cinfo * enc = NET_cinfo_find_enc(cd->filename);
        cinfo_struct = NET_cinfo_find_type(cd->filename);

		TRACEMSG(("%s", cinfo_struct->is_default ?
							"Default cinfo type found" : ""));

		TRACEMSG(("Current FTP transfer mode is: %d", cd->cc->cur_mode));

        /* use binary mode always except for "text" types
		 * don't use ASCII for default types
		 *
		 * if it has an encoding value, transfer as binary
         */
        if(!cinfo_struct->is_default 
			&& !strncasecomp(cinfo_struct->type, "text",4)
			&& (!enc || !enc->encoding))
          {
            if(cd->cc->cur_mode != FTP_MODE_ASCII)
                cd->next_state = FTP_SEND_ASCII;
            else
                cd->next_state = FTP_GET_FILE_SIZE;
          }
        else
          {
            if(cd->cc->cur_mode != FTP_MODE_BINARY)
                cd->next_state = FTP_SEND_BIN;
            else
                cd->next_state = FTP_GET_FILE_SIZE;
          }
      }
    else
      {  /* use the specified type */
        if(cd->cc->cur_mode == cd->type)
          {
            cd->next_state = FTP_GET_FILE_SIZE;
          }
        else
          {
            if(cd->type == FTP_MODE_ASCII)
                cd->next_state = FTP_SEND_ASCII;
            else
                cd->next_state = FTP_SEND_BIN;
          }
      }
  

    return(0);  /* continue */
}

PRIVATE int
net_send_bin(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "TYPE I%c%c", CR, LF);
    cd->cc->cur_mode = FTP_MODE_BINARY;

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_BIN_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_send_ascii(FTPConData * cd)
{

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "TYPE A%c%c", CR, LF);
    cd->cc->cur_mode = FTP_MODE_ASCII;

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_ASCII_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_bin_or_ascii_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->response_code != 2)
	  {
		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CHANGE_FTP_MODE);
        return MK_UNABLE_TO_CHANGE_FTP_MODE;
	  }

    /* else */
	if(cd->file_to_post || ce->URL_s->method == URL_PUT_METHOD)
    	cd->next_state = FTP_SEND_CWD;
	else
    	cd->next_state = FTP_GET_FILE_SIZE;

    return(0); /* good */
}

PRIVATE int
net_get_ftp_file_size(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->cc->server_type == FTP_VMS_TYPE) 
	  {
		/* skip this since I'm not sure how it should work on VMS
		 */
		cd->next_state = FTP_GET_FILE; /* we can skip the mdtm request, too */
		return(0);
	  }

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "SIZE %.1024s%c%c", cd->path, CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_GET_FILE_SIZE_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_get_ftp_file_size_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if(cd->response_code == 2 && cd->return_msg)
	  {
          int32 cl = atol(cd->return_msg);
		/* The response looks like  "160452"
		 * where "160452" is the size in bytes.
		 */

          if( cd->restart )
          {
              TRACEMSG(("Verifying content length: was %ld, is %ld", ce->URL_s->real_content_length, cl));
              if( ce->URL_s->real_content_length != cl )
              {
                  TRACEMSG(("Doesn't match!  Clearing restart flag."));
                  cd->restart = FALSE;
                  ce->URL_s->content_length = cl;
                  ce->URL_s->real_content_length = cl;
              }
              else
              {
                  TRACEMSG(("Length didn't change."));
              }
          }
          else
          {
              ce->URL_s->content_length = cl;
              ce->URL_s->real_content_length = cl;
              TRACEMSG(("Set content length: %ld",  ce->URL_s->content_length));
          }
	  }
	else
	  {
		/* unknown length */
      	ce->URL_s->content_length = 0;
        ce->URL_s->real_content_length = 0;
        cd->restart = FALSE;
	  }

	cd->next_state = FTP_GET_FILE_MDTM;
	
	return(0);
}

PRIVATE int
net_get_ftp_file_mdtm(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->cc->server_type == FTP_VMS_TYPE) 
	  {
		/* skip this since I'm not sure how it should work on VMS
		 */
		cd->next_state = FTP_GET_FILE;
		return(0);
	  }

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "MDTM %.1024s%c%c", cd->path, CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_GET_FILE_MDTM_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_get_ftp_file_mdtm_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if(cd->response_code == 2 && cd->return_msg)
	{
        /* 
         * The time is returned in ISO 3307 "Representation
         * of the Time of Day" format. This format is YYYYMMDDHHmmSS or
         * YYYYMMDDHHmmSS.xxx, where
         *         YYYY    is the year
         *         MM      is the month (01-12)
         *         DD      is the day of the month (01-31)
         *         HH      is the hour of the day (00-23)
         *         mm      is the minute of the hour (00-59)
         *         SS      is the second of the hour (00-59)
         *         xxx     if present, is a fractional second and may be any length
         * Time is expressed in UTC (GMT), not local time.
         */
#ifndef NSPR20
        PRTime ts;
        int64 t, s2us, st;
#else
        PRExplodedTime ts;
        int64 s2us, st;
		PRTime t;
#endif /* NSPR20 */
        time_t tt;

        TRACEMSG(("Parsing MDTM \"%.1024s\"", cd->return_msg, CR, LF));

        ts.tm_year = (cd->return_msg[0] - '0') * 1000 +
                     (cd->return_msg[1] - '0') *  100 +
                     (cd->return_msg[2] - '0') *   10 +
                     (cd->return_msg[3] - '0');
#ifndef NSPR20
        ts.tm_mon = ((cd->return_msg[4] - '0') * 10 + (cd->return_msg[5] - '0')) - 1;
#else
        ts.tm_month = ((cd->return_msg[4] - '0') * 10 + (cd->return_msg[5] - '0')) - 1;
#endif /* NSPR20 */
        ts.tm_mday = (cd->return_msg[6] - '0') * 10 + (cd->return_msg[7] - '0');
        ts.tm_hour = (cd->return_msg[8] - '0') * 10 + (cd->return_msg[9] - '0');
        ts.tm_min  = (cd->return_msg[10] - '0') * 10 + (cd->return_msg[11] - '0');
        ts.tm_sec  = (cd->return_msg[12] - '0') * 10 + (cd->return_msg[13] - '0');
        ts.tm_usec = 0;

        /* 
         * if( '.' == cd->return_msg[14] )
         * {
         *     long int i = 1000000;
         *     char *p = &cd->return_msg[15];
         *     while( isdigit(*p) )
         *     {
         *         ts.tm_usec += (*p - '0') * i;
         *         i /= 10;
         *         p++;
         *     }
         * }
         */

#ifndef NSPR20
        t = PR_ComputeTime(&ts);
#else
        t = PR_ImplodeTime(&ts);
#endif /* NSPR20 */
        LL_I2L(s2us, PR_USEC_PER_SEC);
        LL_DIV(st, t, s2us);
        LL_L2I(tt, st);

#ifdef DEBUG
        {
            char line[256];
#ifndef NSPR20
            PRTime et;
            int64 t2;
#else
            PRExplodedTime et;
			PRTime t2;
#endif /* NSPR20 */
            LL_MUL(t2, st, s2us);
#ifndef NSPR20
            PR_ExplodeTime(&et, t2);
#else
            PR_ExplodeTime(t2, PR_GMTParameters, &et);
#endif /* NSPR20 */
            PR_FormatTimeUSEnglish(line, sizeof(line), "%Y%m%d%H%M%S", &et);
            TRACEMSG(("Parse check: mdtm is \"%s\"", line));
        }
#endif

        if( cd->restart && (ce->URL_s->last_modified != 0) )
        {
            TRACEMSG(("Verifying last modified date: was %ld, is %ld", 
                      ce->URL_s->last_modified, tt));
            if( ce->URL_s->last_modified != tt )
            {
                TRACEMSG(("Doesn't match!  Clearing restart flag."));
                cd->restart = FALSE;
                ce->URL_s->last_modified = tt;
            }
            else
            {
                TRACEMSG(("Time of last modification didn't change."));
            }
        }
        else
        {
            ce->URL_s->last_modified = tt;
            TRACEMSG(("set last modified: %ld", ce->URL_s->last_modified));
        }
	}

	cd->next_state = FTP_GET_FILE;
	
	return(0);
}

PRIVATE int
net_get_ftp_file(FTPConData * cd)
{
    
    /* if this is a VMS server then we need to 
     * do some special things to map a UNIX syntax
     * ftp url to a VMS type file syntax
     */
    if (cd->cc->server_type == FTP_VMS_TYPE) 
      {
          cd->restart = FALSE;
        /* If we want the VMS account's top directory speed to it 
		 */
        if (cd->path[0] == '\0' || (cd->path[0] == '/' && cd->path[1]== '\0')) 
          {
        	cd->is_directory = YES;
        	cd->next_state = FTP_SEND_LIST_OR_NLST;
          }
		else if(!XP_STRCHR(cd->path, '/'))
		  {
			/* if we want a file out of the top directory skip the PWD
			 */
			cd->next_state = FTP_SEND_RETR;
		  }
		else
		  {
    	    cd->next_state = FTP_SEND_CWD;
		  }
    
      }
    else
      {  /* non VMS */
		/* if we have already done the RETR or
		 * if the path ends with a slash
		 */
		if(cd->retr_already_failed ||
			'/' == cd->path[XP_STRLEN(cd->path)-1])
        	cd->next_state = FTP_SEND_CWD;
		else
        {
            if( cd->restart ) cd->next_state = FTP_SEND_REST;
            else              cd->next_state = FTP_SEND_RETR;
        }
      }

    return(0); /* continue */
}

PRIVATE int
net_send_cwd(FTPConData * cd)
{

    if (cd->cc->server_type == FTP_VMS_TYPE) 
      {
        char *cp, *cp1;
        /** Try to go to the appropriate directory and doctor filename **/
        if ((cp=XP_STRRCHR(cd->path, '/'))!=NULL) 
          {
			*cp = '\0';
        	PR_snprintf(cd->output_buffer, 
					   OUTPUT_BUFFER_SIZE, 
					   "CWD [%.1024s]" CRLF, 
					   cd->path);
			*cp = '/';  /* set it back so it is left unmodified */

			/* turn slashes into '.' */
        	while ((cp1=XP_STRCHR(cd->output_buffer, '/')) != NULL)
            	*cp1 = '.';
          }
        else
          {
			PR_snprintf(cd->output_buffer, 
					   OUTPUT_BUFFER_SIZE, 
					   "CWD [.%.1024s]" CRLF, 
					   cd->path);
          }
      } /* if FTP_VMS_TYPE */
    else
      {  /* non VMS server */
        PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "CWD %.1024s" CRLF, cd->path);
      }

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_CWD_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_cwd_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->response_code == 2) /* Successed : let's NAME LIST it */
      {  

		if(cd->file_to_post)
		  {
            cd->next_state = FTP_BEGIN_PUT;
            return(0); /* good */
          }
		else if(ce->URL_s->method == URL_PUT_METHOD)
		  {
            cd->next_state = FTP_BEGIN_PUT;
            return(0); /* good */
		  }

        ce->URL_s->is_directory = TRUE;
        cd->restart = FALSE;

        if (cd->cc->server_type == FTP_VMS_TYPE)
          {
			if(*cd->filename)
                cd->next_state = FTP_SEND_RETR;
			else
			 	cd->next_state = FTP_SEND_LIST_OR_NLST;
            return(0); /* good */
          }

        /* non VMS server */
        /* we are in the correct directory and we
         * already failed on the RETR so lets 
         * LIST or NLST it
         */
        cd->next_state = FTP_SEND_LIST_OR_NLST;
        return(0); /* good */
      }

    /* else */
	ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LOCATE_FILE, 
													*cd->path ? cd->path : "/");

    return MK_UNABLE_TO_LOCATE_FILE;  /* error */
}

PRIVATE int
net_begin_put(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;
	char *path, *filename;

	if(cd->file_to_post)
	  {
       /* Use post_file_to as destination filename, if supplied. */
       /* All path info in post_file_to is thrown away.  Assumed to
          be in same directory. */
       if (cd->post_file_to) {
      	  path = XP_STRDUP(cd->post_file_to);
 		      if(!path)
			      return(MK_OUT_OF_MEMORY);
       	  filename = XP_STRRCHR(path, '/');
       }
       else {
		    path = XP_STRDUP(cd->file_to_post);
		    if(!path)
			    return(MK_OUT_OF_MEMORY);
#if defined(XP_WIN) || defined(XP_OS2)               /* IBM - SAH */
		    filename = XP_STRRCHR(path, '\\');
#else
		    filename = XP_STRRCHR(path, '/');
#endif
       }
	  }
	else
	  {
		path = NET_ParseURL(ce->URL_s->address, GET_PATH_PART);
		if(!path)
			return(MK_OUT_OF_MEMORY);
		filename = XP_STRRCHR(path, '/');
	  }

	if(!filename)
		filename = path;
	else
		filename++; /* go past delimiter */

	PR_snprintf(cd->output_buffer, 
			   OUTPUT_BUFFER_SIZE, 
			   "STOR %.1024s" CRLF, 
			   filename);

#ifdef EDITOR
    if(cd->destroy_file_upload_progress_dialog)
    	FE_SaveDialogSetFilename(ce->window_id, filename);
#endif /* EDITOR */

	FREE(path);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_BEGIN_PUT_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_begin_put_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if (cd->response_code != 1) 
	  {
	    /* failed */
		ce->URL_s->error_msg = NET_ExplainErrorDetails(
								MK_COULD_NOT_PUT_FILE, 
								cd->file_to_post ? 
										cd->file_to_post : ce->URL_s->address,
								cd->return_msg ? cd->return_msg : "");
		return(MK_COULD_NOT_PUT_FILE);
	  }

    NET_ClearReadSelect(ce->window_id, ce->socket);
    ce->socket = cd->dsock;
    ce->con_sock = cd->dsock;

#if defined(XP_WIN16) || (defined(XP_OS2) && !defined(XP_OS2_DOUGSOCK)) /* stuff not for windows since setsockopt doesnt work */
	{
	    PRSocketOptionData opt;
		  /* turn the linger off so that we don't
		   * wait but we don't abort either
		   * since aborting will lose quede data
		   */
		opt.option = PR_SockOpt_Linger;
		opt.value.linger.polarity = PR_FALSE;
		opt.value.linger.linger = PR_INTERVAL_NO_WAIT;
		PR_SetSocketOption(cd->listen_sock, &opt);
	}
#endif /* XP_WIN16 */

    /* set connect select because we are writting
     * not reading
     */
	if(cd->pasv_mode)
		NET_SetConnectSelect(ce->window_id, ce->socket);
#ifdef XP_WIN
	cd->calling_netlib_all_the_time = TRUE;
	NET_SetCallNetlibAllTheTime(ce->window_id, "mkftp");
#endif


  /* Maybe should change string if cd->post_file_to */
	PR_snprintf(cd->output_buffer,
			   OUTPUT_BUFFER_SIZE,
				XP_GetString( XP_POSTING_FILE ),
			   cd->file_to_post);

	NET_Progress(ce->window_id, cd->output_buffer);
		
    if(cd->pasv_mode)
      {
        cd->next_state = FTP_DO_PUT;
      }
    else
      { /* non PASV */
        cd->next_state = FTP_WAIT_FOR_ACCEPT;
      }

	return(0);

}

/* finally send the file
 */
PRIVATE int
net_do_put(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    /* Default to adding crlf if ftp ASCII mode. */
    XP_Bool add_crlf = (cd->cc->cur_mode == FTP_MODE_ASCII);


	if(!cd->write_post_data_data)
	  {
		/* first time through */

		/* if file_to_post is filled in then
		 * throw the name into the post data
		 * so that we can use the WritePostData
		 * function
		 */
		if(cd->file_to_post)
		  {
			ce->URL_s->post_data = cd->file_to_post;
			ce->URL_s->post_data_is_file = TRUE;
		  }
	  }

    /* If the add_crlf field is set in the URL struct, find the
       entry corresponding to the current file being posted. */
    if (ce->URL_s->files_to_post && ce->URL_s->add_crlf) {
      int n = 0;
      while (ce->URL_s->files_to_post[n]) {
        n++;
      }
      /* n should now point after the last entry in files_to_post,
         which is the current file being uploaded. */      
      add_crlf = ce->URL_s->add_crlf[n];
    }

    /* returns 0 on done and negative on error
     * positive if it needs to continue.
     */
    ce->status = NET_WritePostData(ce->window_id, ce->URL_s,
                                  ce->socket,
                                  &cd->write_post_data_data,
								                  add_crlf);

	cd->pause_for_read = TRUE;

    if(ce->status == 0)
      {
        /* normal done
         */
   		NET_ClearConnectSelect(ce->window_id, ce->socket);
#ifdef XP_WIN
		if(cd->calling_netlib_all_the_time)
		{
			cd->calling_netlib_all_the_time = FALSE;
			NET_ClearCallNetlibAllTheTime(ce->window_id, "mkftp");
		}
#endif
   		ce->socket = cd->cc->csock;
   		ce->con_sock = NULL;
   		NET_SetReadSelect(ce->window_id, ce->socket);

        PR_Close(cd->dsock);
        cd->dsock = NULL;

        if(cd->destroy_graph_progress)
		  {
            FE_GraphProgressDestroy(ce->window_id, 	
									ce->URL_s, 	
									cd->original_content_length,
									ce->bytes_received);
			cd->destroy_graph_progress = FALSE;
		  }

       	/* go read the response since we need to
       	 * get the "226 sucessful transfer" message
       	 * out of the read queue in order to cache
       	 * connections properly
       	 */
       	cd->next_state = FTP_WAIT_FOR_RESPONSE;

		if(cd->file_to_post)
		  {
			ce->URL_s->post_data = 0;
			ce->URL_s->post_data_is_file = FALSE;
			FREE_AND_CLEAR(cd->file_to_post);
		  }

    if (cd->post_file_to)
      {
      FREE_AND_CLEAR(cd->post_file_to);
      }

		/* after getting the server response start
		 * over again if there are more
		 * files to send or to do a new directory
		 * listing...
		 */
      	cd->next_state_after_response = FTP_DONE;


		/* if this was the last file, check the history
		 * to see if the window is currently displaying an ftp
		 * listing.  If it is update the listing.  If it isnt
		 * quit.
		 */
		if(ce->URL_s->files_to_post)
		  {
			if(ce->URL_s->files_to_post[0])
			  {
				/* more files, keep going */
				cd->next_state_after_response = FTP_FIGURE_OUT_WHAT_TO_DO;
			  }
      /* Don't want to show a directory when remote editing a FTP page, bug 50942 */
			else if (!EDT_IS_EDITOR(ce->window_id))
		  	  {
				/* no more files, figure out if we should
				 * show an index
				 */
#ifdef MOZILLA_CLIENT
		  		History_entry * his = SHIST_GetCurrent(&ce->window_id->hist);
#else
            	History_entry * his = NULL;
#endif /* MOZILLA_CLIENT */

				if(his && !strncasecomp(his->address, "ftp:", 4))
			  	  {
					/* go get the index of the directory */
					cd->next_state_after_response = FTP_FIGURE_OUT_WHAT_TO_DO;
			  	  }
			  }
		  }
	  }
	else if(ce->status > 0)
	  {
    	ce->bytes_received += ce->status;
    	FE_GraphProgress(ce->window_id,
                     	ce->URL_s,
                     	ce->bytes_received,
                     	ce->status,
                     	ce->URL_s->content_length);
    	FE_SetProgressBarPercent(ce->window_id,
                             	(long)(((double)ce->bytes_received*100) / (double)(uint32)ce->URL_s->content_length) );
	  }

	return(ce->status);
}

PRIVATE int
net_send_rest(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "REST %ld%c%c", 
                ce->URL_s->content_length, CR, LF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_REST_RESPONSE;
    cd->pause_for_read = TRUE;

    TRACEMSG(("FTP Rx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock,
                             cd->output_buffer,
                             XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_rest_response(ActiveEntry *ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if(cd->response_code == 3 && cd->return_msg)
	{
        TRACEMSG(("Restarting!  Yay!"));
    }
    else
    {
        TRACEMSG(("Didn't understand the REST command."));
        cd->cc->no_restart = TRUE;
        cd->restart = FALSE;
    }

    cd->next_state = FTP_SEND_RETR;
    return(0);
}


PRIVATE int
net_send_retr(FTPConData * cd)
{

    if(cd->cc->server_type == FTP_VMS_TYPE)
        PR_snprintf(cd->output_buffer, 
				   OUTPUT_BUFFER_SIZE,
				   "RETR %.1024s" CRLF, 
				   cd->filename);
    else
        PR_snprintf(cd->output_buffer, 
					OUTPUT_BUFFER_SIZE, 
					"RETR %.1024s" CRLF, 
					cd->path);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_RETR_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}


PRIVATE int
net_send_retr_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *) ce->con_data;

    if(cd->response_code != 1) 
      {
        if(cd->cc->server_type == FTP_VMS_TYPE) 
          {
	        ce->URL_s->error_msg = NET_ExplainErrorDetails(
												  MK_UNABLE_TO_LOCATE_FILE, 
												  *cd->path ? cd->path : "/");
            return(MK_UNABLE_TO_LOCATE_FILE);
          }
        else
          { /* non VMS */

#ifdef DOUBLE_PASV 
			/* this will use a new PASV connection
			 * for each command even if the one only
			 * failed and didn't transmit
			 */
			/* close the old pasv connection */
			NETCLOSE(cd->dsock);
			/* invalidate the old pasv connection */
			cd->dsock = NULL;

			cd->next_state = FTP_SEND_PASV; /* send it again */
			cd->retr_already_failed = TRUE;
#else
			/* we need to use this to make the mac FTPD work
			 */
			/* old way */
            cd->next_state = FTP_SEND_CWD; 
#endif /* DOUBLE_PASV */
          }
      }
    else
      {   /* successful RETR */
        cd->next_state = FTP_SETUP_STREAM;
      }

    return(0); /* continue */
}

PRIVATE int
net_send_list_or_nlst(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if(cd->cc->use_list)
        XP_STRCPY(cd->output_buffer, "LIST" CRLF);
    else
        XP_STRCPY(cd->output_buffer, "NLST" CRLF);

    cd->next_state = FTP_WAIT_FOR_RESPONSE;
    cd->next_state_after_response = FTP_SEND_LIST_OR_NLST_RESPONSE;
    cd->pause_for_read = TRUE;

	TRACEMSG(("FTP Tx: %s", cd->output_buffer));

    return((int) NET_BlockingWrite(cd->cc->csock, 
							 cd->output_buffer, 
							 XP_STRLEN(cd->output_buffer)));
}

PRIVATE int
net_send_list_or_nlst_response(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if(cd->response_code == 1)
      {  /* succesful list or nlst */
    	cd->is_directory = TRUE;
        cd->restart = FALSE;
    	cd->next_state = FTP_SETUP_STREAM;

		/* add a slash to the end of the uRL if it doesnt' have one now
		 */
		if(ce->URL_s->address[XP_STRLEN(ce->URL_s->address)-1] != '/')
		  {
			/* update the global history before modification
			 */
#ifdef MOZILLA_CLIENT
			GH_UpdateGlobalHistory(ce->URL_s);
#endif /* MOZILLA_CLIENT */
			StrAllocCat(ce->URL_s->address, "/");
			ce->URL_s->address_modified = TRUE;
		  }

    	return(0); /* good */
      }
  
	ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LOCATE_FILE, 
												  *cd->path ? cd->path : "/");
    return(MK_UNABLE_TO_LOCATE_FILE);
}

    
PRIVATE int
net_setup_ftp_stream(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    /* set up the data stream now
     */
    if(cd->is_directory)
      {
 		char * pUrl ;
		int status;
 
 		status = PREF_CopyCharPref("protocol.mimefor.ftp",&pUrl);
 
 		if (status >= 0 && pUrl) {
 			StrAllocCopy(ce->URL_s->content_type, pUrl);
 			XP_FREE(pUrl);
 		} else {
 	 		StrAllocCopy(ce->URL_s->content_type, APPLICATION_HTTP_INDEX);
 		}

        cd->stream = net_ftp_make_stream(ce->format_out, ce->URL_s, ce->window_id);
      }
    else
      {
		if(!ce->URL_s->preset_content_type || !ce->URL_s->content_type )
		  {
			NET_cinfo * cinfo_struct = NET_cinfo_find_type(cd->filename);
	 		StrAllocCopy(ce->URL_s->content_type, cinfo_struct->type);

			cinfo_struct = NET_cinfo_find_enc(cd->filename);
	 		StrAllocCopy(ce->URL_s->content_encoding, cinfo_struct->encoding);
		  }

        cd->stream = net_ftp_make_stream(ce->format_out, ce->URL_s, ce->window_id);
      }

    if (!cd->stream)
      {
        cd->next_state = FTP_ERROR_DONE;
	    ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONVERT); 
        return(MK_UNABLE_TO_CONVERT);
      }

HG69136

    if(cd->pasv_mode)
      {
        if(cd->is_directory)
           cd->next_state = FTP_START_READ_DIRECTORY;
        else
           cd->next_state = FTP_START_READ_FILE;
      }
    else
      { /* non PASV */
        cd->next_state = FTP_WAIT_FOR_ACCEPT;
      }

	/* start the graph progress indicator
     */
    FE_GraphProgressInit(ce->window_id, ce->URL_s, ce->URL_s->real_content_length);
	cd->destroy_graph_progress = TRUE;
	cd->original_content_length = ce->URL_s->real_content_length;

    return(0); /* continue */
}
    
PRIVATE int
net_wait_for_ftp_accept(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;
	PRNetAddr pr_addr;

/* make this non-blocking sometime */

    /* Wait for the connection
     */
#define ACCEPT_TIMEOUT 20
    cd->dsock = PR_Accept(cd->listen_sock, &pr_addr, ACCEPT_TIMEOUT);
   
    if (cd->dsock == NULL)
      {
		if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
		  {
			cd->pause_for_read = TRUE; /* pause */
    		NET_ClearReadSelect(ce->window_id, ce->socket);
    		ce->socket = cd->listen_sock;
    		NET_SetReadSelect(ce->window_id, ce->socket);
			return(0);
		  }
		else
		  {
	        ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_ACCEPT_SOCKET); 
            return MK_UNABLE_TO_ACCEPT_SOCKET;
		  }
      }

    TRACEMSG(("FTP: Accepted new socket %d\n", cd->dsock));

	if(cd->file_to_post || ce->URL_s->method == URL_POST_METHOD)
	  {
		ce->socket = cd->dsock;
		NET_SetConnectSelect(ce->window_id, ce->socket);
		cd->next_state = FTP_DO_PUT;
	  }
    else if (cd->is_directory)
      {
        cd->next_state = FTP_START_READ_DIRECTORY;
      }
    else
      {
        cd->next_state = FTP_START_READ_FILE;
      }

    return(0);
}

PRIVATE int
net_ftp_push_partial_cache_file(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    int32 write_ready, status;

    write_ready = (*cd->stream->is_write_ready)(cd->stream);

    if( 0 == write_ready )
      {
        cd->pause_for_read = TRUE;
        return(0);  /* wait until we are ready to write */
      }

    write_ready = MIN(write_ready, NET_Socket_Buffer_Size);

    status = XP_FileRead(NET_Socket_Buffer, write_ready, cd->partial_cache_fp);

    if( status < 0 )
    {
        ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO);
        return MK_TCP_READ_ERROR;
    }
    else if( status == 0 )
    {
        /* All done, switch over to incoming data */
        TRACEMSG(("Closing FTP cache file"));
        NET_Progress(ce->window_id, XP_GetString(XP_PROGRESS_FILEDONE));
        NET_ClearFileReadSelect(ce->window_id, XP_Fileno(cd->partial_cache_fp));
        XP_FileClose(cd->partial_cache_fp);
        cd->partial_cache_fp = NULL;
        ce->socket = cd->dsock;
        NET_SetReadSelect(ce->window_id, ce->socket);
        ce->local_file = FALSE;
        cd->next_state = FTP_READ_FILE;
        NET_Progress (ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_FTPFILE));
        return 0;
    }
    else
    {
        /* write the data */
        TRACEMSG(("Transferring %ld bytes from cache file.", status));
        ce->bytes_received += status;
        FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, status, ce->URL_s->real_content_length);
        FE_SetProgressBarPercent(ce->window_id, (long)(((double)ce->bytes_received*100) / (double)(uint32)ce->URL_s->real_content_length) );
        status = (*cd->stream->put_block)(cd->stream, NET_Socket_Buffer, status);
        cd->pause_for_read = TRUE;
        return status;
    }
}

PRIVATE int
net_start_ftp_read_file(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if( cd->restart )
    {
        char *cache_file = ce->URL_s->cache_file;
        XP_File fp;

        TRACEMSG(("Opening FTP cache file"));

        if( (NULL == cache_file) || (NULL == (fp = XP_FileOpen(cache_file, xpCache, XP_FILE_READ_BIN))) )
        {
            ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_OPEN_FILE, cache_file);
            return MK_UNABLE_TO_OPEN_FILE;
        }

        NET_ClearReadSelect(ce->window_id, ce->socket);
        /* ce->socket = XP_Fileno(fp); */
		ce->socket = NULL;
        NET_SetFileReadSelect(ce->window_id, XP_Fileno(fp));
        ce->local_file = TRUE;
        cd->next_state = FTP_READ_CACHE;
        cd->partial_cache_fp = fp;
        NET_Progress(ce->window_id, XP_GetString(XP_PROGRESS_READFILE));
        return net_ftp_push_partial_cache_file(ce);
    }

    /* nothing much to be done */
    cd->next_state = FTP_READ_FILE;
    cd->pause_for_read = TRUE;

    NET_ClearReadSelect(ce->window_id, ce->socket);
    ce->socket = cd->dsock;
    NET_SetReadSelect(ce->window_id, ce->socket);

    NET_Progress (ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_FTPFILE));

    return(0); /* continue */
}

PRIVATE int
net_ftp_read_file(ActiveEntry * ce) 
{ 
	FTPConData * cd = (FTPConData *)ce->con_data; 
	int status; 
    unsigned int write_ready, read_size;

    /* check to see if the stream is ready for writing
     */
    write_ready = (*cd->stream->is_write_ready)(cd->stream);

    if(!write_ready)
      {
        cd->pause_for_read = TRUE;
        return(0);  /* wait until we are ready to write */
      }
    else if(write_ready < (unsigned int) NET_Socket_Buffer_Size)
      {
        read_size = write_ready;
      }
    else
      {
        read_size = NET_Socket_Buffer_Size;
      }

	status = PR_Read(cd->dsock, NET_Socket_Buffer, read_size); 

	if(status > 0) 
	  { 
		ce->bytes_received += status; 
		FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, status, ce->URL_s->real_content_length);
		if (ce->URL_s->real_content_length == 0)
			FE_SetProgressBarPercent(ce->window_id, 0);
		else
    		FE_SetProgressBarPercent(ce->window_id, (long)(((double)ce->bytes_received*100) / (double)(uint32)ce->URL_s->real_content_length) );
        status = PUTBLOCK(NET_Socket_Buffer, status);
        cd->pause_for_read = TRUE;
      }
    else if(status == 0)
      {
        /* go read the response since we need to
         * get the "226 sucessful transfer" message
         * out of the read queue in order to cache
         * connections properly
         */
        cd->next_state = FTP_WAIT_FOR_RESPONSE;
        cd->next_state_after_response = FTP_DONE;

		if(cd->dsock != NULL)
	      {
		    NET_ClearReadSelect(ce->window_id, cd->dsock);
            PR_Close(cd->dsock);
            cd->dsock = NULL;
		  }

		/* set select on the control socket */
    	ce->socket = cd->cc->csock;
    	NET_SetReadSelect(ce->window_id, ce->socket);
        /* try and read the response immediately,
         * so don't pause for read
         *
         * cd->pause_for_read = TRUE;
         */

        return(MK_DATA_LOADED);
      }
	else
	  {
		/* status less than zero
	     */
	      int rv = PR_GetError();

		if (rv == PR_WOULD_BLOCK_ERROR)
		  {
			cd->pause_for_read = TRUE;
			return 0;
		  }
	      status = (rv < 0) ? rv : (-rv);
	  }

   return(status);
}

PRIVATE int
net_start_ftp_read_dir(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;
	char buf[512];
	char *line;

    cd->sort_base = NET_SortInit();

    NET_Progress (ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_FTPDIR));

    if (*cd->path != '\0')  /* Not Empty path: */
      {
        int end;

        end = XP_STRLEN(cd->path)-1;
        /* if the path ends with a slash kill it.
         * that includes the path "/"
         */
        if(cd->path[end] == '/')
          {
             cd->path[end] = 0; /* kill the last slash */
          }
      }

    if(cd->login_message_buffer) /* Don't check !(*cd->path) here or this
									message never shows up.  It's supposed to
									be shown the first time you connect to
									this server, whether you started at the
									root or not. */
      {
	
		/* print it out like so:  101: message line 1
		 *                        101: message line 2, etc...
	 	 */
		line = XP_STRTOK(cd->login_message_buffer, "\n");
		while(ce->status >= 0 && line)
		{
			XP_STRCPY(buf, "101: ");
			XP_STRNCAT_SAFE (buf, line, sizeof(buf)-8);
			XP_STRCAT (buf, CRLF);

			ce->status = PUTSTRING(buf);

			line = XP_STRTOK(NULL, "\n");
		}

		/* if there is also a cwd message add a blank line */
    	if(ce->status >= 0 && cd->cwd_message_buffer)
		{
			XP_STRCPY(buf, "101: "CRLF);
			ce->status = PUTSTRING(buf);
		}
	}

    if(cd->cwd_message_buffer)
    {
		/* print it out like so:  101: message line 1
		 *                        101: message line 2, etc...
	 	 */
		char *end_line = XP_STRCHR(cd->cwd_message_buffer, '\n');
		if(end_line)
			*end_line = '\0';
		line = cd->cwd_message_buffer;

		while(ce->status >= 0 && line)
		{
			XP_STRCPY(buf, "101: ");
			XP_STRNCAT_SAFE (buf, line, sizeof(buf)-8);
			XP_STRCAT (buf, CRLF);

			ce->status = PUTSTRING(buf);

			if(end_line)
			{
				line = end_line+1;
				end_line = XP_STRCHR(line, '\n');
				if(end_line)
					*end_line = '\0';
			}
			else
			{
				line = NULL;
			}
		} 
    }

    /* clear the buffer so we can use it on the data sock */
    FREE_AND_CLEAR(cd->data_buf);
    cd->data_buf_size = 0;

    cd->next_state = FTP_READ_DIRECTORY;

    /* clear the select on the control socket
     */
    NET_ClearReadSelect(ce->window_id, ce->socket);
    ce->socket = cd->dsock;
    NET_SetReadSelect(ce->window_id, ce->socket);

    return(0); /* continue */
}

PRIVATE int
net_ftp_read_dir(ActiveEntry * ce)
{
   
    char * line;
    NET_FileEntryInfo * entry_info;
    FTPConData * cd = (FTPConData *)ce->con_data;

    ce->status = NET_BufferedReadLine(cd->dsock, &line, &cd->data_buf, 
                    &cd->data_buf_size, &cd->pause_for_read);

    if(ce->status == 0)
      {
		cd->next_state = FTP_PRINT_DIR;
        return(ce->status);
      }
    else if(ce->status < 0)
	  {
		if(cd->stream && (*cd->stream->is_write_ready)(cd->stream))
			NET_PrintDirectory(&cd->sort_base, cd->stream, cd->path, ce->URL_s);

		ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, ce->status);

        /* return TCP error
         */
        return MK_TCP_READ_ERROR;
	  }

    /* not exactly right but really close
	 */
    if(ce->status > 1)
      {
    	ce->bytes_received += ce->status;
		FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, ce->status, ce->URL_s->content_length);
	  }

    if(!line)
        return(0); /* no line ready */

    TRACEMSG(("MKFTP: Line in %s is %s\n", cd->path, line));

    entry_info = net_parse_dir_entry(line, cd->cc->server_type);

    if(!entry_info)
	  {
	    ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
    	return(MK_OUT_OF_MEMORY);
	  }

    if(entry_info->display)
      {
        TRACEMSG(("Adding file to sort list: %s\n", entry_info->filename));
        NET_SortAdd(cd->sort_base, (void *)entry_info);
      }
    else
      {
        NET_FreeEntryInfoStruct(entry_info);
      }

    return(ce->status);
}

PRIVATE int
net_ftp_print_dir(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    if(!cd->calling_netlib_all_the_time)
    {

        cd->calling_netlib_all_the_time = TRUE;
	    NET_SetCallNetlibAllTheTime(ce->window_id, "mkftp");
    }

	if(cd->stream 
	   && (ce->status = (*cd->stream->is_write_ready)(cd->stream)) != 0)
	{
    	ce->status = NET_PrintDirectory(&cd->sort_base, cd->stream, cd->path, ce->URL_s);

        /* go read the response since we need to
         * get the "226 sucessful transfer" message
         * out of the read queue in order to cache
         * connections properly
         */
        cd->next_state = FTP_WAIT_FOR_RESPONSE;
        cd->next_state_after_response = FTP_DONE;
          
        /* clear select on the data sock and set select on the control sock
         */
        if(cd->dsock != NULL)
          {
            NET_ClearReadSelect(ce->window_id, cd->dsock);
            PR_Close(cd->dsock);
            cd->dsock = NULL;
          }
        
        ce->socket = cd->cc->csock;
        NET_SetReadSelect(ce->window_id, cd->cc->csock);

		return(ce->status);
	  }
	else
	  {
		/* come back into this state and try again later */
		/* @@@ busy wait */
		cd->pause_for_read = TRUE;
		return(0);
	  }
}

/*
 * is_ls_date() --
 *      Return 1 if the ptr points to a string of the form:
 *              "Sep  1  1990 " or
 *              "Sep 11 11:59 " or
 *              "Dec 12 1989  " or
 *              "FCv 23 1990  " ...
 */
PRIVATE Bool 
net_is_ls_date (char *s)
{
    /* must start with three alpha characters */
    if (!isalpha(*s++) || !isalpha(*s++) || !isalpha(*s++))
        return FALSE;

    /* space */
    if (*s++ != ' ')
        return FALSE;

    /* space or digit */
    if ((*s != ' ') && !isdigit(*s))
        return FALSE;
    s++;

    /* digit */
    if (!isdigit(*s++))
        return FALSE;

    /* space */
    if (*s++ != ' ')
        return FALSE;

    /* space or digit */
    if ((*s != ' ') && !isdigit(*s))
        return FALSE;
    s++;

    /* digit */
    if (!isdigit(*s++))
        return FALSE;

    /* colon or digit */
    if ((*s != ':') && !isdigit(*s))
        return FALSE;
    s++;

    /* digit */
    if (!isdigit(*s++))
        return FALSE;

    /* space or digit */
    if ((*s != ' ') && !isdigit(*s))
        return FALSE;
    s++;

    /* space */
    if (*s++ != ' ')
        return FALSE;

    return TRUE;
} /* is_ls_date() */

/*       
 *  Converts a date string from 'ls -l' to a time_t number
 *
 *              "Sep  1  1990 " or
 *              "Sep 11 11:59 " or
 *              "Dec 12 1989  " or
 *              "FCv 23 1990  " ...
 *
 *  Returns 0 on error.
 */
PRIVATE time_t net_convert_unix_date (char * datestr)
{
    struct tm *time_info;         /* Points to static tm structure */
    char *bcol = datestr;         /* Column begin */
    char *ecol;                   /* Column end */
    long tval;
    int cnt;
    time_t curtime = time(NULL);

    if ((time_info = gmtime(&curtime)) == NULL) 
      {
    	return (time_t) 0;
      }

    time_info->tm_isdst = -1;                 /* Disable summer time */
    for (cnt=0; cnt<3; cnt++)                 /* Month */
    	*bcol++ = toupper(*bcol);

    if ((time_info->tm_mon = NET_MonthNo(datestr)) < 0)
    	return (time_t) 0;

    ecol = bcol;                        /* Day */
    while (*ecol++ == ' ') ;            /* Spool to other side of day */
    while (*ecol++ != ' ') ;
    *--ecol = '\0';

    time_info->tm_mday = atoi(bcol);
    time_info->tm_wday = 0;
    time_info->tm_yday = 0;
    bcol = ++ecol;                                   /* Year */
    if ((ecol = XP_STRCHR(bcol, ':')) == NULL) 
      {
    	time_info->tm_year = atoi(bcol)-1900;
    	time_info->tm_sec = 0;
    	time_info->tm_min = 0;
    	time_info->tm_hour = 0;
      } 
	else 
      {                                 /* Time */
    	/* If the time is given as hh:mm, then the file is less than 1 year
         *	old, but we might shift calandar year. This is avoided by checking
         *	if the date parsed is future or not. 
		 */
    	*ecol = '\0';
    	time_info->tm_sec = 0;
    	time_info->tm_min = atoi(++ecol);           /* Right side of ':' */
    	time_info->tm_hour = atoi(bcol);         /* Left side of ':' */
    	if (mktime(time_info) > curtime)
        	--time_info->tm_year;
      }
    return ((tval = mktime(time_info)) == -1 ? (time_t) 0 : tval);
}

/* a dos date/time string looks like this
 * 04-06-95  02:03PM 
 * 07-13-95  11:39AM 
 */
PRIVATE time_t 
net_parse_dos_date_time (char * datestr)
{
    struct tm *time_info;         /* Points to static tm structure */
    long tval;
    time_t curtime = time(NULL);

    if ((time_info = gmtime(&curtime)) == NULL) 
      {
    	return (time_t) 0;
      }

    time_info->tm_isdst = -1;                 /* Disable summer time */

    time_info->tm_mon = (datestr[1]-'0')-1;

    time_info->tm_mday = (((datestr[3]-'0')*10) + datestr[4]-'0');
    time_info->tm_year = (((datestr[6]-'0')*10) + datestr[7]-'0');
    time_info->tm_hour = (((datestr[10]-'0')*10) + datestr[11]-'0');
	if(datestr[15] == 'P')
    	time_info->tm_hour += 12;
    time_info->tm_min = (((datestr[13]-'0')*10) + datestr[14]-'0');

    time_info->tm_wday = 0;
    time_info->tm_yday = 0;
    time_info->tm_sec = 0;

    return ((tval = mktime(time_info)) == -1 ? (time_t) 0 : tval);
}

/*         
 *  Converts a date string from vms to a time_t number
 *  This is needed in order to put out the date using the same format
 *  for all directory listings.
 *
 *  Returns 0 on error
 */
PRIVATE time_t net_convert_vms_date (char * datestr)
{
    struct tm *time_info;           /* Points to static tm structure */
    char *col;
    long tval;
    time_t curtime = time(NULL);

    if ((time_info = gmtime(&curtime)) == NULL)
    	return (time_t) 0;

    time_info->tm_isdst = -1;                 /* Disable summer time */

    if ((col = XP_STRTOK(datestr, "-")) == NULL)
    	return (time_t) 0;

    time_info->tm_mday = atoi(col);                   /* Day */
    time_info->tm_wday = 0;
    time_info->tm_yday = 0;

    if ((col = XP_STRTOK(NULL, "-")) == NULL || (time_info->tm_mon = NET_MonthNo(col)) < 0)
    	return (time_t) 0;

    if ((col = XP_STRTOK(NULL, " ")) == NULL)               /* Year */
    	return (time_t) 0;

    time_info->tm_year = atoi(col)-1900;

    if ((col = XP_STRTOK(NULL, ":")) == NULL)               /* Hour */
    	return (time_t) 0;

    time_info->tm_hour = atoi(col);

    if ((col = XP_STRTOK(NULL, " ")) == NULL)               /* Mins */
    	return (time_t) 0;

    time_info->tm_min = atoi(col);
    time_info->tm_sec = 0;

    return ((tval = mktime(time_info)) < 0 ? (time_t) 0 : tval);
}


/*
 * parse_ls_line() --
 *      Extract the name, size, and date from an ls -l line.
 *
 * ls -l listing
 *
 * drwxr-xr-x    2 montulli eng          512 Nov  8 23:23 CVS
 * -rw-r--r--    1 montulli eng         2244 Nov  8 23:23 Imakefile
 * -rw-r--r--    1 montulli eng        14615 Nov  9 17:03 Makefile
 *
 * or a dl listing
 *
 * cs.news/              =  ICSI Computing Systems Newsletter
 * dash/                 -  DASH group documents and software
 * elisp/                -  Emacs lisp code
 * 
 */
PRIVATE void 
net_parse_ls_line (char *line, NET_FileEntryInfo *entry_info)
{
    int32   base=1;
	int32   size_num=0;
	char    save_char;
	char   *ptr;

    for (ptr = &line[XP_STRLEN(line) - 1];
            (ptr > line+13) && (!XP_IS_SPACE(*ptr) || !net_is_ls_date(ptr-12)); ptr--)
                ; /* null body */
	save_char = *ptr;
    *ptr = '\0';
    if (ptr > line+13) 
	  {
        entry_info->date = net_convert_unix_date(ptr-12);
	  }
	else
	  {
	    /* must be a dl listing
	     */
		/* unterminate the line */
		*ptr = save_char;
		/* find the first whitespace and  terminate
		 */
		for(ptr=line; *ptr != '\0'; ptr++)
			if(XP_IS_SPACE(*ptr))
			  {
				*ptr = '\0';
				break;
			  }
        entry_info->filename = NET_Escape(line, URL_PATH);
	
		return;
	  }

    /* escape and copy
     */
    entry_info->filename = NET_Escape(ptr+1, URL_PATH);

	/* parse size
	 */
	if(ptr > line+15)
	  {
        ptr -= 14;
        while (isdigit(*ptr))
          {
            size_num += ((int32) (*ptr - '0')) * base;
            base *= 10;
            ptr--;
          }
    
        entry_info->size = size_num;
	  }
    
} /* parse_ls_line() */

/*
 * net_parse_vms_dir_entry()
 *      Format the name, date, and size from a VMS LIST line
 *      into the EntryInfo structure
 */
PRIVATE void 
net_parse_vms_dir_entry (char *line, NET_FileEntryInfo *entry_info)
{
        int i, j;
        int32 ialloc;
        char *cp, *cpd, *cps, date[64], *sp = " ";
        time_t NowTime;
        static char ThisYear[32];
        static Bool HaveYear = FALSE; 

        /**  Get rid of blank lines, and information lines.  **/
        /**  Valid lines have the ';' version number token.  **/
        if (!XP_STRLEN(line) || (cp=XP_STRCHR(line, ';')) == NULL) 
		  {
            entry_info->display = FALSE;
            return;
          }

        /** Cut out file or directory name at VMS version number. **/
    	*cp++ ='\0';
		/* escape and copy
	 	 */
		entry_info->filename = NET_Escape(line, URL_PATH);

        /** Cast VMS file and directory names to lowercase. **/
    	for (i=0; entry_info->filename[i]; i++)
            entry_info->filename[i] = tolower(entry_info->filename[i]);

        /** Uppercase terminal .z's or _z's. **/
    	if ((--i > 2) && entry_info->filename[i] == 'z' &&
             	(entry_info->filename[i-1] == '.' || entry_info->filename[i-1] == '_'))
            entry_info->filename[i] = 'Z';

        /** Convert any tabs in rest of line to spaces. **/
    	cps = cp-1;
        while ((cps=XP_STRCHR(cps+1, '\t')) != NULL)
            *cps = ' ';

        /** Collapse serial spaces. **/
        i = 0; j = 1;
    	cps = cp;
        while (cps[j] != '\0') 
		  {
            if (cps[i] == ' ' && cps[j] == ' ')
                j++;
            else
                cps[++i] = cps[j++];
		  }
        cps[++i] = '\0';

        /* Save the current year.       
    	 * It could be wrong on New Year's Eve.
		 */
    	if (!HaveYear) 
	 	  {
        	NowTime = time(NULL);
        	XP_STRCPY(ThisYear, (char *)ctime(&NowTime)+20);
        	ThisYear[4] = '\0';
        	HaveYear = TRUE;
    	  }

        /* get the date. 
		 */
        if ((cpd=XP_STRCHR(cp, '-')) != NULL &&
                XP_STRLEN(cpd) > 9 && isdigit(*(cpd-1)) &&
                isalpha(*(cpd+1)) && *(cpd+4) == '-') 
		  {

            /* Month 
			 */
            *(cpd+4) = '\0';
            *(cpd+2) = tolower(*(cpd+2));
            *(cpd+3) = tolower(*(cpd+3));
            XP_SPRINTF(date, "%.32s ", cpd+1);
            *(cpd+4) = '-';

            /** Day **/
            *cpd = '\0';
            if (isdigit(*(cpd-2)))
                XP_SPRINTF(date+4, "%.32s ", cpd-2);
            else
                XP_SPRINTF(date+4, " %.32s ", cpd-1);
            *cpd = '-';

            /** Time or Year **/
            if (!XP_STRNCMP(ThisYear, cpd+5, 4) && XP_STRLEN(cpd) > 15 && *(cpd+12) == ':')
			  {
                *(cpd+15) = '\0';
                XP_SPRINTF(date+7, "%.32s", cpd+10);
                *(cpd+15) = ' ';
              } 
			else 
			  {
                *(cpd+9) = '\0';
                XP_SPRINTF(date+7, " %.32s", cpd+5);
                *(cpd+9) = ' ';
              }

            entry_info->date = net_convert_vms_date(date);
          }

        /* get the size 
		 */
        if ((cpd=XP_STRCHR(cp, '/')) != NULL) 
		  {
            /* Appears be in used/allocated format */
            cps = cpd;
            while (isdigit(*(cps-1)))
                cps--;
            if (cps < cpd)
                *cpd = '\0';
            entry_info->size = atol(cps);
            cps = cpd+1;
            while (isdigit(*cps))
                cps++;
            *cps = '\0';
            ialloc = atoi(cpd+1);
            /* Check if used is in blocks or bytes */
            if (entry_info->size <= ialloc)
                entry_info->size *= 512;
          }
        else if ((cps=XP_STRTOK(cp, sp)) != NULL) 
		  {
            /* We just initialized on the version number 
             * Now let's find a lone, size number   
			 */
            while ((cps=XP_STRTOK(NULL, sp)) != NULL) 
			  {
                 cpd = cps;
                 while (isdigit(*cpd))
                     cpd++;
                 if (*cpd == '\0') 
				   {
                     /* Assume it's blocks */
                     entry_info->size = atol(cps) * 512;
                     break;
                   }
               }
          }

        /** Wrap it up **/
        TRACEMSG(("MKFTP: VMS filename: %s  date: %d  size: %ld\n",
                         entry_info->filename, entry_info->date, entry_info->size));
        return;

} /* net_parse_vms_dir_entry() */

/*
 *     parse_dir_entry() 
 *      Given a line of LIST/NLST output in entry, return results 
 *      and a file/dir name in entry_info struct
 *
 */
PRIVATE NET_FileEntryInfo * 
net_parse_dir_entry (char *entry, int server_type)
{
        NET_FileEntryInfo *entry_info;
        int  i;
        int  len;
        Bool remove_size=FALSE;

        entry_info = NET_CreateFileEntryInfoStruct();
        if(!entry_info)
            return(NULL);

        entry_info->display = TRUE;

		/* do special case for ambiguous NT servers
		 *
		 * ftp.microsoft.com is an NT server with UNIX ls -l
		 * syntax.  But most NT servers use the DOS dir syntax.
		 * If there is a space at position 8 then it's a DOS dir syntax
		 */
		if(server_type == FTP_NT_TYPE && !XP_IS_SPACE(entry[8]))
			server_type = FTP_UNIX_TYPE;

        switch (server_type)
        {
            case FTP_UNIX_TYPE:
            case FTP_PETER_LEWIS_TYPE:
            case FTP_MACHTEN_TYPE:
				/* interpret and edit LIST output from Unix server */

                if(!XP_STRNCMP(entry, "total ", 6)  
						|| (XP_STRSTR(entry, "Permission denied") != NULL)
						    || 	(XP_STRSTR(entry, "not available") != NULL))
                  {
                    entry_info->display=FALSE;
                    return(entry_info);
                  }
 
                len = XP_STRLEN(entry);

                if( '+' == entry[0] )
                {
                    /*
                     * EPLF: see http://pobox.com/~djb/proto/eplf.txt
                     * "+i8388621.29609,m824255902,/,\tdev"
                     * "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF"
                     *
                     * A plus, a bunch of comma-separated facts, a tab, 
                     * and then the name.  Facts include m for mdtm (as
                     * seconds since the Unix epoch), s for size, r for
                     * (readable) file, and / for (listable) directory.
                     */
                    char *tab = XP_STRCHR(entry, '\t');
                    char *begin, *end;
                    remove_size = TRUE; /* It'll be put back if we have data */
                    if( (char *)0 == tab ) break;
                    for( begin = &entry[1]; begin < tab; begin = &end[1])
                    {
                        end = XP_STRCHR(begin, ',');
                        if( (char *)0 == end ) break;
                        switch( *begin )
                        {
                            case 'm':
                                *end = '\0';
                                entry_info->date = XP_ATOI(&begin[1]);
                                *end = ',';
                                break;
                            case 's':
                                *end = '\0';
                                entry_info->size = XP_ATOI(&begin[1]);
                                *end = ',';
                                remove_size = FALSE;
                                break;
                            case 'r':
                                entry_info->special_type = NET_FILE_TYPE;
                                break;
                            case '/':
                                entry_info->special_type = NET_DIRECTORY;
                                break;
                            default:
                                break;
                        }
                    }

                    entry_info->filename = NET_Escape(&tab[1], URL_PATH);
                    break;
                }
                else 
                /* check first character of ls -l output */
                if (toupper(entry[0]) == 'D') 
                  {
                    /* it's a directory */
				    entry_info->special_type = NET_DIRECTORY;
                    remove_size=TRUE; /* size is not useful */
                  }
                else if (entry[0] == 'l')
                  {
                    /* it's a symbolic link, does the user care about
                     * knowing if it is symbolic?  I think so since
                     * it might be a directory
                     */
					entry_info->special_type = NET_SYM_LINK;
                    remove_size=TRUE; /* size is not useful */

                    /* strip off " -> pathname" */
                    for (i = len - 1; (i > 3) && (!XP_IS_SPACE(entry[i])
                    || (entry[i-1] != '>') 
                    || (entry[i-2] != '-')
                    || (entry[i-3] != ' ')); i--)
                             ; /* null body */
                    if (i > 3)
                      {
                        entry[i-3] = '\0';
                        len = i - 3;
                      }
                  } /* link */

                net_parse_ls_line(entry, entry_info); 

                if(!XP_STRCMP(entry_info->filename,"..") || !XP_STRCMP(entry_info->filename,"."))
            		entry_info->display=FALSE;

				/* add a trailing slash to all directories */
				if(entry_info->special_type == NET_DIRECTORY)
				    StrAllocCat(entry_info->filename, "/");

                /* goto the bottom and get real type */
                break;

            case FTP_VMS_TYPE:
                /* Interpret and edit LIST output from VMS server */
                /* and convert information lines to zero length.  */
                net_parse_vms_dir_entry(entry, entry_info);

                /* Get rid of any junk lines */
                if(!entry_info->display)
                    return(entry_info);

                /** Trim off VMS directory extensions **/
                len = XP_STRLEN(entry_info->filename);
                if ((len > 4) && !XP_STRCMP(&entry_info->filename[len-4], ".dir"))
                  {
                    entry_info->filename[len-4] = '\0';
				    entry_info->special_type = NET_DIRECTORY;
                    remove_size=TRUE; /* size is not useful */
					/* add trailing slash to directories
					 */
					StrAllocCat(entry_info->filename, "/");
                  }
                /* goto the bottom and get real type */
                break;

            case FTP_CMS_TYPE:
                /* can't be directory... */
                /*
                 * "entry" already equals the correct filename
                 */
		        /* escape and copy
	 	         */
		        entry_info->filename = NET_Escape(entry, URL_PATH);
                /* goto the bottom and get real type */
                break;

            case FTP_NCSA_TYPE:
            case FTP_TCPC_TYPE:
                /* directories identified by trailing "/" characters */
		        /* escape and copy
	 	         */
		        entry_info->filename = NET_Escape(entry, URL_PATH);
                len = XP_STRLEN(entry);
                if (entry[len-1] == '/')
                  {
				    entry_info->special_type = NET_DIRECTORY;
                    remove_size=TRUE; /* size is not useful */
                  }
                  /* goto the bottom and get real type */
                break;

			case FTP_NT_TYPE:
				/* windows NT DOS dir syntax.
				 * looks like:  
				 *            1         2         3         4         5
				 *  012345678901234567890123456789012345678901234567890
				 *  06-29-95  03:05PM       <DIR>          muntemp
				 *  05-02-95  10:03AM               961590 naxp11e.zip
				 *
				 *  The date time directory indicator and filename
				 *  are always in a fixed position.  The file
			 	 *  size always ends at position 37.
				 */
				  {
					char *date, *size_s, *name;
		
					if(XP_STRLEN(entry) > 37)
					  {
					    date = entry;
					    entry[17] = '\0';
					    size_s = &entry[18];
					    entry[38] = '\0';
					    name = &entry[39];
    
					    if(XP_STRSTR(size_s, "<DIR>"))
						    entry_info->special_type = NET_DIRECTORY;
					    else
						    entry_info->size = atol(XP_StripLine(size_s));
    
					    entry_info->date = net_parse_dos_date_time(date);

					    StrAllocCopy(entry_info->filename, name);
					  }
					else
					  {
					    StrAllocCopy(entry_info->filename, entry);
					  }
				  }
				break;
    
            default:
                /* we cant tell if it is a directory since we only
                 * did an NLST   
                 */
		        /* escape and copy
	 	         */
		        entry_info->filename = NET_Escape(entry, URL_PATH);
                return(entry_info); /* mostly empty info */
                /* break; Not needed */

        } /* switch (server_type) */


    if(remove_size && entry_info->size)
      {
        entry_info->size = 0;
      }

    /* get real types eventually */
    if(!entry_info->special_type) 
      {
        entry_info->cinfo = NET_cinfo_find_type(entry_info->filename);
      }

    return(entry_info);

} /* net_parse_dir_entry */

PRIVATE int
net_get_ftp_password(ActiveEntry *ce)
{
    FTPConData *cd = (FTPConData *) ce->con_data;

	if(!cd->password)
	  {
        char * host_string = NET_ParseURL(ce->URL_s->address, GET_HOST_PART);

#ifndef MCC_PROXY
		if(ftp_last_password 
				&& ftp_last_password_host 
					&& !XP_STRCMP(ftp_last_password_host, host_string)) 
		  {
			StrAllocCopy(cd->password, ftp_last_password);
		  }
		else
#endif
	      {
			/* check for cached password */
			char *key = gen_ftp_password_key(ce->URL_s->address, cd->username);

			if(key)
			{
				PCNameValueArray * array = PC_CheckForStoredPasswordArray(PC_FTP_MODULE_KEY, key);

				if(array)
					cd->password = PC_FindInNameValueArray(array, PC_FTP_PASSWORD_KEY);

				PC_FreeNameValueArray(array);

				FREE(key);
			}

			if(!cd->password)
			{
            	PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE,
							XP_GetString(XP_PROMPT_ENTER_PASSWORD),
							host_string);
            	cd->password = (char *)PC_PromptPassword(ce->window_id,
													 cd->output_buffer,
													 &cd->store_password,
													 FALSE /* not secure */);

            	if(!cd->password)
			  	{
					FREE(host_string);
                	return(MK_INTERRUPTED);  /* user canceled */
			  	}
			}

			StrAllocCopy(ftp_last_password_host, host_string);
			StrAllocCopy(ftp_last_password, cd->password);
		  }

		FREE(host_string);
	  }

	cd->next_state = FTP_SEND_PASSWORD;

    return 0;
}

PRIVATE int
net_get_ftp_password_response(FTPConData *cd)
{
	/* not used yet
	 */
    return 0; /* #### what should return value be? */
}

/* the load initializeation routine for FTP.
 *
 * this sets up all the data structures and begin's the
 * connection by calling processFTP
 */
PRIVATE int32
net_FTPLoad (ActiveEntry * ce)
{
	char * host = NET_ParseURL(ce->URL_s->address, GET_HOST_PART);
	char * unamePwd = NET_ParseURL(ce->URL_s->address, GET_USERNAME_PART | GET_PASSWORD_PART);
	char *username,*colon,*password=NULL;
    char * filename;
    char * semi_colon;
    FTPConData * cd = XP_NEW(FTPConData);

    if(!cd)
	  {
	    ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
		ce->status = MK_OUT_OF_MEMORY;
        return(MK_OUT_OF_MEMORY);  
	  }

	/* get the username & password out of the combo string */
	if( (colon = XP_STRCHR(unamePwd, ':')) != NULL ) {
		*colon='\0';
		username=XP_STRDUP(unamePwd);
		password=XP_STRDUP(colon+1);
		*colon=':';
		XP_FREE(unamePwd);
	} else {
		username=unamePwd;
	}
	
    ce->con_data = (FTPConData *)cd;

    /* init the connection data struct 
     */
    XP_MEMSET(cd, 0, sizeof(FTPConData));
    cd->dsock         = NULL;
    cd->listen_sock   = NULL;
    cd->pasv_mode     = TRUE;
    cd->is_directory  = FALSE;
    cd->next_state_after_response = FTP_ERROR_DONE;
    cd->cont_response     = -1;

	/* @@@@ hope we never need a buffer larger than this 
	 */
	cd->output_buffer = (char *) XP_ALLOC(OUTPUT_BUFFER_SIZE);
	if(!cd->output_buffer)
	  {
		FREE(cd);
	    ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
		ce->status = MK_OUT_OF_MEMORY;
        return(MK_OUT_OF_MEMORY);  
	  }

	if (username && *username) {
		StrAllocCopy(cd->username, username);
		if (password && *password)
			StrAllocCopy(cd->password, password);
	} else {
		const char * user = NULL;
		XP_FREEIF(username);
		username = NULL;
		XP_FREEIF(password);
		password = NULL;
		
#ifdef MOZ_MAIL_NEWS
#ifdef MOZILLA_CLIENT
		if(net_get_send_email_address_as_password())
			user = FE_UsersMailAddress();
#endif
#endif /* MOZ_MAIL_NEWS */

		if(user && *user)
		  {
		    StrAllocCopy(cd->password, user);

			/* make sure it has an @ sign in it or else the ftp
			 * server won't like it
			 */
			if(!XP_STRCHR(cd->password, '@'))
				StrAllocCat(cd->password, "@");
		  }
		else
	      {
			StrAllocCopy(cd->password, "mozilla@");
		  }
	}

    /* get the path */
    cd->path = NET_ParseURL(ce->URL_s->address, GET_PATH_PART);

    if(!cd->path)
      {
        FREE(ce->con_data);
        FREE(cd->output_buffer);
	    ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
		ce->status = MK_OUT_OF_MEMORY;
        return(MK_OUT_OF_MEMORY);
      }

	NET_UnEscape(cd->path);

	if(*cd->path == '\0')
	  {
		TRACEMSG(("Found ftp url with NO PATH"));
		
		cd->use_default_path = TRUE;
	  }
	else
	  {
		/* terminate at an "\r" or "\n" in the string
		 * this prevents URL's of the form "foo\nDELE foo"
		 */
		XP_STRTOK(cd->path, "\r");
		XP_STRTOK(cd->path, "\n");
	  }

	/* if the path begins with /./ 
	 * use pwd to get the sub path
	 * and append the end to it
	 */
	if(!XP_STRNCMP(cd->path, "/./", 3) || !XP_STRCMP(cd->path, "/."))
	  {
		char * tmp = cd->path;

		cd->use_default_path = TRUE;

		/* skip the "/." and leave a slash at the beginning */
		cd->path = XP_STRDUP(cd->path+2);
		XP_FREE(tmp);

		if(!cd->path)
		  {
			FREE(ce->con_data);
        	FREE(cd->output_buffer);
	    	ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
			ce->status = MK_OUT_OF_MEMORY;
			return(MK_OUT_OF_MEMORY);
		  }
	  }

    /* set the default type for use later
     */
    cd->type = FTP_MODE_UNKNOWN;

    TRACEMSG(("content length: %d, real content length: %d",
           ce->URL_s->content_length, ce->URL_s->real_content_length));
    if( (ce->URL_s->real_content_length > ce->URL_s->content_length) &&
        (NULL != ce->URL_s->cache_file) )
    {
        cd->restart = TRUE;
        TRACEMSG(("Considering a restart."));
    }

    /* look for the extra type information at the end per
     * the RFC 1630.  It will be delimited by a ';'
     */
    if((semi_colon = XP_STRRCHR(cd->path, ';')) != NULL)
      {
        /* just switch on the character at the end of this whole
         * thing since it must be the type to use
         */
        switch(semi_colon[XP_STRLEN(semi_colon)-1])
          {
            case 'a':
            case 'A':
                cd->type = FTP_MODE_ASCII;
				TRACEMSG(("Explicitly setting type ASCII"));
                break;

            case 'i':
            case 'I':
                cd->type = FTP_MODE_BINARY;
				TRACEMSG(("Explicitly setting type BINARY"));
                break;
          }

        /* chop off the bits after the semi colon
         */
        *semi_colon = '\0';

		/* also chop of the semi colon from the real URL */
		XP_STRTOK(ce->URL_s->address, ";");
      }

    /* find the filename in the path */
    filename = XP_STRRCHR(cd->path, '/');
    if(!filename)
        filename = cd->path;
	else
		filename++;  /* go past slash */

    cd->filename = 0;
    StrAllocCopy(cd->filename, filename);

    /* Try and find an open connection to this 
     * server that is not currently being used
     */
    if(connection_list)
      {
        FTPConnection * tmp_con;
        XP_List * list_entry = connection_list;

        while((tmp_con = (FTPConnection *)XP_ListNextObject(list_entry)) != NULL)
          {
            /* if the hostnames match up exactly and the connection
             * is not busy at the moment then reuse this connection.
             */
            if(!tmp_con->busy && !XP_STRCMP(tmp_con->hostname, host))
              {
                cd->cc = tmp_con;
                ce->socket = cd->cc->csock;  /* set select on the control socket */
    			NET_SetReadSelect(ce->window_id, cd->cc->csock);
                cd->cc->prev_cache = TRUE;  /* this was from the cache */
                break;
              }
          }
      }
    else
      {
    /* initialize the connection list 
     */
        connection_list = XP_ListNew();
      }
    
    if(!cd->cc)
      {

		TRACEMSG(("cached connection not found making new FTP connection"));

        /* build a control connection structure so we
         * can store the data as we go along
         */
        cd->cc = XP_NEW(FTPConnection);
        if(!cd->cc)
          {
            XP_FREE(host);  /* free from way up above */
			XP_FREEIF(username);
			XP_FREEIF(password);
	        ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); 
            return(MK_OUT_OF_MEMORY);
          }

        cd->cc->hostname = 0;
        StrAllocCopy(cd->cc->hostname, host);
        /* set the mode of the control connection unknown
         */
        cd->cc->cur_mode = FTP_MODE_UNKNOWN;
		cd->cc->csock = NULL;
		cd->cc->server_type = FTP_GENERIC_TYPE;
		cd->cc->use_list = FALSE;
		cd->cc->no_pasv  = FALSE;

        cd->cc->prev_cache = FALSE;  /* this wasn't from the cache */
        cd->cc->no_restart = FALSE; /* as far as we know so far */

        /* add this structure to the connection list even
         * though it's not really valid yet.
         * we will fill it in as we go and if
         * an error occurs will will remove it from the
         * list.  No one else will be able to use it since
         * we will mark it busy.
         */
        XP_ListAddObjectToEnd(connection_list, cd->cc);

        /* set this connection busy so no one else can use it
         */
        cd->cc->busy = TRUE;

		/* gate the maximum number of cached connections
		 * to one
		 */
		if(XP_ListCount(connection_list) > 1)
		  {
            XP_List * list_ptr = connection_list->next;
            FTPConnection * con;

            while(list_ptr)
              {
                con = (FTPConnection *) list_ptr->object;
                list_ptr = list_ptr->next;
                if(!con->busy)
                  {
                    XP_ListRemoveObject(connection_list, con);
                    PR_Close(con->csock);
					/* dont reduce the number of
					 * open connections since cached ones
					 * dont count as active
					 */
                    FREEIF(con->hostname);
                    XP_FREE(con);
                    break;
                  }
              }
		  }

        cd->next_state = FTP_CONTROL_CONNECT;
      }
    else
      {
		TRACEMSG(("Found cached FTP connection! YES!!! "));

    	/* don't send PASV if the server cant do it */
    	if(cd->cc->no_pasv || !net_use_pasv)
            cd->next_state = FTP_SEND_PORT;
        else
            cd->next_state = FTP_SEND_PASV;

        if( cd->cc->no_restart )
            ce->URL_s->server_can_do_restart = FALSE;
        else
            ce->URL_s->server_can_do_restart = TRUE;

        /* set this connection busy so no one else can use it
         */
        cd->cc->busy = TRUE;
		/* add this connection to the number that is open
		 * since it is now active
		 */
		NET_TotalNumberOfOpenConnections++;
      }

    XP_FREE(host);  /* free from way up above */
	XP_FREEIF(username);
	XP_FREEIF(password);
    TRACEMSG(("Server can do restarts: %s", ce->URL_s->server_can_do_restart ? 
              "TRUE" : "FALSE"));
    return(net_ProcessFTP(ce));
} 


/* the main state machine control routine.  Calls
 * the individual state processors
 * 
 * returns -1 when all done or 0 or positive all other times
 */
PRIVATE int32
net_ProcessFTP(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    cd->pause_for_read = FALSE;

    while(!cd->pause_for_read)
      {
        TRACEMSG(("In ProcessFTP switch loop with state: %d \n", cd->next_state));

        switch(cd->next_state) {

          case FTP_WAIT_FOR_RESPONSE:
            ce->status = net_ftp_response(ce);
            break;
    
          /* begin login states */
          case FTP_CONTROL_CONNECT:
            ce->status = NET_BeginConnect(ce->URL_s->address,
										  ce->URL_s->IPAddressString,
										 "FTP",
                               			 FTP_PORT,
										 &cd->cc->csock, 
										 FALSE, 
										 &cd->tcp_con_data, 
										 ce->window_id,
										 &ce->URL_s->error_msg,
										  ce->socks_host,
										  ce->socks_port);
            ce->socket = cd->cc->csock;

			if(cd->cc->csock != NULL)
				NET_TotalNumberOfOpenConnections++;
    
            cd->pause_for_read = TRUE;
    
            if(ce->status == MK_CONNECTED)
              {
                cd->next_state = FTP_WAIT_FOR_RESPONSE;
                cd->next_state_after_response = FTP_SEND_USERNAME;
			    NET_SetReadSelect(ce->window_id, cd->cc->csock);
              }
            else if(ce->status > -1)
              {
                cd->next_state = FTP_CONTROL_CONNECT_WAIT;
                ce->con_sock = cd->cc->csock;  /* set con sock so we can select on it */
                NET_SetConnectSelect(ce->window_id, ce->con_sock);
    
              }
            break;
    
          case FTP_CONTROL_CONNECT_WAIT:
            ce->status = NET_FinishConnect(ce->URL_s->address,
										  "FTP",
                            	  		  FTP_PORT,
										  &cd->cc->csock, 
										  &cd->tcp_con_data, 
										  ce->window_id,
										  &ce->URL_s->error_msg);
    
            cd->pause_for_read = TRUE;
    
            if(ce->status == MK_CONNECTED)
              {
                cd->next_state = FTP_WAIT_FOR_RESPONSE;
                cd->next_state_after_response = FTP_SEND_USERNAME;
                NET_ClearConnectSelect(ce->window_id, ce->con_sock);
                ce->con_sock = NULL;  /* reset con sock so we don't select on it */
			    NET_SetReadSelect(ce->window_id, ce->socket);
              }
			else
			  {
				/* the not yet connected state */

        		/* unregister the old CE_SOCK from the select list
         		 * and register the new value in the case that it changes
         		 */
				if(ce->con_sock != cd->cc->csock)
				  {
            		NET_ClearConnectSelect(ce->window_id, ce->con_sock);
					ce->con_sock = cd->cc->csock;
            		NET_SetConnectSelect(ce->window_id, ce->con_sock);
				  }
			  }
            break;

          case FTP_SEND_USERNAME:
            ce->status = net_send_username((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_USERNAME_RESPONSE:
            ce->status = net_send_username_response(ce);
            break;
    
		  case FTP_GET_PASSWORD:
            ce->status = net_get_ftp_password(ce);
            break;

		  case FTP_GET_PASSWORD_RESPONSE:
			/* currently skipped 
			 */
            ce->status = net_get_ftp_password_response((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_PASSWORD:
            ce->status = net_send_password((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_PASSWORD_RESPONSE:
            ce->status = net_send_password_response(ce);
            break;
        
          case FTP_SEND_ACCT:
            ce->status = net_send_acct((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_ACCT_RESPONSE:
            ce->status = net_send_acct_response(ce);
            break;
    
          case FTP_SEND_REST_ZERO:
            ce->status = net_send_rest_zero(ce);
            break;
    
          case FTP_SEND_REST_ZERO_RESPONSE:
            ce->status = net_send_rest_zero_response(ce);
            break;
    
          case FTP_SEND_SYST:
            ce->status = net_send_syst((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_SYST_RESPONSE:
            ce->status = net_send_syst_response((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_MAC_BIN:
            ce->status = net_send_mac_bin((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_MAC_BIN_RESPONSE:
            ce->status = net_send_mac_bin_response((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_PWD:
            ce->status = net_send_pwd((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_PWD_RESPONSE:
            ce->status = net_send_pwd_response(ce);
            break;
        /* end login states */

		  case FTP_FIGURE_OUT_WHAT_TO_DO:
            ce->status = net_figure_out_what_to_do(ce);
			break;

		  case FTP_SEND_MKDIR:
            ce->status = net_send_mkdir(ce);
			break;

		  case FTP_SEND_MKDIR_RESPONSE:
            ce->status = net_send_mkdir_response(ce);
			break;
    
		  case FTP_SEND_DELETE_FILE:
            ce->status = net_send_delete_file(ce);
			break;

		  case FTP_SEND_DELETE_FILE_RESPONSE:
            ce->status = net_send_delete_file_response(ce);
			break;
    
		  case FTP_SEND_DELETE_DIR:
            ce->status = net_send_delete_dir(ce);
			break;

		  case FTP_SEND_DELETE_DIR_RESPONSE:
            ce->status = net_send_delete_dir_response(ce);
			break;

          case FTP_SEND_PASV:
            ce->status = net_send_pasv((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_PASV_RESPONSE:
            ce->status = net_send_pasv_response((FTPConData *)ce->con_data);
            break;
    
          case FTP_PASV_DATA_CONNECT:
            ce->status = NET_BeginConnect(cd->data_con_address, 
										  NULL,
										 "FTP Data Connection",
                       					FTP_PORT, 
										 &cd->dsock, 
										 FALSE, 
										 &cd->tcp_con_data, 
										 ce->window_id,
										 &ce->URL_s->error_msg,
										  ce->socks_host,
										  ce->socks_port);
    
            if(ce->status == MK_CONNECTED)
              {
                cd->next_state = FTP_GET_FILE_TYPE;
              }
            else if(ce->status > -1)
              {
                cd->pause_for_read = TRUE;
                cd->next_state = FTP_PASV_DATA_CONNECT_WAIT;
                ce->con_sock = cd->dsock;  /* set con sock so we can select on it */
                NET_SetConnectSelect(ce->window_id, ce->con_sock);
              }
            break;

          case FTP_PASV_DATA_CONNECT_WAIT:
                ce->status = NET_FinishConnect(cd->data_con_address, 
											  "FTP Data Connection",
                                			  FTP_PORT,
											  &cd->dsock,
											  &cd->tcp_con_data,
											  ce->window_id,
											  &ce->URL_s->error_msg);
                TRACEMSG(("FTP: got pasv data connection on port #%d\n",cd->cc->csock));
    
                if(ce->status == MK_CONNECTED)
                  {
                    cd->next_state = FTP_GET_FILE_TYPE;
                    NET_ClearConnectSelect(ce->window_id, ce->con_sock);
                    ce->con_sock = NULL;  /* reset con sock so we don't select on it */
                  }
                else
                  {
					ce->con_sock = cd->dsock; /* it might change */
                    cd->pause_for_read = TRUE;
                  }
            break;
    
          case FTP_SEND_PORT:
            ce->status = net_send_port(ce);
            break;
    
          case FTP_SEND_PORT_RESPONSE:
            ce->status = net_send_port_response(ce);
            break;
    
		  /* This is the state that gets called after
		   * login and port/pasv are done
		   */
          case FTP_GET_FILE_TYPE:
            ce->status = net_get_ftp_file_type(ce);
            break;
    
          case FTP_SEND_BIN:
            ce->status = net_send_bin((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_ASCII:
            ce->status = net_send_ascii((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_BIN_RESPONSE:
          case FTP_SEND_ASCII_RESPONSE:
            ce->status = net_send_bin_or_ascii_response(ce);
            break;

		  case FTP_GET_FILE_SIZE:
            ce->status = net_get_ftp_file_size(ce);
            break;

		  case FTP_GET_FILE_SIZE_RESPONSE:
            ce->status = net_get_ftp_file_size_response(ce);
            break;
    
		  case FTP_GET_FILE_MDTM:
            ce->status = net_get_ftp_file_mdtm(ce);
            break;

		  case FTP_GET_FILE_MDTM_RESPONSE:
            ce->status = net_get_ftp_file_mdtm_response(ce);
            break;
    
          case FTP_GET_FILE:
            ce->status = net_get_ftp_file((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_REST:
            ce->status = net_send_rest(ce);
            break;

          case FTP_SEND_REST_RESPONSE:
            ce->status = net_send_rest_response(ce);
            break;

          case FTP_SEND_RETR:
            ce->status = net_send_retr((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_RETR_RESPONSE:
            ce->status = net_send_retr_response(ce);
            break;
    
          case FTP_SEND_CWD:
            ce->status = net_send_cwd((FTPConData *)ce->con_data);
            break;
    
          case FTP_SEND_CWD_RESPONSE:
            ce->status = net_send_cwd_response(ce);
            break;
    
          case FTP_BEGIN_PUT:
            ce->status = net_begin_put(ce);
            break;
    
          case FTP_BEGIN_PUT_RESPONSE:
            ce->status = net_begin_put_response(ce);
            break;
    
          case FTP_DO_PUT:
            ce->status = net_do_put(ce);
            break;
    
          case FTP_SEND_LIST_OR_NLST:
            ce->status = net_send_list_or_nlst(ce);
            break;
    
          case FTP_SEND_LIST_OR_NLST_RESPONSE:
            ce->status = net_send_list_or_nlst_response(ce);
            break;

          case FTP_SETUP_STREAM:
            ce->status = net_setup_ftp_stream(ce);
            break;
    
          case FTP_WAIT_FOR_ACCEPT:
            ce->status = net_wait_for_ftp_accept(ce);
            break;
    
          case FTP_READ_CACHE:
            ce->status = net_ftp_push_partial_cache_file(ce);
            break;

          case FTP_START_READ_FILE:
            ce->status = net_start_ftp_read_file(ce);
            break;
    
          case FTP_READ_FILE:
            ce->status = net_ftp_read_file(ce);
            break;
    
          case FTP_START_READ_DIRECTORY:
            ce->status = net_start_ftp_read_dir(ce);
            break;
    
          case FTP_READ_DIRECTORY:
    
            ce->status = net_ftp_read_dir(ce);
            break;

		  case FTP_PRINT_DIR:
            ce->status = net_ftp_print_dir(ce);
            break;

          case FTP_DONE:
			if(cd->stream)
            	COMPLETE_STREAM;

            /* Make sure that any previous cache entry is not locked anymore */
            NET_ChangeCacheFileLock(ce->URL_s, FALSE);

    	    /* don't close the control sock since we need 
			 * it for connection caching
			 */
            if(cd->dsock != NULL)
			  {
			    NET_ClearReadSelect(ce->window_id, cd->dsock);
				TRACEMSG(("Closing and clearing sock cd->dsock: %d", cd->dsock));
				PR_Close(cd->dsock);
				cd->dsock = NULL;
			  }

            if(cd->listen_sock != NULL)
              {
				TRACEMSG(("Closing and clearing sock cd->listen_sock: %d", cd->listen_sock));
			    NET_ClearReadSelect(ce->window_id, cd->listen_sock);
				PR_Close(cd->listen_sock);
				/* not counted as a connection */
			  }
				
            cd->next_state = FTP_FREE_DATA;
    
            cd->cc->busy = FALSE;  /* we are no longer using the connection */
			NET_ClearReadSelect(ce->window_id, cd->cc->csock);

			/* we can't reuse VMS control connections since they are screwed up
		   	 */
			if(cd->cc->server_type == FTP_VMS_TYPE)
			  {
	            /* remove the connection from the cache list
                 * and free the data
                 */
                XP_ListRemoveObject(connection_list, cd->cc);
                FREEIF(cd->cc->hostname);
                if(cd->cc->csock != NULL)
                  {
				    TRACEMSG(("Closing and clearing sock cd->cc->csock: %d", cd->cc->csock));
                    PR_Close(cd->cc->csock);
					cd->cc->csock = NULL;
                  }
                XP_FREE(cd->cc);
                cd->cc = 0;
			  }

			/* remove the cached connection from the total number
			 * counted, since it is no longer active
			 */
			NET_TotalNumberOfOpenConnections--;

            break;
    
          case FTP_ERROR_DONE:
            if(cd->sort_base)
              {
                 /* we have to do this to get it free'd
                  */
		 if(cd->stream && (*cd->stream->is_write_ready)(cd->stream))
                 	NET_PrintDirectory(&cd->sort_base, cd->stream, cd->path, ce->URL_s);
              }

            if(cd->stream && cd->stream->data_object)
            {
                ABORT_STREAM(ce->status);
                /* (ABORT_STREAM frees cd->stream->data_object:) */
                cd->stream->data_object = NULL;
            }

            /* lock the object in cache if we can restart it */
            /* This way even if it's huge, we can restart. */
            TRACEMSG(("FTP aborting!  content_length = %ld, real_content_length = %ld\n", 
                      ce->URL_s->content_length, ce->URL_s->real_content_length));
            TRACEMSG(("  bytes_received = %ld\n", ce->bytes_received));
            if(ce->URL_s->server_can_do_restart)
                NET_ChangeCacheFileLock(ce->URL_s, TRUE);

            if(cd->dsock != NULL)
              {
			    NET_ClearConnectSelect(ce->window_id, cd->dsock);
			    NET_ClearReadSelect(ce->window_id, cd->dsock);
                NET_ClearDNSSelect(ce->window_id, cd->dsock);

#ifdef XP_WIN
				if(cd->calling_netlib_all_the_time)
				{
					cd->calling_netlib_all_the_time = FALSE;
					NET_ClearCallNetlibAllTheTime(ce->window_id, "mkftp");
				}
#endif /* XP_WIN */

				TRACEMSG(("Closing and clearing sock cd->dsock: %d", cd->dsock));
                PR_Close(cd->dsock);
                cd->dsock = NULL;
              }

			if(cd->file_to_post_fp)
			  {
				XP_FileClose(cd->file_to_post_fp);
				cd->file_to_post_fp = 0;
			  }
    
			if(cd->cc->csock != NULL)
			  {
			    NET_ClearConnectSelect(ce->window_id, cd->cc->csock);
			    NET_ClearReadSelect(ce->window_id, cd->cc->csock);
                NET_ClearDNSSelect(ce->window_id, cd->cc->csock);
				TRACEMSG(("Closing and clearing sock cd->cc->csock: %d", cd->cc->csock));
                PR_Close(cd->cc->csock);
				cd->cc->csock = NULL;
				NET_TotalNumberOfOpenConnections--;
			  }

            if(cd->listen_sock != NULL)
              {
				TRACEMSG(("Closing and clearing sock cd->listen_sock: %d", cd->listen_sock));
				NET_ClearReadSelect(ce->window_id, cd->listen_sock);
				PR_Close(cd->listen_sock);
				/* not counted as a connection */
			  }


            /* check if this connection came from the cache or if it was
             * a new connection.  If it was new lets start it over again
             */
            if(cd->cc->prev_cache 
				&& ce->status != MK_INTERRUPTED
				  && ce->status != MK_UNABLE_TO_CONVERT
					&& ce->status != MK_OUT_OF_MEMORY
					&& ce->status != MK_DISK_FULL)
              {
                cd->cc->prev_cache = NO;
                cd->next_state = FTP_CONTROL_CONNECT;
				cd->pasv_mode = TRUE;  /* until we learn otherwise */
            	cd->pause_for_read = FALSE;
                ce->status = 0;  /* keep going */
            	cd->cc->cur_mode = FTP_MODE_UNKNOWN;
              }
            else
              {
                cd->next_state = FTP_FREE_DATA;
        
                /* remove the connection from the cache list
                 * and free the data
                 */
                XP_ListRemoveObject(connection_list, cd->cc);
                FREEIF(cd->cc->hostname);
                XP_FREE(cd->cc);
              }
    
            break;
    
          case FTP_FREE_DATA:
				if(cd->calling_netlib_all_the_time)
				  {
					NET_ClearCallNetlibAllTheTime(ce->window_id, "mkftp");
                    cd->calling_netlib_all_the_time = FALSE;
				  }

                if(cd->destroy_graph_progress)
                    FE_GraphProgressDestroy(ce->window_id, 	
											ce->URL_s, 	
											cd->original_content_length,
											ce->bytes_received);
#ifdef EDITOR
                if(cd->destroy_file_upload_progress_dialog) {
                    /* Convert from netlib errors to editor errors. */
                    ED_FileError error = ED_ERROR_NONE;
                    if ( ce->status < 0 )
                        error = ED_ERROR_PUBLISHING;
                    FE_SaveDialogDestroy(ce->window_id, error, ce->URL_s->post_data);
                    /*  FE_SaveDialogDestroy(ce->window_id, ce->status, cd->file_to_post); */
                }
#endif /* EDITOR */
                FREEIF(cd->cwd_message_buffer);
                FREEIF(cd->login_message_buffer);
                FREEIF(cd->data_buf)
                FREEIF(cd->return_msg)
                FREEIF(cd->data_con_address)
                FREEIF(cd->filename)
                FREEIF(cd->path)
                FREEIF(cd->username)
                FREEIF(cd->password)
                FREEIF(cd->stream);  /* don't forget the stream */
				FREEIF(cd->output_buffer);
            	if(cd->tcp_con_data)
                	NET_FreeTCPConData(cd->tcp_con_data);
				

                XP_FREE(cd);

                /* gate the maximum number of cached connections
			 	 * to one
                 */
                if(XP_ListCount(connection_list) > 1)
                  {
                    XP_List * list_ptr = connection_list->next;
                    FTPConnection * con;
        
                    while(list_ptr)
                      {
						con = (FTPConnection *) list_ptr->object;
						list_ptr = list_ptr->next;
                        if(!con->busy)
                          {
                            XP_ListRemoveObject(connection_list, con);

							if(con->csock != NULL)
							  {
                			    PR_Close(con->csock);
								/* dont reduce the number of
								 * open connections since cached ones
							 	 * dont count as active
								 */
							  }
                            FREEIF(con->hostname);
                            XP_FREE(con);
                            break;
                          }
                      }
                  }

                return(-1);
    
              default:
                TRACEMSG(("Bad state in ProcessFTP\n"));
                return(-1);
         } /* end switch */
    
       if(ce->status < 0 && cd->next_state != FTP_FREE_DATA)
          {
            cd->pause_for_read = FALSE;
            cd->next_state = FTP_ERROR_DONE;
          }
    } /* end while */

    return(ce->status);
}
    
/* abort the connection in progress
 */
PRIVATE int32
net_InterruptFTP(ActiveEntry * ce)
{
    FTPConData * cd = (FTPConData *)ce->con_data;

    cd->next_state = FTP_ERROR_DONE;
    ce->status = MK_INTERRUPTED;
	cd->cc->prev_cache = FALSE;  /* to keep it from reconnecting */
 
    return(net_ProcessFTP(ce));
}

/* Free any connections or memory that are cached
 */
PRIVATE void
net_CleanupFTP(void)
{
    if(connection_list)
      {
        FTPConnection * tmp_con;

        while((tmp_con = (FTPConnection *)XP_ListRemoveTopObject(connection_list)) != NULL)
          {
			if(!tmp_con->busy)
			  {
            	FREE(tmp_con->hostname);       /* hostname string */
				if(tmp_con->csock != NULL)
			      {
            	    PR_Close(tmp_con->csock);      /* control socket */
            	    tmp_con->csock = NULL;
					/* don't count cached connections as open ones
					 */
			      }
		        FREE(tmp_con);
              }
		  }
      }

    return;
}


MODULE_PRIVATE void
NET_InitFTPProtocol(void)
{
	static NET_ProtoImpl ftp_proto_impl;

	ftp_proto_impl.init = net_FTPLoad;
	ftp_proto_impl.process = net_ProcessFTP;
	ftp_proto_impl.interrupt = net_InterruptFTP;
	ftp_proto_impl.cleanup = net_CleanupFTP;

	NET_RegisterProtocolImplementation(&ftp_proto_impl, FTP_TYPE_URL);
}
