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(¤t_ftp, NULL, NULL, &status); 336 /* status == 226 */ 337 } 338 339 void 340 closeFTP(void) 341 { 342 ftp_close(¤t_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(¤t_ftp, "NOOP", NULL, &status); 379 if (status != 200) 380 ftp_close(¤t_ftp); 381 else 382 goto ftp_read; 383 } 384 else 385 ftp_quit(¤t_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(¤t_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(¤t_ftp, "TYPE", "I", &status); 442 if (ftp_pasv(¤t_ftp) < 0) { 443 ftp_quit(¤t_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(¤t_ftp, realpathname); 455 ftp_command(¤t_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(¤t_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(¤t_ftp, "LIST", NULL, &status); 495 else 496 ftp_command(¤t_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(¤t_ftp, "CWD", realpathname, &status); 505 if (status == 250) 506 ftp_command(¤t_ftp, "LIST", NULL, &status); 507 } 508 else 509 ftp_command(¤t_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(¤t_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 }