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(¤t_ftp, NULL, NULL, &status);
340 /* status == 226 */
341 }
342
343 void
closeFTP(void)344 closeFTP(void)
345 {
346 ftp_close(¤t_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(¤t_ftp, "NOOP", NULL, &status);
383 if (status != 200)
384 ftp_close(¤t_ftp);
385 else
386 goto ftp_read;
387 }
388 else
389 ftp_quit(¤t_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(¤t_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(¤t_ftp, "TYPE", "I", &status);
446 if (ftp_pasv(¤t_ftp) < 0) {
447 ftp_quit(¤t_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(¤t_ftp, realpathname);
459 ftp_command(¤t_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(¤t_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(¤t_ftp, "LIST", NULL, &status);
499 else
500 ftp_command(¤t_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(¤t_ftp, "CWD", realpathname, &status);
509 if (status == 250)
510 ftp_command(¤t_ftp, "LIST", NULL, &status);
511 }
512 else
513 ftp_command(¤t_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(¤t_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