/*
* $LynxId: HTFTP.c,v 1.126 2014/07/24 22:08:24 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 unsigned master_socket; /* Listening socket = invalid */
static char port_command[255]; /* Command for setting the port */
static fd_set open_sockets; /* Mask of active channels */
static unsigned 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");
assert(filename != NULL);
assert(nodename != NULL);
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");
assert(con != NULL);
}
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);
} /* 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((char *) 0); /* 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 = (unsigned) 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", 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)
{
#ifdef INET6
struct sockaddr_storage soc_address; /* Binary network address */
struct sockaddr_in *soc_in = (struct sockaddr_in *) &soc_address;
int af;
LY_SOCKLEN slen;
#else
struct sockaddr_in soc_address; /* Binary network address */
struct sockaddr_in *soc_in = &soc_address;
#endif /* INET6 */
int new_socket; /* Will be master_socket */
FD_ZERO(&open_sockets); /* Clear our record of open sockets */
num_sockets = 0;
#ifndef REPEAT_LISTEN
if (have_socket)
return master_socket; /* Done already */
#endif /* !REPEAT_LISTEN */
#ifdef INET6
/* query address family of control connection */
slen = (LY_SOCKLEN) sizeof(soc_address);
if (getsockname(control->socket, (struct sockaddr *) &soc_address,
&slen) < 0) {
return HTInetStatus("getsockname failed");
}
af = ((struct sockaddr *) &soc_address)->sa_family;
memset(&soc_address, 0, sizeof(soc_address));
#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_address, 0, sizeof(soc_address));
((struct sockaddr *) &soc_address)->sa_family = af;
switch (af) {
case AF_INET:
#ifdef SIN6_LEN
((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in);
#endif /* SIN6_LEN */
break;
case AF_INET6:
#ifdef SIN6_LEN
((struct sockaddr *) &soc_address)->sa_len = sizeof(struct sockaddr_in6);
#endif /* SIN6_LEN */
break;
default:
HTInetStatus("AF");
}
#else
soc_in->sin_family = AF_INET; /* Family = internet, host order */
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_in->sin_port = htons(port_number);
#else
soc_address.sin_port = htons(port_number);
#endif /* INET6 */
#ifdef SOCKS
if (socks_flag)
if ((status = Rbind(new_socket,
(struct sockaddr *) &soc_address,
/* Cast to generic sockaddr */
SOCKADDR_LEN(soc_address)
#ifndef SHORTENED_RBIND
,socks_bind_remoteAddr
#endif /* !SHORTENED_RBIND */
)) == 0) {
break;
} else
#endif /* SOCKS */
if ((status = bind(new_socket,
(struct sockaddr *) &soc_address,
/* Cast to generic sockaddr */
SOCKADDR_LEN(soc_address)
)) == 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_address);
#ifdef SOCKS
if (socks_flag)
status = Rgetsockname(control->socket,
(struct sockaddr *) &soc_address,
&address_length);
else
#endif /* SOCKS */
status = getsockname(control->socket,
(struct sockaddr *) &soc_address,
&address_length);
if (status < 0) {
close(new_socket);
return HTInetStatus("getsockname");
}
#ifdef INET6
CTRACE((tfp, "HTFTP: This host is %s\n",
HTInetString((void *) soc_in)));
soc_in->sin_port = 0; /* Unspecified: please allocate */
#else
CTRACE((tfp, "HTFTP: This host is %s\n",
HTInetString(soc_in)));
soc_address.sin_port = 0; /* Unspecified: please allocate */
#endif /* INET6 */
#ifdef SOCKS
if (socks_flag)
status = Rbind(new_socket,
(struct sockaddr *) &soc_address,
/* Cast to generic sockaddr */
sizeof(soc_address)
#ifndef SHORTENED_RBIND
,socks_bind_remoteAddr
#endif /* !SHORTENED_RBIND */
);
else
#endif /* SOCKS */
status = bind(new_socket,
(struct sockaddr *) &soc_address,
/* Cast to generic sockaddr */
SOCKADDR_LEN(soc_address)
);
if (status < 0) {
close(new_socket);
return HTInetStatus("bind");
}
address_length = sizeof(soc_address);
#ifdef SOCKS
if (socks_flag)
status = Rgetsockname(new_socket,
(struct sockaddr *) &soc_address,
&address_length);
else
#endif /* SOCKS */
status = getsockname(new_socket,
(struct sockaddr *) &soc_address,
&address_length);
if (status < 0) {
close(new_socket);
return HTInetStatus("getsockname");
}
}
#endif /* POLL_PORTS */
#ifdef INET6
CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
(int) ntohs(soc_in->sin_port),
HTInetString((void *) soc_in)));
#else
CTRACE((tfp, "HTFTP: bound to port %d on %s\n",
(int) ntohs(soc_in->sin_port),
HTInetString(soc_in)));
#endif /* INET6 */
#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 (((struct sockaddr *) &soc_address)->sa_family) {
case AF_INET:
#endif /* INET6 */
sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d%c%c",
(int) *((unsigned char *) (&soc_in->sin_addr) + 0),
(int) *((unsigned char *) (&soc_in->sin_addr) + 1),
(int) *((unsigned char *) (&soc_in->sin_addr) + 2),
(int) *((unsigned char *) (&soc_in->sin_addr) + 3),
(int) *((unsigned char *) (&soc_in->sin_port) + 0),
(int) *((unsigned char *) (&soc_in->sin_port) + 1),
CR, LF);
#ifdef INET6
break;
case AF_INET6:
{
char hostbuf[MAXHOSTNAMELEN];
char portbuf[MAXHOSTNAMELEN];
getnameinfo((struct sockaddr *) &soc_address,
SOCKADDR_LEN(soc_address),
hostbuf,
(socklen_t) sizeof(hostbuf),
portbuf,
(socklen_t) sizeof(portbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
sprintf(port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf,
CR, LF);
break;
}
default:
sprintf(port_command, "JUNK%c%c", CR, LF);
break;
}
#endif /* INET6 */
/* 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, day);
TheDate = atoi(date);
LYStrNCpy(ThisYear, printable + 20, 4);
sprintf(LastYear, "%d", (atoi(ThisYear) - 1));
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) {
FREE(entry_info->filename);
FREE(entry_info->linkname);
FREE(entry_info->type);
FREE(entry_info->date);
}
/* dont free the struct */
}
/*
* is_ls_date() --
* Return TRUE if s points to a string of the form:
* "Sep 1 1990 " or
* "Sep 11 11:59 " or
* "Dec 12 1989 " or
* "FCv 23 1990 " ...
*/
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");
assert(entry_info != NULL);
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 ! */
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, "text/plain");
}
}
}
/*
* 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);
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) - 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) - 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",
entry_info->size);
else
sprintf(string_buffer, " %luKb",
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(0) 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);
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;
struct sockaddr_storage 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, (struct sockaddr *) &ss,
&sslen) < 0) {
fprintf(tfp, "HTFTP: getpeername(control) failed\n");
status = HT_NO_CONNECTION;
break;
}
if (getnameinfo((struct sockaddr *) &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 != HTAtom_for("8bit") &&
encoding != HTAtom_for("7bit"));
if (!binary &&
/*
* Force binary if we're in source, download or dump mode and this is
* not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for
* newlines in text files. Can't do this for VM/CMS or we'll get raw
* EBCDIC. - FM
*/
(format_out == WWW_SOURCE ||
format_out == HTAtom_for("www/download") ||
format_out == HTAtom_for("www/dump")) &&
(server_type != CMS_SERVER))
binary = TRUE;
if (!binary && type && *type == 'I') {
/*
* Force binary if we had ;type=I - FM
*/
binary = TRUE;
} else if (binary && type && *type == 'A') {
/*
* Force ASCII if we had ;type=A - FM
*/
binary = FALSE;
}
if (binary != control->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;
off_t size;
status = send_cmd_2("SIZE", filename);
if (status == 2 &&
sscanf(response_text, "%d %" PRI_off_t, &code, &size) == 2) {
anchor->content_length = size;
}
}
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 */
#ifdef INET6
struct sockaddr_storage soc_address;
#else
struct sockaddr_in soc_address;
#endif /* INET6 */
LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_address);
#ifdef SOCKS
if (socks_flag)
status = Raccept((int) master_socket,
(struct sockaddr *) &soc_address,
&soc_addrlen);
else
#endif /* SOCKS */
status = accept((int) master_socket,
(struct sockaddr *) &soc_address,
&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(0);
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 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(0); /* 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(0);
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 */