aboutsummaryrefslogtreecommitdiffstats
path: root/ftp.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ftp.c871
1 files changed, 871 insertions, 0 deletions
diff --git a/ftp.c b/ftp.c
new file mode 100644
index 0000000..b2e3def
--- /dev/null
+++ b/ftp.c
@@ -0,0 +1,871 @@
+#include <stdio.h>
+#include <pwd.h>
+#include <Str.h>
+#include <ctype.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#include "fm.h"
+#include "html.h"
+#include "myctype.h"
+
+#ifdef DEBUG
+#include <malloc.h>
+#endif /* DEBUG */
+
+#include <sys/socket.h>
+#if defined(FTPPASS_HOSTNAMEGEN) || defined(INET6)
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#endif
+
+typedef struct _FTP {
+ FILE *rcontrol;
+ FILE *wcontrol;
+ FILE *data;
+} *FTP;
+
+#define FtpError(status) ((status)<0)
+#define FTPDATA(ftp) ((ftp)->data)
+
+typedef int STATUS;
+
+static FTP ftp;
+
+static Str
+read_response1(FTP ftp)
+{
+ char c;
+ Str buf = Strnew();
+ while (1) {
+ c = getc(ftp->rcontrol);
+ if (c == '\r') {
+ c = getc(ftp->rcontrol);
+ if (c == '\n') {
+ Strcat_charp(buf, "\r\n");
+ break;
+ }
+ else {
+ Strcat_char(buf, '\r');
+ Strcat_char(buf, c);
+ }
+ }
+ else if (c == '\n') {
+ Strcat_charp(buf, "\r\n");
+ break;
+ }
+ else if (feof(ftp->rcontrol))
+ break;
+ else
+ Strcat_char(buf, c);
+ }
+ return buf;
+}
+
+Str
+read_response(FTP ftp)
+{
+ Str tmp;
+
+ tmp = read_response1(ftp);
+ if (feof(ftp->rcontrol)) {
+ return tmp;
+ }
+ if (tmp->ptr[3] == '-') {
+ /* RFC959 4.2 FTP REPLIES */
+ /* multi-line response start */
+ /*
+ * Thus the format for multi-line replies is that the
+ * first line will begin with the exact required reply
+ * code, followed immediately by a Hyphen, "-" (also known
+ * as Minus), followed by text. The last line will begin
+ * with the same code, followed immediately by Space <SP>,
+ * optionally some text, and the Telnet end-of-line code. */
+ while (1) {
+ tmp = read_response1(ftp);
+ if (feof(ftp->rcontrol)) {
+ break;
+ }
+ if (IS_DIGIT(tmp->ptr[0])
+ && IS_DIGIT(tmp->ptr[1])
+ && IS_DIGIT(tmp->ptr[2])
+ && tmp->ptr[3] == ' ') {
+ break;
+ }
+ }
+ }
+ return tmp;
+}
+
+int
+FtpLogin(FTP * ftp_return, char *host, char *user, char *pass)
+{
+ Str tmp;
+ FTP ftp = New(struct _FTP);
+ int fd;
+ *ftp_return = ftp;
+ fd = openSocket(host, "ftp", 21);
+ if (fd < 0)
+ return -1;
+#ifdef FTPPASS_HOSTNAMEGEN
+ if (!strcmp(user, "anonymous")) {
+ size_t n = strlen(pass);
+
+ if (n > 0 && pass[n - 1] == '@') {
+ struct sockaddr_in sockname;
+ int socknamelen = sizeof(sockname);
+
+ if (!getsockname(fd, (struct sockaddr *) &sockname, &socknamelen)) {
+ struct hostent *sockent;
+ Str tmp2 = Strnew_charp(pass);
+
+ if (sockent = gethostbyaddr((char *) &sockname.sin_addr,
+ sizeof(sockname.sin_addr),
+ sockname.sin_family))
+ Strcat_charp(tmp2, sockent->h_name);
+ else
+ Strcat_m_charp(tmp2, "[", inet_ntoa(sockname.sin_addr), "]", NULL);
+
+ pass = tmp2->ptr;
+ }
+ }
+ }
+#endif
+ ftp->rcontrol = fdopen(fd, "rb");
+ ftp->wcontrol = fdopen(dup(fd), "wb");
+ ftp->data = NULL;
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 220)
+ return -1;
+ if (fmInitialized) {
+ message(Sprintf("Sending FTP username (%s) to remote server.\n", user)->ptr, 0, 0);
+ refresh();
+ }
+ tmp = Sprintf("USER %s\r\n", user);
+ fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ /*
+ * Some ftp daemons(e.g. publicfile) return code 230 for user command.
+ */
+ if (atoi(tmp->ptr) == 230)
+ goto succeed;
+ if (atoi(tmp->ptr) != 331)
+ return -1;
+ if (fmInitialized) {
+ message(Sprintf("Sending FTP password to remote server.\n")->ptr, 0, 0);
+ refresh();
+ }
+ tmp = Sprintf("PASS %s\r\n", pass);
+ fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 230)
+ return -1;
+ succeed:
+ return 0;
+}
+
+int
+FtpBinary(FTP ftp)
+{
+ Str tmp;
+ fwrite("TYPE I\r\n", 8, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 200)
+ return -1;
+ return 0;
+}
+
+int
+ftp_pasv(FTP ftp)
+{
+ int n1, n2, n3, n4, p1, p2;
+ int data_s;
+ char *p;
+ Str tmp;
+ int family;
+#ifdef INET6
+ struct sockaddr_storage sin;
+ int sinlen, port;
+ unsigned char d1, d2, d3, d4;
+ char abuf[INET6_ADDRSTRLEN];
+#endif
+
+#ifdef INET6
+ sinlen = sizeof(sin);
+ if (getpeername(fileno(ftp->wcontrol),
+ (struct sockaddr *)&sin, &sinlen) < 0)
+ return -1;
+ family = sin.ss_family;
+#else
+ family = AF_INET;
+#endif
+ switch (family) {
+#ifdef INET6
+ case AF_INET6:
+ fwrite("EPSV\r\n", 6, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 229)
+ return -1;
+ for (p = tmp->ptr + 4; *p && *p != '('; p++);
+ if (*p == '\0')
+ return -1;
+ if (sscanf(++p, "%c%c%c%d%c", &d1, &d2, &d3, &port, &d4) != 5
+ || d1 != d2 || d1 != d3 || d1 != d4)
+ return -1;
+ if (getnameinfo((struct sockaddr *)&sin, sinlen,
+ abuf, sizeof(abuf),
+ NULL, 0, NI_NUMERICHOST) != 0)
+ return -1;
+ tmp = Sprintf("%s", abuf);
+ data_s = openSocket(tmp->ptr, "", port);
+ break;
+#endif
+ case AF_INET:
+ fwrite("PASV\r\n", 6, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 227)
+ return -1;
+ for (p = tmp->ptr + 4; *p && !IS_DIGIT(*p); p++);
+ if (*p == '\0')
+ return -1;
+ sscanf(p, "%d,%d,%d,%d,%d,%d", &n1, &n2, &n3, &n4, &p1, &p2);
+ tmp = Sprintf("%d.%d.%d.%d", n1, n2, n3, n4);
+ data_s = openSocket(tmp->ptr, "", p1 * 256 + p2);
+ break;
+ default:
+ return -1;
+ }
+ if (data_s < 0)
+ return -1;
+ ftp->data = fdopen(data_s, "rb");
+ return 0;
+}
+
+int
+FtpCwd(FTP ftp, char *path)
+{
+ Str tmp;
+
+ tmp = Sprintf("CWD %s\r\n", path);
+ fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (tmp->ptr[0] == '5') {
+ return -1;
+ }
+ return 0;
+}
+
+int
+FtpOpenRead(FTP ftp, char *path)
+{
+ Str tmp;
+
+ if (ftp_pasv(ftp) < 0)
+ return -1;
+ tmp = Sprintf("RETR %s\r\n", path);
+ fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (tmp->ptr[0] == '5') {
+ fclose(ftp->data);
+ ftp->data = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+int
+Ftpfclose(FILE * f)
+{
+ fclose(f);
+ if (f == ftp->data)
+ ftp->data = NULL;
+ read_response(ftp);
+ return 0;
+}
+
+int
+FtpData(FTP ftp, char *cmd, char *arg, char *mode)
+{
+ Str tmp;
+
+ if (ftp_pasv(ftp) < 0)
+ return -1;
+ tmp = Sprintf(cmd, arg);
+ Strcat_charp(tmp, "\r\n");
+ fwrite(tmp->ptr, tmp->length, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (tmp->ptr[0] == '5') {
+ fclose(ftp->data);
+ ftp->data = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+int
+FtpClose(FTP ftp)
+{
+ Str tmp;
+
+ fclose(ftp->data);
+ ftp->data = NULL;
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 226)
+ return -1;
+ return 0;
+}
+
+int
+FtpBye(FTP ftp)
+{
+ Str tmp;
+ int ret_val, control_closed;
+
+ fwrite("QUIT\r\n", 6, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (atoi(tmp->ptr) != 221)
+ ret_val = -1;
+ else
+ ret_val = 0;
+ control_closed = 0;
+ if (ftp->rcontrol != NULL) {
+ fclose(ftp->rcontrol);
+ ftp->rcontrol = NULL;
+ control_closed = 1;
+ }
+ if (ftp->wcontrol != NULL) {
+ fclose(ftp->wcontrol);
+ ftp->wcontrol = NULL;
+ control_closed = 1;
+ }
+ if (control_closed && ftp->data != NULL) {
+ fclose(ftp->data);
+ ftp->data = NULL;
+ }
+ return ret_val;
+}
+
+
+Str FTPDIRtmp;
+
+static int ex_ftpdir_name_size_date(char *, char **, char **, char **);
+static int ftp_system(FTP);
+static char *ftp_escape_str(char *);
+static char *ftp_restore_str(char *);
+
+#define SERVER_NONE 0
+#define UNIXLIKE_SERVER 1
+
+#define FTPDIR_NONE 0
+#define FTPDIR_DIR 1
+#define FTPDIR_LINK 2
+#define FTPDIR_FILE 3
+
+FILE *
+openFTP(ParsedURL * pu)
+{
+ Str tmp2 = Strnew();
+ Str tmp3 = Strnew();
+ Str host;
+ STATUS s;
+ Str curdir;
+ char *user;
+ char *pass;
+ char *fn;
+ char *qdir;
+ char **flist;
+ int i, nfile, nfile_max = 100;
+ Str pwd;
+ int add_auth_cookie_flag;
+ char *realpath = NULL;
+#ifdef JP_CHARSET
+ char code = '\0', ic;
+ Str pathStr;
+#endif
+ int sv_type;
+
+ add_auth_cookie_flag = 0;
+ if (pu->user)
+ user = pu->user;
+ else {
+ Strcat_charp(tmp3, "anonymous");
+ user = tmp3->ptr;
+ }
+ if (pu->pass)
+ pass = pu->pass;
+ else if (pu->user) {
+ pwd = find_auth_cookie(pu->host, pu->user);
+ if (pwd == NULL) {
+ if (fmInitialized) {
+ term_raw();
+ pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
+ term_cbreak();
+ }
+ else {
+ pwd = Strnew_charp((char *) getpass("Password: "));
+ }
+ add_auth_cookie_flag = 1;
+ }
+ pass = pwd->ptr;
+ }
+ else if (ftppasswd != NULL && *ftppasswd != '\0')
+ pass = ftppasswd;
+ else {
+ struct passwd *mypw = getpwuid(getuid());
+ if (mypw == NULL)
+ Strcat_charp(tmp2, "anonymous");
+ else
+ Strcat_charp(tmp2, mypw->pw_name);
+ Strcat_char(tmp2, '@');
+ pass = tmp2->ptr;
+ }
+ s = FtpLogin(&ftp, pu->host, user, pass);
+ if (FtpError(s))
+ return NULL;
+ if (add_auth_cookie_flag)
+ add_auth_cookie(pu->host, pu->user, pwd);
+ if (pu->file == NULL || *pu->file == '\0')
+ goto ftp_dir;
+ else
+ realpath = ftp_restore_str(pu->file);
+
+ /* Get file */
+ FtpBinary(ftp);
+ s = FtpOpenRead(ftp, realpath);
+ if (!FtpError(s)) {
+#ifdef JP_CHARSET
+ pathStr = Strnew_charp(realpath);
+ if ((ic = checkShiftCode(pathStr, code)) != '\0') {
+ pathStr = conv_str(pathStr, (code = ic), InnerCode);
+ realpath = pathStr->ptr;
+ }
+#endif /* JP_CHARSET */
+ pu->file = realpath;
+ return FTPDATA(ftp);
+ }
+
+ /* Get directory */
+ ftp_dir:
+ pu->scheme = SCM_FTPDIR;
+ FTPDIRtmp = Strnew();
+ sv_type = ftp_system(ftp);
+ if (pu->file == NULL || *pu->file == '\0') {
+ if (sv_type == UNIXLIKE_SERVER) {
+ s = FtpData(ftp, "LIST", NULL, "r");
+ }
+ else {
+ s = FtpData(ftp, "NLST", NULL, "r");
+ }
+ curdir = Strnew_charp("/");
+ }
+ else {
+ if (sv_type == UNIXLIKE_SERVER) {
+ s = FtpCwd(ftp, realpath);
+ if (!FtpError(s)) {
+ s = FtpData(ftp, "LIST", NULL, "r");
+ }
+ }
+ else {
+ s = FtpData(ftp, "NLST %s", realpath, "r");
+ }
+ if (realpath[0] == '/')
+ curdir = Strnew_charp(realpath);
+ else
+ curdir = Sprintf("/%s", realpath);
+ if (Strlastchar(curdir) != '/')
+ Strcat_char(curdir, '/');
+ }
+ if (FtpError(s)) {
+ FtpBye(ftp);
+ return NULL;
+ }
+ host = Strnew_charp("ftp://");
+ if (pu->user) {
+ Strcat_m_charp(host, pu->user, "@", NULL);
+ }
+ Strcat_charp(host, pu->host);
+ if (Strlastchar(host) == '/')
+ Strshrink(host, 1);
+ qdir = htmlquote_str(curdir->ptr);
+ FTPDIRtmp = Sprintf("<html><head><title>%s%s</title></head><body><h1>Index of %s%s</h1>\n",
+ host->ptr, qdir, host->ptr, qdir);
+ curdir = Strnew_charp(ftp_escape_str(curdir->ptr));
+ qdir = curdir->ptr;
+ tmp2 = Strdup(curdir);
+ if (Strcmp_charp(curdir, "/") != 0) {
+ Strshrink(tmp2, 1);
+ while (Strlastchar(tmp2) != '/' && tmp2->length > 0)
+ Strshrink(tmp2, 1);
+ }
+ if (sv_type == UNIXLIKE_SERVER) {
+ Strcat_charp(FTPDIRtmp, "<pre><a href=\"");
+ }
+ else {
+ Strcat_charp(FTPDIRtmp, "<ul><li><a href=\"");
+ }
+ Strcat_m_charp(FTPDIRtmp, host->ptr,
+ htmlquote_str(tmp2->ptr),
+ "\">[Upper Directory]</a>\n", NULL);
+
+ flist = New_N(char *, nfile_max);
+ nfile = 0;
+ if (sv_type == UNIXLIKE_SERVER) {
+ char *name, *date, *size, *type_str;
+ int ftype, max_len, len, i, j;
+ Str line_tmp;
+
+ max_len = 0;
+ while (tmp2 = Strfgets(FTPDATA(ftp)), tmp2->length > 0) {
+ Strchop(tmp2);
+ if ((ftype = ex_ftpdir_name_size_date(tmp2->ptr, &name, &date, &size))
+ == FTPDIR_NONE) {
+ continue;
+ }
+ if (!strcmp(".", name) || !strcmp("..", name)) {
+ continue;
+ }
+ len = strlen(name);
+ if (!len)
+ continue;
+ if (ftype == FTPDIR_DIR) {
+ len++;
+ type_str = "/";
+ }
+ else if (ftype == FTPDIR_LINK) {
+ len++;
+ type_str = "@";
+ }
+ else {
+ type_str = "";
+ }
+ if (max_len < len)
+ max_len = len;
+ line_tmp = Sprintf("%s%s %-12.12s %6.6s", name, type_str, date, size);
+ flist[nfile++] = line_tmp->ptr;
+ if (nfile == nfile_max) {
+ nfile_max *= 2;
+ flist = New_Reuse(char *, flist, nfile_max);
+ }
+ }
+ qsort(flist, nfile, sizeof(char *), strCmp);
+ for (j = 0; j < nfile; j++) {
+ fn = flist[j];
+ date = fn + strlen(fn) - 20;
+ if (*(date - 1) == '/') {
+ ftype = FTPDIR_DIR;
+ *(date - 1) = '\0';
+ }
+ else if (*(date - 1) == '@') {
+ ftype = FTPDIR_LINK;
+ *(date - 1) = '\0';
+ }
+ else {
+ ftype = FTPDIR_FILE;
+ *date = '\0';
+ }
+ date++;
+ len = strlen(fn);
+ Strcat_m_charp(FTPDIRtmp, "<a href=\"",
+ host->ptr,
+ qdir,
+ ftp_escape_str(fn),
+ "\">",
+ htmlquote_str(fn), NULL);
+ if (ftype == FTPDIR_DIR) {
+ Strcat_charp(FTPDIRtmp, "/");
+ len++;
+ }
+ else if (ftype == FTPDIR_LINK) {
+ Strcat_charp(FTPDIRtmp, "@");
+ len++;
+ }
+ Strcat_charp(FTPDIRtmp, "</a>");
+ for (i = len; i <= max_len; i++) {
+ if ((max_len % 2 + i) % 2) {
+ Strcat_charp(FTPDIRtmp, ".");
+ }
+ else {
+ Strcat_charp(FTPDIRtmp, " ");
+ }
+ }
+ Strcat_m_charp(FTPDIRtmp, date, "\n", NULL);
+ }
+ Strcat_charp(FTPDIRtmp, "</pre></body></html>\n");
+ }
+ else {
+ while (tmp2 = Strfgets(FTPDATA(ftp)), tmp2->length > 0) {
+ Strchop(tmp2);
+ flist[nfile++] = mybasename(tmp2->ptr);
+ if (nfile == nfile_max) {
+ nfile_max *= 2;
+ flist = New_Reuse(char *, flist, nfile_max);
+ }
+ }
+ qsort(flist, nfile, sizeof(char *), strCmp);
+ for (i = 0; i < nfile; i++) {
+ fn = flist[i];
+ Strcat_m_charp(FTPDIRtmp, "<li><a href=\"",
+ host->ptr, qdir,
+ ftp_escape_str(fn),
+ "\">",
+ htmlquote_str(fn),
+ "</a>\n", NULL);
+ }
+ Strcat_charp(FTPDIRtmp, "</ul></body></html>\n");
+ }
+
+ FtpClose(ftp);
+ FtpBye(ftp);
+ return NULL;
+}
+
+static int
+ftp_system(FTP ftp)
+{
+ int sv_type = SERVER_NONE;
+ Str tmp;
+
+ fwrite("SYST\r\n", 6, sizeof(char), ftp->wcontrol);
+ fflush(ftp->wcontrol);
+ tmp = read_response(ftp);
+ if (strstr(tmp->ptr, "UNIX") != NULL
+ || !strncmp(tmp->ptr + 4, "Windows_NT", 10)) { /* :-) */
+ sv_type = UNIXLIKE_SERVER;
+ }
+
+ return (sv_type);
+}
+
+static char *
+ftp_escape_str(char *str)
+{
+ Str s = Strnew();
+ char *p, buf[5];
+ unsigned char c;
+
+ for (; (c = (unsigned char) *str) != '\0'; str++) {
+ p = NULL;
+ if (c < '!' || c > '~'
+ || c == '#' || c == '?' || c == '+'
+ || c == '&' || c == '<' || c == '>' || c == '"' || c == '%') {
+ sprintf(buf, "%%%02X", c);
+ p = buf;
+ }
+ if (p)
+ Strcat_charp(s, p);
+ else
+ Strcat_char(s, *str);
+ }
+ return s->ptr;
+}
+
+#define XD_CTOD(c) {\
+ if (c >= '0' && c <= '9') {\
+ c -= (unsigned char)'0';\
+ } else if (c >= 'a' && c <= 'f') {\
+ c = c - (unsigned char)'a' + (unsigned char)10;\
+ } else if (c >= 'A' && c <= 'F') {\
+ c = c - (unsigned char)'A' + (unsigned char)10;\
+ } else {\
+ goto skip;\
+ }\
+}
+
+static char *
+ftp_restore_str(char *str)
+{
+ Str s = Strnew();
+ char *p;
+ unsigned char c, code[2];
+
+ for (; *str; str++) {
+ p = NULL;
+ if (*str == '%' && str[1] != '\0' && str[2] != '\0') {
+ c = (unsigned char) str[1];
+ XD_CTOD(c)
+ code[0] = c * (unsigned char) 16;
+ c = (unsigned char) str[2];
+ XD_CTOD(c)
+ code[0] += c;
+ code[1] = '\0';
+ p = (char *) code;
+ str += 2;
+ }
+ skip:
+ if (p)
+ Strcat_charp(s, p);
+ else
+ Strcat_char(s, *str);
+ }
+ return s->ptr;
+}
+
+#define EX_SKIP_SPACE(cp) {\
+ while (IS_SPACE(*cp) && *cp != '\0') cp++;\
+ if (*cp == '\0') {\
+ goto done;\
+ }\
+}
+#define EX_SKIP_NONE_SPACE(cp) {\
+ while (!IS_SPACE(*cp) && *cp != '\0') cp++;\
+ if (*cp == '\0') {\
+ goto done;\
+ }\
+}
+
+static Str size_int2str(unsigned long);
+
+static int
+ex_ftpdir_name_size_date(char *line, char **name, char **date, char **sizep)
+{
+ int ftype = FTPDIR_NONE;
+ char *cp, *endp;
+ Str date_str, name_str, size_str;
+ unsigned long size;
+
+ if (strlen(line) < 11) {
+ goto done;
+ }
+ /* skip permission */
+ if (!IS_SPACE(line[10])) {
+ goto done;
+ }
+ cp = line + 11;
+
+ /* skip link count */
+ EX_SKIP_SPACE(cp)
+ while (IS_DIGIT(*cp) && *cp != '\0')
+ cp++;
+ if (!IS_SPACE(*cp) || *cp == '\0') {
+ goto done;
+ }
+ cp++;
+
+ /* skip owner string */
+ EX_SKIP_SPACE(cp)
+ EX_SKIP_NONE_SPACE(cp)
+ cp++;
+
+ /* skip group string */
+ EX_SKIP_SPACE(cp)
+ EX_SKIP_NONE_SPACE(cp)
+ cp++;
+
+ /* extract size */
+ EX_SKIP_SPACE(cp)
+ size = 0;
+ while (*cp && IS_DIGIT(*cp)) {
+ size = size * 10 + *(cp++) - '0';
+ }
+ if (*cp == '\0') {
+ goto done;
+ }
+
+ /* extract date */
+ EX_SKIP_SPACE(cp)
+ if (IS_ALPHA(cp[0]) && IS_ALPHA(cp[1]) && IS_ALPHA(cp[2])
+ && IS_SPACE(cp[3])
+ && (IS_SPACE(cp[4]) || IS_DIGIT(cp[4])) && IS_DIGIT(cp[5])
+ && IS_SPACE(cp[6])
+ && (IS_SPACE(cp[7]) || IS_DIGIT(cp[7])) && IS_DIGIT(cp[8])
+ && (cp[9] == ':' || IS_DIGIT(cp[9]))
+ && IS_DIGIT(cp[10]) && IS_DIGIT(cp[11])
+ && IS_SPACE(cp[12])) {
+ cp[12] = '\0';
+ date_str = Strnew_charp(cp);
+ cp += 13;
+ }
+ else {
+ goto done;
+ }
+
+ /* extract file name */
+ EX_SKIP_SPACE(cp)
+ if (line[0] == 'l') {
+ if ((endp = strstr(cp, " -> ")) == NULL) {
+ goto done;
+ }
+ *endp = '\0';
+ size_str = Strnew_charp("-");
+ ftype = FTPDIR_LINK;
+ }
+ else if (line[0] == 'd') {
+ size_str = Strnew_charp("-");
+ ftype = FTPDIR_DIR;
+ }
+ else {
+ size_str = size_int2str(size);
+ ftype = FTPDIR_FILE;
+ }
+ name_str = Strnew_charp(cp);
+ *date = date_str->ptr;
+ *name = name_str->ptr;
+ *sizep = size_str->ptr;
+
+ done:
+ return (ftype);
+}
+
+static Str
+size_int2str(unsigned long size)
+{
+ Str size_str;
+ int unit;
+ double dtmp;
+ char *size_format, *unit_str;
+
+ dtmp = (double) size;
+ for (unit = 0; unit < 3; unit++) {
+ if (dtmp < 1024) {
+ break;
+ }
+ dtmp /= 1024;
+ }
+ if (!unit || dtmp > 100) {
+ size_format = "%.0f%s";
+ }
+ else if (dtmp > 10) {
+ size_format = "%.1f%s";
+ }
+ else {
+ size_format = "%.2f%s";
+ }
+ switch (unit) {
+ case 3:
+ unit_str = "G";
+ break;
+ case 2:
+ unit_str = "M";
+ break;
+ case 1:
+ unit_str = "K";
+ break;
+ default:
+ unit_str = "";
+ break;
+ }
+ size_str = Sprintf(size_format, dtmp, unit_str);
+
+ return (size_str);
+}
+
+void
+closeFTP(FILE * f)
+{
+ if (f) {
+ fclose(f);
+ if (f == ftp->data)
+ ftp->data = NULL;
+ }
+ FtpBye(ftp);
+}