w3m

Unnamed repository; edit this file to name it for gitweb.
git clone https://logand.com/git/w3m.git/
Log | Files | Refs | README

ftp.c (18203B)


      1 /* $Id$ */
      2 #include <stdio.h>
      3 #ifndef __MINGW32_VERSION
      4 #include <pwd.h>
      5 #endif /* __MINGW32_VERSION */
      6 #include <Str.h>
      7 #include <signal.h>
      8 #include <setjmp.h>
      9 #include <time.h>
     10 
     11 #include "fm.h"
     12 #include "html.h"
     13 #include "myctype.h"
     14 
     15 #ifdef DEBUG
     16 #include <malloc.h>
     17 #endif				/* DEBUG */
     18 
     19 #ifndef __MINGW32_VERSION
     20 #include <sys/socket.h>
     21 #include <netinet/in.h>
     22 #include <netdb.h>
     23 #include <arpa/inet.h>
     24 #else
     25 #include <winsock.h>
     26 #endif /* __MINGW32_VERSION */
     27 
     28 #ifndef HAVE_SOCKLEN_T
     29 typedef int socklen_t;
     30 #endif
     31 
     32 typedef struct _FTP {
     33     char *host;
     34     int port;
     35     char *user;
     36     char *pass;
     37     InputStream rf;
     38     FILE *wf;
     39     FILE *data;
     40 } *FTP;
     41 
     42 static struct _FTP current_ftp = {
     43     NULL, 0, NULL, NULL, NULL, NULL, NULL
     44 };
     45 
     46 static JMP_BUF AbortLoading;
     47 
     48 static MySignalHandler
     49 KeyAbort(SIGNAL_ARG)
     50 {
     51     LONGJMP(AbortLoading, 1);
     52     SIGNAL_RETURN;
     53 }
     54 
     55 static Str
     56 ftp_command(FTP ftp, char *cmd, char *arg, int *status)
     57 {
     58     Str tmp;
     59 
     60     if (!ftp->host)
     61 	return NULL;
     62     if (cmd) {
     63 	if (arg)
     64 	    tmp = Sprintf("%s %s\r\n", cmd, arg);
     65 	else
     66 	    tmp = Sprintf("%s\r\n", cmd);
     67 	fwrite(tmp->ptr, sizeof(char), tmp->length, ftp->wf);
     68 	fflush(ftp->wf);
     69     }
     70     if (!status)
     71 	return NULL;
     72     *status = -1;		/* error */
     73     tmp = StrISgets(ftp->rf);
     74     if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
     75 	IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ')
     76 	sscanf(tmp->ptr, "%d", status);
     77 
     78     if (tmp->ptr[3] != '-')
     79 	return tmp;
     80     /* RFC959 4.2 FTP REPLIES */
     81     /* multi-line response start */
     82     /* 
     83      * Thus the format for multi-line replies is that the
     84      * first line will begin with the exact required reply
     85      * code, followed immediately by a Hyphen, "-" (also known 
     86      * as Minus), followed by text.  The last line will begin
     87      * with the same code, followed immediately by Space <SP>, 
     88      * optionally some text, and the Telnet end-of-line code. */
     89     while (1) {
     90 	tmp = StrISgets(ftp->rf);
     91 	if (IS_DIGIT(tmp->ptr[0]) && IS_DIGIT(tmp->ptr[1]) &&
     92 	    IS_DIGIT(tmp->ptr[2]) && tmp->ptr[3] == ' ') {
     93 	    sscanf(tmp->ptr, "%d", status);
     94 	    break;
     95 	}
     96     }
     97     return tmp;
     98 }
     99 
    100 static void
    101 ftp_close(FTP ftp)
    102 {
    103     if (!ftp->host)
    104 	return;
    105     if (ftp->rf) {
    106 	IStype(ftp->rf) &= ~IST_UNCLOSE;
    107 	ISclose(ftp->rf);
    108 	ftp->rf = NULL;
    109     }
    110     if (ftp->wf) {
    111 	fclose(ftp->wf);
    112 	ftp->wf = NULL;
    113     }
    114     if (ftp->data) {
    115 	fclose(ftp->data);
    116 	ftp->data = NULL;
    117     }
    118     ftp->host = NULL;
    119     return;
    120 }
    121 
    122 static int
    123 ftp_login(FTP ftp)
    124 {
    125     int sock, status;
    126 
    127     sock = openSocket(ftp->host, "ftp", 21);
    128     if (sock < 0)
    129 	goto open_err;
    130     if (ftppass_hostnamegen && !strcmp(ftp->user, "anonymous")) {
    131 	size_t n = strlen(ftp->pass);
    132 
    133 	if (n > 0 && ftp->pass[n - 1] == '@') {
    134 #ifdef INET6
    135 	    struct sockaddr_storage sockname;
    136 #else
    137 	    struct sockaddr_in sockname;
    138 #endif
    139 	    socklen_t socknamelen = sizeof(sockname);
    140 
    141 	    if (!getsockname(sock, (struct sockaddr *)&sockname, &socknamelen)) {
    142 		struct hostent *sockent;
    143 		Str tmp = Strnew_charp(ftp->pass);
    144 #ifdef INET6
    145 		char hostbuf[NI_MAXHOST];
    146 
    147 		if (getnameinfo((struct sockaddr *)&sockname, socknamelen,
    148 				hostbuf, sizeof hostbuf, NULL, 0, NI_NAMEREQD)
    149 			== 0)
    150 		    Strcat_charp(tmp, hostbuf);
    151 		else if (getnameinfo((struct sockaddr *)&sockname, socknamelen,
    152 				        hostbuf, sizeof hostbuf, NULL, 0, NI_NUMERICHOST)
    153 			== 0)
    154 		    Strcat_m_charp(tmp, "[", hostbuf, "]", NULL);
    155 		else
    156 		    Strcat_charp(tmp, "unknown");
    157 #else
    158 
    159 		if ((sockent = gethostbyaddr((char *)&sockname.sin_addr,
    160 					     sizeof(sockname.sin_addr),
    161 					     sockname.sin_family)))
    162 		    Strcat_charp(tmp, sockent->h_name);
    163 		else
    164 		    Strcat_m_charp(tmp, "[", inet_ntoa(sockname.sin_addr),
    165 				   "]", NULL);
    166 #endif
    167 		ftp->pass = tmp->ptr;
    168 	    }
    169 	}
    170     }
    171     ftp->rf = newInputStream(sock);
    172     ftp->wf = fdopen(dup(sock), "wb");
    173     if (!ftp->rf || !ftp->wf)
    174 	goto open_err;
    175     IStype(ftp->rf) |= IST_UNCLOSE;
    176     ftp_command(ftp, NULL, NULL, &status);
    177     if (status != 220)
    178 	goto open_err;
    179     if (fmInitialized) {
    180 	message(Sprintf("Sending FTP username (%s) to remote server.",
    181 			ftp->user)->ptr, 0, 0);
    182 	refresh();
    183     }
    184     ftp_command(ftp, "USER", ftp->user, &status);
    185     /*
    186      * Some ftp daemons(e.g. publicfile) return code 230 for user command.
    187      */
    188     if (status == 230)
    189 	goto succeed;
    190     if (status != 331)
    191 	goto open_err;
    192     if (fmInitialized) {
    193 	message("Sending FTP password to remote server.", 0, 0);
    194 	refresh();
    195     }
    196     ftp_command(ftp, "PASS", ftp->pass, &status);
    197     if (status != 230)
    198 	goto open_err;
    199   succeed:
    200     return TRUE;
    201   open_err:
    202     ftp_close(ftp);
    203     return FALSE;
    204 }
    205 
    206 static int
    207 ftp_pasv(FTP ftp)
    208 {
    209     int status;
    210     int n1, n2, n3, n4, p1, p2;
    211     int data;
    212     char *p;
    213     Str tmp;
    214     int family;
    215 #ifdef INET6
    216     struct sockaddr_storage sockaddr;
    217     int port;
    218     socklen_t sockaddrlen;
    219     unsigned char d1, d2, d3, d4;
    220     char abuf[INET6_ADDRSTRLEN];
    221 #endif
    222 
    223 #ifdef INET6
    224     sockaddrlen = sizeof(sockaddr);
    225     if (getpeername(fileno(ftp->wf),
    226 		    (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
    227 	return -1;
    228 #ifdef HAVE_OLD_SS_FAMILY
    229     family = sockaddr.__ss_family;
    230 #else
    231     family = sockaddr.ss_family;
    232 #endif
    233 #else
    234     family = AF_INET;
    235 #endif
    236     switch (family) {
    237 #ifdef INET6
    238     case AF_INET6:
    239 	tmp = ftp_command(ftp, "EPSV", NULL, &status);
    240 	if (status != 229)
    241 	    return -1;
    242 	for (p = tmp->ptr + 4; *p && *p != '('; p++) ;
    243 	if (*p == '\0')
    244 	    return -1;
    245 	if (sscanf(++p, "%c%c%c%d%c", &d1, &d2, &d3, &port, &d4) != 5
    246 	    || d1 != d2 || d1 != d3 || d1 != d4)
    247 	    return -1;
    248 	if (getnameinfo((struct sockaddr *)&sockaddr, sockaddrlen,
    249 			abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST) != 0)
    250 	    return -1;
    251 	data = openSocket(abuf, "", port);
    252 	break;
    253 #endif
    254     case AF_INET:
    255 	tmp = ftp_command(ftp, "PASV", NULL, &status);
    256 	if (status != 227)
    257 	    return -1;
    258 	for (p = tmp->ptr + 4; *p && !IS_DIGIT(*p); p++) ;
    259 	if (*p == '\0')
    260 	    return -1;
    261 	sscanf(p, "%d,%d,%d,%d,%d,%d", &n1, &n2, &n3, &n4, &p1, &p2);
    262 	tmp = Sprintf("%d.%d.%d.%d", n1, n2, n3, n4);
    263 	data = openSocket(tmp->ptr, "", p1 * 256 + p2);
    264 	break;
    265     default:
    266 	return -1;
    267     }
    268     if (data < 0)
    269 	return -1;
    270     ftp->data = fdopen(data, "rb");
    271     return 0;
    272 }
    273 
    274 static time_t
    275 ftp_modtime(FTP ftp, char *path)
    276 {
    277     int status;
    278     Str tmp;
    279     char *p;
    280     struct tm tm;
    281     time_t t, lt, gt;
    282 
    283     tmp = ftp_command(ftp, "MDTM", path, &status);
    284     if (status != 213)
    285 	return -1;
    286     for (p = tmp->ptr + 4; *p && *p == ' '; p++) ;
    287     memset(&tm, 0, sizeof(struct tm));
    288     if (sscanf(p, "%04d%02d%02d%02d%02d%02d",
    289 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
    290 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) < 6)
    291 	return -1;
    292     tm.tm_year -= 1900;
    293     tm.tm_mon--;
    294     t = mktime(&tm);
    295     lt = mktime(localtime(&t));
    296     gt = mktime(gmtime(&t));
    297     return t + (lt - gt);
    298 }
    299 
    300 static int
    301 ftp_quit(FTP ftp)
    302 {
    303     /*
    304      * int status;
    305      * ftp_command(ftp, "QUIT", NULL, &status);
    306      * ftp_close(ftp);
    307      * if (status != 221)
    308      * return -1;
    309      */
    310     ftp_command(ftp, "QUIT", NULL, NULL);
    311     ftp_close(ftp);
    312     return 0;
    313 }
    314 
    315 static int ex_ftpdir_name_size_date(char *, char **, char **, char **,
    316 				    char **);
    317 
    318 #define	SERVER_NONE	0
    319 #define	UNIXLIKE_SERVER	1
    320 
    321 #define	FTPDIR_NONE	0
    322 #define	FTPDIR_DIR	1
    323 #define	FTPDIR_LINK	2
    324 #define	FTPDIR_FILE	3
    325 
    326 static void
    327 closeFTPdata(FILE * f)
    328 {
    329     int status;
    330     if (f) {
    331 	fclose(f);
    332 	if (f == current_ftp.data)
    333 	    current_ftp.data = NULL;
    334     }
    335     ftp_command(&current_ftp, NULL, NULL, &status);
    336     /* status == 226 */
    337 }
    338 
    339 void
    340 closeFTP(void)
    341 {
    342     ftp_close(&current_ftp);
    343 }
    344 
    345 InputStream
    346 openFTPStream(ParsedURL *pu, URLFile *uf)
    347 {
    348     Str tmp;
    349     int status;
    350     char *user = NULL;
    351     char *pass = NULL;
    352     Str uname = NULL;
    353     Str pwd = NULL;
    354     int add_auth_cookie_flag = FALSE;
    355     char *realpathname = NULL;
    356 
    357     if (!pu->host)
    358 	return NULL;
    359 
    360     if (pu->user == NULL && pu->pass == NULL) {
    361 	if (find_auth_user_passwd(pu, NULL, &uname, &pwd, 0)) {
    362 	    if (uname)
    363 		user = uname->ptr;
    364 	    if (pwd)
    365 		pass = pwd->ptr;
    366 	}
    367     }
    368     if (user)
    369 	/* do nothing */ ;
    370     else if (pu->user)
    371 	user = pu->user;
    372     else
    373 	user = "anonymous";
    374 
    375     if (current_ftp.host) {
    376 	if (!strcmp(current_ftp.host, pu->host) &&
    377 	    current_ftp.port == pu->port && !strcmp(current_ftp.user, user)) {
    378 	    ftp_command(&current_ftp, "NOOP", NULL, &status);
    379 	    if (status != 200)
    380 		ftp_close(&current_ftp);
    381 	    else
    382 		goto ftp_read;
    383 	}
    384 	else
    385 	    ftp_quit(&current_ftp);
    386     }
    387 
    388     if (pass)
    389 	/* do nothing */ ;
    390     else if (pu->pass)
    391 	pass = pu->pass;
    392     else if (pu->user) {
    393 	pwd = NULL;
    394 	find_auth_user_passwd(pu, NULL, &uname, &pwd, 0);
    395 	if (pwd == NULL) {
    396 	    if (fmInitialized) {
    397 		term_raw();
    398 		pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
    399 		pwd = Str_conv_to_system(pwd);
    400 		term_cbreak();
    401 	    }
    402 	    else {
    403 #ifndef __MINGW32_VERSION
    404 		pwd = Strnew_charp((char *)getpass("Password: "));
    405 #else
    406 		term_raw();
    407 		pwd = Strnew_charp(inputLine("Password: ", NULL, IN_PASSWORD));
    408 		pwd = Str_conv_to_system(pwd);
    409 		term_cbreak();
    410 #endif /* __MINGW32_VERSION */
    411 	    }
    412 	    add_auth_cookie_flag = TRUE;
    413 	}
    414 	pass = pwd->ptr;
    415     }
    416     else if (ftppasswd != NULL && *ftppasswd != '\0')
    417 	pass = ftppasswd;
    418     else {
    419 #ifndef __MINGW32_VERSION
    420 	struct passwd *mypw = getpwuid(getuid());
    421 	tmp = Strnew_charp(mypw ? mypw->pw_name : "anonymous");
    422 #else
    423 	tmp = Strnew_charp("anonymous");
    424 #endif /* __MINGW32_VERSION */
    425 	Strcat_char(tmp, '@');
    426 	pass = tmp->ptr;
    427     }
    428 
    429     if (!current_ftp.host) {
    430 	current_ftp.host = allocStr(pu->host, -1);
    431 	current_ftp.port = pu->port;
    432 	current_ftp.user = allocStr(user, -1);
    433 	current_ftp.pass = allocStr(pass, -1);
    434 	if (!ftp_login(&current_ftp))
    435 	    return NULL;
    436     }
    437     if (add_auth_cookie_flag)
    438 	add_auth_user_passwd(pu, NULL, uname, pwd, 0);
    439 
    440   ftp_read:
    441     ftp_command(&current_ftp, "TYPE", "I", &status);
    442     if (ftp_pasv(&current_ftp) < 0) {
    443 	ftp_quit(&current_ftp);
    444 	return NULL;
    445     }
    446     if (pu->file == NULL || *pu->file == '\0' ||
    447 	pu->file[strlen(pu->file) - 1] == '/')
    448 	goto ftp_dir;
    449 
    450     realpathname = file_unquote(pu->file);
    451     if (*realpathname == '/' && *(realpathname + 1) == '~')
    452 	realpathname++;
    453     /* Get file */
    454     uf->modtime = ftp_modtime(&current_ftp, realpathname);
    455     ftp_command(&current_ftp, "RETR", realpathname, &status);
    456     if (status == 125 || status == 150)
    457 	return newFileStream(current_ftp.data, (void (*)())closeFTPdata);
    458 
    459   ftp_dir:
    460     pu->scheme = SCM_FTPDIR;
    461     return NULL;
    462 }
    463 
    464 #ifdef USE_M17N
    465 Str
    466 loadFTPDir(ParsedURL *pu, wc_ces * charset)
    467 #else
    468 Str
    469 loadFTPDir0(ParsedURL *pu)
    470 #endif
    471 {
    472     Str FTPDIRtmp;
    473     Str tmp;
    474     int status;
    475     volatile int sv_type;
    476     char *realpathname, *fn, *q;
    477     char **flist;
    478     int i, nfile, nfile_max;
    479     MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
    480 #ifdef USE_M17N
    481     wc_ces doc_charset = DocumentCharset;
    482 
    483     *charset = WC_CES_US_ASCII;
    484 #endif
    485     if (current_ftp.data == NULL)
    486 	return NULL;
    487     tmp = ftp_command(&current_ftp, "SYST", NULL, &status);
    488     if (strstr(tmp->ptr, "UNIX") != NULL || !strncmp(tmp->ptr + 4, "Windows_NT", 10))	/* :-) */
    489 	sv_type = UNIXLIKE_SERVER;
    490     else
    491 	sv_type = SERVER_NONE;
    492     if (pu->file == NULL || *pu->file == '\0') {
    493 	if (sv_type == UNIXLIKE_SERVER)
    494 	    ftp_command(&current_ftp, "LIST", NULL, &status);
    495 	else
    496 	    ftp_command(&current_ftp, "NLST", NULL, &status);
    497 	pu->file = "/";
    498     }
    499     else {
    500 	realpathname = file_unquote(pu->file);
    501 	if (*realpathname == '/' && *(realpathname + 1) == '~')
    502 	    realpathname++;
    503 	if (sv_type == UNIXLIKE_SERVER) {
    504 	    ftp_command(&current_ftp, "CWD", realpathname, &status);
    505 	    if (status == 250)
    506 		ftp_command(&current_ftp, "LIST", NULL, &status);
    507 	}
    508 	else
    509 	    ftp_command(&current_ftp, "NLST", realpathname, &status);
    510     }
    511     if (status != 125 && status != 150) {
    512 	fclose(current_ftp.data);
    513 	current_ftp.data = NULL;
    514 	return NULL;
    515     }
    516     tmp = parsedURL2Str(pu);
    517     if (Strlastchar(tmp) != '/')
    518 	Strcat_char(tmp, '/');
    519     fn = html_quote(tmp->ptr);
    520     tmp =
    521 	convertLine(NULL, Strnew_charp(file_unquote(tmp->ptr)), RAW_MODE,
    522 		    charset, doc_charset);
    523     q = html_quote(tmp->ptr);
    524     FTPDIRtmp = Strnew_m_charp("<html>\n<head>\n<base href=\"", fn,
    525 			       "\">\n<title>", q,
    526 			       "</title>\n</head>\n<body>\n<h1>Index of ", q,
    527 			       "</h1>\n", NULL);
    528 
    529     if (SETJMP(AbortLoading) != 0) {
    530 	if (sv_type == UNIXLIKE_SERVER)
    531 	    Strcat_charp(FTPDIRtmp, "</a></pre>\n");
    532 	else
    533 	    Strcat_charp(FTPDIRtmp, "</a></ul>\n");
    534 	Strcat_charp(FTPDIRtmp, "<p>Transfer Interrupted!\n");
    535 	goto ftp_end;
    536     }
    537     TRAP_ON;
    538 
    539     if (sv_type == UNIXLIKE_SERVER)
    540 	Strcat_charp(FTPDIRtmp, "<pre>\n");
    541     else
    542 	Strcat_charp(FTPDIRtmp, "<ul>\n<li>");
    543     Strcat_charp(FTPDIRtmp, "<a href=\"..\">[Upper Directory]</a>\n");
    544 
    545     nfile_max = 100;
    546     flist = New_N(char *, nfile_max);
    547     nfile = 0;
    548     if (sv_type == UNIXLIKE_SERVER) {
    549 	char *name, *link, *date, *size, *type_str;
    550 	int ftype, max_len, len, j;
    551 
    552 	max_len = 20;
    553 	while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
    554 	    Strchop(tmp);
    555 	    if ((ftype =
    556 		 ex_ftpdir_name_size_date(tmp->ptr, &name, &link, &date,
    557 					  &size)) == FTPDIR_NONE)
    558 		continue;
    559 	    if (!strcmp(".", name) || !strcmp("..", name))
    560 		continue;
    561 	    len = strlen(name);
    562 	    if (!len)
    563 		continue;
    564 	    if (ftype == FTPDIR_DIR) {
    565 		len++;
    566 		type_str = "/";
    567 	    }
    568 	    else if (ftype == FTPDIR_LINK) {
    569 		len++;
    570 		type_str = "@";
    571 	    }
    572 	    else {
    573 		type_str = " ";
    574 	    }
    575 	    if (max_len < len)
    576 		max_len = len;
    577 	    flist[nfile++] = Sprintf("%s%s\n%s  %5s%s", name, type_str, date,
    578 				     size, link)->ptr;
    579 	    if (nfile == nfile_max) {
    580 		nfile_max *= 2;
    581 		flist = New_Reuse(char *, flist, nfile_max);
    582 	    }
    583 	}
    584 	qsort(flist, nfile, sizeof(char *), strCmp);
    585 	for (j = 0; j < nfile; j++) {
    586 	    fn = flist[j];
    587 	    date = strchr(fn, '\n');
    588 	    if (*(date - 1) == '/') {
    589 		ftype = FTPDIR_DIR;
    590 		*date = '\0';
    591 	    }
    592 	    else if (*(date - 1) == '@') {
    593 		ftype = FTPDIR_LINK;
    594 		*(date - 1) = '\0';
    595 	    }
    596 	    else {
    597 		ftype = FTPDIR_FILE;
    598 		*(date - 1) = '\0';
    599 	    }
    600 	    date++;
    601 	    tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
    602 			      doc_charset);
    603 	    if (ftype == FTPDIR_LINK)
    604 		Strcat_char(tmp, '@');
    605 	    Strcat_m_charp(FTPDIRtmp, "<a href=\"", html_quote(file_quote(fn)),
    606 			   "\">", html_quote(tmp->ptr), "</a>", NULL);
    607 	    for (i = get_Str_strwidth(tmp); i <= max_len; i++) {
    608 		if ((max_len % 2 + i) % 2)
    609 		    Strcat_char(FTPDIRtmp, '.');
    610 		else
    611 		    Strcat_char(FTPDIRtmp, ' ');
    612 	    }
    613 	    tmp = convertLine(NULL, Strnew_charp(date), RAW_MODE, charset,
    614 			      doc_charset);
    615 	    Strcat_m_charp(FTPDIRtmp, html_quote(tmp->ptr), "\n", NULL);
    616 	}
    617 	Strcat_charp(FTPDIRtmp, "</pre>\n");
    618     }
    619     else {
    620 	while (tmp = Strfgets(current_ftp.data), tmp->length > 0) {
    621 	    Strchop(tmp);
    622 	    flist[nfile++] = mybasename(tmp->ptr);
    623 	    if (nfile == nfile_max) {
    624 		nfile_max *= 2;
    625 		flist = New_Reuse(char *, flist, nfile_max);
    626 	    }
    627 	}
    628 	qsort(flist, nfile, sizeof(char *), strCmp);
    629 	for (i = 0; i < nfile; i++) {
    630 	    fn = flist[i];
    631 	    tmp = convertLine(NULL, Strnew_charp(fn), RAW_MODE, charset,
    632 			      doc_charset);
    633 	    Strcat_m_charp(FTPDIRtmp, "<li><a href=\"",
    634 			   html_quote(file_quote(fn)), "\">",
    635 			   html_quote(tmp->ptr), "</a>\n", NULL);
    636 	}
    637 	Strcat_charp(FTPDIRtmp, "</ul>\n");
    638     }
    639 
    640   ftp_end:
    641     Strcat_charp(FTPDIRtmp, "</body>\n</html>\n");
    642     TRAP_OFF;
    643     closeFTPdata(current_ftp.data);
    644     return FTPDIRtmp;
    645 }
    646 
    647 void
    648 disconnectFTP(void)
    649 {
    650     ftp_quit(&current_ftp);
    651 }
    652 
    653 #define EX_SKIP_SPACE(cp) {\
    654     while (IS_SPACE(*cp) && *cp != '\0') cp++;\
    655     if (*cp == '\0')\
    656 	goto done;\
    657 }
    658 #define EX_SKIP_NONE_SPACE(cp) {\
    659     while (!IS_SPACE(*cp) && *cp != '\0') cp++;\
    660     if (*cp == '\0')\
    661 	goto done;\
    662 }
    663 #define EX_COUNT_DIGIT(cp) {\
    664     size = 0;\
    665     while (*cp && IS_DIGIT(*cp))\
    666 	size = size * 10 + *(cp++) - '0';\
    667     if (*cp == '\0')\
    668 	goto done;\
    669 }
    670 
    671 static Str size_int2str(clen_t);
    672 
    673 static int
    674 ex_ftpdir_name_size_date(char *line, char **name, char **link, char **date,
    675 			 char **sizep)
    676 {
    677     int ftype = FTPDIR_NONE;
    678     char *cp = line, *p;
    679     clen_t size;
    680 
    681     if (strlen(cp) < 11)
    682 	goto done;
    683     /* skip permission */
    684     cp += 10;
    685     if (!IS_SPACE(*cp))
    686 	goto done;
    687     cp++;
    688 
    689     /* skip link count */
    690     EX_SKIP_SPACE(cp);
    691     EX_COUNT_DIGIT(cp);
    692     cp++;
    693 
    694     /* skip owner string */
    695     EX_SKIP_SPACE(cp);
    696     EX_SKIP_NONE_SPACE(cp);
    697     cp++;
    698 
    699     /* skip group string */
    700     EX_SKIP_SPACE(cp);
    701     EX_SKIP_NONE_SPACE(cp);
    702     cp++;
    703 
    704     /* extract size */
    705     EX_SKIP_SPACE(cp);
    706     p = cp;
    707     EX_COUNT_DIGIT(cp);
    708     if (*cp == ',') {		/* device file ? */
    709 	cp++;
    710 	EX_SKIP_SPACE(cp);
    711 	EX_SKIP_NONE_SPACE(cp);
    712 	*sizep = allocStr(p, cp - p);
    713     }
    714     else {
    715 	*sizep = size_int2str(size)->ptr;
    716     }
    717     cp++;
    718 
    719     /* extract date */
    720     /* loose check for i18n server */
    721     p = cp;
    722     EX_SKIP_SPACE(cp);
    723     EX_SKIP_NONE_SPACE(cp);	/* month ? */
    724     EX_SKIP_SPACE(cp);
    725     EX_SKIP_NONE_SPACE(cp);	/* day ? */
    726     EX_SKIP_SPACE(cp);
    727     EX_SKIP_NONE_SPACE(cp);	/* year or time ? */
    728     *date = allocStr(p, cp - p);
    729     cp++;
    730 
    731     /* extract file name */
    732     EX_SKIP_SPACE(cp);
    733     switch (line[0]) {
    734     case 'l':
    735 	ftype = FTPDIR_LINK;
    736 	if ((p = strstr(cp, " -> ")) == NULL)
    737 	    goto done;
    738 	*name = allocStr(cp, p - cp);
    739 	*link = allocStr(p, -1);
    740 	*sizep = "";
    741 	break;
    742     case 'd':
    743 	ftype = FTPDIR_DIR;
    744 	*name = allocStr(cp, -1);
    745 	*link = "";
    746 	*sizep = "";
    747 	break;
    748     default:
    749 	ftype = FTPDIR_FILE;
    750 	*name = allocStr(cp, -1);
    751 	*link = "";
    752 	break;
    753     }
    754 
    755   done:
    756     return (ftype);
    757 }
    758 
    759 static Str
    760 size_int2str(clen_t size)
    761 {
    762     Str size_str;
    763     int unit;
    764     double dtmp;
    765     char *size_format, *unit_str;
    766 
    767     dtmp = (double)size;
    768     for (unit = 0; unit < 3; unit++) {
    769 	if (dtmp < 1024) {
    770 	    break;
    771 	}
    772 	dtmp /= 1024;
    773     }
    774     if (!unit || dtmp > 100) {
    775 	size_format = "%.0f%s";
    776     }
    777     else if (dtmp > 10) {
    778 	size_format = "%.1f%s";
    779     }
    780     else {
    781 	size_format = "%.2f%s";
    782     }
    783     switch (unit) {
    784     case 3:
    785 	unit_str = "G";
    786 	break;
    787     case 2:
    788 	unit_str = "M";
    789 	break;
    790     case 1:
    791 	unit_str = "K";
    792 	break;
    793     default:
    794 	unit_str = "";
    795 	break;
    796     }
    797     size_str = Sprintf(size_format, dtmp, unit_str);
    798 
    799     return (size_str);
    800 }