summaryrefslogtreecommitdiffstats
path: root/WWW/Library/Implementation/HTFTP.c
diff options
context:
space:
mode:
Diffstat (limited to 'WWW/Library/Implementation/HTFTP.c')
-rw-r--r--WWW/Library/Implementation/HTFTP.c4177
1 files changed, 4177 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c
new file mode 100644
index 0000000..decf559
--- /dev/null
+++ b/WWW/Library/Implementation/HTFTP.c
@@ -0,0 +1,4177 @@
+/*
+ * $LynxId: HTFTP.c,v 1.148 2023/01/05 09:17:15 tom Exp $
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * BUGS: @@@ Limit connection cache size!
+ * Error reporting to user.
+ * 400 & 500 errors are ack'ed by user with windows.
+ * Use configuration file for user names
+ *
+ * Note for portability this version does not use select() and
+ * so does not watch the control and data channels at the
+ * same time.
+ */
+
+#include <HTUtils.h>
+
+#include <HTAlert.h>
+
+#include <HTFTP.h> /* Implemented here */
+#include <HTTCP.h>
+#include <HTTP.h>
+#include <HTFont.h>
+
+#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
+
+#include <HTParse.h>
+#include <HTAnchor.h>
+#include <HTFile.h> /* For HTFileFormat() */
+#include <HTBTree.h>
+#include <HTChunk.h>
+#ifndef IPPORT_FTP
+#define IPPORT_FTP 21
+#endif /* !IPORT_FTP */
+
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYLeaks.h>
+
+typedef struct _connection {
+ struct _connection *next; /* Link on list */
+ int socket; /* Socket number for communication */
+ BOOL is_binary; /* Binary mode? */
+} connection;
+
+/* Hypertext object building machinery
+*/
+#include <HTML.h>
+
+/*
+ * socklen_t is the standard, but there are many pre-standard variants.
+ * This ifdef works around a few of those cases.
+ *
+ * Information was obtained from header files on these platforms:
+ * AIX 4.3.2, 5.1
+ * HPUX 10.20, 11.00, 11.11
+ * IRIX64 6.5
+ * Tru64 4.0G, 4.0D, 5.1
+ */
+#if defined(SYS_IRIX64)
+ /* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */
+# if _NO_XOPEN4 && _NO_XOPEN5
+# define LY_SOCKLEN socklen_t
+# elif _ABIAPI
+# define LY_SOCKLEN int
+# elif _XOPEN5
+# if (_MIPS_SIM != _ABIO32)
+# define LY_SOCKLEN socklen_t
+# else
+# define LY_SOCKLEN int
+# endif
+# else
+# define LY_SOCKLEN size_t
+# endif
+#elif defined(SYS_HPUX)
+# if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE)
+# define LY_SOCKLEN socklen_t
+# else /* HPUX 10.20, etc. */
+# define LY_SOCKLEN int
+# endif
+#elif defined(SYS_TRU64)
+# if defined(_POSIX_PII_SOCKET)
+# define LY_SOCKLEN socklen_t
+# elif defined(_XOPEN_SOURCE_EXTENDED)
+# define LY_SOCKLEN size_t
+# else
+# define LY_SOCKLEN int
+# endif
+#else
+# define LY_SOCKLEN socklen_t
+#endif
+
+#define PUTC(c) (*target->isa->put_character) (target, c)
+#define PUTS(s) (*target->isa->put_string) (target, s)
+#define START(e) (*target->isa->start_element) (target, e, 0, 0, -1, 0)
+#define END(e) (*target->isa->end_element) (target, e, 0)
+#define FREE_TARGET (*target->isa->_free) (target)
+#define ABORT_TARGET (*target->isa->_free) (target)
+
+#define TRACE_ENTRY(tag, entry_info) \
+ CTRACE((tfp, "HTFTP: %s filename: %s date: %s size: %" PRI_off_t "\n", \
+ tag, \
+ entry_info->filename, \
+ NonNull(entry_info->date), \
+ CAST_off_t(entry_info->size)))
+
+struct _HTStructured {
+ const HTStructuredClass *isa;
+ /* ... */
+};
+
+/* Global Variables
+ * ---------------------
+ */
+int HTfileSortMethod = FILE_BY_NAME;
+
+#ifndef DISABLE_FTP /*This disables everything to end-of-file */
+static char ThisYear[8];
+static char LastYear[8];
+static int TheDate;
+static BOOLEAN HaveYears = FALSE;
+
+/* Module-Wide Variables
+ * ---------------------
+ */
+static connection *connections = NULL; /* Linked list of connections */
+static char response_text[LINE_LENGTH + 1]; /* Last response from ftp host */
+static connection *control = NULL; /* Current connection */
+static int data_soc = -1; /* Socket for data transfer =invalid */
+static char *user_entered_password = NULL;
+static char *last_username_and_host = NULL;
+
+/*
+ * Some ftp servers are known to have a broken implementation of RETR. If
+ * asked to retrieve a directory, they get confused and fail subsequent
+ * commands such as CWD and LIST.
+ */
+static int Broken_RETR = FALSE;
+
+/*
+ * Some ftp servers are known to have a broken implementation of EPSV. The
+ * server will hang for a long time when we attempt to connect after issuing
+ * this command.
+ */
+#ifdef INET6
+static int Broken_EPSV = FALSE;
+#endif
+
+typedef enum {
+ GENERIC_SERVER
+ ,MACHTEN_SERVER
+ ,UNIX_SERVER
+ ,VMS_SERVER
+ ,CMS_SERVER
+ ,DCTS_SERVER
+ ,TCPC_SERVER
+ ,PETER_LEWIS_SERVER
+ ,NCSA_SERVER
+ ,WINDOWS_NT_SERVER
+ ,WINDOWS_2K_SERVER
+ ,MS_WINDOWS_SERVER
+ ,MSDOS_SERVER
+ ,APPLESHARE_SERVER
+ ,NETPRESENZ_SERVER
+ ,DLS_SERVER
+} eServerType;
+
+static eServerType server_type = GENERIC_SERVER; /* the type of ftp host */
+static int unsure_type = FALSE; /* sure about the type? */
+static BOOLEAN use_list = FALSE; /* use the LIST command? */
+
+static int interrupted_in_next_data_char = FALSE;
+
+#ifdef POLL_PORTS
+static PortNumber port_number = FIRST_TCP_PORT;
+#endif /* POLL_PORTS */
+
+static BOOL have_socket = FALSE; /* true if master_socket is valid */
+static LYNX_FD master_socket; /* Listening socket = invalid */
+
+static char *port_command; /* Command for setting the port */
+static fd_set open_sockets; /* Mask of active channels */
+static LYNX_FD num_sockets; /* Number of sockets to scan */
+static PortNumber passive_port; /* Port server specified for data */
+
+#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */
+
+#define DATA_BUFFER_SIZE 2048
+static char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */
+static char *data_read_pointer;
+static char *data_write_pointer;
+
+#define NEXT_DATA_CHAR next_data_char()
+static int close_connection(connection * con);
+
+#ifndef HAVE_ATOLL
+off_t LYatoll(const char *value)
+{
+ off_t result = 0;
+
+ while (*value != '\0') {
+ result = (result * 10) + (off_t) (*value++ - '0');
+ }
+ return result;
+}
+#endif
+
+#ifdef LY_FIND_LEAKS
+/*
+ * This function frees module globals. - FM
+ */
+static void free_FTPGlobals(void)
+{
+ FREE(user_entered_password);
+ FREE(last_username_and_host);
+ if (control) {
+ if (control->socket != -1)
+ close_connection(control);
+ FREE(control);
+ }
+}
+#endif /* LY_FIND_LEAKS */
+
+/* PUBLIC HTVMS_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
+ */
+char *HTVMS_name(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;
+ 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 */
+
+ const char *hostname = 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)) {
+ const char *p;
+ const char *q;
+
+ for (p = hostname, q = nn;
+ *p && *p != '.' && *q && *q != '.'; p++, q++) {
+ if (TOUPPER(*p) != TOUPPER(*q)) {
+ char *r;
+
+ strcpy(nodename, nn);
+ r = StrChr(nodename, '.'); /* Mismatch */
+ if (r)
+ *r = '\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 */
+ HTSprintf0(&vmsname, "%s%s", nodename, filename + 1);
+ } else if (second == last) { /* Exactly two slashes */
+ *second = '\0'; /* Split filename from disk */
+ HTSprintf0(&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 */
+ HTSprintf0(&vmsname, "%s%s:[%s]%s",
+ nodename, filename + 1, second + 1, last + 1);
+ *second = *last = '/'; /* restore filename */
+ if ((p = StrChr(vmsname, '[')) != 0) {
+ while (*p != '\0' && *p != ']') {
+ if (*p == '/')
+ *p = '.'; /* Convert dir sep. to dots */
+ ++p;
+ }
+ }
+ }
+ FREE(nodename);
+ FREE(filename);
+ return vmsname;
+}
+
+/* Procedure: Read a character from the data connection
+ * ----------------------------------------------------
+ */
+static int next_data_char(void)
+{
+ 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 EOF;
+ data_write_pointer = data_buffer + status;
+ data_read_pointer = data_buffer;
+ }
+#ifdef NOT_ASCII
+ {
+ char c = *data_read_pointer++;
+
+ return FROMASCII(c);
+ }
+#else
+ return UCH(*data_read_pointer++);
+#endif /* NOT_ASCII */
+}
+
+/* Close an individual connection
+ *
+ */
+static int close_connection(connection * con)
+{
+ connection *scan;
+ int status;
+
+ CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket));
+ status = NETCLOSE(con->socket);
+ if (TRACE && status != 0) {
+#ifdef UNIX
+ CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno)));
+#else
+ if (con->socket != INVSOC)
+ HTInetStatus("HTFTP:close_connection");
+#endif
+ }
+ 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. */
+}
+
+static char *help_message_buffer = NULL; /* global :( */
+
+static void init_help_message_cache(void)
+{
+ FREE(help_message_buffer);
+}
+
+static void help_message_cache_add(char *string)
+{
+ if (help_message_buffer)
+ StrAllocCat(help_message_buffer, string);
+ else
+ StrAllocCopy(help_message_buffer, string);
+
+ CTRACE((tfp, "Adding message to help cache: %s\n", string));
+}
+
+static char *help_message_cache_non_empty(void)
+{
+ return (help_message_buffer);
+}
+
+static char *help_message_cache_contents(void)
+{
+ return (help_message_buffer);
+}
+
+/* Send One Command
+ * ----------------
+ *
+ * This function checks whether we have a control connection, and sends
+ * one command if given.
+ *
+ * On entry,
+ * control points to the connection which is established.
+ * cmd points to a command, or is zero to just get the response.
+ *
+ * The command should already be terminated with the CRLF pair.
+ *
+ * On exit,
+ * returns: 1 for success,
+ * or negative for communication failure (in which case
+ * the control connection will be closed).
+ */
+static int write_cmd(const char *cmd)
+{
+ int status;
+
+ if (!control) {
+ CTRACE((tfp, "HTFTP: No control connection set up!!\n"));
+ return HT_NO_CONNECTION;
+ }
+
+ if (cmd) {
+ CTRACE((tfp, " Tx: %s", cmd));
+#ifdef NOT_ASCII
+ {
+ char *p;
+
+ for (p = cmd; *p; p++) {
+ *p = TOASCII(*p);
+ }
+ }
+#endif /* NOT_ASCII */
+ status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd));
+ if (status < 0) {
+ CTRACE((tfp,
+ "HTFTP: Error %d sending command: closing socket %d\n",
+ status, control->socket));
+ close_connection(control);
+ return status;
+ }
+ }
+ return 1;
+}
+
+/*
+ * For each string in the list, check if it is found in the response text.
+ * If so, return TRUE.
+ */
+static BOOL find_response(HTList *list)
+{
+ BOOL result = FALSE;
+ HTList *p = list;
+ char *value;
+
+ while ((value = (char *) HTList_nextObject(p)) != NULL) {
+ if (LYstrstr(response_text, value)) {
+ result = TRUE;
+ break;
+ }
+ }
+ return result;
+}
+
+/* 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,
+ * control points to the connection which is established.
+ * cmd points to a command, or is zero to just get the response.
+ *
+ * The command must already be terminated with the CRLF pair.
+ *
+ * On exit,
+ * returns: The first digit of the reply type,
+ * or negative for communication failure.
+ */
+static int response(const char *cmd)
+{
+ int result; /* Three-digit decimal code */
+ int continuation_response = -1;
+ int status;
+
+ if ((status = write_cmd(cmd)) < 0)
+ return status;
+
+ do {
+ char *p = response_text;
+
+ for (;;) {
+ int ich = NEXT_CHAR;
+
+ if (((*p++ = (char) ich) == LF)
+ || (p == &response_text[LINE_LENGTH])) {
+
+ char continuation;
+
+ if (interrupted_in_htgetcharacter) {
+ CTRACE((tfp,
+ "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ return HT_INTERRUPTED;
+ }
+
+ *p = '\0'; /* Terminate the string */
+ CTRACE((tfp, " 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 */
+ }
+ if (result == 220 && find_response(broken_ftp_retr)) {
+ Broken_RETR = TRUE;
+ CTRACE((tfp, "This server is broken (RETR)\n"));
+ }
+#ifdef INET6
+ if (result == 220 && find_response(broken_ftp_epsv)) {
+ Broken_EPSV = TRUE;
+ CTRACE((tfp, "This server is broken (EPSV)\n"));
+ }
+#endif
+ break;
+ }
+ /* if end of line */
+ if (interrupted_in_htgetcharacter) {
+ CTRACE((tfp,
+ "HTFTP: Interrupted in HTGetCharacter, apparently.\n"));
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ return HT_INTERRUPTED;
+ }
+
+ if (ich == EOF) {
+ CTRACE((tfp, "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) {
+ CTRACE((tfp, "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;
+}
+
+static int send_cmd_1(const char *verb)
+{
+ char command[80];
+
+ sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF);
+ return response(command);
+}
+
+static int send_cmd_2(const char *verb, const char *param)
+{
+ char *command = 0;
+ int status;
+
+ HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF);
+ status = response(command);
+ FREE(command);
+
+ return status;
+}
+
+#define send_cwd(path) send_cmd_2("CWD", path)
+
+/*
+ * This function should try to set the macintosh server into binary mode. Some
+ * servers need an additional letter after the MACB command.
+ */
+static int set_mac_binary(eServerType ServerType)
+{
+ /* try to set mac binary mode */
+ if (ServerType == APPLESHARE_SERVER ||
+ ServerType == NETPRESENZ_SERVER) {
+ /*
+ * Presumably E means "Enable". - KW
+ */
+ return (2 == response("MACB E\r\n"));
+ } else {
+ return (2 == response("MACB\r\n"));
+ }
+}
+
+/* This function gets the current working directory to help
+ * determine what kind of host it is
+ */
+
+static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList)
+{
+ 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 (*ServerType == TCPC_SERVER) {
+ *ServerType = ((response_text[5] == '/') ?
+ NCSA_SERVER : TCPC_SERVER);
+ CTRACE((tfp, "HTFTP: Treating as %s server.\n",
+ ((*ServerType == NCSA_SERVER) ?
+ "NCSA" : "TCPC")));
+ } else if (response_text[5] == '/') {
+ /* path names beginning with / imply Unix,
+ * right?
+ */
+ if (set_mac_binary(*ServerType)) {
+ *ServerType = NCSA_SERVER;
+ CTRACE((tfp, "HTFTP: Treating as NCSA server.\n"));
+ } else {
+ *ServerType = UNIX_SERVER;
+ *UseList = TRUE;
+ CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
+ }
+ return;
+ } else if (response_text[strlen(response_text) - 1] == ']') {
+ /* path names ending with ] imply VMS, right? */
+ *ServerType = VMS_SERVER;
+ *UseList = TRUE;
+ CTRACE((tfp, "HTFTP: Treating as VMS server.\n"));
+ } else {
+ *ServerType = GENERIC_SERVER;
+ CTRACE((tfp, "HTFTP: Treating as Generic server.\n"));
+ }
+
+ if ((*ServerType == NCSA_SERVER) ||
+ (*ServerType == TCPC_SERVER) ||
+ (*ServerType == PETER_LEWIS_SERVER) ||
+ (*ServerType == NETPRESENZ_SERVER))
+ set_mac_binary(*ServerType);
+ }
+}
+
+/* This function turns MSDOS-like directory output off for
+ * Windows NT servers.
+ */
+
+static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList)
+{
+ char *cp;
+
+ /* This is a toggle. It seems we have to toggle in order to see
+ * the current state (after toggling), so we may end up toggling
+ * twice. - kw
+ */
+ int status = response("SITE DIRSTYLE\r\n");
+
+ if (status != 2) {
+ *ServerType = GENERIC_SERVER;
+ CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n"));
+ return;
+ } else {
+ *UseList = TRUE;
+ /* Expecting one of:
+ * 200 MSDOS-like directory output is off
+ * 200 MSDOS-like directory output is on
+ * The following code doesn't look for the full exact string -
+ * who knows how the wording may change in some future version.
+ * If the first response isn't recognized, we toggle again
+ * anyway, under the assumption that it's more likely that
+ * the MSDOS setting was "off" originally. - kw
+ */
+ cp = strstr(response_text + 4, "MSDOS");
+ if (cp && strstr(cp, " off")) {
+ return; /* already off now. */
+ } else {
+ response("SITE DIRSTYLE\r\n");
+ }
+ }
+}
+
+#define CheckForInterrupt(msg) \
+ if (status == HT_INTERRUPTED) { \
+ CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \
+ _HTProgress(CONNECTION_INTERRUPTED); \
+ NETCLOSE(control->socket); \
+ control->socket = -1; \
+ return HT_INTERRUPTED; \
+ }
+
+/* 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.
+ */
+static int get_connection(const char *arg,
+ HTParentAnchor *anchor)
+{
+ int status;
+ char *command = 0;
+ connection *con;
+ char *username = NULL;
+ char *password = NULL;
+ static BOOLEAN firstuse = TRUE;
+
+ if (firstuse) {
+ /*
+ * Set up freeing at exit. - FM
+ */
+#ifdef LY_FIND_LEAKS
+ atexit(free_FTPGlobals);
+#endif
+ firstuse = FALSE;
+ }
+
+ if (control != 0) {
+ connection *next = control->next;
+
+ if (control->socket != -1) {
+ NETCLOSE(control->socket);
+ }
+ memset(con = control, 0, sizeof(*con));
+ con->next = next;
+ } else {
+ con = typecalloc(connection);
+ if (con == NULL)
+ outofmem(__FILE__, "get_connection");
+ }
+ con->socket = -1;
+
+ if (isEmpty(arg)) {
+ free(con);
+ return -1; /* Bad if no name specified */
+ }
+
+ /* Get node name:
+ */
+ CTRACE((tfp, "get_connection(%s)\n", arg));
+ {
+ 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 = NULL;
+
+ HTSprintf0(&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);
+ HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"),
+ username, p1);
+ FREE(user_entered_password);
+ user_entered_password = HTPromptPassword(tmp, NULL);
+
+ } /* else we already know the password */
+ password = user_entered_password;
+ FREE(tmp);
+ }
+ }
+
+ if (!username)
+ FREE(p1);
+ } /* scope of p1 */
+
+ status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket);
+
+ if (status < 0) {
+ if (status == HT_INTERRUPTED) {
+ CTRACE((tfp, "HTFTP: Interrupted on connect\n"));
+ } else {
+ CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n",
+ arg));
+ }
+ if (status == HT_INTERRUPTED) {
+ _HTProgress(CONNECTION_INTERRUPTED);
+ status = HT_NOT_LOADED;
+ } else {
+ HTAlert(gettext("Unable to connect to FTP host."));
+ }
+ if (con->socket != -1) {
+ NETCLOSE(con->socket);
+ }
+
+ FREE(username);
+ if (control == con)
+ control = NULL;
+ FREE(con);
+ return status; /* Bad return */
+ }
+
+ CTRACE((tfp, "FTP connected, socket %d control %p\n",
+ con->socket, (void *) 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.
+ */
+ status = response(NULL); /* Get greeting */
+ CheckForInterrupt("at beginning of login");
+
+ server_type = GENERIC_SERVER; /* reset */
+ if (status == 2) { /* Send username */
+ char *cp; /* look at greeting text */
+
+ /* don't gettext() this -- incoming text: */
+ if (strlen(response_text) > 4) {
+ if ((cp = strstr(response_text, " awaits your command")) ||
+ (cp = strstr(response_text, " ready."))) {
+ *cp = '\0';
+ }
+ cp = response_text + 4;
+ if (!strncasecomp(cp, "NetPresenz", 10))
+ server_type = NETPRESENZ_SERVER;
+ } else {
+ cp = response_text;
+ }
+ StrAllocCopy(anchor->server, cp);
+
+ status = send_cmd_2("USER", (username && *username)
+ ? username
+ : "anonymous");
+
+ CheckForInterrupt("while sending username");
+ }
+ if (status == 3) { /* Send password */
+ if (non_empty(password)) {
+ HTSprintf0(&command, "PASS %s%c%c", password, CR, LF);
+ } else {
+ /*
+ * No password was given; use mail-address.
+ */
+ const char *the_address;
+ char *user = NULL;
+ const char *host = NULL;
+ char *cp;
+
+ the_address = anonftp_password;
+ if (isEmpty(the_address))
+ the_address = personal_mail_address;
+ if (isEmpty(the_address))
+ the_address = LYGetEnv("USER");
+ if (isEmpty(the_address))
+ the_address = "WWWuser";
+
+ StrAllocCopy(user, the_address);
+ if ((cp = StrChr(user, '@')) != NULL) {
+ *cp++ = '\0';
+ if (*cp == '\0')
+ host = HTHostName();
+ else
+ host = cp;
+ } else {
+ host = 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 = "";
+
+ HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF);
+ FREE(user);
+ }
+ status = response(command);
+ FREE(command);
+ CheckForInterrupt("while sending password");
+ }
+ FREE(username);
+
+ if (status == 3) {
+ status = send_cmd_1("ACCT noaccount");
+ CheckForInterrupt("while sending password");
+ }
+ if (status != 2) {
+ CTRACE((tfp, "HTFTP: Login fail: %s", response_text));
+ /* if (control->socket > 0) close_connection(control->socket); */
+ return -1; /* Bad return */
+ }
+ CTRACE((tfp, "HTFTP: Logged in.\n"));
+
+ /* Check for host type */
+ if (server_type != NETPRESENZ_SERVER)
+ server_type = GENERIC_SERVER; /* reset */
+ use_list = FALSE; /* reset */
+ if (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;
+ CTRACE((tfp, "HTFTP: Treating as MachTen server.\n"));
+
+ } else if (strstr(response_text + 4, "UNIX") != NULL ||
+ strstr(response_text + 4, "Unix") != NULL) {
+ server_type = UNIX_SERVER;
+ unsure_type = FALSE; /* to the best of out knowledge... */
+ use_list = TRUE;
+ CTRACE((tfp, "HTFTP: Treating as Unix server.\n"));
+
+ } else if (strstr(response_text + 4, "MSDOS") != NULL) {
+ server_type = MSDOS_SERVER;
+ use_list = TRUE;
+ CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n"));
+
+ } else if (StrNCmp(response_text + 4, "VMS", 3) == 0) {
+ char *tilde = strstr(arg, "/~");
+
+ use_list = TRUE;
+ if (tilde != 0
+ && tilde[2] != 0
+ && strstr(response_text + 4, "MadGoat") != 0) {
+ server_type = UNIX_SERVER;
+ CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n"));
+ } else {
+ server_type = VMS_SERVER;
+ CTRACE((tfp, "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;
+ CTRACE((tfp, "HTFTP: Treating as CMS server.\n"));
+
+ } else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) {
+ server_type = DCTS_SERVER;
+ CTRACE((tfp, "HTFTP: Treating as DCTS server.\n"));
+
+ } else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) {
+ server_type = TCPC_SERVER;
+ CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n"));
+ get_ftp_pwd(&server_type, &use_list);
+ unsure_type = TRUE;
+
+ } else if (server_type == NETPRESENZ_SERVER) { /* already set above */
+ use_list = TRUE;
+ set_mac_binary(server_type);
+ CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n"));
+
+ } else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) {
+ server_type = PETER_LEWIS_SERVER;
+ use_list = TRUE;
+ set_mac_binary(server_type);
+ CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n"));
+
+ } else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) {
+ server_type = WINDOWS_NT_SERVER;
+ CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n"));
+ set_unix_dirstyle(&server_type, &use_list);
+
+ } else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) {
+ server_type = WINDOWS_2K_SERVER;
+ CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n"));
+ set_unix_dirstyle(&server_type, &use_list);
+
+ } else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) {
+ server_type = MS_WINDOWS_SERVER;
+ use_list = TRUE;
+ CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n"));
+
+ } else if (StrNCmp(response_text + 4,
+ "MACOS AppleShare IP FTP Server", 30) == 0) {
+ server_type = APPLESHARE_SERVER;
+ use_list = TRUE;
+ set_mac_binary(server_type);
+ CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n"));
+
+ } else {
+ server_type = GENERIC_SERVER;
+ CTRACE((tfp, "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);
+ }
+
+ return con->socket; /* Good return */
+}
+
+static void reset_master_socket(void)
+{
+ have_socket = FALSE;
+}
+
+static void set_master_socket(int value)
+{
+ have_socket = (BOOLEAN) (value >= 0);
+ if (have_socket)
+ master_socket = (LYNX_FD) value;
+}
+
+/* Close Master (listening) socket
+ * -------------------------------
+ *
+ *
+ */
+static int close_master_socket(void)
+{
+ int status;
+
+ if (have_socket)
+ FD_CLR(master_socket, &open_sockets);
+
+ status = NETCLOSE((int) master_socket);
+ CTRACE((tfp, "HTFTP: Closed master socket %u\n", (unsigned) master_socket));
+
+ reset_master_socket();
+
+ if (status < 0)
+ return HTInetStatus(gettext("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,
+ * have_socket Must be false, if master_socket is not setup already
+ * 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.
+ */
+static int get_listen_socket(void)
+{
+ LY_SOCKADDR soc_A;
+
+#ifdef INET6
+ unsigned short af;
+ LY_SOCKLEN slen;
+#endif /* INET6 */
+ int new_socket; /* Will be master_socket */
+
+ FD_ZERO(&open_sockets); /* Clear our record of open sockets */
+ num_sockets = 0;
+
+ FREE(port_command);
+#ifndef REPEAT_LISTEN
+ if (have_socket)
+ return master_socket; /* Done already */
+#endif /* !REPEAT_LISTEN */
+
+#ifdef INET6
+ /* query address family of control connection */
+ memset(&soc_A, 0, sizeof(soc_A));
+ slen = (LY_SOCKLEN) sizeof(soc_A);
+ if (getsockname(control->socket, SOCKADDR_OF(soc_A), &slen) < 0) {
+ return HTInetStatus("getsockname failed");
+ }
+ af = SOCKADDR_OF(soc_A)->sa_family;
+#endif /* INET6 */
+
+/* Create internet socket
+*/
+#ifdef INET6
+ new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP);
+#else
+ new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+#endif /* INET6 */
+
+ if (new_socket < 0)
+ return HTInetStatus(gettext("socket for master socket"));
+
+ CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket));
+
+/* Search for a free port.
+*/
+#ifdef INET6
+ memset(&soc_A, 0, sizeof(soc_A));
+ SOCKADDR_OF(soc_A)->sa_family = (unsigned short) af;
+ switch (af) {
+ case AF_INET:
+#ifdef SIN6_LEN
+ SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in);
+#endif /* SIN6_LEN */
+ break;
+ case AF_INET6:
+#ifdef SIN6_LEN
+ SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in6);
+#endif /* SIN6_LEN */
+ break;
+ default:
+ HTInetStatus("AF");
+ }
+#else
+ soc_A.soc_in.sin_family = AF_INET; /* Family = internet, host order */
+ soc_A.soc_in.sin_addr.s_addr = INADDR_ANY; /* Any peer address */
+#endif /* INET6 */
+#ifdef POLL_PORTS
+ {
+ PortNumber 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");
+ }
+#ifdef INET6
+ soc_A.soc_in.sin_port = htons(port_number);
+#else
+ soc_A.sin_port = htons(port_number);
+#endif /* INET6 */
+#ifdef SOCKS
+ if (socks_flag)
+ if ((status = Rbind(new_socket,
+ SOCKADDR_OF(soc_A),
+ SOCKADDR_LEN(soc_A))) == 0) {
+ break;
+ } else
+#endif /* SOCKS */
+ if ((status = bind(new_socket,
+ SOCKADDR_OF(soc_A),
+ SOCKADDR_LEN(soc_A)
+ )) == 0) {
+ break;
+ }
+ CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n",
+ port_number, status, SOCKET_ERRNO));
+ } /* for */
+ }
+#else
+ {
+ int status;
+ LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_A);
+
+#ifdef SOCKS
+ if (socks_flag)
+ status = Rgetsockname(control->socket,
+ SOCKADDR_OF(soc_A),
+ &address_length);
+ else
+#endif /* SOCKS */
+ status = getsockname(control->socket,
+ SOCKADDR_OF(soc_A),
+ &address_length);
+ if (status < 0) {
+ close(new_socket);
+ return HTInetStatus("getsockname");
+ }
+ CTRACE((tfp, "HTFTP: This host is %s\n",
+ HTInetString((void *) &soc_A.soc_in)));
+
+ soc_A.soc_in.sin_port = 0; /* Unspecified: please allocate */
+#ifdef SOCKS
+ if (socks_flag)
+ status = Rbind(new_socket,
+ SOCKADDR_OF(soc_A),
+ sizeof(soc_A));
+ else
+#endif /* SOCKS */
+ status = bind(new_socket,
+ SOCKADDR_OF(soc_A),
+ SOCKADDR_LEN(soc_A));
+ if (status < 0) {
+ close(new_socket);
+ return HTInetStatus("bind");
+ }
+
+ address_length = sizeof(soc_A);
+#ifdef SOCKS
+ if (socks_flag)
+ status = Rgetsockname(new_socket,
+ SOCKADDR_OF(soc_A),
+ &address_length);
+ else
+#endif /* SOCKS */
+ status = getsockname(new_socket,
+ SOCKADDR_OF(soc_A),
+ &address_length);
+ if (status < 0) {
+ close(new_socket);
+ return HTInetStatus("getsockname");
+ }
+ }
+#endif /* POLL_PORTS */
+
+ CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
+ (int) ntohs(soc_A.soc_in.sin_port),
+ HTInetString((void *) &soc_A.soc_in)));
+
+#ifdef REPEAT_LISTEN
+ if (have_socket)
+ (void) close_master_socket();
+#endif /* REPEAT_LISTEN */
+
+ set_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 */
+#ifdef INET6
+ switch (SOCKADDR_OF(soc_A)->sa_family) {
+ case AF_INET:
+#endif /* INET6 */
+ HTSprintf0(&port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 0),
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 1),
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 2),
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 3),
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 0),
+ (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 1),
+ CR, LF);
+
+#ifdef INET6
+ break;
+
+ case AF_INET6:
+ {
+ char hostbuf[MAXHOSTNAMELEN];
+ char portbuf[MAXHOSTNAMELEN];
+
+ getnameinfo(SOCKADDR_OF(soc_A),
+ SOCKADDR_LEN(soc_A),
+ hostbuf,
+ (socklen_t) sizeof(hostbuf),
+ portbuf,
+ (socklen_t) sizeof(portbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ HTSprintf0(&port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
+ CR, LF);
+ break;
+ }
+ default:
+ HTSprintf0(&port_command, "JUNK%c%c", CR, LF);
+ break;
+ }
+#endif /* INET6 */
+ if (port_command == NULL)
+ return -1;
+
+ /* Inform TCP that we will accept connections
+ */
+ {
+ int status;
+
+#ifdef SOCKS
+ if (socks_flag)
+ status = Rlisten((int) master_socket, 1);
+ else
+#endif /* SOCKS */
+ status = listen((int) master_socket, 1);
+ if (status < 0) {
+ reset_master_socket();
+ 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 (int) master_socket; /* Good */
+
+} /* get_listen_socket */
+
+static const 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
+ */
+static void set_years_and_date(void)
+{
+ char day[8], month[8], date[12];
+ time_t NowTime;
+ int i;
+ char *printable;
+
+ NowTime = time(NULL);
+ printable = ctime(&NowTime);
+ LYStrNCpy(day, printable + 8, 2);
+ if (day[0] == ' ') {
+ day[0] = '0';
+ }
+ LYStrNCpy(month, printable + 4, 3);
+ for (i = 0; i < 12; i++) {
+ if (!strcasecomp(month, months[i])) {
+ break;
+ }
+ }
+ i++;
+ sprintf(date, "9999%02d%.2s", i % 100, day);
+ TheDate = atoi(date);
+ LYStrNCpy(ThisYear, printable + 20, 4);
+ sprintf(LastYear, "%d", (atoi(ThisYear) - 1) % 10000);
+ HaveYears = TRUE;
+}
+
+typedef struct _EntryInfo {
+ char *filename;
+ char *linkname; /* symbolic link, if any */
+ char *type;
+ char *date;
+ off_t size;
+ BOOLEAN display; /* show this entry? */
+#ifdef LONG_LIST
+ unsigned long file_links;
+ char *file_mode;
+ char *file_user;
+ char *file_group;
+#endif
+} EntryInfo;
+
+static void free_entryinfo_struct_contents(EntryInfo *entry_info)
+{
+ if (entry_info) {
+#ifdef LONG_LIST
+ FREE(entry_info->file_mode);
+ FREE(entry_info->file_user);
+ FREE(entry_info->file_group);
+#endif
+ FREE(entry_info->filename);
+ FREE(entry_info->linkname);
+ FREE(entry_info->type);
+ FREE(entry_info->date);
+ }
+ /* don't 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 " ...
+ */
+static BOOLEAN is_ls_date(char *s)
+{
+ /* must start with three alpha characters */
+ if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++)))
+ return FALSE;
+
+ /* space or HT_NON_BREAK_SPACE */
+ if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) {
+ return FALSE;
+ }
+ s++;
+
+ /* space or digit */
+ if (!(*s == ' ' || isdigit(UCH(*s)))) {
+ return FALSE;
+ }
+ s++;
+
+ /* digit */
+ if (!isdigit(UCH(*s++)))
+ return FALSE;
+
+ /* space */
+ if (*s++ != ' ')
+ return FALSE;
+
+ /* space or digit */
+ if (!(*s == ' ' || isdigit(UCH(*s)))) {
+ return FALSE;
+ }
+ s++;
+
+ /* digit */
+ if (!isdigit(UCH(*s++)))
+ return FALSE;
+
+ /* colon or digit */
+ if (!(*s == ':' || isdigit(UCH(*s)))) {
+ return FALSE;
+ }
+ s++;
+
+ /* digit */
+ if (!isdigit(UCH(*s++)))
+ return FALSE;
+
+ /* space or digit */
+ if (!(*s == ' ' || isdigit(UCH(*s)))) {
+ return FALSE;
+ }
+ s++;
+
+ /* space */
+ if (*s != ' ')
+ return FALSE;
+
+ return TRUE;
+} /* is_ls_date() */
+
+/*
+ * Extract the name, size, and date from an EPLF line. - 08-06-96 DJB
+ */
+static void parse_eplf_line(char *line,
+ EntryInfo *info)
+{
+ char *cp = line;
+ char ct[26];
+ off_t 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) + (off_t) (*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 */
+ LYStrNCpy(ct, ctime(&secs), 24);
+ StrAllocCopy(info->date, ct);
+ break;
+ case '/':
+ StrAllocCopy(info->type, ENTRY_IS_DIRECTORY);
+ /* FALLTHRU */
+ default:
+ while (*cp) {
+ if (*cp++ == ',')
+ break;
+ }
+ break;
+ }
+ }
+} /* parse_eplf_line */
+
+/*
+ * Extract the name, size, and date from an ls -l line.
+ */
+static void parse_ls_line(char *line,
+ EntryInfo *entry)
+{
+#ifdef LONG_LIST
+ char *next;
+ char *cp;
+#endif
+ int i, j;
+ off_t base = 1;
+ off_t size_num = 0;
+
+ for (i = (int) strlen(line) - 1;
+ (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12]));
+ i--) {
+ ; /* null body */
+ }
+ line[i] = '\0';
+ if (i > 13) {
+ StrAllocCopy(entry->date, &line[i - 12]);
+ /* replace the 4th location with nbsp if it is a space or zero */
+ if (entry->date[4] == ' ' || entry->date[4] == '0')
+ entry->date[4] = HT_NON_BREAK_SPACE;
+ /* make sure year or time is flush right */
+ if (entry->date[11] == ' ') {
+ for (j = 11; j > 6; j--) {
+ entry->date[j] = entry->date[j - 1];
+ }
+ }
+ }
+ j = i - 14;
+ while (isdigit(UCH(line[j]))) {
+ size_num += ((off_t) (line[j] - '0') * base);
+ base *= 10;
+ j--;
+ }
+ entry->size = size_num;
+ StrAllocCopy(entry->filename, &line[i + 1]);
+
+#ifdef LONG_LIST
+ line[j] = '\0';
+
+ /*
+ * Extract the file-permissions, as a string.
+ */
+ if ((cp = StrChr(line, ' ')) != 0) {
+ if ((cp - line) == 10) {
+ *cp = '\0';
+ StrAllocCopy(entry->file_mode, line);
+ *cp = ' ';
+ }
+
+ /*
+ * Next is the link-count.
+ */
+ next = 0;
+ entry->file_links = (unsigned long) strtol(cp, &next, 10);
+ if (next == 0 || *next != ' ') {
+ entry->file_links = 0;
+ next = cp;
+ } else {
+ cp = next;
+ }
+ /*
+ * Next is the user-name.
+ */
+ while (isspace(UCH(*cp)))
+ ++cp;
+ if ((next = StrChr(cp, ' ')) != 0)
+ *next = '\0';
+ if (*cp != '\0')
+ StrAllocCopy(entry->file_user, cp);
+ /*
+ * Next is the group-name (perhaps).
+ */
+ if (next != NULL) {
+ cp = (next + 1);
+ while (isspace(UCH(*cp)))
+ ++cp;
+ if ((next = StrChr(cp, ' ')) != 0)
+ *next = '\0';
+ if (*cp != '\0')
+ StrAllocCopy(entry->file_group, cp);
+ }
+ }
+#endif
+}
+
+/*
+ * Extract the name and size info and whether it refers to a directory from a
+ * LIST line in "dls" format.
+ */
+static void parse_dls_line(char *line,
+ EntryInfo *entry_info,
+ char **pspilledname)
+{
+ short j;
+ int base = 1;
+ off_t size_num = 0;
+ int len;
+ char *cps = NULL;
+
+ /* README 763 Information about this server\0
+ bin/ - \0
+ etc/ = \0
+ ls-lR 0 \0
+ ls-lR.Z 3 \0
+ pub/ = Public area\0
+ usr/ - \0
+ morgan 14 -> ../real/morgan\0
+ TIMIT.mostlikely.Z\0
+ 79215 \0
+ */
+
+ len = (int) strlen(line);
+ if (len == 0) {
+ FREE(*pspilledname);
+ entry_info->display = FALSE;
+ return;
+ }
+ cps = LYSkipNonBlanks(line);
+ if (*cps == '\0') { /* only a filename, save it and return. */
+ StrAllocCopy(*pspilledname, line);
+ entry_info->display = FALSE;
+ return;
+ }
+ if (len < 24 || line[23] != ' ' ||
+ (isspace(UCH(line[0])) && !*pspilledname)) {
+ /* this isn't the expected "dls" format! */
+ if (!isspace(UCH(line[0])))
+ *cps = '\0';
+ if (*pspilledname && !*line) {
+ entry_info->filename = *pspilledname;
+ *pspilledname = NULL;
+ if (entry_info->filename[strlen(entry_info->filename) - 1] == '/')
+ StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
+ else
+ StrAllocCopy(entry_info->type, "");
+ } else {
+ StrAllocCopy(entry_info->filename, line);
+ if (cps != line && *(cps - 1) == '/')
+ StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
+ else
+ StrAllocCopy(entry_info->type, "");
+ FREE(*pspilledname);
+ }
+ return;
+ }
+
+ j = 22;
+ if (line[j] == '=' || line[j] == '-') {
+ StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
+ } else {
+ while (isdigit(UCH(line[j]))) {
+ size_num += (line[j] - '0') * base;
+ base *= 10;
+ j--;
+ }
+ }
+ entry_info->size = size_num;
+
+ cps = LYSkipBlanks(&line[23]);
+ if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') {
+ StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK);
+ StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3));
+ entry_info->size = 0; /* don't display size */
+ }
+
+ if (j > 0)
+ line[j] = '\0';
+
+ LYTrimTrailing(line);
+
+ len = (int) strlen(line);
+ if (len == 0 && *pspilledname && **pspilledname) {
+ line = *pspilledname;
+ len = (int) strlen(*pspilledname);
+ }
+ if (len > 0 && line[len - 1] == '/') {
+ /*
+ * It's a dir, remove / and mark it as such.
+ */
+ if (len > 1)
+ line[len - 1] = '\0';
+ if (!entry_info->type)
+ StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
+ }
+
+ StrAllocCopy(entry_info->filename, line);
+ FREE(*pspilledname);
+} /* parse_dls_line() */
+
+/*
+ * parse_vms_dir_entry()
+ * Format the name, date, and size from a VMS LIST line
+ * into the EntryInfo structure - FM
+ */
+static void parse_vms_dir_entry(char *line,
+ EntryInfo *entry_info)
+{
+ int i, j;
+ off_t ialloc;
+ char *cp, *cpd, *cps, date[16];
+ const char *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) {
+ LYLowerCase(entry_info->filename);
+ i = (int) strlen(entry_info->filename);
+ } else {
+ i = (int) ((strstr(entry_info->filename, "READ")
+ - entry_info->filename)
+ + 4);
+ if (!StrNCmp(&entry_info->filename[i], "ME", 2)) {
+ i += 2;
+ while (entry_info->filename[i] && entry_info->filename[i] != '.') {
+ i++;
+ }
+ } else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) {
+ i = (int) strlen(entry_info->filename);
+ } else {
+ i = 0;
+ }
+ LYLowerCase(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 = 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(UCH(*(cpd - 1))) &&
+ isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') {
+
+ /* Month */
+ *(cpd + 2) = (char) TOLOWER(*(cpd + 2));
+ *(cpd + 3) = (char) TOLOWER(*(cpd + 3));
+ sprintf(date, "%.3s ", cpd + 1);
+
+ /* Day */
+ if (isdigit(UCH(*(cpd - 2))))
+ sprintf(date + 4, "%.2s ", cpd - 2);
+ else
+ sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1);
+
+ /* Time or Year */
+ if (!StrNCmp(ThisYear, cpd + 5, 4) &&
+ strlen(cpd) > 15 && *(cpd + 12) == ':') {
+ sprintf(date + 7, "%.5s", cpd + 10);
+ } else {
+ sprintf(date + 7, " %.4s", cpd + 5);
+ }
+
+ StrAllocCopy(entry_info->date, date);
+ }
+
+ /* Track down the size */
+ if ((cpd = StrChr(cp, '/')) != NULL) {
+ /* Appears be in used/allocated format */
+ cps = cpd;
+ while (isdigit(UCH(*(cps - 1))))
+ cps--;
+ if (cps < cpd)
+ *cpd = '\0';
+ entry_info->size = LYatoll(cps);
+ cps = cpd + 1;
+ while (isdigit(UCH(*cps)))
+ cps++;
+ *cps = '\0';
+ ialloc = LYatoll(cpd + 1);
+ /* Check if used is in blocks or bytes */
+ if (entry_info->size <= ialloc)
+ entry_info->size *= 512;
+
+ } else if (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(UCH(*cpd)))
+ cpd++;
+ if (*cpd == '\0') {
+ /* Assume it's blocks */
+ entry_info->size = (LYatoll(cps) * 512);
+ break;
+ }
+ }
+ }
+
+ TRACE_ENTRY("VMS", entry_info);
+ 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
+ */
+static void parse_ms_windows_dir_entry(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. */
+ cp = LYSkipBlanks(cp);
+ if (!(*cp)) {
+ entry_info->display = FALSE;
+ return;
+ }
+
+ /* Cut out file or directory name. */
+ cps = LYSkipNonBlanks(cp);
+ *cps++ = '\0';
+ cpd = cps;
+ StrAllocCopy(entry_info->filename, cp);
+
+ /* Track down the size */
+ if (cps < end) {
+ cps = LYSkipBlanks(cps);
+ cpd = LYSkipNonBlanks(cps);
+ *cpd++ = '\0';
+ if (isdigit(UCH(*cps))) {
+ entry_info->size = LYatoll(cps);
+ } else {
+ StrAllocCopy(entry_info->type, ENTRY_IS_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) {
+ cpd = LYSkipBlanks(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, "%.6s %.4s", cpd, (cpd + 7));
+ else
+ /* Is this year, so show the time */
+ sprintf(date, "%.6s %.5s", 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;
+ }
+ }
+ }
+
+ TRACE_ENTRY("MS Windows", entry_info);
+ 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
+ */
+#ifdef NOTDEFINED
+static void parse_windows_nt_dir_entry(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. */
+ cp = LYSkipBlanks(cp);
+ if (!(*cp)) {
+ entry_info->display = FALSE;
+ return;
+ }
+
+ /* Cut out file or directory name. */
+ cpd = cp;
+ cps = LYSkipNonBlanks(end - 1);
+ 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;
+ cps = LYSkipNonBlanks(cps);
+ *cps++ = '\0';
+ if (cps > end) {
+ entry_info->display = FALSE;
+ return;
+ }
+ cps = LYSkipBlanks(cps);
+ cpd = LYSkipNonBlanks(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 + 4)) && *(cp + 5) == '-') {
+ *(cp + 2) = '\0'; /* Month */
+ i = atoi(cp) - 1;
+ *(cp + 5) = '\0'; /* Day */
+ sprintf(date, "%.3s %.2s", 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) < 70) {
+ sprintf(&date[6], " 20%.2s", cp);
+ } else {
+ sprintf(&date[6], " 19%.2s", 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;
+ sprintf(&date[6], " %02d:%.2s", 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) {
+ cps = LYSkipBlanks(cps);
+ cpd = LYSkipNonBlanks(cps);
+ *cpd = '\0';
+ if (isdigit(*cps)) {
+ entry_info->size = LYatoll(cps);
+ } else {
+ StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY);
+ }
+ } else {
+ StrAllocCopy(entry_info->type, "");
+ }
+
+ /* Wrap it up */
+ CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n",
+ entry_info->filename,
+ NonNull(entry_info->date),
+ entry_info->size));
+ return;
+} /* parse_windows_nt_dir_entry */
+#endif /* NOTDEFINED */
+
+/*
+ * parse_cms_dir_entry() --
+ * Format the name, date, and size from a VM/CMS line into
+ * the EntryInfo structure. - FM
+ */
+static void parse_cms_dir_entry(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. */
+ cp = LYSkipBlanks(cp);
+ if (!(*cp)) {
+ entry_info->display = FALSE;
+ return;
+ }
+
+ /* Cut out file or directory name. */
+ cps = LYSkipNonBlanks(cp);
+ *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 = LYSkipBlanks(cps);
+ 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 = LYSkipNonBlanks(cp);
+ *cps++ = '\0';
+ if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) {
+ /* It's an SFS directory. */
+ StrAllocCopy(entry_info->type, ENTRY_IS_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) {
+ cp = LYSkipBlanks(cp);
+ cps = LYSkipNonBlanks(cp);
+ *cps++ = '\0';
+ /* Check cp here, if it's relevant someday. */
+ }
+ }
+
+ /* Track down the record length or dash. */
+ cp = cps;
+ if (cp < end) {
+ cp = LYSkipBlanks(cp);
+ cps = LYSkipNonBlanks(cp);
+ *cps++ = '\0';
+ if (isdigit(UCH(*cp))) {
+ RecordLength = atoi(cp);
+ }
+ }
+
+ /* Track down the number of records or the dash. */
+ cp = cps;
+ if (cps < end) {
+ cp = LYSkipBlanks(cp);
+ cps = LYSkipNonBlanks(cp);
+ *cps++ = '\0';
+ if (isdigit(UCH(*cp))) {
+ Records = atoi(cp);
+ }
+ if (Records > 0 && RecordLength > 0) {
+ /* Compute an approximate size. */
+ entry_info->size = ((off_t) Records * (off_t) 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(UCH(*(cps + 1))) && isdigit(UCH(*(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, "%.3s %.2s", 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) < 70) {
+ sprintf(&date[6], " 20%.2s", cpd);
+ } else {
+ sprintf(&date[6], " 19%.2s", cpd);
+ }
+ } else {
+ /* Is this year, so show the time. */
+ *(cps + 2) = '\0'; /* Hour */
+ i = atoi(cps);
+ sprintf(&date[6], " %02d:%.2s", 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;
+ }
+ }
+ }
+
+ TRACE_ENTRY("VM/CMS", entry_info);
+ return;
+} /* parse_cms_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.
+ */
+static EntryInfo *parse_dir_entry(char *entry,
+ BOOLEAN *first,
+ char **pspilledname)
+{
+ EntryInfo *entry_info;
+ int i;
+ int len;
+ BOOLEAN remove_size = FALSE;
+ char *cp;
+
+ entry_info = typecalloc(EntryInfo);
+
+ if (entry_info == NULL)
+ outofmem(__FILE__, "parse_dir_entry");
+
+ entry_info->display = TRUE;
+
+ switch (server_type) {
+ case DLS_SERVER:
+
+ /*
+ * Interpret and edit LIST output from a Unix server in "dls" format.
+ * This one must have claimed to be Unix in order to get here; if the
+ * first line looks fishy, we revert to Unix and hope that fits better
+ * (this recovery is untested). - kw
+ */
+
+ if (*first) {
+ len = (int) strlen(entry);
+ if (!len || entry[0] == ' ' ||
+ (len >= 24 && entry[23] != ' ') ||
+ (len < 24 && StrChr(entry, ' '))) {
+ server_type = UNIX_SERVER;
+ CTRACE((tfp,
+ "HTFTP: Falling back to treating as Unix server.\n"));
+ } else {
+ *first = FALSE;
+ }
+ }
+
+ if (server_type == DLS_SERVER) {
+ /* if still unchanged... */
+ parse_dls_line(entry, entry_info, pspilledname);
+
+ if (isEmpty(entry_info->filename)) {
+ entry_info->display = FALSE;
+ return (entry_info);
+ }
+ if (!strcmp(entry_info->filename, "..") ||
+ !strcmp(entry_info->filename, "."))
+ entry_info->display = FALSE;
+ if (entry_info->type && *entry_info->type == '\0') {
+ FREE(entry_info->type);
+ return (entry_info);
+ }
+ /*
+ * Goto the bottom and get real type.
+ */
+ break;
+ }
+ /* fall through if server_type changed for *first == TRUE ! */
+ /* FALLTHRU */
+ case UNIX_SERVER:
+ case PETER_LEWIS_SERVER:
+ case MACHTEN_SERVER:
+ case MSDOS_SERVER:
+ case WINDOWS_NT_SERVER:
+ case WINDOWS_2K_SERVER:
+ case APPLESHARE_SERVER:
+ case NETPRESENZ_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 = (int) strlen(entry);
+ if (*first) {
+ /* don't gettext() this -- incoming text: */
+ if (!strcmp(entry, "can not access directory .")) {
+ /*
+ * Don't reset *first, nothing real will follow. - KW
+ */
+ entry_info->display = FALSE;
+ return (entry_info);
+ }
+ *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, ENTRY_IS_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, ENTRY_IS_SYMBOLIC_LINK);
+ remove_size = TRUE; /* size is not useful */
+
+ /*
+ * Strip off " -> pathname".
+ */
+ for (i = len - 1; (i > 3) &&
+ (!isspace(UCH(entry[i])) ||
+ (entry[i - 1] != '>') ||
+ (entry[i - 2] != '-') ||
+ (entry[i - 3] != ' ')); i--) ; /* null body */
+ if (i > 3) {
+ entry[i - 3] = '\0';
+ StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i));
+ }
+ }
+ /* 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 = (int) strlen(entry_info->filename);
+ if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) {
+ entry_info->filename[len - 4] = '\0';
+ StrAllocCopy(entry_info->type, ENTRY_IS_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;
+
+#ifdef NOTDEFINED
+ 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;
+#endif /* NOTDEFINED */
+
+ 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 = (int) strlen(entry);
+ if (entry[len - 1] == '/') {
+ /*
+ * It's a dir, remove / and mark it as such.
+ */
+ entry[len - 1] = '\0';
+ StrAllocCopy(entry_info->type, ENTRY_IS_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 */
+
+ } /* switch (server_type) */
+
+#ifdef LONG_LIST
+ (void) remove_size;
+#else
+ if (remove_size && entry_info->size) {
+ entry_info->size = 0;
+ }
+#endif
+
+ if (isEmpty(entry_info->filename)) {
+ entry_info->display = FALSE;
+ return (entry_info);
+ }
+ if (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, STR_PLAINTEXT);
+ }
+ }
+ }
+
+ /*
+ * Get real types eventually.
+ */
+ if (!entry_info->type) {
+ const char *cp2;
+ HTFormat format;
+ HTAtom *encoding; /* @@ not used at all */
+
+ format = HTFileFormat(entry_info->filename, &encoding, &cp2);
+
+ if (cp2 == NULL) {
+ if (!StrNCmp(HTAtom_name(format), "application", 11)) {
+ cp2 = HTAtom_name(format) + 12;
+ if (!StrNCmp(cp2, "x-", 2))
+ cp2 += 2;
+ } else {
+ cp2 = HTAtom_name(format);
+ }
+ }
+
+ StrAllocCopy(entry_info->type, cp2);
+ }
+
+ return (entry_info);
+}
+
+static void formatDate(char target[16], EntryInfo *entry)
+{
+ char temp[8], month[4];
+ int i;
+
+ /*
+ * Set up for sorting in reverse chronological order. - FM
+ */
+ if (entry->date[9] == ':') {
+ strcpy(target, "9999");
+ LYStrNCpy(temp, &entry->date[7], 5);
+ if (temp[0] == ' ') {
+ temp[0] = '0';
+ }
+ } else {
+ LYStrNCpy(target, &entry->date[8], 4);
+ strcpy(temp, "00:00");
+ }
+ LYStrNCpy(month, entry->date, 3);
+ for (i = 0; i < 12; i++) {
+ if (!strcasecomp(month, months[i])) {
+ break;
+ }
+ }
+ i++;
+ sprintf(month, "%02d", i % 100);
+ strcat(target, month);
+ StrNCat(target, &entry->date[4], 2);
+ if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) {
+ target[6] = '0';
+ }
+
+ /* If no year given, assume last year if it would otherwise be in the
+ * future by more than one day. The one day tolerance is to account for a
+ * possible timezone difference. - kw
+ */
+ if (target[0] == '9' && atoi(target) > TheDate + 1) {
+ for (i = 0; i < 4; i++) {
+ target[i] = LastYear[i];
+ }
+ }
+ strcat(target, temp);
+}
+
+static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2)
+{
+ int status;
+ char date1[16], date2[16];
+ int result = strcmp(entry1->filename, entry2->filename);
+
+ switch (HTfileSortMethod) {
+ case FILE_BY_SIZE:
+ /* both equal or both 0 */
+ if (entry1->size > entry2->size)
+ result = 1;
+ else if (entry1->size < entry2->size)
+ result = -1;
+ break;
+
+ case FILE_BY_TYPE:
+ if (entry1->type && entry2->type) {
+ status = strcasecomp(entry1->type, entry2->type);
+ if (status)
+ result = status;
+ }
+ break;
+
+ case FILE_BY_DATE:
+ if (entry1->date && entry2->date &&
+ strlen(entry1->date) == 12 &&
+ strlen(entry2->date) == 12) {
+ /*
+ * Set the years and date, if we don't have them yet.
+ */
+ if (!HaveYears) {
+ set_years_and_date();
+ }
+ formatDate(date1, entry1);
+ formatDate(date2, entry2);
+ /*
+ * Do the comparison. - FM
+ */
+ status = strcasecomp(date2, date1);
+ if (status)
+ result = status;
+ }
+ break;
+
+ case FILE_BY_NAME:
+ default:
+ break;
+ }
+ return result;
+}
+
+#ifdef LONG_LIST
+static char *FormatStr(char **bufp,
+ char *start,
+ const char *value)
+{
+ char fmt[512];
+
+ if (*start) {
+ sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start);
+ HTSprintf(bufp, fmt, value);
+ } else if (*bufp && !(value && *value)) {
+ ;
+ } else if (value) {
+ StrAllocCat(*bufp, value);
+ }
+ return *bufp;
+}
+
+static char *FormatSize(char **bufp,
+ char *start,
+ off_t value)
+{
+ char fmt[512];
+
+ if (*start) {
+ sprintf(fmt, "%%%.*s" PRI_off_t,
+ (int) sizeof(fmt) - DigitsOf(start) - 3, start);
+
+ HTSprintf(bufp, fmt, value);
+ } else {
+ sprintf(fmt, "%" PRI_off_t, CAST_off_t (value));
+
+ StrAllocCat(*bufp, fmt);
+ }
+ return *bufp;
+}
+
+static char *FormatNum(char **bufp,
+ char *start,
+ unsigned long value)
+{
+ char fmt[512];
+
+ if (*start) {
+ sprintf(fmt, "%%%.*sld",
+ (int) sizeof(fmt) - DigitsOf(start) - 3, start);
+ HTSprintf(bufp, fmt, value);
+ } else {
+ sprintf(fmt, "%lu", value);
+ StrAllocCat(*bufp, fmt);
+ }
+ return *bufp;
+}
+
+static void FlushParse(HTStructured * target, char **buf)
+{
+ if (*buf && **buf) {
+ PUTS(*buf);
+ **buf = '\0';
+ }
+}
+
+static void LYListFmtParse(const char *fmtstr,
+ EntryInfo *data,
+ HTStructured * target,
+ char *tail)
+{
+ char c;
+ char *s;
+ char *end;
+ char *start;
+ char *str = NULL;
+ char *buf = NULL;
+ BOOL is_directory = (BOOL) (data->file_mode != 0 &&
+ (TOUPPER(data->file_mode[0]) == 'D'));
+ BOOL is_symlinked = (BOOL) (data->file_mode != 0 &&
+ (TOUPPER(data->file_mode[0]) == 'L'));
+ BOOL remove_size = (BOOL) (is_directory || is_symlinked);
+
+ StrAllocCopy(str, fmtstr);
+ s = str;
+ end = str + strlen(str);
+ while (*s) {
+ start = s;
+ while (*s) {
+ if (*s == '%') {
+ if (*(s + 1) == '%') /* literal % */
+ s++;
+ else
+ break;
+ }
+ s++;
+ }
+ /* s is positioned either at a % or at \0 */
+ *s = '\0';
+ if (s > start) { /* some literal chars. */
+ StrAllocCat(buf, start);
+ }
+ if (s == end)
+ break;
+ start = ++s;
+ while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' ||
+ *s == '#' || *s == '+' || *s == '\'')
+ s++;
+ c = *s; /* the format char. or \0 */
+ *s = '\0';
+
+ switch (c) {
+ case '\0':
+ StrAllocCat(buf, start);
+ continue;
+
+ case 'A':
+ case 'a': /* anchor */
+ FlushParse(target, &buf);
+ HTDirEntry(target, tail, data->filename);
+ FormatStr(&buf, start, data->filename);
+ PUTS(buf);
+ END(HTML_A);
+ if (buf != 0)
+ *buf = '\0';
+ if (c != 'A' && data->linkname != 0) {
+ PUTS(" -> ");
+ PUTS(data->linkname);
+ }
+ break;
+
+ case 'T': /* MIME type */
+ case 't': /* MIME type description */
+ if (is_directory) {
+ if (c != 'T') {
+ FormatStr(&buf, start, ENTRY_IS_DIRECTORY);
+ } else {
+ FormatStr(&buf, start, "");
+ }
+ } else if (is_symlinked) {
+ if (c != 'T') {
+ FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK);
+ } else {
+ FormatStr(&buf, start, "");
+ }
+ } else {
+ const char *cp2;
+ HTFormat format;
+
+ format = HTFileFormat(data->filename, NULL, &cp2);
+
+ if (c != 'T') {
+ if (cp2 == NULL) {
+ if (!StrNCmp(HTAtom_name(format),
+ "application", 11)) {
+ cp2 = HTAtom_name(format) + 12;
+ if (!StrNCmp(cp2, "x-", 2))
+ cp2 += 2;
+ } else {
+ cp2 = HTAtom_name(format);
+ }
+ }
+ FormatStr(&buf, start, cp2);
+ } else {
+ FormatStr(&buf, start, HTAtom_name(format));
+ }
+ }
+ break;
+
+ case 'd': /* date */
+ if (data->date) {
+ FormatStr(&buf, start, data->date);
+ } else {
+ FormatStr(&buf, start, " * ");
+ }
+ break;
+
+ case 's': /* size in bytes */
+ FormatSize(&buf, start, data->size);
+ break;
+
+ case 'K': /* size in Kilobytes but not for directories */
+ if (remove_size) {
+ FormatStr(&buf, start, "");
+ StrAllocCat(buf, " ");
+ break;
+ }
+ /* FALL THROUGH */
+ case 'k': /* size in Kilobytes */
+ /* FIXME - this is inconsistent with HTFile.c, but historical */
+ if (data->size < 1024) {
+ FormatSize(&buf, start, data->size);
+ StrAllocCat(buf, " bytes");
+ } else {
+ FormatSize(&buf, start, data->size / 1024);
+ StrAllocCat(buf, "Kb");
+ }
+ break;
+
+#ifdef LONG_LIST
+ case 'p': /* unix-style permission bits */
+ FormatStr(&buf, start, NonNull(data->file_mode));
+ break;
+
+ case 'o': /* owner */
+ FormatStr(&buf, start, NonNull(data->file_user));
+ break;
+
+ case 'g': /* group */
+ FormatStr(&buf, start, NonNull(data->file_group));
+ break;
+
+ case 'l': /* link count */
+ FormatNum(&buf, start, data->file_links);
+ break;
+#endif
+
+ case '%': /* literal % with flags/width */
+ FormatStr(&buf, start, "%");
+ break;
+
+ default:
+ fprintf(stderr,
+ "Unknown format character `%c' in list format\n", c);
+ break;
+ }
+
+ s++;
+ }
+ if (buf) {
+ LYTrimTrailing(buf);
+ FlushParse(target, &buf);
+ FREE(buf);
+ }
+ PUTC('\n');
+ FREE(str);
+}
+#endif /* LONG_LIST */
+
+/* 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.
+ */
+static int read_directory(HTParentAnchor *parent,
+ const char *address,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ int status;
+ BOOLEAN WasInterrupted = FALSE;
+ HTStructured *target = HTML_new(parent, format_out, sink);
+ char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
+ EntryInfo *entry_info;
+ BOOLEAN first = TRUE;
+ char *lastpath = NULL; /* prefix for link, either "" (for root) or xxx */
+ BOOL tildeIsTop = FALSE;
+
+#ifndef LONG_LIST
+ char string_buffer[64];
+#endif
+
+ _HTProgress(gettext("Receiving FTP directory."));
+
+ /*
+ * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be
+ * recalculated for each directory request. Otherwise we have a problem
+ * with long-running sessions assuming the wrong date for today. - kw
+ */
+ HaveYears = FALSE;
+ /*
+ * Check whether we always want the home directory treated as Welcome. -
+ * FM
+ */
+ if (server_type == VMS_SERVER)
+ tildeIsTop = TRUE;
+
+ /*
+ * This should always come back FALSE, since the flag is set only for local
+ * directory listings if LONG_LIST was defined on compilation, but we could
+ * someday set up an equivalent listing for Unix ftp servers. - FM
+ */
+ (void) HTDirTitles(target, parent, format_out, tildeIsTop);
+
+ 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);
+ int ic;
+ HTChunk *chunk = HTChunkCreate(128);
+ int BytesReceived = 0;
+ int BytesReported = 0;
+ char NumBytes[64];
+ char *spilledname = NULL;
+
+ PUTC('\n'); /* prettier LJM */
+ for (ic = 0; ic != EOF;) { /* For each entry in the directory */
+ HTChunkClear(chunk);
+
+ if (HTCheckForInterrupt()) {
+ CTRACE((tfp,
+ "read_directory: interrupted after %d bytes\n",
+ BytesReceived));
+ WasInterrupted = TRUE;
+ if (BytesReceived) {
+ goto unload_btree; /* unload btree */
+ } else {
+ ABORT_TARGET;
+ HTBTreeAndObject_free(bt);
+ FREE(spilledname);
+ HTChunkFree(chunk);
+ return HT_INTERRUPTED;
+ }
+ }
+
+ /* read directory entry
+ */
+ interrupted_in_next_data_char = FALSE;
+ for (;;) { /* Read in one line as filename */
+ ic = NEXT_DATA_CHAR;
+ AgainForMultiNet:
+ if (interrupted_in_next_data_char) {
+ CTRACE((tfp,
+ "read_directory: interrupted_in_next_data_char after %d bytes\n",
+ BytesReceived));
+ WasInterrupted = TRUE;
+ if (BytesReceived) {
+ goto unload_btree; /* unload btree */
+ } else {
+ ABORT_TARGET;
+ HTBTreeAndObject_free(bt);
+ FREE(spilledname);
+ HTChunkFree(chunk);
+ return HT_INTERRUPTED;
+ }
+ } else if ((char) ic == CR || (char) ic == 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) {
+ ic = 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 (ic == EOF) {
+ break; /* End of file */
+ } else {
+ HTChunkPutc(chunk, UCH(ic));
+ }
+ }
+ HTChunkTerminate(chunk);
+
+ BytesReceived += chunk->size;
+ if (BytesReceived > BytesReported + 1024) {
+#ifdef _WINDOWS
+ sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"),
+ BytesReceived, ws_read_per_sec);
+#else
+ sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived);
+#endif
+ HTProgress(NumBytes);
+ BytesReported = BytesReceived;
+ }
+
+ if (ic == EOF && chunk->size == 1)
+ /* 1 means empty: includes terminating 0 */
+ break;
+ CTRACE((tfp, "HTFTP: Line in %s is %s\n",
+ lastpath, chunk->data));
+
+ entry_info = parse_dir_entry(chunk->data, &first, &spilledname);
+ if (entry_info->display) {
+ FREE(spilledname);
+ CTRACE((tfp, "Adding file to BTree: %s\n",
+ entry_info->filename));
+ HTBTree_add(bt, entry_info);
+ } else {
+ free_entryinfo_struct_contents(entry_info);
+ FREE(entry_info);
+ }
+
+ } /* next entry */
+
+ unload_btree:
+
+ HTChunkFree(chunk);
+ FREE(spilledname);
+
+ /* print out the handy help message if it exists :) */
+ if (help_message_cache_non_empty()) {
+ START(HTML_PRE);
+ START(HTML_HR);
+ PUTC('\n');
+ PUTS(help_message_cache_contents());
+ init_help_message_cache(); /* to free memory */
+ START(HTML_HR);
+ PUTC('\n');
+ } else {
+ START(HTML_PRE);
+ PUTC('\n');
+ }
+
+ /* Run through tree printing out in order
+ */
+ {
+#ifndef LONG_LIST
+#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */
+ char *p, name_buff[256];
+ int name_len, dot_len;
+
+#define FNAME_WIDTH 30
+#define FILE_GAP 1
+
+#endif
+ int i;
+#endif
+ HTBTElement *ele;
+
+ for (ele = HTBTree_next(bt, NULL);
+ ele != NULL;
+ ele = HTBTree_next(bt, ele)) {
+ entry_info = (EntryInfo *) HTBTree_object(ele);
+
+#ifdef LONG_LIST
+ LYListFmtParse(ftp_format,
+ entry_info,
+ target,
+ lastpath);
+#else
+ if (entry_info->date) {
+ PUTS(entry_info->date);
+ PUTS(" ");
+ } else {
+ PUTS(" * ");
+ }
+
+ if (entry_info->type) {
+ for (i = 0; entry_info->type[i] != '\0' && i < 16; i++)
+ PUTC(entry_info->type[i]);
+ for (; i < 17; i++)
+ PUTC(' ');
+ }
+ /* start the anchor */
+ HTDirEntry(target, lastpath, entry_info->filename);
+#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */
+ name_len = strlen(entry_info->filename);
+
+ sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename);
+
+ if (name_len < FNAME_WIDTH) {
+ dot_len = FNAME_WIDTH - FILE_GAP - name_len;
+ if (dot_len > 0) {
+ p = name_buff + name_len + 1;
+ while (dot_len-- > 0)
+ *p++ = '.';
+ }
+ } else {
+ name_buff[FNAME_WIDTH] = '\0';
+ }
+
+ PUTS(name_buff);
+#else
+ PUTS(entry_info->filename);
+#endif
+ END(HTML_A);
+
+ if (entry_info->size) {
+#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */
+ if (entry_info->size < 1024)
+ sprintf(string_buffer, "%6ld bytes",
+ entry_info->size);
+ else
+ sprintf(string_buffer, "%6ld Kb",
+ entry_info->size / 1024);
+#else
+ if (entry_info->size < 1024)
+ sprintf(string_buffer, " %lu bytes",
+ (unsigned long) entry_info->size);
+ else
+ sprintf(string_buffer, " %luKb",
+ (unsigned long) entry_info->size / 1024);
+#endif
+ PUTS(string_buffer);
+ } else if (entry_info->linkname != 0) {
+ PUTS(" -> ");
+ PUTS(entry_info->linkname);
+ }
+
+ PUTC('\n'); /* end of this entry */
+#endif
+
+ free_entryinfo_struct_contents(entry_info);
+ }
+ }
+ END(HTML_PRE);
+ END(HTML_BODY);
+ FREE_TARGET;
+ HTBTreeAndObject_free(bt);
+ }
+
+ FREE(lastpath);
+
+ if (WasInterrupted || data_soc != -1) { /* should always be true */
+ /*
+ * Without closing the data socket first, the response(NULL) later may
+ * hang. Some servers expect the client to fin/ack the close of the
+ * data connection before proceeding with the conversation on the
+ * control connection. - kw
+ */
+ CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
+ status = NETCLOSE(data_soc);
+ if (status == -1)
+ HTInetStatus("close"); /* Comment only */
+ data_soc = -1;
+ }
+
+ if (WasInterrupted || HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ }
+ return HT_LOADED;
+}
+
+/*
+ * Setup an FTP connection.
+ */
+static int setup_connection(const char *name,
+ HTParentAnchor *anchor)
+{
+ int retry; /* How many times tried? */
+ int status = HT_NO_CONNECTION;
+
+ CTRACE((tfp, "setup_connection(%s)\n", name));
+
+ /* 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;
+ Broken_RETR = FALSE;
+
+#ifdef INET6
+ Broken_EPSV = FALSE;
+#endif
+
+ for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */
+ status = get_connection(name, anchor);
+ if (status < 0) {
+ break;
+ }
+
+ if (!ftp_local_passive) {
+ status = get_listen_socket();
+ if (status < 0) {
+ NETCLOSE(control->socket);
+ control->socket = -1;
+#ifdef INET6
+ if (have_socket)
+ (void) close_master_socket();
+#else
+ close_master_socket();
+#endif /* INET6 */
+ /* HT_INTERRUPTED would fall through, if we could interrupt
+ somehow in the middle of it, which we currently can't. */
+ break;
+ }
+#ifdef REPEAT_PORT
+ /* Inform the server of the port number we will listen on
+ */
+ status = response(port_command);
+ FREE(port_command);
+ if (status == HT_INTERRUPTED) {
+ CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n"));
+ _HTProgress(CONNECTION_INTERRUPTED);
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ close_master_socket();
+ status = HT_INTERRUPTED;
+ break;
+ }
+ if (status != 2) { /* Could have timed out */
+ if (status < 0)
+ continue; /* try again - net error */
+ status = -status; /* bad reply */
+ break;
+ }
+ CTRACE((tfp, "HTFTP: Port defined.\n"));
+#endif /* REPEAT_PORT */
+ } else { /* Tell the server to be passive */
+ char *command = NULL;
+ const char *p = "?";
+ int h0, h1, h2, h3, p0, p1; /* Parts of reply */
+
+#ifdef INET6
+ char dst[LINE_LENGTH + 1];
+#endif
+
+ data_soc = status;
+
+#ifdef INET6
+ /* see RFC 2428 */
+ if (Broken_EPSV)
+ status = 1;
+ else
+ status = send_cmd_1(p = "EPSV");
+ if (status < 0) /* retry or Bad return */
+ continue;
+ else if (status != 2) {
+ status = send_cmd_1(p = "PASV");
+ if (status < 0) { /* retry or Bad return */
+ continue;
+ } else if (status != 2) {
+ status = -status; /* bad reply */
+ break;
+ }
+ }
+
+ if (strcmp(p, "PASV") == 0) {
+ 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(tfp, "HTFTP: PASV reply has no inet address!\n");
+ status = HT_NO_CONNECTION;
+ break;
+ }
+ passive_port = (PortNumber) ((p0 << 8) + p1);
+ sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3);
+ } else if (strcmp(p, "EPSV") == 0) {
+ char c0, c1, c2, c3;
+ LY_SOCKADDR ss;
+ LY_SOCKLEN sslen;
+
+ /*
+ * EPSV bla (|||port|)
+ */
+ for (p = response_text; *p && !isspace(UCH(*p)); p++) {
+ ; /* null body */
+ }
+ for ( /*nothing */ ;
+ *p && *p != '(';
+ p++) { /*) */
+ ; /* null body */
+ }
+ status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3);
+ if (status != 5) {
+ fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n");
+ status = HT_NO_CONNECTION;
+ break;
+ }
+ passive_port = (PortNumber) p0;
+
+ sslen = (LY_SOCKLEN) sizeof(ss);
+ if (getpeername(control->socket, SOCKADDR_OF(ss), &sslen) < 0) {
+ fprintf(tfp, "HTFTP: getpeername(control) failed\n");
+ status = HT_NO_CONNECTION;
+ break;
+ }
+ if (getnameinfo(SOCKADDR_OF(ss),
+ sslen,
+ dst,
+ (socklen_t) sizeof(dst),
+ NULL, 0, NI_NUMERICHOST)) {
+ fprintf(tfp, "HTFTP: getnameinfo failed\n");
+ status = HT_NO_CONNECTION;
+ break;
+ }
+ }
+#else
+ status = send_cmd_1("PASV");
+ if (status != 2) {
+ if (status < 0)
+ continue; /* retry or Bad return */
+ status = -status; /* bad reply */
+ break;
+ }
+ 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(tfp, "HTFTP: PASV reply has no inet address!\n");
+ status = HT_NO_CONNECTION;
+ break;
+ }
+ passive_port = (PortNumber) ((p0 << 8) + p1);
+#endif /* INET6 */
+ CTRACE((tfp, "HTFTP: Server is listening on port %d\n",
+ passive_port));
+
+ /* Open connection for data: */
+
+#ifdef INET6
+ HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port);
+#else
+ HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/",
+ STR_FTP_URL, h0, h1, h2, h3, passive_port);
+#endif
+ status = HTDoConnect(command, "FTP data", passive_port, &data_soc);
+ FREE(command);
+
+ if (status < 0) {
+ (void) HTInetStatus(gettext("connect for data"));
+ NETCLOSE(data_soc);
+ break;
+ }
+
+ CTRACE((tfp, "FTP data connected, socket %d\n", data_soc));
+ }
+ status = 0;
+ break; /* No more retries */
+
+ } /* for retries */
+ CTRACE((tfp, "setup_connection returns %d\n", status));
+ return status;
+}
+
+/* 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.
+ */
+int HTFTPLoad(const char *name,
+ HTParentAnchor *anchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ BOOL isDirectory = NO;
+ HTAtom *encoding = NULL;
+ int status, final_status;
+ int outstanding = 1; /* outstanding control connection responses
+
+ that we are willing to wait for, if we
+ get to the point of reading data - kw */
+ HTFormat format;
+
+ CTRACE((tfp, "HTFTPLoad(%s) %s connection\n",
+ name,
+ (ftp_local_passive
+ ? "passive"
+ : "normal")));
+
+ HTReadProgress((off_t) 0, (off_t) 0);
+
+ status = setup_connection(name, anchor);
+ 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() */
+ char *vmsname = NULL;
+ BOOL binary;
+ const char *type = NULL;
+ char *types = 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;
+ CTRACE((tfp,
+ "HTFTP: Rejecting path due to illegal escaped slash.\n"));
+ return -1;
+ }
+ }
+
+ if (!*filename) {
+ StrAllocCopy(filename, "/");
+ type = "D";
+ } else if ((type = types = strrchr(filename, ';')) != NULL) {
+ /*
+ * Check and trim the type= parameter. - FM
+ */
+ if (!strncasecomp((type + 1), "type=", 5)) {
+ switch (TOUPPER(*(type + 6))) {
+ case 'D':
+ *types = '\0';
+ type = "D";
+ break;
+ case 'A':
+ *types = '\0';
+ type = "A";
+ break;
+ case 'I':
+ *types = '\0';
+ type = "I";
+ break;
+ default:
+ type = "";
+ break;
+ }
+ if (!*filename) {
+ *filename = '/';
+ *(filename + 1) = '\0';
+ }
+ }
+ if (*type != '\0') {
+ CTRACE((tfp, "HTFTP: type=%s\n", type));
+ }
+ }
+ HTUnEscape(filename);
+ CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename));
+ if (filename[1] == '~') {
+ /*
+ * Check if translation of HOME as tilde is supported,
+ * and adjust filename if so. - FM
+ */
+ char *cp2 = NULL;
+ char *fn = NULL;
+
+ if ((cp2 = StrChr((filename + 1), '/')) != NULL) {
+ *cp2 = '\0';
+ }
+ status = send_cmd_1("PWD");
+ if (status == 2 && response_text[5] == '/') {
+ status = send_cwd(filename + 1);
+ if (status == 2) {
+ StrAllocCopy(fn, (filename + 1));
+ if (cp2) {
+ *cp2 = '/';
+ if (fn[strlen(fn) - 1] != '/') {
+ StrAllocCat(fn, cp2);
+ } else {
+ StrAllocCat(fn, (cp2 + 1));
+ }
+ cp2 = NULL;
+ }
+ FREE(fname);
+ fname = filename = fn;
+ }
+ }
+ if (cp2) {
+ *cp2 = '/';
+ }
+ }
+ if (strlen(filename) > 3) {
+ char *cp2;
+
+ if (((cp2 = strrchr(filename, '.')) != NULL &&
+ 0 == strncasecomp(cp2, ".me", 3)) &&
+ (cp2[3] == '\0' || cp2[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) ||
+ (cp2 > (filename + 3) &&
+ 0 == strncasecomp((cp2 - 4), "read.me", 7))) {
+ *cp2 = '\0';
+ format = HTFileFormat(filename, &encoding, NULL);
+ *cp2 = '.';
+ } else {
+ format = HTFileFormat(filename, &encoding, NULL);
+ }
+ } else {
+ format = HTFileFormat(filename, &encoding, NULL);
+ }
+ } else {
+ format = HTFileFormat(filename, &encoding, NULL);
+ }
+ format = HTCharsetFormat(format, anchor, -1);
+ binary = (BOOL) (encoding != WWW_ENC_8BIT &&
+ encoding != WWW_ENC_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 == WWW_DOWNLOAD ||
+ format_out == 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->is_binary) {
+ /*
+ * Act on our setting if not already set. - FM
+ */
+ const char *mode = binary ? "I" : "A";
+
+ status = send_cmd_2("TYPE", mode);
+ if (status != 2) {
+ init_help_message_cache(); /* to free memory */
+ return ((status < 0) ? status : -status);
+ }
+ control->is_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;
+ BOOL found_tilde = 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;
+ CTRACE((tfp,
+ "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';
+ CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename));
+ cp = HTVMS_name("", filename);
+ CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp));
+ if ((cp1 = strrchr(cp, ']')) != NULL) {
+ strcpy(filename, ++cp1);
+ CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
+ *cp1 = '\0';
+ status = send_cwd(cp);
+ if (status != 2) {
+ char *dotslash = 0;
+
+ if ((cp1 = StrChr(cp, '[')) != NULL) {
+ *cp1++ = '\0';
+ status = send_cwd(cp);
+ if (status != 2) {
+ FREE(fname);
+ init_help_message_cache(); /* to free memory */
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ return ((status < 0) ? status : -status);
+ }
+ HTSprintf0(&dotslash, "[.%s", cp1);
+ status = send_cwd(dotslash);
+ FREE(dotslash);
+ 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') {
+ int cplen = (int) (cp1 - cp);
+
+ strcpy(filename, cp1);
+ CTRACE((tfp, "HTFTP: Filename '%s'\n", filename));
+ HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename);
+ status = send_cwd(vmsname);
+ if (status != 2) {
+ HTSprintf(&vmsname, "%.*s[000000]", cplen, cp);
+ status = send_cwd(vmsname);
+ if (status != 2) {
+ HTSprintf(&vmsname, "%.*s", cplen, cp);
+ status = send_cwd(vmsname);
+ if (status != 2) {
+ FREE(fname);
+ init_help_message_cache();
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ return ((status < 0) ? status : -status);
+ }
+ }
+ } else {
+ HTSprintf0(&vmsname, "000000");
+ filename = vmsname;
+ }
+ }
+ } else if (0 == strcmp(cp, (filename + 1))) {
+ status = send_cwd(cp);
+ if (status != 2) {
+ HTSprintf0(&vmsname, "%s:", cp);
+ status = send_cwd(vmsname);
+ if (status != 2) {
+ FREE(fname);
+ init_help_message_cache(); /* to free memory */
+ NETCLOSE(control->socket);
+ control->socket = -1;
+ return ((status < 0) ? status : -status);
+ }
+ }
+ HTSprintf0(&vmsname, "000000");
+ filename = vmsname;
+ }
+ }
+ /* 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] */
+ status = send_cmd_1("PWD");
+ 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) {
+ char *tmp = 0;
+ unsigned len = 4;
+
+ StrAllocCopy(tmp, cp);
+ if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) {
+ len += (cp2 - cp);
+ } else {
+ len += (cp1 - cp);
+ }
+ tmp[len] = 0;
+ StrAllocCat(tmp, "]");
+
+ status = send_cwd(tmp);
+ FREE(tmp);
+
+ 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 (!(strcmp(filename, "/~")) ||
+ (included_device && 0 == strcmp(filename, "000000")) ||
+ (strlen(filename) == 1 && *filename == '/')) {
+ isDirectory = YES;
+ status = send_cmd_1("LIST");
+ 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 (!StrNCmp(filename, "/~", 2)) {
+ filename += 2;
+ found_tilde = TRUE;
+ }
+ CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename));
+ if (!included_device &&
+ (cp = StrChr(filename, '/')) != NULL &&
+ (cp1 = strrchr(cp, '/')) != NULL &&
+ (cp1 - cp) > 1) {
+ char *tmp = 0;
+
+ HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1);
+
+ CTRACE((tfp, "change path '%s'\n", tmp));
+ while ((cp2 = strrchr(tmp, '/')) != NULL)
+ *cp2 = '.';
+ CTRACE((tfp, "...to path '%s'\n", tmp));
+
+ status = send_cwd(tmp);
+ FREE(tmp);
+
+ 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 && !found_tilde) {
+ 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') {
+ status = send_cwd(filename + 1);
+ 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)
+ status = send_cmd_1("LIST");
+ else
+ status = send_cmd_1("NLST");
+ 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';
+ status = send_cwd(filename);
+ if (status == 2) {
+ if (*cp == '\0') {
+ isDirectory = YES;
+ if (use_list)
+ status = send_cmd_1("LIST");
+ else
+ status = send_cmd_1("NLST");
+ 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')) {
+ /*
+ * If we are retrieving a file we will (except for CMS) use
+ * binary mode, which lets us use the size command supported by
+ * ftp servers which implement RFC 3659. Knowing the size lets
+ * us in turn display ETA in the progress message -TD
+ */
+ if (control->is_binary) {
+ int code;
+
+ status = send_cmd_2("SIZE", filename);
+ if (status == 2) {
+#if !defined(HAVE_LONG_LONG) && defined(GUESS_PRI_off_t)
+ long size;
+
+ if (sscanf(response_text, "%d %ld", &code, &size) == 2) {
+ anchor->content_length = (off_t) size;
+ }
+#else
+ off_t size;
+ if (sscanf(response_text, "%d %" SCN_off_t, &code, &size)
+ == 2) {
+ anchor->content_length = size;
+ }
+#endif
+ }
+ }
+ status = send_cmd_2("RETR", filename);
+ if (status >= 5) {
+ int check;
+
+ if (Broken_RETR) {
+ CTRACE((tfp, "{{reconnecting...\n"));
+ close_connection(control);
+ check = setup_connection(name, anchor);
+ CTRACE((tfp, "...done }}reconnecting\n"));
+ if (check < 0)
+ return check;
+ }
+ }
+ } 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();
+
+ status = send_cwd(filename);
+ if (status == 2) { /* Succeeded : let's NAME LIST it */
+ isDirectory = YES;
+ if (use_list)
+ status = send_cmd_1("LIST");
+ else
+ status = send_cmd_1("NLST");
+ }
+ }
+ FREE(fname);
+ FREE(vmsname);
+ 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:
+ if (!ftp_local_passive) {
+ /* Wait for the connection */
+ LY_SOCKADDR soc_A;
+ LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_A);
+
+#ifdef SOCKS
+ if (socks_flag)
+ status = Raccept((int) master_socket,
+ SOCKADDR_OF(soc_A),
+ &soc_addrlen);
+ else
+#endif /* SOCKS */
+ status = accept((int) master_socket,
+ SOCKADDR_OF(soc_A),
+ &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;
+ }
+
+ if (isDirectory) {
+ if (server_type == UNIX_SERVER && !unsure_type &&
+ !strcmp(response_text,
+ "150 Opening ASCII mode data connection for /bin/dl.\n")) {
+ CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n"));
+ server_type = DLS_SERVER;
+ }
+ final_status = read_directory(anchor, name, format_out, sink);
+ if (final_status > 0) {
+ if (server_type != CMS_SERVER)
+ if (outstanding-- > 0) {
+ status = response(NULL);
+ if (status < 0 ||
+ (status == 2 && !StrNCmp(response_text, "221", 3)))
+ outstanding = 0;
+ }
+ } else { /* HT_INTERRUPTED */
+ /* User may have pressed 'z' to give up because no
+ packets got through, so let's not make them wait
+ any longer - kw */
+ outstanding = 0;
+ }
+
+ if (data_soc != -1) { /* normally done in read_directory */
+ CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
+ status = NETCLOSE(data_soc);
+ if (status == -1)
+ HTInetStatus("close"); /* Comment only */
+ }
+ status = final_status;
+ } else {
+ int rv;
+ 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 (!IsUnityEnc(encoding)) {
+ /*
+ * We already know from the call to HTFileFormat above that this is
+ * a compressed file, no need to look at the filename again. - kw
+ */
+ StrAllocCopy(anchor->content_type, format->name);
+ StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding));
+ format = HTAtom_for("www/compressed");
+
+ } else {
+ int rootlen;
+ CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen);
+
+ if (cft != cftNone) {
+ FileName[rootlen] = '\0';
+ format = HTFileFormat(FileName, &encoding, NULL);
+ format = HTCharsetFormat(format, anchor, -1);
+ StrAllocCopy(anchor->content_type, format->name);
+ format = HTAtom_for("www/compressed");
+ }
+
+ switch (cft) {
+ case cftCompress:
+ StrAllocCopy(anchor->content_encoding, "x-compress");
+ break;
+ case cftGzip:
+ StrAllocCopy(anchor->content_encoding, "x-gzip");
+ break;
+ case cftDeflate:
+ StrAllocCopy(anchor->content_encoding, "x-deflate");
+ break;
+ case cftBzip2:
+ StrAllocCopy(anchor->content_encoding, "x-bzip2");
+ break;
+ case cftBrotli:
+ StrAllocCopy(anchor->content_encoding, "x-brotli");
+ break;
+ case cftNone:
+ break;
+ }
+ }
+ FREE(FileName);
+
+ _HTProgress(gettext("Receiving FTP file."));
+ rv = HTParseSocket(format, format_out, anchor, data_soc, sink);
+
+ HTInitInput(control->socket);
+ /* Reset buffering to control connection DD 921208 */
+
+ if (rv < 0) {
+ if (rv == -2) /* weird error, don't expect much response */
+ outstanding--;
+ else if (rv == HT_INTERRUPTED || rv == -1)
+ /* User may have pressed 'z' to give up because no
+ packets got through, so let's not make them wait
+ longer - kw */
+ outstanding = 0;
+ CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc));
+ status = NETCLOSE(data_soc);
+ } else {
+ status = 2; /* data_soc already closed in HTCopy - kw */
+ }
+
+ if (status < 0 && rv != HT_INTERRUPTED && rv != -1) {
+ (void) HTInetStatus("close"); /* Comment only */
+ } else {
+ if (rv != HT_LOADED && outstanding--) {
+ status = response(NULL); /* Pick up final reply */
+ if (status != 2 && rv != HT_INTERRUPTED && rv != -1) {
+ data_soc = -1; /* invalidate it */
+ init_help_message_cache(); /* to free memory */
+ return HTLoadError(sink, 500, response_text);
+ } else if (status == 2 && !StrNCmp(response_text, "221", 3)) {
+ outstanding = 0;
+ }
+ }
+ }
+ final_status = HT_LOADED;
+ }
+ while (outstanding-- > 0 &&
+ (status > 0)) {
+ status = response(NULL);
+ if (status == 2 && !StrNCmp(response_text, "221", 3))
+ break;
+ }
+ data_soc = -1; /* invalidate it */
+ CTRACE((tfp, "HTFTPLoad: normal end; "));
+ if (control->socket < 0) {
+ CTRACE((tfp, "control socket is %d\n", control->socket));
+ } else {
+ CTRACE((tfp, "closing control socket %d\n", control->socket));
+ status = NETCLOSE(control->socket);
+ if (status == -1)
+ HTInetStatus("control connection close"); /* Comment only */
+ }
+ control->socket = -1;
+ init_help_message_cache(); /* to free memory */
+ /* returns HT_LOADED (always for file if we get here) or error */
+ return final_status;
+} /* open_file_read */
+
+/*
+ * This function frees any user entered password, so that
+ * it must be entered again for a future request. - FM
+ */
+void HTClearFTPPassword(void)
+{
+ /*
+ * Need code to check cached documents from non-anonymous ftp accounts and
+ * do something to ensure that they no longer can be accessed without a new
+ * retrieval. - FM
+ */
+
+ /*
+ * Now free the current user entered password, if any. - FM
+ */
+ FREE(user_entered_password);
+}
+
+#endif /* ifndef DISABLE_FTP */