diff options
author | Thomas E. Dickey <dickey@invisible-island.net> | 1996-09-02 19:39:24 -0400 |
---|---|---|
committer | Thomas E. Dickey <dickey@invisible-island.net> | 1996-09-02 19:39:24 -0400 |
commit | e087f6d44e87f489fcb3056e86319ebba4218156 (patch) | |
tree | d045b58011bfbbf5186d34c4fed9e0dedb363275 /WWW/Library/Implementation/HTFTP.c | |
download | lynx-snapshots-e087f6d44e87f489fcb3056e86319ebba4218156.tar.gz |
snapshot of project "lynx", label v2_6
Diffstat (limited to 'WWW/Library/Implementation/HTFTP.c')
-rw-r--r-- | WWW/Library/Implementation/HTFTP.c | 3214 |
1 files changed, 3214 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c new file mode 100644 index 00000000..9d19610f --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.c @@ -0,0 +1,3214 @@ +/* File Transfer Protocol (FTP) Client +** for a WorldWideWeb browser +** =================================== +** +** A cache of control connections is kept. +** +** Note: Port allocation +** +** It is essential that the port is allocated by the system, rather +** than chosen in rotation by us (POLL_PORTS), or the following +** problem occurs. +** +** It seems that an attempt by the server to connect to a port which has +** been used recently by a listen on the same socket, or by another +** socket this or another process causes a hangup of (almost exactly) +** one minute. Therefore, we have to use a rotating port number. +** The problem remains that if the application is run twice in quick +** succession, it will hang for what remains of a minute. +** +** Authors +** TBL Tim Berners-lee <timbl@info.cern.ch> +** DD Denis DeLaRoca 310 825-4580 <CSP1DWD@mvs.oac.ucla.edu> +** LM Lou Montulli <montulli@ukanaix.cc.ukans.edu> +** FM Foteos Macrides <macrides@sci.wfeb.edu> +** History: +** 2 May 91 Written TBL, as a part of the WorldWideWeb project. +** 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc +** 10 Feb 92 Retry if cached connection times out or breaks +** 8 Dec 92 Bug fix 921208 TBL after DD +** 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD +** fails on princeton.edu! +** 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path +** must be Unix-style and cannot include the device +** or top directory. +** ?? ??? ?? (LM) Added code to prompt and send passwords for non +** anonymous FTP +** 25 Mar 94 (LM) Added code to recognize different ftp server types +** and code to parse dates and sizes on most hosts. +** 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts. +** +** Options: +** LISTEN We listen, the other guy connects for data. +** Otherwise, other way round, but problem finding our +** internet address! +** +** Notes: +** Portions Copyright 1994 Trustees of Dartmouth College +** Code for recognizing different FTP servers and +** parsing "ls -l" output taken from Macintosh Fetch +** program with permission from Jim Matthews, +** Dartmouth Software Development Team. +*/ + +/* +** If LISTEN is not defined, PASV is used instead of PORT, and not +** all FTP servers support PASV, so define it unless there is no +** alternative for your system. +*/ +#ifndef NOPORT +#define LISTEN /* @@@@ Test LJM */ +#endif /* !NOPORT */ + +/* +BUGS: @@@ Limit connection cache size! + Error reporting to user. + 400 & 500 errors are acked by user with windows. + Use configuration file for user names + +** Note for portablility this version does not use select() and +** so does not watch the control and data channels at the +** same time. +*/ + +#include "HTUtils.h" +#include "tcp.h" + +#include "HTAlert.h" + +#include "HTFTP.h" /* Implemented here */ + +/* this define should be in HTFont.h :( */ +#define HT_NON_BREAK_SPACE ((char)1) /* For now */ + +#define REPEAT_PORT /* Give the port number for each file */ +#define REPEAT_LISTEN /* Close each listen socket and open a new one */ + +/* define POLL_PORTS If allocation does not work, poll ourselves.*/ +#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP)*/ + +#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */ +#define LAST_TCP_PORT 5999 + +#define LINE_LENGTH 256 +#define COMMAND_LENGTH 256 + +#define INFINITY 512 + +#include "HTParse.h" +#include "HTTCP.h" +#include "HTAnchor.h" +#include "HTFile.h" /* For HTFileFormat() */ +#include "HTBTree.h" +#include "HTChunk.h" +#include "HTAlert.h" +#ifndef IPPORT_FTP +#define IPPORT_FTP 21 +#endif /* !IPORT_FTP */ + +#include "LYLeaks.h" + +#ifdef REMOVED_CODE +extern char *malloc(); +extern void free(); +extern char *strncpy(); +#endif /* REMOVED_CODE */ + +typedef struct _connection { + struct _connection * next; /* Link on list */ + u_long addr; /* IP address */ + int socket; /* Socket number for communication */ + BOOL binary; /* Binary mode? */ +} connection; + +#ifndef NIL +#define NIL 0 +#endif /* !NIL */ + +/* Hypertext object building machinery +*/ +#include "HTML.h" + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) +#define ABORT_TARGET (*targetClass._free)(target) +struct _HTStructured { + CONST HTStructuredClass * isa; + /* ... */ +}; + +#define FREE(x) if (x) {free(x); x = NULL;} + +extern int HTCheckForInterrupt NOPARAMS; + + +/* Global Variables +** --------------------- +*/ +PUBLIC BOOLEAN HTfileSortMethod = FILE_BY_NAME; +PRIVATE char ThisYear[8]; +PRIVATE char LastYear[8]; +PRIVATE int TheDate; +PRIVATE BOOLEAN HaveYears = FALSE; +#ifdef SOCKS +extern BOOLEAN socks_flag; +extern unsigned long socks_bind_remoteAddr; +#endif /* SOCKS */ +extern char *personal_mail_address; + +/* Module-Wide Variables +** --------------------- +*/ +PRIVATE connection * connections = 0; /* Linked list of connections */ +PRIVATE char response_text[LINE_LENGTH+1];/* Last response from NewsHost */ +PRIVATE connection * control; /* Current connection */ +PRIVATE int data_soc = -1; /* Socket for data transfer =invalid */ + +#define GENERIC_SERVER 0 +#define MACHTEN_SERVER 1 +#define UNIX_SERVER 2 +#define VMS_SERVER 3 +#define CMS_SERVER 4 +#define DCTS_SERVER 5 +#define TCPC_SERVER 6 +#define PETER_LEWIS_SERVER 7 +#define NCSA_SERVER 8 +#define WINDOWS_NT_SERVER 9 +#define MS_WINDOWS_SERVER 10 +#define MSDOS_SERVER 11 + +PRIVATE int server_type = GENERIC_SERVER; /* the type of ftp host */ +PRIVATE int unsure_type = FALSE; /* sure about the type? */ +PRIVATE BOOLEAN use_list = FALSE; /* use the LIST command? */ + +PRIVATE interrupted_in_next_data_char = FALSE; + +#ifdef POLL_PORTS +PRIVATE unsigned short port_number = FIRST_TCP_PORT; +#endif /* POLL_PORTS */ + +#ifdef LISTEN +PRIVATE int master_socket = -1; /* Listening socket = invalid */ +PRIVATE char port_command[255]; /* Command for setting the port */ +PRIVATE fd_set open_sockets; /* Mask of active channels */ +PRIVATE int num_sockets; /* Number of sockets to scan */ +#else +PRIVATE unsigned short passive_port; /* Port server specified for data */ +#endif /* LISTEN */ + + +#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */ + +#define DATA_BUFFER_SIZE 2048 +PRIVATE char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */ +PRIVATE char * data_read_pointer; +PRIVATE char * data_write_pointer; +#define NEXT_DATA_CHAR next_data_char() + + +/* PUBLIC HTMake_VMS_name() +** CONVERTS WWW name into a VMS name +** ON ENTRY: +** nn Node Name (optional) +** fn WWW file name +** +** ON EXIT: +** returns vms file specification +** +** Bug: Returns pointer to static -- non-reentrant +*/ +PUBLIC char * HTMake_VMS_name ARGS2( + CONST char *, nn, + CONST char *, fn) +{ + +/* We try converting the filename into Files-11 syntax. That is, we assume +** first that the file is, like us, on a VMS node. We try remote +** (or local) DECnet access. Files-11, VMS, VAX and DECnet +** are trademarks of Digital Equipment Corporation. +** The node is assumed to be local if the hostname WITHOUT DOMAIN +** matches the local one. @@@ +*/ + static char vmsname[INFINITY]; /* returned */ + char * filename = (char*)malloc(strlen(fn)+1); + char * nodename = (char*)malloc(strlen(nn)+2+1); /* Copies to hack */ + char *second; /* 2nd slash */ + char *last; /* last slash */ + + char * hostname = (char *)HTHostName(); + + if (!filename || !nodename) + outofmem(__FILE__, "HTVMSname"); + strcpy(filename, fn); + strcpy(nodename, ""); /* On same node? Yes if node names match */ + if (strncmp(nn, "localhost", 9)) { + char *p, *q; + for (p = hostname, q = (char *)nn; + *p && *p != '.' && *q && *q != '.'; p++, q++){ + if (TOUPPER(*p) != TOUPPER(*q)) { + strcpy(nodename, nn); + q = strchr(nodename, '.'); /* Mismatch */ + if (q) + *q = '\0'; /* Chop domain */ + strcat(nodename, "::"); /* Try decnet anyway */ + break; + } + } + } + + second = strchr(filename+1, '/'); /* 2nd slash */ + last = strrchr(filename, '/'); /* last slash */ + + if (!second) { /* Only one slash */ + sprintf(vmsname, "%s%s", nodename, filename + 1); + } else if (second == last) { /* Exactly two slashes */ + *second = '\0'; /* Split filename from disk */ + sprintf(vmsname, "%s%s:%s", nodename, filename+1, second+1); + *second = '/'; /* restore */ + } else { /* More than two slashes */ + char * p; + *second = '\0'; /* Split disk from directories */ + *last = '\0'; /* Split dir from filename */ + sprintf(vmsname, "%s%s:[%s]%s", + nodename, filename+1, second+1, last+1); + *second = *last = '/'; /* restore filename */ + for (p = strchr(vmsname, '['); *p!=']'; p++) + if (*p == '/') + *p = '.'; /* Convert dir sep. to dots */ + } + FREE(nodename); + FREE(filename); + return vmsname; +} + +/* Procedure: Read a character from the data connection +** ---------------------------------------------------- +*/ +PRIVATE char next_data_char NOARGS +{ + int status; + if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) + interrupted_in_next_data_char = 1; + if (status <= 0) + return (char)-1; + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + } +#ifdef NOT_ASCII + { + char c = *data_read_pointer++; + return FROMASCII(c); + } +#else + return *data_read_pointer++; +#endif /* NOT_ASCII */ +} + + +/* Close an individual connection +** +*/ +PRIVATE int close_connection ARGS1( + connection *, con) +{ + connection * scan; + int status = NETCLOSE(con->socket); + if (TRACE) + fprintf(stderr, "HTFTP: Closing control socket %d\n", con->socket); + con->socket = -1; + if (connections == con) { + connections = con->next; + return status; + } + for (scan = connections; scan; scan = scan->next) { + if (scan->next == con) { + scan->next = con->next; /* Unlink */ + if (control == con) + control = (connection*)0; + return status; + } /*if */ + } /* for */ + return -1; /* very strange -- was not on list. */ +} + +PRIVATE char *help_message_buffer = NULL; /* global :( */ + +PRIVATE void init_help_message_cache NOARGS +{ + FREE(help_message_buffer); +} + +PRIVATE void help_message_cache_add ARGS1( + char *, string) +{ + if (help_message_buffer) + StrAllocCat(help_message_buffer, string); + else + StrAllocCopy(help_message_buffer, string); + + if (TRACE) + fprintf(stderr,"Adding message to help cache: %s\n",string); +} + +PRIVATE char *help_message_cache_non_empty NOARGS +{ + return(help_message_buffer); +} +PRIVATE char *help_message_cache_contents NOARGS +{ + return(help_message_buffer); +} + +/* Execute Command and get Response +** -------------------------------- +** +** See the state machine illustrated in RFC959, p57. This implements +** one command/reply sequence. It also interprets lines which are to +** be continued, which are marked with a "-" immediately after the +** status code. +** +** Continuation then goes on until a line with a matching reply code +** an a space after it. +** +** On entry, +** con points to the connection which is established. +** cmd points to a command, or is NIL to just get the response. +** +** The command is terminated with the CRLF pair. +** +** On exit, +** returns: The first digit of the reply type, +** or negative for communication failure. +*/ +PRIVATE int response ARGS1( + char *, cmd) +{ + int result; /* Three-digit decimal code */ + int continuation_response = -1; + int status; + extern int interrupted_in_htgetcharacter; + + if (!control) { + if (TRACE) + fprintf(stderr, "HTFTP: No control connection set up!!\n"); + return -99; + } + + if (cmd) { + if (TRACE) + fprintf(stderr, " Tx: %s", cmd); +#ifdef NOT_ASCII + { + char * p; + for (p = cmd; *p; p++) { + *p = TOASCII(*p); + } + } +#endif /* NOT_ASCII */ + status = NETWRITE(control->socket, cmd, (int)strlen(cmd)); + if (status < 0) { + if (TRACE) + fprintf(stderr, + "HTFTP: Error %d sending command: closing socket %d\n", + status, control->socket); + close_connection(control); + return status; + } + } + + do { + char *p = response_text; + for (;;) { + if (((*p++ = NEXT_CHAR) == LF) + || (p == &response_text[LINE_LENGTH])) { + + char continuation; + + if (interrupted_in_htgetcharacter) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n"); + NETCLOSE (control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + *p = '\0'; /* Terminate the string */ + if (TRACE) + fprintf(stderr, " Rx: %s", response_text); + + /* Check for login or help messages */ + if (!strncmp(response_text,"230-",4) || + !strncmp(response_text,"250-",4) || + !strncmp(response_text,"220-",4)) + help_message_cache_add(response_text+4); + + sscanf(response_text, "%d%c", &result, &continuation); + if (continuation_response == -1) { + if (continuation == '-') /* start continuation */ + continuation_response = result; + } else { /* continuing */ + if (continuation_response == result && + continuation == ' ') + continuation_response = -1; /* ended */ + } + break; + } /* if end of line */ + + if (interrupted_in_htgetcharacter) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n"); + NETCLOSE (control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + if (*(p-1) == (char) EOF) { + if (TRACE) + fprintf(stderr, "Error on rx: closing socket %d\n", + control->socket); + strcpy(response_text, "000 *** TCP read error on response\n"); + close_connection(control); + return -1; /* End of file on response */ + } + } /* Loop over characters */ + + } while (continuation_response != -1); + + if (result == 421) { + if (TRACE) + fprintf(stderr, "HTFTP: They close so we close socket %d\n", + control->socket); + close_connection(control); + return -1; + } + if ((result == 255 && server_type == CMS_SERVER) && + (0 == strncasecomp(cmd, "CWD", 3) || + 0 == strcasecomp(cmd, "CDUP"))) { + /* + ** Alas, CMS returns 255 on failure to CWD to parent of root. - PG + */ + result = 555; + } + return result/100; +} + +/* this function should try to set the macintosh server into binary mode + */ +PRIVATE int set_mac_binary NOARGS +{ + /* try to set mac binary mode */ + return(2 == response("MACB\r\n")); +} + +/* This function gets the current working directory to help + * determine what kind of host it is + */ + +PRIVATE void get_ftp_pwd ARGS2( + int *, server_type, + BOOLEAN *, use_list) +{ + + char *cp; + /* get the working directory (to see what it looks like) */ + int status = response("PWD\r\n"); + if (status < 0) { + return; + } else { + cp = strchr(response_text+5,'"'); + if (cp) + *cp = '\0'; + if (*server_type == TCPC_SERVER) { + *server_type = ((response_text[5] == '/') ? + NCSA_SERVER : TCPC_SERVER); + if (TRACE) + fprintf(stderr, "HTFTP: Treating as %s server.\n", + ((*server_type == NCSA_SERVER) ? + "NCSA" : "TCPC")); + } else if (response_text[5] == '/') { + /* path names beginning with / imply Unix, + * right? + */ + if (set_mac_binary()) { + *server_type = NCSA_SERVER; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as NCSA server.\n"); + } else { + *server_type = UNIX_SERVER; + *use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as Unix server.\n"); + } + return; + } else if (response_text[strlen(response_text)-1] == ']') { + /* path names ending with ] imply VMS, right? */ + *server_type = VMS_SERVER; + *use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as VMS server.\n"); + } else { + *server_type = GENERIC_SERVER; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as Generic server.\n"); + } + + if ((*server_type == NCSA_SERVER) || + (*server_type == TCPC_SERVER) || + (*server_type == PETER_LEWIS_SERVER)) + set_mac_binary(); + } +} + +/* Get a valid connection to the host +** ---------------------------------- +** +** On entry, +** arg points to the name of the host in a hypertext address +** On exit, +** returns <0 if error +** socket number if success +** +** This routine takes care of managing timed-out connections, and +** limiting the number of connections in use at any one time. +** +** It ensures that all connections are logged in if they exist. +** It ensures they have the port number transferred. +*/ +PRIVATE int get_connection ARGS1( + CONST char *, arg) +{ + int status; + char * command; + connection * con; + char * username=NULL; + char * password=NULL; + static char *user_entered_password=NULL; + static char *last_username_and_host=NULL; + + /* + ** Allocate and init control struct. + */ + con = (connection *)calloc(1, sizeof(connection)); + + if (!arg) return -1; /* Bad if no name sepcified */ + if (!*arg) return -1; /* Bad if name had zero length */ + +/* Get node name: +*/ + { + char *p1 = HTParse(arg, "", PARSE_HOST); + char *p2 = strrchr(p1, '@'); /* user? */ + char * pw = NULL; + + if (p2 != NULL) { + username = p1; + *p2 = '\0'; /* terminate */ + p1 = p2+1; /* point to host */ + pw = strchr(username, ':'); + if (pw != NULL) { + *pw++ = '\0'; + password = HTUnEscape(pw); + } + if (*username) + HTUnEscape(username); + + /* if the password doesn't exist then we are going to have + * to ask the user for it. The only problem is that we + * don't want to ask for it every time, so we will store + * away in a primitive fashion. + */ + if (!password) { + char tmp[256]; + + sprintf(tmp, "%s@%s", username, p1); + /* if the user@host is not equal to the last time through + * or user_entered_password has no data then we need + * to ask the user for the password + */ + if (!last_username_and_host || + strcmp(tmp, last_username_and_host) || + !user_entered_password) { + + StrAllocCopy(last_username_and_host, tmp); + sprintf(tmp, "Enter password for user %s@%s:", + username, p1); + FREE(user_entered_password); + user_entered_password = (char *)HTPromptPassword(tmp); + + } /* else we already know the password */ + password = user_entered_password; + } + } + + if (!username) + FREE(p1); + } /* scope of p1 */ + + + con->socket = -1; + status = HTDoConnect (arg, "FTP", IPPORT_FTP, (int *)&con->socket); + + if (status < 0) + { + if (TRACE) + { + if (status == HT_INTERRUPTED) + fprintf (stderr, + "HTFTP: Interrupted on connect\n"); + else + fprintf(stderr, + "HTFTP: Unable to connect to remote host for `%s'.\n", + arg); + } + if (status == HT_INTERRUPTED) + _HTProgress ("Connection interrupted."); + else + HTAlert("Unable to connect to FTP host."); + if (con->socket != -1) + { + NETCLOSE(con->socket); + } + + FREE(username); + FREE(con); + return status; /* Bad return */ + } + + if (TRACE) + fprintf(stderr, "FTP connected, socket %ld\n", (long)con); + control = con; /* Current control connection */ + + /* Initialise buffering for control connection */ + HTInitInput(control->socket); + init_help_message_cache(); /* Clear the login message buffer. */ + + +/* Now we log in Look up username, prompt for pw. +*/ + { + int status = response((char *)0); /* Get greeting */ + + if (status == HT_INTERRUPTED) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted at beginning of login.\n"); + _HTProgress ("Connection interrupted."); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + if (status == 2) { /* Send username */ + if (username && *username) { + command = (char*)malloc(10+strlen(username)+2+1); + if (command == NULL) + outofmem(__FILE__, "get_connection"); + sprintf(command, "USER %s%c%c", username, CR, LF); + } else { + command = (char*)malloc(24); + if (command == NULL) + outofmem(__FILE__, "get_connection"); + sprintf(command, "USER anonymous%c%c", CR, LF); + } + status = response(command); + FREE(command); + if (status == HT_INTERRUPTED) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted while sending username.\n"); + _HTProgress ("Connection interrupted."); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + } + if (status == 3) { /* Send password */ + if (password) { + /* + * We have non-zero length password, so send it. - FM + */ + command = (char*)malloc(10+strlen(password)+2+1); + if (command == NULL) + outofmem(__FILE__, "get_connection"); + sprintf(command, "PASS %s%c%c", password, CR, LF); + } else { + /* + * Create and send a mail address as the password. - FM + */ + char *user = NULL; + char *host = NULL; + char * cp; + + if (personal_mail_address && *personal_mail_address) { + /* + * We have a non-zero length personal + * mail address, so use that. - FM + */ + StrAllocCopy(user, personal_mail_address); + if ((cp=strchr(user, '@')) != NULL) { + *cp++ = '\0'; + host = cp; + } else { + host = (char *)HTHostName(); + } + } else { + /* + * Use an environment variable and the host global. - FM + */ + if ((cp=getenv("USER")) != NULL) + StrAllocCopy(user, cp); + else + StrAllocCopy(user, "WWWuser"); + host = (char *)HTHostName(); + } + + /* + * If host is not fully qualified, suppress it + * as ftp.uu.net prefers a blank to a bad name + */ + if (!(host) || strchr(host, '.') == NULL) + host = ""; + + command = (char*)malloc(10+strlen(user)+1+strlen(host)+2+1); + if (command == NULL) + outofmem(__FILE__, "get_connection"); + sprintf(command, "PASS %s@%s%c%c", user, host, CR, LF); + FREE(user); + } + status = response(command); + FREE(command); + if (status == HT_INTERRUPTED) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted while sending password.\n"); + _HTProgress ("Connection interrupted."); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + } + FREE(username); + + if (status == 3) { + char temp[80]; + sprintf(temp, "ACCT noaccount%c%c", CR, LF); + status = response(temp); + if (status == HT_INTERRUPTED) + { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted while sending password.\n"); + _HTProgress ("Connection interrupted."); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + } + if (status != 2) { + if (TRACE) + fprintf(stderr, "HTFTP: Login fail: %s", response_text); + /* if (control->socket > 0) close_connection(control->socket); */ + return -1; /* Bad return */ + } + if (TRACE) fprintf(stderr, "HTFTP: Logged in.\n"); + + /** Check for host type **/ + server_type = GENERIC_SERVER; /* reset */ + use_list = FALSE; /* reset */ + if ((status=response("SYST\r\n")) == 2) { + /* we got a line -- what kind of server are we talking to? */ + if (strncmp(response_text+4, + "UNIX Type: L8 MAC-OS MachTen", 28) == 0) { + server_type = MACHTEN_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as MachTen server.\n"); + + } else if (strstr(response_text+4, "UNIX") != NULL || + strstr(response_text+4, "Unix") != NULL) { + server_type = UNIX_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as Unix server.\n"); + + } else if (strstr(response_text+4, "MSDOS") != NULL) { + server_type = MSDOS_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, + "HTFTP: Treating as MSDOS (Unix emulation) server.\n"); + + } else if (strncmp(response_text+4, "VMS", 3) == 0) { + server_type = VMS_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as VMS server.\n"); + + } else if ((strncmp(response_text+4, "VM/CMS", 6) == 0) || + (strncmp(response_text+4, "VM ", 3) == 0)) { + server_type = CMS_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as CMS server.\n"); + + } else if (strncmp(response_text+4, "DCTS", 4) == 0) { + server_type = DCTS_SERVER; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as DCTS server.\n"); + + } else if (strstr(response_text+4, "MAC-OS TCP/Connect II") != NULL) { + server_type = TCPC_SERVER; + if (TRACE) + fprintf(stderr, "HTFTP: Looks like a TCPC server.\n"); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + + } else if (strncmp(response_text+4, "MACOS Peter's Server", 20) == 0) { + server_type = PETER_LEWIS_SERVER; + use_list = TRUE; + set_mac_binary(); + if (TRACE) + fprintf(stderr, + "HTFTP: Treating as Peter Lewis (MACOS) server.\n"); + + } else if (strncmp(response_text+4, "Windows_NT", 10) == 0) { + server_type = WINDOWS_NT_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as Window_NT server.\n"); + + } else if (strncmp(response_text+4, "MS Windows", 10) == 0) { + server_type = MS_WINDOWS_SERVER; + use_list = TRUE; + if (TRACE) + fprintf(stderr, "HTFTP: Treating as MS Windows server.\n"); + + } else { + server_type = GENERIC_SERVER; + if (TRACE) + fprintf(stderr, "HTFTP: Ugh! A Generic server.\n"); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + } + } else { + /* SYST fails :( try to get the type from the PWD command */ + get_ftp_pwd(&server_type, &use_list); + } + +/* Now we inform the server of the port number we will listen on +*/ +#ifdef NOTREPEAT_PORT + { + int status = response(port_command); + if (status != 2) { + if (control->socket) + close_connection(control->socket); + return -status; /* Bad return */ + } + if (TRACE) + fprintf(stderr, "HTFTP: Port defined.\n"); + } +#endif /* NOTREPEAT_PORT */ + return con->socket; /* Good return */ + } /* Scope of con */ +} + + +#ifdef LISTEN + +/* Close Master (listening) socket +** ------------------------------- +** +** +*/ +PRIVATE int close_master_socket NOARGS +{ + int status; + FD_CLR(master_socket, &open_sockets); + status = NETCLOSE(master_socket); + if (TRACE) + fprintf(stderr, "HTFTP: Closed master socket %d\n", master_socket); + master_socket = -1; + if (status < 0) + return HTInetStatus("close master socket"); + else + return status; +} + + +/* Open a master socket for listening on +** ------------------------------------- +** +** When data is transferred, we open a port, and wait for the server to +** connect with the data. +** +** On entry, +** master_socket Must be negative if not set up already. +** On exit, +** Returns socket number if good +** less than zero if error. +** master_socket is socket number if good, else negative. +** port_number is valid if good. +*/ +PRIVATE int get_listen_socket NOARGS +{ + struct sockaddr_in soc_address; /* Binary network address */ + struct sockaddr_in* sin = &soc_address; + int new_socket; /* Will be master_socket */ + + + FD_ZERO(&open_sockets); /* Clear our record of open sockets */ + num_sockets = 0; + +#ifndef REPEAT_LISTEN + if (master_socket >= 0) + return master_socket; /* Done already */ +#endif /* !REPEAT_LISTEN */ + +/* Create internet socket +*/ + new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (new_socket < 0) + return HTInetStatus("socket for master socket"); + + if (TRACE) + fprintf(stderr, "HTFTP: Opened master socket number %d\n", new_socket); + +/* Search for a free port. +*/ + sin->sin_family = AF_INET; /* Family = internet, host order */ + sin->sin_addr.s_addr = INADDR_ANY; /* Any peer address */ +#ifdef POLL_PORTS + { + unsigned short old_port_number = port_number; + for (port_number = (old_port_number+1); ; port_number++) { + int status; + if (port_number > LAST_TCP_PORT) + port_number = FIRST_TCP_PORT; + if (port_number == old_port_number) { + return HTInetStatus("bind"); + } + soc_address.sin_port = htons(port_number); +#ifdef SOCKS + if (socks_flag) + if ((status=Rbind(new_socket, + (struct sockaddr*)&soc_address, + /* Cast to generic sockaddr */ + sizeof(soc_address) +#ifndef SHORTENED_RBIND + ,socks_bind_remoteAddr +#endif /* !SHORTENED_RBIND */ + )) == 0) + break; + else +#endif /* SOCKS */ + if ((status=bind(new_socket, + (struct sockaddr*)&soc_address, + /* Cast to generic sockaddr */ + sizeof(soc_address))) == 0) + break; + if (TRACE) + fprintf(stderr, + "TCP bind attempt to port %d yields %d, errno=%d\n", + port_number, status, SOCKET_ERRNO); + } /* for */ + } +#else + { + int status; + int address_length = sizeof(soc_address); +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(control->socket, + (struct sockaddr *)&soc_address, + (void *)&address_length); + else +#endif /* SOCKS */ + status = getsockname(control->socket, + (struct sockaddr *)&soc_address, + (void *)&address_length); + if (status<0) return HTInetStatus("getsockname"); + CTRACE(tfp, "HTFTP: This host is %s\n", + HTInetString(sin)); + + soc_address.sin_port = 0; /* Unspecified: please allocate */ +#ifdef SOCKS + if (socks_flag) + status=Rbind(new_socket, + (struct sockaddr*)&soc_address, + /* Cast to generic sockaddr */ + sizeof(soc_address) +#ifndef SHORTENED_RBIND + ,socks_bind_remoteAddr +#endif /* !SHORTENED_RBIND */ + ); + else +#endif /* SOCKS */ + status=bind(new_socket, + (struct sockaddr*)&soc_address, + /* Cast to generic sockaddr */ + sizeof(soc_address)); + if (status<0) return HTInetStatus("bind"); + + address_length = sizeof(soc_address); +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(new_socket, + (struct sockaddr*)&soc_address, + (void *)&address_length); + else +#endif /* SOCKS */ + status = getsockname(new_socket, + (struct sockaddr*)&soc_address, + (void *)&address_length); + if (status<0) return HTInetStatus("getsockname"); + } +#endif /* POLL_PORTS */ + + CTRACE(tfp, "HTFTP: bound to port %d on %s\n", + (int)ntohs(sin->sin_port), + HTInetString(sin)); + +#ifdef REPEAT_LISTEN + if (master_socket >= 0) + (void) close_master_socket(); +#endif /* REPEAD_LISTEN */ + + master_socket = new_socket; + +/* Now we must find out who we are to tell the other guy +*/ + (void)HTHostName(); /* Make address valid - doesn't work*/ + sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d%c%c", + (int)*((unsigned char *)(&sin->sin_addr)+0), + (int)*((unsigned char *)(&sin->sin_addr)+1), + (int)*((unsigned char *)(&sin->sin_addr)+2), + (int)*((unsigned char *)(&sin->sin_addr)+3), + (int)*((unsigned char *)(&sin->sin_port)+0), + (int)*((unsigned char *)(&sin->sin_port)+1), + CR, LF); + + +/* Inform TCP that we will accept connections +*/ + { + int status; +#ifdef SOCKS + if (socks_flag) + status = Rlisten(master_socket, 1); + else +#endif /* SOCKS */ + status = listen(master_socket, 1); + if (status < 0) { + master_socket = -1; + return HTInetStatus("listen"); + } + } + CTRACE(tfp, "TCP: Master socket(), bind() and listen() all OK\n"); + FD_SET(master_socket, &open_sockets); + if ((master_socket+1) > num_sockets) + num_sockets = master_socket+1; + + return master_socket; /* Good */ + +} /* get_listen_socket */ +#endif /* LISTEN */ + +PRIVATE char * months[12] = { + "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" +}; + +/* Procedure: Set the current and last year strings and date integer +** ----------------------------------------------------------------- +** +** Bug: +** This code is for sorting listings by date, if that option +** is selected in Lynx, and doesn't take into account time +** zones or ensure resetting at midnight, so the sort may not +** be perfect, but the actual date isn't changed in the display, +** i.e., the date is still correct. - FM +*/ +PRIVATE void set_years_and_date NOARGS +{ + char day[8], month[8], date[12]; + time_t NowTime; + int i; + + NowTime = time(NULL); + strncpy(day, (char *)ctime(&NowTime)+8, 2); + day[2] = '\0'; + if (day[0] == ' ') { + day[0] = '0'; + } + strncpy(month, (char *)ctime(&NowTime)+4, 3); + strncpy(month, (char *)ctime(&NowTime)+4, 3); + month[3] = '\0'; + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%s%d", (i < 10 ? "0" : ""), i); + sprintf(date, "9999%s%s", month, day); + TheDate = atoi(date); + strcpy(ThisYear, (char *)ctime(&NowTime)+20); + ThisYear[4] = '\0'; + sprintf(LastYear, "%d", (atoi(ThisYear) - 1)); + HaveYears = TRUE; +} + +typedef struct _EntryInfo { + char * filename; + char * type; + char * date; + unsigned int size; + BOOLEAN display; /* show this entry? */ +} EntryInfo; + +PRIVATE void free_entryinfo_struct_contents ARGS1( + EntryInfo *, entry_info) +{ + if (entry_info) { + FREE(entry_info->filename); + FREE(entry_info->type); + FREE(entry_info->date); + } + /* dont free the struct */ +} + +/* + * is_ls_date() -- + * Return TRUE if s points to a string of the form: + * "Sep 1 1990 " or + * "Sep 11 11:59 " or + * "Dec 12 1989 " or + * "FCv 23 1990 " ... + */ +PRIVATE BOOLEAN is_ls_date ARGS1( + char *, s) +{ + /* must start with three alpha characters */ + if (!isalpha(*s++) || !isalpha(*s++) || !isalpha(*s++)) + return FALSE; + + /* space or HT_NON_BREAK_SPACE */ + if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) { + s++; + return FALSE; + } + s++; + + /* space or digit */ + if (!(*s == ' ' || isdigit(*s))) { + s++; + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(*s++)) + return FALSE; + + /* space */ + if (*s++ != ' ') + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(*s))) { + s++; + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(*s++)) + return FALSE; + + /* colon or digit */ + if (!(*s == ':' || isdigit(*s))) { + s++; + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(*s++)) + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(*s))) { + s++; + return FALSE; + } + s++; + + /* space */ + if (*s++ != ' ') + return FALSE; + + return TRUE; +} /* is_ls_date() */ + +/* + * parse_eplf_line() -- + * Extract the name, size, and date from an EPLF line. - 08-06-96 DJB + */ +PRIVATE void parse_eplf_line ARGS2( + char *, line, + EntryInfo *, info) +{ + char *cp = line; + char ct[26]; + unsigned long size; + time_t secs; + static time_t base; /* time() value on this OS in 1970 */ + static int flagbase = 0; + + if (!flagbase) { + struct tm t; + t.tm_year = 70; t.tm_mon = 0; t.tm_mday = 0; + t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0; + t.tm_isdst = -1; + base = mktime(&t); /* could return -1 */ + flagbase = 1; + } + + while (*cp) { + switch(*cp) { + case '\t': + StrAllocCopy(info->filename, cp + 1); + return; + case 's': + size = 0; + while (*(++cp) && (*cp != ',')) + size = (size * 10) + (*cp - '0'); + info->size = size; + break; + case 'm': + secs = 0; + while (*(++cp) && (*cp != ',')) + secs = (secs * 10) + (*cp - '0'); + secs += base; /* assumes that time_t is #seconds */ + strcpy(ct, ctime(&secs)); + ct[24] = 0; + StrAllocCopy(info->date, ct); + break; + case '/': + StrAllocCopy(info->type, "Directory"); + default: + while (*cp) { + if (*cp++ == ',') + break; + } + break; + } + } +} /* parse_eplf_line */ + +/* + * parse_ls_line() -- + * Extract the name, size, and date from an ls -l line. + */ +PRIVATE void parse_ls_line ARGS2( + char *, line, + EntryInfo *, entry_info) +{ + short i, j; + int base=1; + int size_num=0; + + for (i = strlen(line) - 1; + (i > 13) && (!isspace(line[i]) || !is_ls_date(&line[i-12])); i--) + ; /* null body */ + line[i] = '\0'; + if (i > 13) { + StrAllocCopy(entry_info->date, &line[i-12]); + /* replace the 4th location with nbsp if it is a space or zero */ + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') + entry_info->date[4] = HT_NON_BREAK_SPACE; + /* make sure year or time is flush right */ + if (entry_info->date[11] == ' ') { + for (j = 11; j > 6; j--) { + entry_info->date[j] = entry_info->date[j-1]; + } + } + } + j = i - 14; + while (isdigit(line[j])) { + size_num += (line[j] - '0') * base; + base *= 10; + j--; + } + entry_info->size = size_num; + StrAllocCopy(entry_info->filename, &line[i + 1]); +} /* parse_ls_line() */ + +/* + * parse_vms_dir_entry() + * Format the name, date, and size from a VMS LIST line + * into the EntryInfo structure - FM + */ +PRIVATE void parse_vms_dir_entry ARGS2( + char *, line, + EntryInfo *, entry_info) +{ + int i, j, ialloc; + char *cp, *cpd, *cps, date[16], *sp = " "; + + /** Get rid of blank lines, and information lines. **/ + /** Valid lines have the ';' version number token. **/ + if (!strlen(line) || (cp=strchr(line, ';')) == NULL) { + entry_info->display = FALSE; + return; + } + + /** Cut out file or directory name at VMS version number. **/ + *cp++ ='\0'; + StrAllocCopy(entry_info->filename,line); + + /** Cast VMS non-README file and directory names to lowercase. **/ + if (strstr(entry_info->filename, "READ") == NULL) { + for (i = 0; entry_info->filename[i]; i++) + entry_info->filename[i] = TOLOWER(entry_info->filename[i]); + } else { + i = strlen(entry_info->filename); + } + + /** 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=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'; + + /* Set the years and date, if we don't have them yet. **/ + if (!HaveYears) { + set_years_and_date(); + } + + /** Track down the date. **/ + if ((cpd=strchr(cp, '-')) != NULL && + 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)); + sprintf(date, "%s ", cpd+1); + *(cpd+4) = '-'; + + /** Day **/ + *cpd = '\0'; + if (isdigit(*(cpd-2))) + sprintf(date+4, "%s ", cpd-2); + else + sprintf(date+4, "%c%s ", HT_NON_BREAK_SPACE, cpd-1); + *cpd = '-'; + + /** Time or Year **/ + if (!strncmp(ThisYear, cpd+5, 4) && + strlen(cpd) > 15 && *(cpd+12) == ':') { + *(cpd+15) = '\0'; + sprintf(date+7, "%s", cpd+10); + *(cpd+15) = ' '; + } else { + *(cpd+9) = '\0'; + sprintf(date+7, " %s", cpd+5); + *(cpd+9) = ' '; + } + + StrAllocCopy(entry_info->date, date); + } + + /** Track down the size **/ + if ((cpd=strchr(cp, '/')) != NULL) { + /* Appears be in used/allocated format */ + cps = cpd; + while (isdigit(*(cps-1))) + cps--; + if (cps < cpd) + *cpd = '\0'; + entry_info->size = atoi(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=strtok(cp, sp)) != NULL) { + /* We just initialized on the version number */ + /* Now let's hunt for a lone, size number */ + while ((cps=strtok(NULL, sp)) != NULL) { + cpd = cps; + while (isdigit(*cpd)) + cpd++; + if (*cpd == '\0') { + /* Assume it's blocks */ + entry_info->size = atoi(cps) * 512; + break; + } + } + } + + /** Wrap it up **/ + if (TRACE) + fprintf(stderr, "HTFTP: VMS filename: %s date: %s size: %d\n", + entry_info->filename, + entry_info->date ? entry_info->date : "", + entry_info->size); + return; +} /* parse_vms_dir_entry() */ + +/* + * parse_ms_windows_dir_entry() -- + * Format the name, date, and size from an MS_WINDOWS LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +PRIVATE void parse_ms_windows_dir_entry ARGS2( + char *, line, + EntryInfo *, entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + + /** Get rid of blank or junk lines. **/ + while (*cp && isspace(*cp)) + cp++; + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /** Cut out file or directory name. **/ + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ ='\0'; + cpd = cps; + StrAllocCopy(entry_info->filename, cp); + + /** Track down the size **/ + if (cps < end) { + while (*cps && isspace(*cps)) + cps++; + cpd = cps; + while (*cpd && !isspace(*cpd)) + cpd++; + *cpd++ = '\0'; + if (isdigit(*cps)) { + entry_info->size = atoi(cps); + } else { + StrAllocCopy(entry_info->type, "Directory"); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Set the years and date, if we don't have them yet. **/ + if (!HaveYears) { + set_years_and_date(); + } + + /** Track down the date. **/ + if (cpd < end) { + while (*cpd && isspace(*cpd)) + cpd++; + if (strlen(cpd) > 17) { + *(cpd+6) = '\0'; /* Month and Day */ + *(cpd+11) = '\0'; /* Year */ + *(cpd+17) = '\0'; /* Time */ + if (strcmp(ThisYear, cpd+7)) + /* Not this year, so show the year */ + sprintf(date, "%s %s", cpd, (cpd+7)); + else + /* Is this year, so show the time */ + sprintf(date, "%s %s", cpd, (cpd+12)); + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + /** Wrap it up **/ + if (TRACE) + fprintf(stderr, "HTFTP: MS Windows filename: %s date: %s size: %d\n", + entry_info->filename, + entry_info->date ? entry_info->date : "", + entry_info->size); + return; +} /* parse_ms_windows_dir_entry */ + +/* + * parse_windows_nt_dir_entry() -- + * Format the name, date, and size from a WINDOWS_NT LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +PRIVATE void parse_windows_nt_dir_entry ARGS2( + char *, line, + EntryInfo *, entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int i; + + /** Get rid of blank or junk lines. **/ + while (*cp && isspace(*cp)) + cp++; + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /** Cut out file or directory name. **/ + cpd = cp; + cps = (end-1); + while (cps >= cpd && !isspace(*cps)) + cps--; + cp = (cps+1); + if (!strcmp(cp, ".") || !strcmp(cp, "..")) { + entry_info->display = FALSE; + return; + } + StrAllocCopy(entry_info->filename, cp); + if (cps < cpd) + return; + *cp = '\0'; + end = cp; + + /* Set the years and date, if we don't have them yet. **/ + if (!HaveYears) { + set_years_and_date(); + } + + /** Cut out the date. **/ + cp = cps = cpd; + while (*cps && !isspace(*cps)) + cps++; + *cps++ ='\0'; + if (cps > end) { + entry_info->display = FALSE; + return; + } + while (*cps && isspace(*cps)) + cps++; + cpd = cps; + while (*cps && !isspace(*cps)) + cps++; + *cps++ ='\0'; + if (cps > end || cpd == cps || strlen(cpd) < 7) { + entry_info->display = FALSE; + return; + } + if (strlen(cp) == 8 && + isdigit(*cp) && isdigit(*(cp+1)) && *(cp+2) == '-' && + isdigit(*(cp+3)) && isdigit(*(cp+1)) && *(cp+5) == '-') { + *(cp+2) = '\0'; /* Month */ + i = atoi(cp) - 1; + *(cp+5) = '\0'; /* Day */ + sprintf(date, "%s %s", months[i], (cp+3)); + if (date[4] == '0') + date[4] = ' '; + cp += 6; /* Year */ + if (strcmp((ThisYear+2), cp)) { + /* Not this year, so show the year */ + if (atoi(cp) < atoi((char *)&ThisYear[2])) { + sprintf((char *)&date[6], " %c%c%s", + ThisYear[0], ThisYear[1], cp); + } else { + sprintf((char *)&date[6], " %c%c%s", + LastYear[0], LastYear[1], cp); + } + } else { + /* Is this year, so show the time */ + *(cpd+2) = '\0'; /* Hour */ + i = atoi(cpd); + if (*(cpd+5) == 'P' || *(cpd+5) == 'p') + i += 12; + *(cpd+5) = '\0'; + sprintf((char*)&date[6], " %s%d:%s", + (i < 10 ? "0" : ""), i, (cpd+3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + + /** Track down the size **/ + if (cps < end) { + while (*cps && isspace(*cps)) + cps++; + cpd = cps; + while (*cpd && !isspace(*cpd)) + cpd++; + *cpd = '\0'; + if (isdigit(*cps)) { + entry_info->size = atoi(cps); + } else { + StrAllocCopy(entry_info->type, "Directory"); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /** Wrap it up **/ + if (TRACE) + fprintf(stderr, "HTFTP: Windows NT filename: %s date: %s size: %d\n", + entry_info->filename, + entry_info->date ? entry_info->date : "", + entry_info->size); + return; +} /* parse_windows_nt_dir_entry */ + +/* + * parse_cms_dir_entry() -- + * Format the name, date, and size from a VM/CMS line into + * the EntryInfo structure. - FM + */ +PRIVATE void parse_cms_dir_entry ARGS2( + char *, line, + EntryInfo *, entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int RecordLength = 0; + int Records = 0; + int i; + + /** Get rid of blank or junk lines. **/ + while (*cp && isspace(*cp)) + cp++; + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /** Cut out file or directory name. **/ + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ ='\0'; + StrAllocCopy(entry_info->filename, cp); + if (strchr(entry_info->filename, '.') != NULL) + /** If we already have a dot, we did an NLST. **/ + return; + cp = cps; + while (*cp && isspace(*cp)) + cp++; + if (!(*cp)) { + /** If we don't have more, we've misparsed. **/ + FREE(entry_info->filename); + FREE(entry_info->type); + entry_info->display = FALSE; + return; + } + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ ='\0'; + if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) { + /** It's an SFS directory. **/ + StrAllocCopy(entry_info->type, "Directory"); + entry_info->size = 0; + } else { + /** It's a file. **/ + cp--; + *cp = '.'; + StrAllocCat(entry_info->filename, cp); + + /** Track down the VM/CMS RECFM or type. **/ + cp = cps; + if (cp < end) { + while (*cp && isspace(*cp)) + cp++; + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ = '\0'; + /** Check cp here, if it's relevant someday. **/ + } + } + + /** Track down the record length or dash. **/ + cp = cps; + if (cp < end) { + while (*cp && isspace(*cp)) + cp++; + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ = '\0'; + if (isdigit(*cp)) { + RecordLength = atoi(cp); + } + } + + /** Track down the number of records or the dash. **/ + cp = cps; + if (cps < end) { + while (*cp && isspace(*cp)) + cp++; + cps = cp; + while (*cps && !isspace(*cps)) + cps++; + *cps++ = '\0'; + if (isdigit(*cp)) { + Records = atoi(cp); + } + if (Records > 0 && RecordLength > 0) { + /** Compute an approximate size. **/ + entry_info->size = (Records * RecordLength); + } + } + + /** Set the years and date, if we don't have them yet. **/ + if (!HaveYears) { + set_years_and_date(); + } + + /** Track down the date. **/ + cpd = cps; + if (((cps < end) && + (cps = strchr(cpd, ':')) != NULL) && + (cps < (end - 3) && + isdigit(*(cps+1)) && isdigit(*(cps+2)) && *(cps+3) == ':')) { + cps += 3; + *cps = '\0'; + if ((cps - cpd) >= 14) { + cpd = (cps - 14); + *(cpd+2) = '\0'; /* Month */ + *(cpd+5) = '\0'; /* Day */ + *(cpd+8) = '\0'; /* Year */ + cps -= 5; /* Time */ + if (*cpd == ' ') + *cpd = '0'; + i = atoi(cpd) - 1; + sprintf(date, "%s %s", months[i], (cpd+3)); + if (date[4] == '0') + date[4] = ' '; + cpd += 6; /* Year */ + if (strcmp((ThisYear+2), cpd)) { + /* Not this year, so show the year. */ + if (atoi(cpd) < atoi((char *)&ThisYear[2])) { + sprintf((char *)&date[6], " %c%c%s", + ThisYear[0], ThisYear[1], cpd); + } else { + sprintf((char *)&date[6], " %c%c%s", + LastYear[0], LastYear[1], cpd); + } + } else { + /* Is this year, so show the time. */ + *(cps+2) = '\0'; /* Hour */ + i = atoi(cps); + sprintf((char*)&date[6], " %s%d:%s", + (i < 10 ? "0" : ""), i, (cps+3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' '|| entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + /** Wrap it up. **/ + if (TRACE) + fprintf(stderr, "HTFTP: VM/CMS filename: %s date: %s size: %d\n", + entry_info->filename, + entry_info->date ? entry_info->date : "", + entry_info->size); + return; +} /* parse_cms_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 + * + * If first is true, this is the first name in a directory. + */ + +PRIVATE EntryInfo * parse_dir_entry ARGS2( + char *, entry, + BOOLEAN *, first) +{ + EntryInfo *entry_info; + int i; + int len; + BOOLEAN remove_size=FALSE; + char *cp; + + entry_info = (EntryInfo *)malloc(sizeof(EntryInfo)); + entry_info->filename = NULL; + entry_info->type = NULL; + entry_info->date = NULL; + entry_info->size = 0; + entry_info->display = TRUE; + + switch (server_type) { + case UNIX_SERVER: + case PETER_LEWIS_SERVER: + case MACHTEN_SERVER: + case MSDOS_SERVER: + /* + ** Check for EPLF output (local times). + */ + if (*entry == '+') { + parse_eplf_line(entry, entry_info); + break; + } + + /* + ** Interpret and edit LIST output from Unix server. + */ + len = strlen(entry); + if (*first) { + *first = FALSE; + if (!strncmp(entry, "total ", 6) || + strstr(entry, "not available") != NULL) { + entry_info->display=FALSE; + return(entry_info); + } else if (unsure_type) { + /* this isn't really a unix server! */ + server_type = GENERIC_SERVER; + entry_info->display=FALSE; + return(entry_info); + } + } + + /* + ** Check first character of ls -l output. + */ + if (TOUPPER(entry[0]) == 'D') { + /* + ** It's a directory. + */ + StrAllocCopy(entry_info->type, "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. + */ + StrAllocCopy(entry_info->type, "Symbolic Link"); + remove_size=TRUE; /* size is not useful */ + + /* + ** Strip off " -> pathname". + */ + for (i = len - 1; (i > 3) && + (!isspace(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 */ + + parse_ls_line(entry, entry_info); + + if (!strcmp(entry_info->filename,"..") || + !strcmp(entry_info->filename,".")) + entry_info->display=FALSE; + /* + ** Goto the bottom and get real type. + */ + break; + + case VMS_SERVER: + /* + ** Interpret and edit LIST output from VMS server + ** and convert information lines to zero length. + */ + 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 = strlen(entry_info->filename); + if ((len > 4) && !strcmp(&entry_info->filename[len-4], ".dir")) { + entry_info->filename[len-4] = '\0'; + StrAllocCopy(entry_info->type, "Directory"); + remove_size=TRUE; /* size is not useful */ + } + /* + ** Goto the bottom and get real type. + */ + break; + + case MS_WINDOWS_SERVER: + /* + ** Interpret and edit LIST output from MS_WINDOWS server + ** and convert information lines to zero length. + */ + parse_ms_windows_dir_entry(entry, entry_info); + + /* + ** Get rid of any junk lines. + */ + if (!entry_info->display) + return(entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return(entry_info); + } + /* + ** Goto the bottom and get real type. + */ + break; + + case WINDOWS_NT_SERVER: + /* + ** Interpret and edit LIST output from MS_WINDOWS server + ** and convert information lines to zero length. + */ + parse_windows_nt_dir_entry(entry, entry_info); + + /* + ** Get rid of any junk lines. + */ + if (!entry_info->display) + return(entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return(entry_info); + } + /* + ** Goto the bottom and get real type. + */ + break; + + case CMS_SERVER: + { + /* + ** Interpret and edit LIST output from VM/CMS server + ** and convert any information lines to zero length. + */ + parse_cms_dir_entry(entry, entry_info); + + /* + ** Get rid of any junk lines. + */ + if (!entry_info->display) + return(entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return(entry_info); + } + /* + ** Goto the bottom and get real type. + */ + break; + } + + case NCSA_SERVER: + case TCPC_SERVER: + /* + ** Directories identified by trailing "/" characters. + */ + StrAllocCopy(entry_info->filename, entry); + len = strlen(entry); + if (entry[len-1] == '/') { + /* + ** It's a dir, remove / and mark it as such. + */ + entry[len-1] = '\0'; + StrAllocCopy(entry_info->type, "Directory"); + remove_size=TRUE; /* size is not useful */ + } + /* + ** Goto the bottom and get real type. + */ + break; + + default: + /* + ** We can't tell if it is a directory since we only + ** did an NLST :( List bad file types anyways? NOT! + */ + StrAllocCopy(entry_info->filename, entry); + return(entry_info); /* mostly empty info */ + break; /* not needed */ + + } /* switch (server_type) */ + + if (remove_size && entry_info->size) { + entry_info->size = 0; + } + + if (entry_info->filename && strlen(entry_info->filename) > 3) { + if (((cp=strrchr(entry_info->filename, '.')) != NULL && + 0 == strncasecomp(cp, ".me", 3)) && + (cp[3] == '\0' || cp[3] == ';')) { + /* + ** Don't treat this as application/x-Troff-me + ** if it's a Unix server but has the string + ** "read.me", or if it's not a Unix server. - FM + */ + if ((server_type != UNIX_SERVER) || + (cp > (entry_info->filename + 3) && + 0 == strncasecomp((cp - 4), "read.me", 7))) { + StrAllocCopy(entry_info->type, "text/plain"); + } + } + } + + /* + ** Get real types eventually. + */ + if (!entry_info->type) { + char *cp; + HTFormat format; + HTAtom * encoding; /* @@ not used at all */ + format = HTFileFormat(entry_info->filename, &encoding); + + if (!strncmp(HTAtom_name(format), "application",11)) { + cp = HTAtom_name(format) + 12; + if (!strncmp(cp,"x-",2)) + cp += 2; + } else { + cp = HTAtom_name(format); + } + + StrAllocCopy(entry_info->type, cp); + } + + return(entry_info); +} /* parse_dir_entry */ + +PUBLIC int compare_EntryInfo_structs ARGS2( + EntryInfo *, entry1, + EntryInfo *, entry2) +{ + int i, status; + char date1[16], date2[16], time1[8], time2[8], month[4]; + + switch(HTfileSortMethod) { + case FILE_BY_SIZE: + /* both equal or both 0 */ + if (entry1->size == entry2->size) + return(strcmp(entry1->filename, entry2->filename)); + else + if (entry1->size > entry2->size) + return(1); + else + return(-1); + break; + + case FILE_BY_TYPE: + if (entry1->type && entry2->type) { + status = strcasecomp(entry1->type, entry2->type); + if (status) + return(status); + /* else fall to filename comparison */ + } + return (strcmp(entry1->filename, entry2->filename)); + break; + + case FILE_BY_DATE: + if (entry1->date && entry2->date) { + /* + ** Make sure we have the correct length. - FM + */ + if (strlen(entry1->date) != 12 || strlen(entry2->date) != 12) { + return(strcmp(entry1->filename, entry2->filename)); + } + /* + ** Set the years and date, + ** if we don't have them yet. + */ + if (!HaveYears) { + set_years_and_date(); + } + /* + ** Set up for sorting in reverse + ** chronological order. - FM + */ + if (entry1->date[9] == ':') { + strcpy(date1, "9999"); + strcpy(time1, (char *)&entry1->date[7]); + if (time1[0] == ' ') { + time1[0] = '0'; + } + } else { + strcpy(date1, (char *)&entry1->date[8]); + strcpy(time1, "00:00"); + } + strncpy(month, entry1->date, 3); + month[3] = '\0'; + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%s%d", (i < 10 ? "0" : ""), i); + strcat(date1, month); + strncat(date1, (char *)&entry1->date[4], 2); + date1[8] = '\0'; + if (date1[6] == ' ' || date1[6] == HT_NON_BREAK_SPACE) { + date1[6] = '0'; + } + if (date1[0] == '9' && atoi(date1) > TheDate) { + for (i = 0; i < 4; i++) { + date1[i] = LastYear[i]; + } + } + strcat(date1, time1); + if (entry2->date[9] == ':') { + strcpy(date2, "9999"); + strcpy(time2, (char *)&entry2->date[7]); + if (time2[0] == ' ') { + time2[0] = '0'; + } + } else { + strcpy(date2, (char *)&entry2->date[8]); + strcpy(time2, "00:00"); + } + strncpy(month, entry2->date, 3); + month[3] = '\0'; + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%s%d", (i < 10 ? "0" : ""), i); + strcat(date2, month); + strncat(date2, (char *)&entry2->date[4], 2); + date2[8] = '\0'; + if (date2[6] == ' ' || date2[6] == HT_NON_BREAK_SPACE) { + date2[6] = '0'; + } + if (date2[0] == '9' && atoi(date2) > TheDate) { + for (i = 0; i < 4; i++) { + date2[i] = LastYear[i]; + } + } + strcat(date2, time2); + /* + ** Do the comparison. - FM + */ + status = strcasecomp(date2, date1); + if (status) + return(status); + /* else fall to filename comparison */ + } + return (strcmp(entry1->filename, entry2->filename)); + break; + + case FILE_BY_NAME: + default: + return (strcmp(entry1->filename, entry2->filename)); + } +} + + +/* Read a directory into an hypertext object from the data socket +** -------------------------------------------------------------- +** +** On entry, +** anchor Parent anchor to link the this node to +** address Address of the directory +** On exit, +** returns HT_LOADED if OK +** <0 if error. +*/ +PRIVATE int read_directory ARGS4( + HTParentAnchor *, parent, + CONST char *, address, + HTFormat, format_out, + HTStream *, sink) +{ + int status; + BOOLEAN WasInterrupted = FALSE; + HTStructured* target = HTML_new(parent, format_out, sink); + HTStructuredClass targetClass; + char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION); + EntryInfo *entry_info; + BOOLEAN first=TRUE; + char string_buffer[64]; + char *lastpath=NULL;/* prefix for link, either "" (for root) or xxx */ + + targetClass = *(target->isa); + + _HTProgress ("Receiving FTP directory."); + HTDirTitles(target, (HTAnchor*)parent); + + data_read_pointer = data_write_pointer = data_buffer; + + if (*filename == '\0') { /* Empty filename: use root. */ + StrAllocCopy (lastpath, "/"); + } else if (!strcmp(filename,"/")) { /* Root path. */ + StrAllocCopy (lastpath, "/foo/.."); + } else { + char * p = strrchr(filename, '/'); /* Find the lastslash. */ + char *cp; + + if (server_type == CMS_SERVER) { + StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */ + } else { + StrAllocCopy(lastpath, p+1); /* Take slash off the beginning. */ + } + if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */ + if (!strncasecomp((cp+1), "type=", 5)) { + if (TOUPPER(*(cp+6)) == 'D' || + TOUPPER(*(cp+6)) == 'A' || + TOUPPER(*(cp+6)) == 'I') + *cp = '\0'; + } + } + } + FREE (filename); + + + { + HTBTree * bt = HTBTree_new((HTComparer)compare_EntryInfo_structs); + char c; + HTChunk * chunk = HTChunkCreate(128); + int BytesReceived = 0; + int BytesReported = 0; + char NumBytes[64]; + PUTS("\n"); /* prettier LJM */ + for (c = 0; c != (char)EOF;) { /* For each entry in the directory */ + HTChunkClear(chunk); + + if (HTCheckForInterrupt()) { + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + return HT_INTERRUPTED; + } + } + + /* read directory entry + */ + for (;;) { /* Read in one line as filename */ + c = NEXT_DATA_CHAR; +AgainForMultiNet: + if (interrupted_in_next_data_char) { + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + return HT_INTERRUPTED; + } + } else if (c == CR || c == LF) { /* Terminator? */ + if (chunk->size != 0) { /* got some text */ + /* Deal with MultiNet's wrapping of long lines */ + if (server_type == VMS_SERVER) { + /* Deal with MultiNet's wrapping of long lines - F.M. */ + if (data_read_pointer < data_write_pointer && + *(data_read_pointer+1) == ' ') + data_read_pointer++; + else if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, + DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) { + interrupted_in_next_data_char = 1; + goto AgainForMultiNet; + } + if (status <= 0) { + c = (char)EOF; + break; + } + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + if (*data_read_pointer == ' ') + data_read_pointer++; + else + break; + } + else + break; + } + else + break; /* finish getting one entry */ + } + } else if (c == (char)EOF) { + break; /* End of file */ + } else { + HTChunkPutc(chunk, c); + } + } + HTChunkTerminate(chunk); + + BytesReceived += chunk->size; + if (BytesReceived > BytesReported + 1024) { + sprintf(NumBytes,"Transferred %d bytes",BytesReceived); + HTProgress(NumBytes); + BytesReported = BytesReceived; + } + + if (c == (char) EOF && chunk->size == 1) + /* 1 means empty: includes terminating 0 */ + break; + if (TRACE) + fprintf(stderr, "HTFTP: Line in %s is %s\n", + lastpath, chunk->data); + + entry_info = parse_dir_entry(chunk->data, &first); + if (entry_info->display) { + if (TRACE) + fprintf(stderr, "Adding file to BTree: %s\n", + entry_info->filename); + HTBTree_add(bt, (EntryInfo *)entry_info); + } + + } /* next entry */ + +unload_btree: + + HTChunkFree(chunk); + + /* print out the handy help message if it exits :) */ + if (help_message_cache_non_empty()) { + START(HTML_PRE); + START(HTML_HR); + PUTS(help_message_cache_contents()); + init_help_message_cache(); /* to free memory */ + START(HTML_HR); + } else { + START(HTML_PRE); + PUTS("\n"); + } + + /* Put up header + */ + /* PUTS(" Date Type Size Filename\n"); + */ + + /* Run through tree printing out in order + */ + { + HTBTElement * ele; + int i; + for (ele = HTBTree_next(bt, NULL); + ele != NULL; + ele = HTBTree_next(bt, ele)) { + entry_info = (EntryInfo *)HTBTree_object(ele); + + if (entry_info->date) { + PUTS(entry_info->date); + PUTS(" "); + } else { + PUTS(" * "); + } + + if (entry_info->type) { + for (i = 0; entry_info->type[i] != '\0' && i < 15; i++) + PUTC(entry_info->type[i]); + for (; i < 17; i++) + PUTC(' '); + } + + /* start the anchor */ + HTDirEntry(target, lastpath, entry_info->filename); + PUTS(entry_info->filename); + END(HTML_A); + + if (entry_info->size) { + if (entry_info->size < 1024) + sprintf(string_buffer, " %d bytes", + entry_info->size); + else + sprintf(string_buffer, " %dKb", + entry_info->size/1024); + PUTS(string_buffer); + } + + PUTC('\n'); /* end of this entry */ + + free_entryinfo_struct_contents(entry_info); + } + } + FREE_TARGET; + HTBTreeAndObject_free(bt); + } + + FREE(lastpath); + if (WasInterrupted || HTCheckForInterrupt()) { + if (server_type != CMS_SERVER) + response(NIL); + _HTProgress("Data transfer interrupted."); + return HT_LOADED; + } + if (server_type != CMS_SERVER) + response(NIL); + return HT_LOADED; +#ifdef NOTDEFINED + return response(NIL) == 2 ? HT_LOADED : -1; +#endif /* NOTDEFINED */ +} + +/* Retrieve File from Server +** ------------------------- +** +** On entry, +** name WWW address of a file: document, including hostname +** On exit, +** returns Socket number for file if good. +** <0 if bad. +*/ +PUBLIC int HTFTPLoad ARGS4( + CONST char *, name, + HTParentAnchor *, anchor, + HTFormat, format_out, + HTStream *, sink) +{ + BOOL isDirectory = NO; + int status; + int retry; /* How many times tried? */ + HTFormat format; + char command[LINE_LENGTH+1]; + + + /* set use_list to NOT since we don't know what kind of server + * this is yet. And set the type to GENERIC + */ + use_list = FALSE; + server_type = GENERIC_SERVER; + + for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */ + status = get_connection(name); + if (status < 0) + return status; + +#ifdef LISTEN + status = get_listen_socket(); + if (status < 0) { + NETCLOSE (control->socket); + control->socket = -1; + close_master_socket (); + /* HT_INTERRUPTED would fall through, if we could interrupt + somehow in the middle of it, which we currently can't. */ + return status; + } + +#ifdef REPEAT_PORT +/* Inform the server of the port number we will listen on +*/ + { + status = response(port_command); + if (status == HT_INTERRUPTED) { + if (TRACE) + fprintf (stderr, + "HTFTP: Interrupted in response (port_command)\n"); + _HTProgress ("Connection interrupted."); + NETCLOSE (control->socket); + control->socket = -1; + close_master_socket (); + return HT_INTERRUPTED; + } + if (status != 2) { /* Could have timed out */ + if (status < 0) + continue; /* try again - net error*/ + return -status; /* bad reply */ + } + if (TRACE) + fprintf(stderr, "HTFTP: Port defined.\n"); + } +#endif /* REPEAT_PORT */ +#else /* Use PASV */ +/* Tell the server to be passive +*/ + { + char *p; + int reply, h0, h1, h2, h3, p0, p1; /* Parts of reply */ + int status; + data_soc = status; + + sprintf(command, "PASV%c%c", CR, LF); + status = response(command); + if (status != 2) { + if (status < 0) + continue; /* retry or Bad return */ + return -status; /* bad reply */ + } + for (p = response_text; *p && *p != ','; p++) + ; /* null body */ + + while (--p > response_text && '0' <= *p && *p <= '9') + ; /* null body */ + + status = sscanf(p+1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(stderr, "HTFTP: PASV reply has no inet address!\n"); + return -99; + } + passive_port = (p0<<8) + p1; + if (TRACE) + fprintf(stderr, "HTFTP: Server is listening on port %d\n", + passive_port); + + +/* Open connection for data: +*/ + sprintf(command, + "ftp://%d.%d.%d.%d:%d/",h0,h1,h2,h3,passive_port); + status = HTDoConnect(name, "FTP", passive_port, &data_soc); + + if (status < 0) { + (void) HTInetStatus("connect for data"); + NETCLOSE(data_soc); + return status; /* Bad return */ + } + + if (TRACE) + fprintf(stderr, "FTP data connected, socket %d\n", data_soc); + } +#endif /* use PASV */ + status = 0; + break; /* No more retries */ + + } /* for retries */ + if (status < 0) + return status; /* Failed with this code */ + +/* Ask for the file: +*/ + { + char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *fname = filename; /** Save for subsequent free() **/ + BOOL binary; + HTAtom * encoding; + char *type = NULL; + char *cp; + + if (server_type == CMS_SERVER) { + /** If the unescaped path has a %2f, reject it as illegal. - FM **/ + if (((cp = strstr(filename, "%2")) != NULL) && + TOUPPER(cp[2]) == 'F') { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (TRACE) { + fprintf(stderr, + "HTFTP: Rejecting path due to illegal escaped slash.\n"); + } + return -1; + } + } + + if (!*filename) { + StrAllocCopy(filename, "/"); + type = "D"; + } else if ((type = strrchr(filename, ';')) != NULL) { + /* + ** Check and trim the type= parameter. - FM + */ + if (!strncasecomp((type+1), "type=", 5)) { + switch(TOUPPER(*(type+6))) { + case 'D': + *type = '\0'; + type = "D"; + break; + case 'A': + *type = '\0'; + type = "A"; + break; + case 'I': + *type = '\0'; + type = "I"; + break; + default: + type = ""; + break; + } + if (!*filename) { + *filename = '/'; + *(filename+1) = '\0'; + } + } + if (TRACE && *type != '\0') { + fprintf(stderr, "HTFTP: type=%s\n", type); + } + } + HTUnEscape(filename); + if (TRACE) + fprintf(stderr, "HTFTP: UnEscaped %s\n", filename); + if (filename[1] == '~') { + /* + ** Check if translation of HOME as tilde is supported, + ** and adjust filename if so. - FM + */ + char *cp = NULL; + char *fn = NULL; + + if ((cp = strchr((filename+1), '/')) != NULL) { + *cp = '\0'; + } + sprintf(command, "PWD%c%c", CR, LF); + status = response(command); + if (status == 2 && response_text[5] == '/') { + sprintf(command, "CWD %s%c%c", (filename+1), CR, LF); + status = response(command); + if (status == 2) { + StrAllocCopy(fn, (filename+1)); + if (cp) { + *cp = '/'; + if (fn[strlen(fn)-1] != '/') { + StrAllocCat(fn, cp); + } else { + StrAllocCat(fn, (cp+1)); + } + } + FREE(fname); + fname = filename = fn; + } + } + } + if (strlen(filename) > 3) { + char *cp; + if (((cp=strrchr(filename, '.')) != NULL && + 0 == strncasecomp(cp, ".me", 3)) && + (cp[3] == '\0' || cp[3] == ';')) { + /* + ** Don't treat this as application/x-Troff-me + ** if it's a Unix server but has the string + ** "read.me", or if it's not a Unix server. - FM + */ + if ((server_type != UNIX_SERVER) || + (cp > (filename + 3) && + 0 == strncasecomp((cp - 4), "read.me", 7))) { + *cp = '\0'; + format = HTFileFormat(filename, &encoding); + *cp = '.'; + } else { + format = HTFileFormat(filename, &encoding); + } + } else { + format = HTFileFormat(filename, &encoding); + } + } else { + format = HTFileFormat(filename, &encoding); + } + format = HTCharsetFormat(format, anchor); + binary = (encoding != HTAtom_for("8bit") && + encoding != HTAtom_for("7bit")); + if (!binary && + /* + ** Force binary if we're in source, download or dump + ** mode and this is not a VM/CMS server, so we don't + ** get CRLF instead of LF (or CR) for newlines in text + ** files. Can't do this for VM/CMS or we'll get + ** raw EBCDIC. - FM + */ + (format_out == WWW_SOURCE || + format_out == HTAtom_for("www/download") || + format_out == HTAtom_for("www/dump")) && + (server_type != CMS_SERVER)) + binary = TRUE; + if (!binary && type && *type == 'I') { + /* + ** Force binary if we had ;type=I - FM + */ + binary = TRUE; + } else if (binary && type && *type == 'A') { + /* + ** Force ASCII if we had ;type=A - FM + */ + binary = FALSE; + } + if (binary != control->binary) { + /* + ** Act on our setting if not alread set. - FM + */ + char * mode = binary ? "I" : "A"; + sprintf(command, "TYPE %s%c%c", mode, CR, LF); + status = response(command); + if (status != 2) { + init_help_message_cache(); /* to free memory */ + return ((status < 0) ? status : -status); + } + control->binary = binary; + } + switch (server_type) { + /* + ** Handle what for Lynx are special case servers, e.g., + ** for which we respect RFC 1738, or which have known + ** conflicts in suffix mappings. - FM + */ + case VMS_SERVER: + { + char *cp1, *cp2; + BOOL included_device = FALSE; + /** Accept only Unix-style filename **/ + if (strchr(filename, ':') != NULL || + strchr(filename, '[') != NULL) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (TRACE) { + fprintf(stderr, + "HTFTP: Rejecting path due to non-Unix-style syntax.\n"); + } + return -1; + } + /** Handle any unescaped "/%2F" path **/ + if (!strncmp(filename, "//", 2)) { + int i; + included_device = TRUE; + for (i = 0; filename[(i+1)]; i++) + filename[i] = filename[(i+1)]; + filename[i] = '\0'; + if (TRACE) { + fprintf(stderr, "HTFTP: Trimmed '%s'\n", filename); + } + cp = HTMake_VMS_name("", filename); + if (TRACE) { + fprintf(stderr, "HTFTP: VMSized '%s'\n", cp); + } + if ((cp1=strrchr(cp, ']')) != NULL) { + cp1++; + for (i = 0; cp1[i]; i++) + filename[i] = cp1[i]; + filename[i] = '\0'; + if (TRACE) { + fprintf(stderr, "HTFTP: Filename '%s'\n", filename); + } + *cp1 = '\0'; + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + if ((cp1=strchr(cp, '[')) != NULL) { + *cp1++ = '\0'; + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + sprintf(command, "CWD [.%s%c%c", cp1, CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } else { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else if ((cp1=strchr(cp, ':')) != NULL && + strchr(cp, '[') == NULL && + strchr(cp, ']') == NULL) { + cp1++; + if (*cp1 != '\0') { + for (i = 0; cp1[i]; i++) + filename[i] = cp1[i]; + filename[i] = '\0'; + if (TRACE) { + fprintf(stderr, "HTFTP: Filename '%s'\n", filename); + } + *cp1 = '\0'; + strcat(cp, "["); + strcat(cp, filename); + strcat(cp, "]"); + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + *cp1 = '\0'; + strcat(cp, "[000000]"); + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + *cp1 = '\0'; + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else { + strcpy(cp, "000000"); + filename = cp; + } + } + } else if (0==strcmp(cp, (filename+1))) { + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + strcat(cp, ":"); + sprintf(command, "CWD %s%c%c", cp, CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + strcpy(cp, "000000"); + filename = cp; + } + } + /** Trim trailing slash if filename is not the top directory **/ + if (strlen(filename) > 1 && filename[strlen(filename)-1] == '/') + filename[strlen(filename)-1] = '\0'; + +#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */ + if (!included_device) { + /** Get the current default VMS device:[directory] **/ + sprintf(command, "PWD%c%c", CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /** Go to the VMS account's top directory **/ + if ((cp=strchr(response_text, '[')) != NULL && + (cp1=strrchr(response_text, ']')) != NULL) { + sprintf(command, "CWD %s", cp); + if ((cp2=strchr(cp, '.')) != NULL && cp2 < cp1) + sprintf(command+(cp2-cp)+4, "]%c%c", CR, LF); + else + sprintf(command+(cp1-cp)+4, "]%c%c", CR, LF); + status = response (command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } +#endif /* MAINTAIN_CONNECTION */ + + /** If we want the VMS account's top directory, list it now **/ + if ((included_device && 0==strcmp(filename, "000000")) || + (strlen(filename) == 1 && *filename == '/')) { + isDirectory = YES; + sprintf(command, "LIST%c%c", CR, LF); + status = response (command); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /** Big goto! **/ + goto listen; + } + /** Otherwise, go to appropriate directory and doctor filename **/ + if (!included_device && + (cp=strchr(filename, '/')) != NULL && + (cp1=strrchr(cp, '/')) != NULL && cp != cp1) { + sprintf(command, "CWD [.%s", cp+1); + sprintf(command+(cp1-cp)+5, "]%c%c", CR, LF); + while ((cp2=strrchr(command, '/')) != NULL) + *cp2 = '.'; + status = response(command); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + filename = cp1+1; + } else { + if (!included_device) { + filename += 1; + } + } + break; + } + case CMS_SERVER: + { + /* + ** If we want the CMS account's top directory, or a base + ** SFS or anonymous directory path (i.e., without a slash), + ** list it now. FM + */ + if ((strlen(filename) == 1 && *filename == '/') || + ((0 == strncasecomp((filename+1), "vmsysu:", 7)) && + (cp = strchr((filename+1), '.')) != NULL && + strchr(cp, '/') == NULL) || + (0 == strncasecomp(filename+1, "anonymou.", 9) && + strchr(filename+1, '/') == NULL)) { + if (filename[1] != '\0') { + sprintf(command, "CWD %s%c%c", (filename+1), CR, LF); + status = response(command); + if (status != 2) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + isDirectory = YES; + if (use_list) + sprintf(command, "LIST%c%c", CR, LF); + else + sprintf(command, "NLST%c%c", CR, LF); + status = response (command); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /** Big goto! **/ + goto listen; + } + filename++; + + /** Otherwise, go to appropriate directory and adjust filename **/ + while ((cp = strchr(filename, '/')) != NULL) { + *cp++ = '\0'; + sprintf(command, "CWD %s%c%c", filename, CR, LF); + status = response(command); + if (status == 2) { + if (*cp == '\0') { + isDirectory = YES; + if (use_list) + sprintf(command, "LIST%c%c", CR, LF); + else + sprintf(command, "NLST%c%c", CR, LF); + status = response (command); + FREE(fname); + if (status != 1) { + /** Action not started **/ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /** Clear any messages from the login directory **/ + init_help_message_cache(); + /** Big goto! **/ + goto listen; + } + filename = cp; + } + } + break; + } + default: + /** Shift for any unescaped "/%2F" path **/ + if (!strncmp(filename, "//", 2)) + filename++; + break; + } + /* + ** Act on a file or listing request, or try to figure out + ** which we're dealing with if we don't know yet. - FM + */ + if (!(type) || (type && *type != 'D')) { + sprintf(command, "RETR %s%c%c", filename, CR, LF); + status = response(command); + } else { + status = 5; /* Failed status set as flag. - FM */ + } + if (status != 1) { /* Failed : try to CWD to it */ + /** Clear any login messages if this isn't the login directory **/ + if (strcmp(filename, "/")) + init_help_message_cache(); + + sprintf(command, "CWD %s%c%c", filename, CR, LF); + status = response(command); + + if (status == 2) { /* Successed : let's NAME LIST it */ + isDirectory = YES; + if (use_list) + sprintf(command, "LIST%c%c", CR, LF); + else + sprintf(command, "NLST%c%c", CR, LF); + status = response (command); + } + } + FREE(fname); + if (status != 1) { + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (status < 0) + return status; + else + return -status; + } + } + +listen: +#ifdef LISTEN +/* Wait for the connection +*/ + { + struct sockaddr_in soc_address; + int soc_addrlen=sizeof(soc_address); +#ifdef SOCKS + if (socks_flag) + status = Raccept(master_socket, + (struct sockaddr *)&soc_address, + (void *)&soc_addrlen); + else +#endif /* SOCKS */ + status = accept(master_socket, + (struct sockaddr *)&soc_address, + (void *)&soc_addrlen); + if (status < 0) { + init_help_message_cache(); /* to free memory */ + return HTInetStatus("accept"); + } + CTRACE(tfp, "TCP: Accepted new socket %d\n", status); + data_soc = status; + } +#else +/* @@ */ +#endif /* LISTEN */ + if (isDirectory) { + status = read_directory (anchor, name, format_out, sink); + NETCLOSE(data_soc); + NETCLOSE(control->socket); + control->socket = -1; + init_help_message_cache(); /* to free memory */ + return status; + /* returns HT_LOADED or error */ + } else { + int rv; + int len; + HTAtom * encoding; + char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + + /** Clear any login messages **/ + init_help_message_cache(); + + /** Fake a Content-Encoding for compressed files. - FM **/ + HTUnEscape(FileName); + if ((len = strlen(FileName)) > 2) { + if ((FileName[len - 1] == 'Z') && + (FileName[len - 2] == '.' || + FileName[len - 2] == '-' || + FileName[len - 2] == '_')) { + + FileName[len - 2] = '\0'; + format = HTFileFormat(FileName, &encoding); + format = HTCharsetFormat(format, anchor); + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, "x-compress"); + format = HTAtom_for("www/compressed"); + } else if ((len > 3) && + !strcasecomp((char *)&FileName[len - 2], "gz")) { + if (FileName[len - 3] == '.' || + FileName[len - 3] == '-' || + FileName[len - 3] == '_') { + FileName[len - 3] = '\0'; + format = HTFileFormat(FileName, &encoding); + format = HTCharsetFormat(format, anchor); + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, "x-gzip"); + format = HTAtom_for("www/compressed"); + } + } + } + FREE(FileName); + + _HTProgress ("Receiving FTP file."); + rv = HTParseSocket(format, format_out, anchor, data_soc, sink); + + if (rv == HT_INTERRUPTED) + _HTProgress("Data transfer interrupted."); + + HTInitInput(control->socket); + /* Reset buffering to control connection DD 921208 */ + + status = NETCLOSE(data_soc); + if (TRACE) + fprintf(stderr, "HTFTP: Closing data socket %d\n", data_soc); + if (status < 0 && rv != HT_INTERRUPTED && rv != -1) { + (void) HTInetStatus("close"); /* Comment only */ + data_soc = -1; /* invalidate it */ + } else { + data_soc = -1; /* invalidate it */ + status = response(NIL); /* Pick up final reply */ + if (status != 2 && rv != HT_INTERRUPTED && rv != -1) { + init_help_message_cache(); /* to free memory */ + return HTLoadError(sink, 500, response_text); + } + } + + NETCLOSE(control->socket); + control->socket = -1; + init_help_message_cache(); /* to free memory */ + return HT_LOADED; + } +} /* open_file_read */ |