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