805 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			805 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* $Id: ftp.c,v 1.42 2010/12/15 10:50:24 htrb Exp $ */
 | |
| #include <stdio.h>
 | |
| #ifndef __MINGW32_VERSION
 | |
| #include <pwd.h>
 | |
| #endif /* __MINGW32_VERSION */
 | |
| #include <Str.h>
 | |
| #include <signal.h>
 | |
| #include <setjmp.h>
 | |
| #include <time.h>
 | |
| 
 | |
| #include "fm.h"
 | |
| #include "html.h"
 | |
| #include "myctype.h"
 | |
| 
 | |
| #ifdef DEBUG
 | |
| #include <malloc.h>
 | |
| #endif				/* DEBUG */
 | |
| 
 | |
| #ifndef __MINGW32_VERSION
 | |
| #include <sys/socket.h>
 | |
| #include <netinet/in.h>
 | |
| #include <netdb.h>
 | |
| #include <arpa/inet.h>
 | |
| #else
 | |
| #include <winsock.h>
 | |
| #endif /* __MINGW32_VERSION */
 | |
| 
 | |
| #ifndef HAVE_SOCKLEN_T
 | |
| typedef int socklen_t;
 | |
| #endif
 | |
| 
 | |
| typedef struct _FTP {
 | |
|     char *host;
 | |
|     int port;
 | |
|     char *user;
 | |
|     char *pass;
 | |
|     InputStream rf;
 | |
|     FILE *wf;
 | |
|     FILE *data;
 | |
| } *FTP;
 | |
| 
 | |
| static struct _FTP current_ftp = {
 | |
|     NULL, 0, NULL, NULL, NULL, NULL, NULL
 | |
| };
 | |
| 
 | |
| static JMP_BUF AbortLoading;
 | |
| 
 | |
| static MySignalHandler
 | |
| KeyAbort(SIGNAL_ARG)
 | |
| {
 | |
|     LONGJMP(AbortLoading, 1);
 | |
|     SIGNAL_RETURN;
 | |
| }
 | |
| 
 | |
| static Str
 | |
| ftp_command(FTP ftp, char *cmd, char *arg, int *status)
 | |
