#include <stdio.h>
#include <pwd.h>
#include <Str.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
#include <errno.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 {
    int keep_alive, binary;
    FILE *rcontrol;
    FILE *wcontrol;
    FILE *data;
} *FTP;

#define FtpRedirected(status) ((status)==-2)
#define FtpError(status) ((status)<0)
#define FTPDATA(ftp) ((ftp)->data)

typedef int STATUS;

static FTP current_ftp;

static Str
read_response1(void)
{
    char c;
    Str buf = Strnew();
    while (1) {
	c = getc(current_ftp->rcontrol);
	if (c == '\r') {
	    c = getc(current_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(current_ftp->rcontrol))
	    break;
	else
	    Strcat_char(buf, c);
    }
    return buf;
}

Str
read_response(void)
{
    Str tmp;

    tmp = read_response1();
    if (feof(current_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();
	    if (feof(current_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(char *host, char *user, char *pass)
{
    Str tmp;
    int fd;
    if ((fd = getKeptSock(host, 21)) >= 0 && current_ftp) {
	if (fileno(current_ftp->rcontrol) == fd)
	    return 0;
	else
	    return -2;
    }
    fd = openSocket(host, "ftp", 21);
    if (fd < 0)
	return fd;
    current_ftp = New(struct _FTP);
    current_ftp->keep_alive = current_ftp->binary = FALSE;
#ifdef FTPPASS_HOSTNAMEGEN
    if (ftppass_hostnamegen && !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
    current_ftp->rcontrol = fdopen(fd, "rb");
    current_ftp->wcontrol = fdopen(dup(fd), "wb");
    current_ftp->data = NULL;
    tmp = read_response();
    if (atoi(tmp->ptr) != 220) {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
    tmp = Sprintf("Sending FTP username (%s) to remote server.", user);
    if (w3m_backend >= BACKEND_VERBOSE)
	backend_message(tmp->ptr, 1);
    else if (fmInitialized) {
	message(tmp->ptr);
	refresh();
    }
    tmp = Sprintf("USER %s\r\n", user);
    fwrite(tmp->ptr, tmp->length, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    tmp = read_response();
    /*
     * Some ftp daemons(e.g. publicfile) return code 230 for user command.
     */
    if (atoi(tmp->ptr) == 230)
	goto succeed;
    if (atoi(tmp->ptr) != 331) {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
    tmp = Sprintf("Sending FTP password to remote server.");
    if (w3m_backend >= BACKEND_VERBOSE)
	backend_message(tmp->ptr, 1);
    else if (fmInitialized) {
	message(tmp->ptr);
	refresh();
    }
    tmp = Sprintf("PASS %s\r\n", pass);
    fwrite(tmp->ptr, tmp->length, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    tmp = read_response();
    if (atoi(tmp->ptr) != 230) {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
succeed:
    return 0;
}

int
FtpBinary(void)
{
    Str tmp;
    if (current_ftp->binary)
	return 0;
    errno = 0;
    tmp = Strnew_charp("Setting FTP data connection binary mode");
    if (w3m_backend >= BACKEND_VERBOSE)
	backend_message(tmp->ptr, 1);
    else if (fmInitialized) {
	message(tmp->ptr);
	refresh();
    }
    fwrite("TYPE I\r\n", 1, sizeof("TYPE I\r\n") - 1, current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    if (ferror(current_ftp->wcontrol))
	return -2;
    tmp = read_response();
    if (!tmp->length)
	return -2;
    if (atoi(tmp->ptr) != 200) {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
    current_ftp->binary = TRUE;
    return 0;
}

int
ftp_pasv(void)
{
    int n1, n2, n3, n4, p1, p2;
    int data_s;
    char *p;
    Str tmp;
    int family;
#ifdef INET6
    struct sockaddr_storage sockaddr;
    int sockaddrlen, port;
    unsigned char d1, d2, d3, d4;
    char abuf[INET6_ADDRSTRLEN];
#endif

    if (current_ftp->data)
	return 0;
    tmp = Strnew_charp("Making FTP data connection");
    if (w3m_backend >= BACKEND_VERBOSE)
	backend_message(tmp->ptr, 1);
    else if (fmInitialized) {
	message(tmp->ptr);
	refresh();
    }
#ifdef INET6
    sockaddrlen = sizeof(sockaddr);
    if (getpeername(fileno(current_ftp->wcontrol),
		    (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
	return -1;
    family = sockaddr.ss_family;
#else
    family = AF_INET;
#endif
    switch (family) {
#ifdef INET6
    case AF_INET6:
	fwrite("EPSV\r\n", 6, sizeof(char), current_ftp->wcontrol);
	fflush(current_ftp->wcontrol);
	tmp = read_response();
	if (!tmp->length)
	    return -2;
	if (atoi(tmp->ptr) != 229) {
	    Strfputs(tmp, stderr);
	    putc('\n', stderr);
	    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 *)&sockaddr, sockaddrlen,
			abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST) != 0)
	    return -1;
	tmp = Strnew_charp(abuf);
	data_s = openSocket(tmp->ptr, "", port);
	break;
#endif
    case AF_INET:
	fwrite("PASV\r\n", 6, sizeof(char), current_ftp->wcontrol);
	fflush(current_ftp->wcontrol);
	tmp = read_response();
	if (!tmp->length)
	    return -2;
	if (atoi(tmp->ptr) != 227) {
	    Strfputs(tmp, stderr);
	    putc('\n', stderr);
	    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;
    current_ftp->data = fdopen(data_s, "rb");
    return 0;
}

int
FtpCwd(char *path)
{
    Str tmp;

    tmp = Sprintf("CWD %s\r\n", path);
    fwrite(tmp->ptr, tmp->length, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    tmp = read_response();
    if (tmp->ptr[0] == '5') {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
    return 0;
}

int
FtpOpenReadBody(char *path)
{
    Str tmp;

    tmp = Sprintf("Retrieving \"%s\" via FTP", path);
    if (w3m_backend >= BACKEND_VERBOSE)
	backend_message(tmp->ptr, 1);
    else if (fmInitialized) {
	message(tmp->ptr);
	refresh();
    }
    tmp = Sprintf("RETR %s\r\n", path);
    fwrite(tmp->ptr, tmp->length, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    tmp = read_response();
    if (tmp->ptr[0] == '5') {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	return -1;
    }
    return 0;
}

int
Ftpfclose(FILE * f)
{
    Str resstr;
    int status = 0, res;

    fclose(f);
    if (f == current_ftp->data) {
	if (current_ftp->keep_alive)
	    for (;;) {
		resstr = read_response();

		switch (res = atoi(resstr->ptr)) {
		case 226:
		    goto end;
		case 426:
		    Strfputs(resstr, stderr);
		    putc('\n', stderr);
		    break;
		default:
		    Strfputs(resstr, stderr);
		    putc('\n', stderr);
		    status = -1;
		    goto end;
		}
	    }
    end:
	current_ftp->data = NULL;
    }
    return status;
}

int
FtpDataBody(char *cmd, char *arg, char *mode)
{
    Str tmp;

    tmp = Sprintf(cmd, arg);
    Strcat_charp(tmp, "\r\n");
    fwrite(tmp->ptr, tmp->length, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    tmp = read_response();
    if (tmp->ptr[0] == '5') {
	Strfputs(tmp, stderr);
	putc('\n', stderr);
	Ftpfclose(current_ftp->data);
	return -1;
    }
    return 0;
}

int
FtpBye(void)
{
    Str tmp;
    int ret_val = 0;

    if (current_ftp->data)
	ret_val = Ftpfclose(current_ftp->data);
    if (current_ftp->wcontrol) {
	fwrite("QUIT\r\n", 6, sizeof(char), current_ftp->wcontrol);
	fflush(current_ftp->wcontrol);
	tmp = read_response();
	if (atoi(tmp->ptr) != 221) {
	    Strfputs(tmp, stderr);
	    putc('\n', stderr);
	    ret_val = -1;
	}
    }
    if (current_ftp->rcontrol) {
	fclose(current_ftp->rcontrol);
	current_ftp->rcontrol = NULL;
    }
    if (current_ftp->wcontrol) {
	fclose(current_ftp->wcontrol);
	current_ftp->wcontrol = NULL;
    }
    discardKeptSock();
    current_ftp->keep_alive = current_ftp->binary = FALSE;
    return ret_val;
}

static void
FtpQuit(void)
{
    if (current_ftp->data) {
	fclose(current_ftp->data);
	current_ftp->data = NULL;
    }

    if (current_ftp->rcontrol) {
	fclose(current_ftp->rcontrol);
	current_ftp->rcontrol = NULL;
    }

    if (current_ftp->wcontrol) {
	fclose(current_ftp->wcontrol);
	current_ftp->wcontrol = NULL;
    }

    current_ftp->keep_alive = current_ftp->binary = FALSE;
}

static int ex_ftpdir_name_size_date(char *, char **, char **, char **);
static int ftp_system(void);
static void closeFTP(FILE *fp);

#define	SERVER_NONE	0
#define	UNIXLIKE_SERVER	1
#define	SYST_FAILURE	2

#define	FTPDIR_NONE	0
#define	FTPDIR_DIR	1
#define	FTPDIR_LINK	2
#define	FTPDIR_FILE	3

void
openFTP(URLFile *uf, ParsedURL *pu, Phase0Env *p0env)
{
    Str FTPDIRtmp;
    Str tmp2 = Strnew();
    Str tmp3 = Strnew();
    Str host;
    STATUS s;
    Str curdir;
    char *user = NULL;
    char *pass = NULL;
    char *fn;
    char *qdir;
    char **flist;
    int i, nfile, nfile_max = 100;
    Str pwd = NULL;
    int add_auth_cookie_flag;
    char *realpathname = NULL;
#ifdef MANY_CHARSET
    const char *code = NULL;
#endif
#ifdef JP_CHARSET
    char code = '\0', ic;
    Str pathStr;
#endif
    int sv_type;

    add_auth_cookie_flag = 0;
    if (pu->user == NULL && pu->pass == NULL) {
	Str uname, pwd;
	if (find_auth_user_passwd(pu, NULL, &uname, &pwd, 0)) {
	    if (uname)
		user = uname->ptr;
	    if (pwd)
		pass = pwd->ptr;
	}
    }
    if (user)
	/* do nothing */ ;
    else if (pu->user)
	user = pu->user;
    else {
	Strcat_charp(tmp3, "anonymous");
	user = tmp3->ptr;
    }
    if (pass)
	/* do nothing */ ;
    else if (pu->pass)
	pass = pu->pass;
    else if (pu->user) {
	pwd = find_sup_auth_cookie(pu->host, pu->port, pu->file, pu->user, p0env);
	if (pwd == NULL) {
	    if (fmInitialized || w3m_backend >= BACKEND_VERBOSE) {
		if (fmInitialized)
		    term_raw();
		pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
		if (fmInitialized)
		    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;
    }
retry_login:
    s = FtpLogin(pu->host, user, pass);
    if (FtpRedirected(s)) {
	uf->redirected = TRUE;
	return;
    }
    if (FtpError(s))
	return;
    if ((p0env->flag & RG_PROC_MASK) == RG_PROC_SUB && !dont_keep_alive &&
	(kept_sock == fileno(current_ftp->rcontrol) ||
	 keepSock(fileno(current_ftp->rcontrol))))
	current_ftp->keep_alive = TRUE;
    if (add_auth_cookie_flag)
	add_sup_auth_cookie(pu->host, pu->port, pu->file, pu->user, pwd, p0env);
    if (pu->file == NULL || *pu->file == '\0')
	goto ftp_dir;
    else
	realpathname = file_unquote(pu->file);

    if (pu->file[strlen(pu->file) - 1] == '/')
	goto ftp_dir;

    /* Get file */
    if (FtpBinary() == -2 && fileno(current_ftp->rcontrol) == kept_sock) {
	discardKeptSock();
	FtpQuit();
	dont_keep_alive = TRUE;
	goto retry_login;
    }
    if (ftp_pasv() < 0) {
	FtpBye();
	return;
    }
    s = FtpOpenReadBody(realpathname);
    if (!FtpError(s)) {
#ifdef JP_CHARSET
	pathStr = Strnew_charp(realpathname);
	if ((ic = checkShiftCode(pathStr, code)) != '\0') {
	    pathStr = conv_str(pathStr, (code = ic), InnerCode);
	    realpathname = pathStr->ptr;
	}
#endif				/* JP_CHARSET */
#ifdef MANY_CHARSET
	realpathname = conv_str2mbStr(realpathname, &code, "")->ptr;
#endif
	pu->file = realpathname;
	uf->stream = newFileStream(FTPDATA(current_ftp), closeFTP);
	return;
    }
    goto ftp_dir1;

    /* Get directory */
ftp_dir:
    switch (ftp_pasv()) {
    case -2:
	discardKeptSock();
	FtpQuit();
	dont_keep_alive = TRUE;
	goto retry_login;
    case -1:
	FtpBye();
	return;
    default:
	break;
    }
ftp_dir1:
    pu->scheme = SCM_FTPDIR;
    FTPDIRtmp = Strnew();
    if ((sv_type = ftp_system()) == SYST_FAILURE &&
	fileno(current_ftp->rcontrol) == kept_sock) {
	discardKeptSock();
	FtpQuit();
	dont_keep_alive = TRUE;
	goto retry_login;
    }
    if (pu->file == NULL || *pu->file == '\0') {
	if (sv_type == UNIXLIKE_SERVER) {
	    s = FtpDataBody("LIST", NULL, "r");
	}
	else {
	    s = FtpDataBody("NLST", NULL, "r");
	}
	curdir = Strnew_charp("/");
    }
    else {
	if (sv_type == UNIXLIKE_SERVER) {
	    s = FtpCwd(realpathname);
	    if (!FtpError(s)) {
		s = FtpDataBody("LIST", NULL, "r");
	    }
	}
	else {
	    s = FtpDataBody("NLST %s", realpathname, "r");
	}
	if (realpathname[0] == '/')
	    curdir = Strnew_charp(realpathname);
	else
	    curdir = Sprintf("/%s", realpathname);
	if (Strlastchar(curdir) != '/')
	    Strcat_char(curdir, '/');
    }
    if (FtpError(s)) {
	FtpBye();
	return;
    }
    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 = html_quote(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(file_quote(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,
		   html_quote(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, j;
	Str line_tmp;

	max_len = 0;
	while (tmp2 = Strfgets(FTPDATA(current_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,
			   html_quote(file_quote(fn)),
			   "\">", html_quote(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(current_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,
			   html_quote(file_quote(fn)),
			   "\">", html_quote(fn), "</a>\n", NULL);
	}
	Strcat_charp(FTPDIRtmp, "</ul></body></html>\n");
    }

    if (current_ftp->keep_alive)
	Ftpfclose(current_ftp->data);
    else
	FtpBye();

    uf->stream = newStrStream(FTPDIRtmp);
    return;
}

static int
ftp_system(void)
{
    int sv_type = SERVER_NONE;
    Str tmp;

    fwrite("SYST\r\n", 6, sizeof(char), current_ftp->wcontrol);
    fflush(current_ftp->wcontrol);
    if (ferror(current_ftp->wcontrol))
	return SYST_FAILURE;
    tmp = read_response();
    if (strstr(tmp->ptr, "UNIX") != NULL
	|| !strncmp(tmp->ptr + 4, "Windows_NT", 10)) {	/* :-) */
	sv_type = UNIXLIKE_SERVER;
    }

    return (sv_type);
}

#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;\
  }\
}

#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(clen_t);

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;
    clen_t 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[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(clen_t 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);
}

static void
closeFTP(FILE *fp)
{
    if (fp)
	Ftpfclose(fp);

    if (fileno(current_ftp->rcontrol) != kept_sock)
	FtpBye();
}

#ifdef SIGALRM
#ifndef FTP_BYE_WAIT
#define FTP_BYE_WAIT (2)
#endif

static MySignalHandler
FtpForceExit(SIGNAL_ARG)
{
    FtpQuit();
    w3m_exit(0);
    SIGNAL_RETURN;
}
#endif

void
FtpExit(void)
{
    if (current_ftp) {
#ifdef SIGALRM
	signal(SIGALRM, FtpForceExit);
	alarm(FTP_BYE_WAIT);
	FtpBye();
#else
	FtpQuit();
#endif
    }
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
