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