| {
 | |
|     Str tmp;
 | |
| 
 | |
|     if (!ftp->host)
 | |
| 	return NULL;
 | |
|     if (cmd) {
 | |
| 	if (arg)
 | |
| 	    tmp = Sprintf("%s %s\r\n", cmd, arg);
 | |
| 	else
 | |
| 	    tmp = Sprintf("%s\r\n", cmd);
 | |
| 	fwrite(tmp->ptr, sizeof(char), tmp->length, ftp->wf);
 | |
| 	fflush(ftp->wf);
 | |
|     }
 | |
|     if (!status)
 | |
| 	return NULL;
 | |
|     *status = -1;		/* error */
 | |
|     tmp = StrISgets(ftp->rf);
 | |
|     if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
 | |
| 	IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ')
 | |
| 	sscanf(tmp->ptr, "%d", status);
 | |
| 
 | |
|     if (tmp->ptr[3] != '-')
 | |
| 	return tmp;
 | |
|     /* 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 = StrISgets(ftp->rf);
 | |
| 	if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
 | |
| 	    IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ') {
 | |
| 	    sscanf(tmp->ptr, "%d", status);
 | |
| 	    break;
 | |
| 	}
 | |
|     }
 | |
|     return tmp;
 | |
| }
 | |
| 
 | |
| static void
 | |
| ftp_close(FTP ftp)
 | |
| {
 | |
|     if (!ftp->host)
 | |
| 	return;
 | |
|     if (ftp->rf) {
 | |
| 	IStype(ftp->rf) &= ~IST_UNCLOSE;
 | |
| 	ISclose(ftp->rf);
 | |
| 	ftp->rf = NULL;
 | |
|     }
 | |
|     if (ftp->wf) {
 | |
| 	fclose(ftp->wf);
 | |
| 	ftp->wf = NULL;
 | |
|     }
 | |
|     if (ftp->data) {
 | |
| 	fclose(ftp->data);
 | |
| 	ftp->data = NULL;
 | |
|     }
 | |
|     ftp->host = NULL;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| static int
 | |
| ftp_login(FTP ftp)
 | |
| {
 | |
|     int sock, status;
 | |
|     int sock_wf;
 | |
| 
 | |
|     sock = openSocket(ftp->host, "ftp", 21);
 | |
|     if (sock < 0)
 | |
| 	goto open_err;
 | |
|     if (ftppass_hostnamegen && !strcmp(ftp->user, "anonymous")) {
 | |
| 	size_t n = strlen(ftp->pass);
 | |
| 
 | |
| 	if (n > 0 && ftp->pass[n - 1] == '@') {
 | |
| #ifdef INET6
 | |
| 	    struct sockaddr_storage sockname;
 | |
| #else
 | |
| 	    struct sockaddr_in sockname;
 | |
| #endif
 | |
| 	    socklen_t socknamelen = sizeof(sockname);
 | |
| 
 | |
| 	    if (!getsockname(sock, (struct sockaddr *)&sockname, &socknamelen)) {
 | |
| 		Str tmp = Strnew_charp(ftp->pass);
 | |
| #ifdef INET6
 | |
| 		char hostbuf[NI_MAXHOST];
 | |
| 
 | |
| 		if (getnameinfo((struct sockaddr *)&sockname, socknamelen,
 | |
| 				hostbuf, sizeof hostbuf, NULL, 0, NI_NAMEREQD)
 | |
| 			== 0)
 | |
| 		    Strcat_charp(tmp, hostbuf);
 | |
| 		else if (getnameinfo((struct sockaddr *)&sockname, socknamelen,
 | |
| 				        hostbuf, sizeof hostbuf, NULL, 0, NI_NUMERICHOST)
 | |
| 			== 0)
 | |
| 		    Strcat_m_charp(tmp, "[", hostbuf, "]", NULL);
 | |
| 		else
 | |
| 		    Strcat_charp(tmp, "unknown");
 | |
| #else
 | |
| 
 | |
| 		struct hostent *sockent;
 | |
| 		if ((sockent = gethostbyaddr((char *)&sockname.sin_addr,
 | |
| 					     sizeof(sockname.sin_addr),
 | |
| 					     sockname.sin_family)))
 | |
| 		    Strcat_charp(tmp, sockent->h_name);
 | |
| 		else
 | |
| 		    Strcat_m_charp(tmp, "[", inet_ntoa(sockname.sin_addr),
 | |
| 				   "]", NULL);
 | |
| #endif
 | |
| 		ftp->pass = tmp->ptr;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     ftp->rf = newInputStream(sock);
 | |
|     if ((sock_wf = dup(sock)) >= 0 )
 | |
| 	    ftp->wf = fdopen(sock_wf, "wb");
 | |
|     else
 | |
| 	    goto open_err;
 | |
|     if (!ftp->rf || !ftp->wf)
 | |
| 	goto open_err;
 | |
|     IStype(ftp->rf) |= IST_UNCLOSE;
 | |
|     ftp_command(ftp, NULL, NULL, &status);
 | |
|     if (status != 220)
 | |
| 	goto open_err;
 | |
|     if (fmInitialized) {
 | |
| 	message(Sprintf("Sending FTP username (%s) to remote server.",
 | |
| 			ftp->user)->ptr, 0, 0);
 | |
| 	refresh();
 | |
|     }
 | |
|     ftp_command(ftp, "USER", ftp->user, &status);
 | |
|     /*
 | |
|      * Some ftp daemons(e.g. publicfile) return code 230 for user command.
 | |
|      */
 | |
|     if (status == 230)
 | |
| 	goto succeed;
 | |
|     if (status != 331)
 | |
| 	goto open_err;
 | |
|     if (fmInitialized) {
 | |
| 	message("Sending FTP password to remote server.", 0, 0);
 | |
| 	refresh();
 | |
|     }
 | |
|     ftp_command(ftp, "PASS", ftp->pass, &status);
 | |
|     if (status != 230)
 | |
| 	goto open_err;
 | |
|   succeed:
 | |
|     return TRUE;
 | |
|   open_err:
 | |
|     ftp_close(ftp);
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| static int
 | |
| ftp_pasv(FTP ftp)
 | |
| {
 | |
|     int status;
 | |
|     int n1, n2, n3, n4, p1, p2;
 | |
|     int data;
 | |
|     char *p;
 | |
|     Str tmp;
 | |
|     int family;
 | |
| #ifdef INET6
 | |
|     struct sockaddr_storage sockaddr;
 | |
|     int port;
 | |
|     socklen_t sockaddrlen;
 | |
|     unsigned char d1, d2, d3, d4;
 | |
|     char abuf[INET6_ADDRSTRLEN];
 | |
| #endif
 | |
| 
 | |
| #ifdef INET6
 | |
|     sockaddrlen = sizeof(sockaddr);
 | |
|     if (getpeername(fileno(ftp->wf),
 | |
| 		    (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
 | |
| 	return -1;
 | |
| #ifdef HAVE_OLD_SS_FAMILY
 | |
|     family = sockaddr.__ss_family;
 | |
| #else
 | |
|     family = sockaddr.ss_family;
 | |
| #endif
 | |
| #else
 | |
|     family = AF_INET;
 | |
| #endif
 | |
|     switch (family) {
 | |
| #ifdef INET6
 | |
|     case AF_INET6:
 | |
| 	tmp = ftp_command(ftp, "EPSV", NULL, &status);
 | |
| 	if (status != 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 *)&sockaddr, sockaddrlen,
 | |
| 			abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST) != 0)
 | |
| 	    return -1;
 | |
| 	data = openSocket(abuf, "", port);
 | |
| 	break;
 | |
| #endif
 | |
|     case AF_INET:
 | |
| 	tmp = ftp_command(ftp, "PASV", NULL, &status);
 | |
| 	if (status != 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 = openSocket(tmp->ptr, "", p1 * 256 + p2);
 | |
| 	break;
 | |
|     default:
 | |
| 	return -1;
 | |
|     }
 | |
|     if (data < 0)
 | |
| 	return -1;
 | |
|     ftp->data = fdopen(data, "rb");
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static time_t
 | |
| ftp_modtime(FTP ftp, char *path)
 | |
| {
 | |
|     int status;
 | |
|     Str tmp;
 | |
|     char *p;
 | |
|     struct tm tm;
 | |
|     time_t t, lt, gt;
 | |
| 
 | |
|     tmp = ftp_command(ftp, "MDTM", path, &status);
 | |
|     if (status != 213)
 | |
| 	return -1;
 | |
|     for (p = tmp->ptr + 4; *p && *p == ' '; p++) ;
 | |
|     memset(&tm, 0, sizeof(struct tm));
 | |
|     if (sscanf(p, "%04d%02d%02d%02d%02d%02d",
 | |
| 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
 | |
| 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) < 6)
 | |
| 	return -1;
 | |
|     tm.tm_year -= 1900;
 | |
|     tm.tm_mon--;
 | |
|     t = mktime(&tm);
 | |
|     lt = mktime(localtime(&t));
 | |
|     gt = mktime(gmtime(&t));
 | |
|     return t + (lt - gt);
 | |
| }
 | |
| 
 | |
| static int
 | |
| ftp_quit(FTP ftp)
 | |
| {
 | |
|     /*
 | |
|      * int status;
 | |
|      * ftp_command(ftp, "QUIT", NULL, &status);
 | |
|      * ftp_close(ftp);
 | |
|      * if (status != 221)
 | |
|      * return -1;
 | |
|      */
 | |
|     ftp_command(ftp, "QUIT", NULL, NULL);
 | |
|     ftp_close(ftp);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int ex_ftpdir_name_size_date(char *, char **, char **, char **,
 | |
| 				    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
 | |
| 
 | |
| static void
 | |
| closeFTPdata(FILE * f)
 | |
| {
 | |
|     int status;
 | |
|     if (f) {
 | |
| 	fclose(f);
 | |
| 	if (f == current_ftp.data)
 | |
| 	    current_ftp.data = NULL;
 | |
|     }
 | |
|     ftp_command(¤t_ftp, NULL, NULL, &status);
 | |
|     /* status == 226 */
 | |
| }
 | |
| 
 | |
| void
 | |
| closeFTP(void)
 | |
| {
 | |
|     ftp_close(¤t_ftp);
 | |
| }
 | |
| 
 | |
| InputStream
 | |
| openFTPStream(ParsedURL *pu, URLFile *uf)
 | |
| {
 | |
|     Str tmp;
 | |
|     int status;
 | |
|     char *user = NULL;
 | |
|     char *pass = NULL;
 | |
|     Str uname = NULL;
 | |
|     Str pwd = NULL;
 | |
|     int add_auth_cookie_flag = FALSE;
 | |
|     char *realpathname = NULL;
 | |
| 
 | |
|     if (!pu->host)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (pu->user == NULL && pu->pass == NULL) {
 | |
| 	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
 | |
| 	user = "anonymous";
 | |
| 
 | |
|     if (current_ftp.host) {
 | |
| 	if (!strcmp(current_ftp.host, pu->host) &&
 | |
| 	    current_ftp.port == pu->port && !strcmp(current_ftp.user, user)) {
 | |
| 	    ftp_command(¤t_ftp, "NOOP", NULL, &status);
 | |
| 	    if (status != 200)
 | |
| 		ftp_close(¤t_ftp);
 | |
| 	    else
 | |
| 		goto ftp_read;
 | |
| 	}
 | |
| 	else
 | |
| 	    ftp_quit(¤t_ftp);
 | |
|     }
 | |
| 
 | |
|     if (pass)
 | |
| 	/* do nothing */ ;
 | |
|     else if (pu->pass)
 | |
| 	pass = pu->pass;
 | |
|     else if (pu->user) {
 | |
| 	pwd = NULL;
 | |
| 	find_auth_user_passwd(pu, NULL, &uname, &pwd, 0);
 | |
| 	if (pwd == NULL) {
 | |
| 	    if (fmInitialized) {
 | |
| 		term_raw();
 | |
| 		pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
 | |
| 		pwd = Str_conv_to_system(pwd);
 | |
| 		term_cbreak();
 | |
| 	    }
 | |
| 	    else {
 | |
| #ifndef __MINGW32_VERSION
 | |
| 		pwd = Strnew_charp((char *)getpass("Password: "));
 | |
| #else
 | |
| 		term_raw();
 | |
| 		pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
 | |
| 		pwd = Str_conv_to_system(pwd);
 | |
| 		term_cbreak();
 | |
| #endif /* __MINGW32_VERSION */
 | |
| 	    }
 | |
| 	    add_auth_cookie_flag = TRUE;
 | |
| 	}
 | |
| 	pass = pwd->ptr;
 | |
|     }
 | |
|     else if (ftppasswd != NULL && *ftppasswd != '\0')
 | |
| 	pass = ftppasswd;
 | |
|     else {
 | |
| #ifndef __MINGW32_VERSION
 | |
| 	struct passwd *mypw = getpwuid(getuid());
 | |
| 	tmp = Strnew_charp(mypw ? mypw->pw_name : "anonymous");
 | |
| #else
 | |
| 	tmp = Strnew_charp("anonymous");
 | |
| #endif /* __MINGW32_VERSION */
 | |
| 	Strcat_char(tmp, '@');
 | |
| 	pass = tmp->ptr;
 | |
|     }
 | |
| 
 | |
|     if (!current_ftp.host) {
 | |
| 	current_ftp.host = allocStr(pu->host, -1);
 | |
| 	current_ftp.port = pu->port;
 | |
| 	current_ftp.user = allocStr(user, -1);
 | |
| 	current_ftp.pass = allocStr(pass, -1);
 | |
| 	if (!ftp_login(¤t_ftp))
 | |
| 	    return NULL;
 | |
|     }
 | |
|     if (add_auth_cookie_flag)
 | |
| 	add_auth_user_passwd(pu, NULL, uname, pwd, 0);
 | |
| 
 | |
|   ftp_read:
 | |
|     ftp_command(¤t_ftp, "TYPE", "I", &status);
 | |
|     if (ftp_pasv(¤t_ftp) < 0) {
 | |
| 	ftp_quit(¤t_ftp);
 | |
| 	return NULL;
 | |
|     }
 | |
|     if (pu->file == NULL || *pu->file == '\0' ||
 | |
| 	pu->file[strlen(pu->file) - 1] == '/')
 | |
| 	goto ftp_dir;
 | |
| 
 | |
|     realpathname = file_unquote(pu->file);
 | |
|     if (*realpathname == '/' && *(realpathname + 1) == '~')
 | |
| 	realpathname++;
 | |
|     /* Get file */
 | |
|     uf->modtime = ftp_modtime(¤t_ftp, realpathname);
 | |
|     ftp_command(¤t_ftp, "RETR", realpathname, &status);
 | |
|     if (status == 125 || status == 150)
 | |
| 	return newFileStream(current_ftp.data, (void (*)())closeFTPdata);
 | |
| 
 | |
|   ftp_dir:
 | |
|     pu->scheme = SCM_FTPDIR;
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| #ifdef USE_M17N
 | |
| Str
 | |
| loadFTPDir(ParsedURL *pu, wc_ces * charset)
 | |
| #else
 | |
| Str
 | |
| loadFTPDir0(ParsedURL *pu)
 | |
| #endif
 | |
| {
 | |
|     Str FTPDIRtmp;
 | |
|     Str tmp;
 | |
|     int status;
 | |
|     volatile int sv_type;
 | |
|     char *realpathname, *fn, *q;
 | |
|     char **flist;
 | |
|     int i, nfile, nfile_max;
 | |
|     MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
 | |
| #ifdef USE_M17N
 | |
|     wc_ces doc_charset = DocumentCharset;
 | |
| 
 | |
|     *charset = WC_CES_US_ASCII;
 | |
| #endif
 | |
|     if (current_ftp.data == NULL)
 | |
| 	return NULL;
 | |
|     tmp = ftp_command(¤t_ftp, "SYST", NULL, &status);
 | |
|     if (strstr(tmp->ptr, "UNIX") != NULL || !strncmp(tmp->ptr + 4, "Windows_NT", 10))	/* :-) */
 | |
| 	sv_type = UNIXLIKE_SERVER;
 | |
|     else
 | |
| 	sv_type = SERVER_NONE;
 | |
|     if (pu->file == NULL || *pu->file == '\0') {
 | |
| 	if (sv_type == UNIXLIKE_SERVER)
 | |
| 	    ftp_command(¤t_ftp, "LIST", NULL, &status);
 | |
| 	else
 | |
| 	    ftp_command(¤t_ftp, "NLST", NULL, &status);
 | |
| 	pu->file = "/";
 | |
|     }
 | |
|     else {
 | |
| 	realpathname = file_unquote(pu->file);
 | |
| 	if (*realpathname == '/' && *(realpathname + 1) == '~')
 | |
| 	    realpathname++;
 | |
| 	if (sv_type == UNIXLIKE_SERVER) {
 | |
| 	    ftp_command(¤t_ftp, "CWD", realpathname, &status);
 | |
| 	    if (status == 250)
 | |
| 		ftp_command(¤t_ftp, "LIST", NULL, &status);
 | |
| 	}
 | |
| 	else
 | |
| 	    ftp_command(¤t_ftp, "NLST", realpathname, &status);
 | |
|     }
 | |
|     if (status != 125 && status != 150) {
 | |
| 	fclose(current_ftp.data);
 | |
| 	current_ftp.data = NULL;
 | |
| 	return NULL;
 | |
|     }
 | |
|     tmp = parsedURL2Str(pu);
 | |
|     if (Strlastchar(tmp) != '/')
 | |
| 	Strcat_char(tmp, '/');
 | |
|     fn = html_quote(tmp->ptr);
 | |
|     tmp =
 | |
| 	convertLine(NULL, Strnew_charp(file_unquote(tmp->ptr)), RAW_MODE,
 | |
| 		    charset, doc_charset);
 | |
|     q = html_quote(tmp->ptr);
 | |
|     FTPDIRtmp = Strnew_m_charp("<html>\n<head>\n<base href=\"", fn,
 | |
| 			       "\">\n<title>", q,
 | |
| 			       "</title>\n</head>\n<body>\n<h1>Index of ", q,
 | |
| 			       "</h1>\n", NULL);
 | |
| 
 | |
|     if (SETJMP(AbortLoading) != 0) {
 | |
| 	if (sv_type == UNIXLIKE_SERVER)
 | |
| 	    Strcat_charp(FTPDIRtmp, "</a></pre>\n");
 | |
| 	else
 | |
| 	    Strcat_charp(FTPDIRtmp, "</a></ul>\n");
 | |
| 	Strcat_charp(FTPDIRtmp, "<p>Transfer Interrupted!\n");
 | |
| 	goto ftp_end;
 | |
|     }
 | |
|     TRAP_ON;
 | |
| 
 | |
|     if (sv_type == UNIXLIKE_SERVER)
 | |
| 	Strcat_charp(FTPDIRtmp, "<pre>\n");
 | |
|     else
 | |
| 	Strcat_charp(FTPDIRtmp, "<ul>\n<li>");
 | |
|     Strcat_charp(FTPDIRtmp, "<a href=\"..\">[Upper Directory]</a>\n");
 | |
| 
 | |
|     nfile_max = 100;
 | |
|     flist = New_N(char *, nfile_max);
 | |
|     nfile = 0;
 | |
|     if (sv_type == UNIXLIKE_SERVER) {
 | |
| 	char *name, *link, *date, *size, *type_str;
 | |
| 	int ftype, max_len, len, j;
 | |
| 
 | |
| 	max_len = 20;
 | |
| 	while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
 | |
| 	    Strchop(tmp);
 | |
| 	    if ((ftype =
 | |
| 		 ex_ftpdir_name_size_date(tmp->ptr, &name, &link, &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;
 | |
| 	    flist[nfile++] = Sprintf("%s%s\n%s  %5s%s", name, type_str, date,
 | |
| 				     size, link)->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 = strchr(fn, '\n');
 | |
| 	    if (*(date - 1) == '/') {
 | |
| 		ftype = FTPDIR_DIR;
 | |
| 		*date = '\0';
 | |
| 	    }
 | |
| 	    else if (*(date - 1) == '@') {
 | |
| 		ftype = FTPDIR_LINK;
 | |
| 		*(date - 1) = '\0';
 | |
| 	    }
 | |
| 	    else {
 | |
| 		ftype = FTPDIR_FILE;
 | |
| 		*(date - 1) = '\0';
 | |
| 	    }
 | |
| 	    date++;
 | |
| 	    tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
 | |
| 			      doc_charset);
 | |
| 	    if (ftype == FTPDIR_LINK)
 | |
| 		Strcat_char(tmp, '@');
 | |
| 	    Strcat_m_charp(FTPDIRtmp, "<a href=\"", html_quote(file_quote(fn)),
 | |
| 			   "\">", html_quote(tmp->ptr), "</a>", NULL);
 | |
| 	    for (i = get_Str_strwidth(tmp); i <= max_len; i++) {
 | |
| 		if ((max_len % 2 + i) % 2)
 | |
| 		    Strcat_char(FTPDIRtmp, '.');
 | |
| 		else
 | |
| 		    Strcat_char(FTPDIRtmp, ' ');
 | |
| 	    }
 | |
| 	    tmp = convertLine(NULL, Strnew_charp(date), RAW_MODE, charset,
 | |
| 			      doc_charset);
 | |
| 	    Strcat_m_charp(FTPDIRtmp, html_quote(tmp->ptr), "\n", NULL);
 | |
| 	}
 | |
| 	Strcat_charp(FTPDIRtmp, "</pre>\n");
 | |
|     }
 | |
|     else {
 | |
| 	while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
 | |
| 	    Strchop(tmp);
 | |
| 	    flist[nfile++] = mybasename(tmp->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];
 | |
| 	    tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
 | |
| 			      doc_charset);
 | |
| 	    Strcat_m_charp(FTPDIRtmp, "<li><a href=\"",
 | |
| 			   html_quote(file_quote(fn)), "\">",
 | |
| 			   html_quote(tmp->ptr), "</a>\n", NULL);
 | |
| 	}
 | |
| 	Strcat_charp(FTPDIRtmp, "</ul>\n");
 | |
|     }
 | |
| 
 | |
|   ftp_end:
 | |
|     Strcat_charp(FTPDIRtmp, "</body>\n</html>\n");
 | |
|     TRAP_OFF;
 | |
|     closeFTPdata(current_ftp.data);
 | |
|     return FTPDIRtmp;
 | |
| }
 | |
| 
 | |
| void
 | |
| disconnectFTP(void)
 | |
| {
 | |
|     ftp_quit(¤t_ftp);
 | |
| }
 | |
| 
 | |
| #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;\
 | |
| }
 | |
| #define EX_COUNT_DIGIT(cp) {\
 | |
|     size = 0;\
 | |
|     while (*cp && IS_DIGIT(*cp))\
 | |
| 	size = size * 10 + *(cp++) - '0';\
 | |
|     if (*cp == '\0')\
 | |
| 	goto done;\
 | |
| }
 | |
| 
 | |
| static Str size_int2str(clen_t);
 | |
| 
 | |
| static int
 | |
| ex_ftpdir_name_size_date(char *line, char **name, char **link, char **date,
 | |
| 			 char **sizep)
 | |
| {
 | |
|     int ftype = FTPDIR_NONE;
 | |
|     char *cp = line, *p;
 | |
|     clen_t size;
 | |
| 
 | |
|     if (strlen(cp) < 11)
 | |
| 	goto done;
 | |
|     /* skip permission */
 | |
|     cp += 10;
 | |
|     if (!IS_SPACE(*cp))
 | |
| 	goto done;
 | |
|     cp++;
 | |
| 
 | |
|     /* skip link count */
 | |
|     EX_SKIP_SPACE(cp);
 | |
|     EX_COUNT_DIGIT(cp);
 | |
|     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);
 | |
|     p = cp;
 | |
|     EX_COUNT_DIGIT(cp);
 | |
|     if (*cp == ',') {		/* device file ? */
 | |
| 	cp++;
 | |
| 	EX_SKIP_SPACE(cp);
 | |
| 	EX_SKIP_NONE_SPACE(cp);
 | |
| 	*sizep = allocStr(p, cp - p);
 | |
|     }
 | |
|     else {
 | |
| 	*sizep = size_int2str(size)->ptr;
 | |
|     }
 | |
|     cp++;
 | |
| 
 | |
|     /* extract date */
 | |
|     /* loose check for i18n server */
 | |
|     p = cp;
 | |
|     EX_SKIP_SPACE(cp);
 | |
|     EX_SKIP_NONE_SPACE(cp);	/* month ? */
 | |
|     EX_SKIP_SPACE(cp);
 | |
|     EX_SKIP_NONE_SPACE(cp);	/* day ? */
 | |
|     EX_SKIP_SPACE(cp);
 | |
|     EX_SKIP_NONE_SPACE(cp);	/* year or time ? */
 | |
|     *date = allocStr(p, cp - p);
 | |
|     cp++;
 | |
| 
 | |
|     /* extract file name */
 | |
|     EX_SKIP_SPACE(cp);
 | |
|     switch (line[0]) {
 | |
|     case 'l':
 | |
| 	ftype = FTPDIR_LINK;
 | |
| 	if ((p = strstr(cp, " -> ")) == NULL)
 | |
| 	    goto done;
 | |
| 	*name = allocStr(cp, p - cp);
 | |
| 	*link = allocStr(p, -1);
 | |
| 	*sizep = "";
 | |
| 	break;
 | |
|     case 'd':
 | |
| 	ftype = FTPDIR_DIR;
 | |
| 	*name = allocStr(cp, -1);
 | |
| 	*link = "";
 | |
| 	*sizep = "";
 | |
| 	break;
 | |
|     default:
 | |
| 	ftype = FTPDIR_FILE;
 | |
| 	*name = allocStr(cp, -1);
 | |
| 	*link = "";
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   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);
 | |
| }
 |