sacc.c (19153B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <locale.h> 7 #include <netdb.h> 8 #include <netinet/in.h> 9 #include <signal.h> 10 #include <stdarg.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 #include <wchar.h> 16 #include <sys/socket.h> 17 #include <sys/stat.h> 18 #include <sys/types.h> 19 #include <sys/wait.h> 20 21 #include "version.h" 22 #include "common.h" 23 #include "io.h" 24 25 enum { 26 TXT, 27 DIR, 28 CSO, 29 ERR, 30 MAC, 31 DOS, 32 UUE, 33 IND, 34 TLN, 35 BIN, 36 MIR, 37 IBM, 38 GIF, 39 IMG, 40 URL, 41 INF, 42 UNK, 43 BRK, 44 }; 45 46 #define NEED_CONF 47 #include "config.h" 48 #undef NEED_CONF 49 50 void (*diag)(char *, ...); 51 52 int interactive; 53 const char ident[] = "@(#) sacc(omys): " VERSION; 54 55 static char intbuf[256]; /* 256B ought to be enough for any URI */ 56 static char *mainurl; 57 static Item *mainentry; 58 static int devnullfd; 59 static int parent = 1; 60 61 static void 62 stddiag(char *fmt, ...) 63 { 64 va_list arg; 65 66 va_start(arg, fmt); 67 vfprintf(stderr, fmt, arg); 68 va_end(arg); 69 fputc('\n', stderr); 70 } 71 72 void 73 die(const char *fmt, ...) 74 { 75 va_list arg; 76 77 va_start(arg, fmt); 78 vfprintf(stderr, fmt, arg); 79 va_end(arg); 80 fputc('\n', stderr); 81 82 exit(1); 83 } 84 85 #ifdef NEED_ASPRINTF 86 int 87 asprintf(char **s, const char *fmt, ...) 88 { 89 va_list ap; 90 int n; 91 92 va_start(ap, fmt); 93 n = vsnprintf(NULL, 0, fmt, ap); 94 va_end(ap); 95 96 if (n == INT_MAX || !(*s = malloc(++n))) 97 return -1; 98 99 va_start(ap, fmt); 100 vsnprintf(*s, n, fmt, ap); 101 va_end(ap); 102 103 return n; 104 } 105 #endif /* NEED_ASPRINTF */ 106 107 #ifdef NEED_STRCASESTR 108 char * 109 strcasestr(const char *h, const char *n) 110 { 111 size_t i; 112 113 if (!n[0]) 114 return (char *)h; 115 116 for (; *h; ++h) { 117 for (i = 0; n[i] && tolower((unsigned char)n[i]) == 118 tolower((unsigned char)h[i]); ++i) 119 ; 120 if (n[i] == '\0') 121 return (char *)h; 122 } 123 124 return NULL; 125 } 126 #endif /* NEED_STRCASESTR */ 127 128 /* print `len' columns of characters. */ 129 size_t 130 mbsprint(const char *s, size_t len) 131 { 132 wchar_t wc; 133 size_t col = 0, i, slen; 134 const char *p; 135 int rl, pl, w; 136 137 if (!len) 138 return col; 139 140 slen = strlen(s); 141 for (i = 0; i < slen; i += rl) { 142 rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4); 143 if (rl == -1) { 144 /* reset state */ 145 mbtowc(NULL, NULL, 0); 146 p = "\xef\xbf\xbd"; /* replacement character */ 147 pl = 3; 148 rl = w = 1; 149 } else { 150 if ((w = wcwidth(wc)) == -1) 151 continue; 152 pl = rl; 153 p = s + i; 154 } 155 if (col + w > len || (col + w == len && s[i + rl])) { 156 fputs("\xe2\x80\xa6", stdout); /* ellipsis */ 157 col++; 158 break; 159 } 160 fwrite(p, 1, pl, stdout); 161 col += w; 162 } 163 return col; 164 } 165 166 static void * 167 xreallocarray(void *m, size_t n, size_t s) 168 { 169 void *nm; 170 171 if (n == 0 || s == 0) { 172 free(m); 173 return NULL; 174 } 175 if (s && n > (size_t)-1/s) 176 die("realloc: overflow"); 177 if (!(nm = realloc(m, n * s))) 178 die("realloc: %s", strerror(errno)); 179 180 return nm; 181 } 182 183 static void * 184 xmalloc(const size_t n) 185 { 186 void *m = malloc(n); 187 188 if (!m) 189 die("malloc: %s", strerror(errno)); 190 191 return m; 192 } 193 194 static void * 195 xcalloc(size_t n) 196 { 197 char *m = calloc(1, n); 198 199 if (!m) 200 die("calloc: %s", strerror(errno)); 201 202 return m; 203 } 204 205 static char * 206 xstrdup(const char *str) 207 { 208 char *s; 209 210 if (!(s = strdup(str))) 211 die("strdup: %s", strerror(errno)); 212 213 return s; 214 } 215 216 static void 217 usage(void) 218 { 219 die("usage: sacc URL"); 220 } 221 222 static void 223 clearitem(Item *item) 224 { 225 Dir *dir; 226 Item *items; 227 char *tag; 228 size_t i; 229 230 if (!item) 231 return; 232 233 if (dir = item->dat) { 234 items = dir->items; 235 for (i = 0; i < dir->nitems; ++i) 236 clearitem(&items[i]); 237 free(items); 238 clear(&item->dat); 239 } 240 241 if (parent && (tag = item->tag) && 242 !strncmp(tag, tmpdir, strlen(tmpdir))) 243 unlink(tag); 244 245 clear(&item->tag); 246 clear(&item->raw); 247 } 248 249 const char * 250 typedisplay(char t) 251 { 252 switch (t) { 253 case '0': 254 return typestr[TXT]; 255 case '1': 256 return typestr[DIR]; 257 case '2': 258 return typestr[CSO]; 259 case '3': 260 return typestr[ERR]; 261 case '4': 262 return typestr[MAC]; 263 case '5': 264 return typestr[DOS]; 265 case '6': 266 return typestr[UUE]; 267 case '7': 268 return typestr[IND]; 269 case '8': 270 return typestr[TLN]; 271 case '9': 272 return typestr[BIN]; 273 case '+': 274 return typestr[MIR]; 275 case 'T': 276 return typestr[IBM]; 277 case 'g': 278 return typestr[GIF]; 279 case 'I': 280 return typestr[IMG]; 281 case 'h': 282 return typestr[URL]; 283 case 'i': 284 return typestr[INF]; 285 default: 286 /* "Characters '0' through 'Z' are reserved." (ASCII) */ 287 if (t >= '0' && t <= 'Z') 288 return typestr[BRK]; 289 else 290 return typestr[UNK]; 291 } 292 } 293 294 int 295 itemuri(Item *item, char *buf, size_t bsz) 296 { 297 int n; 298 299 switch (item->type) { 300 case '8': 301 n = snprintf(buf, bsz, "telnet://%s@%s:%s", 302 item->selector, item->host, item->port); 303 break; 304 case 'T': 305 n = snprintf(buf, bsz, "tn3270://%s@%s:%s", 306 item->selector, item->host, item->port); 307 break; 308 case 'h': 309 n = snprintf(buf, bsz, "%s", item->selector + 310 (strncmp(item->selector, "URL:", 4) ? 0 : 4)); 311 break; 312 default: 313 n = snprintf(buf, bsz, "gopher://%s", item->host); 314 315 if (n < bsz-1 && strcmp(item->port, "70")) 316 n += snprintf(buf+n, bsz-n, ":%s", item->port); 317 if (n < bsz-1) { 318 n += snprintf(buf+n, bsz-n, "/%c%s", 319 item->type, item->selector); 320 } 321 if (n < bsz-1 && item->type == '7' && item->tag) { 322 n += snprintf(buf+n, bsz-n, "%%09%s", 323 item->tag + strlen(item->selector)); 324 } 325 break; 326 } 327 328 return n; 329 } 330 331 static void 332 printdir(Item *item) 333 { 334 Dir *dir; 335 Item *items; 336 size_t i, nitems; 337 338 if (!item || !(dir = item->dat)) 339 return; 340 341 items = dir->items; 342 nitems = dir->nitems; 343 344 for (i = 0; i < nitems; ++i) { 345 printf("%s%s\n", 346 typedisplay(items[i].type), items[i].username); 347 } 348 } 349 350 static void 351 displaytextitem(Item *item) 352 { 353 struct sigaction sa; 354 FILE *pagerin; 355 int pid, wpid; 356 357 sigemptyset(&sa.sa_mask); 358 sa.sa_flags = SA_RESTART; 359 sa.sa_handler = SIG_DFL; 360 sigaction(SIGWINCH, &sa, NULL); 361 362 uicleanup(); 363 364 switch (pid = fork()) { 365 case -1: 366 diag("Couldn't fork."); 367 return; 368 case 0: 369 parent = 0; 370 if (!(pagerin = popen("$PAGER", "w"))) 371 _exit(1); 372 fputs(item->raw, pagerin); 373 exit(pclose(pagerin)); 374 default: 375 while ((wpid = wait(NULL)) >= 0 && wpid != pid) 376 ; 377 } 378 uisetup(); 379 380 sa.sa_handler = uisigwinch; 381 sigaction(SIGWINCH, &sa, NULL); 382 uisigwinch(SIGWINCH); /* force redraw */ 383 } 384 385 static char * 386 pickfield(char **raw, const char *sep) 387 { 388 char c, *r, *f = *raw; 389 390 for (r = *raw; (c = *r) && !strchr(sep, c); ++r) { 391 if (c == '\n') 392 goto skipsep; 393 } 394 395 *r++ = '\0'; 396 skipsep: 397 *raw = r; 398 399 return f; 400 } 401 402 static char * 403 invaliditem(char *raw) 404 { 405 char c; 406 int tabs; 407 408 for (tabs = 0; (c = *raw) && c != '\n'; ++raw) { 409 if (c == '\t') 410 ++tabs; 411 } 412 if (tabs < 3) { 413 *raw++ = '\0'; 414 return raw; 415 } 416 417 return NULL; 418 } 419 420 static void 421 molditem(Item *item, char **raw) 422 { 423 char *next; 424 425 if (!*raw) 426 return; 427 428 if ((next = invaliditem(*raw))) { 429 item->username = *raw; 430 *raw = next; 431 return; 432 } 433 434 item->type = *raw[0]++; 435 item->username = pickfield(raw, "\t"); 436 item->selector = pickfield(raw, "\t"); 437 item->host = pickfield(raw, "\t"); 438 item->port = pickfield(raw, "\t\r"); 439 while (*raw[0] != '\n') 440 ++*raw; 441 *raw[0]++ = '\0'; 442 } 443 444 static Dir * 445 molddiritem(char *raw) 446 { 447 Item *item, *items = NULL; 448 char *nl, *p; 449 Dir *dir; 450 size_t i, n, nitems; 451 452 for (nl = raw, nitems = 0; p = strchr(nl, '\n'); nl = p+1) 453 ++nitems; 454 455 if (!nitems) { 456 diag("Couldn't parse dir item"); 457 return NULL; 458 } 459 460 dir = xmalloc(sizeof(Dir)); 461 items = xreallocarray(items, nitems, sizeof(Item)); 462 memset(items, 0, nitems * sizeof(Item)); 463 464 for (i = 0; i < nitems; ++i) { 465 item = &items[i]; 466 molditem(item, &raw); 467 if (item->type == '+') { 468 for (n = i - 1; n < (size_t)-1; --n) { 469 if (items[n].type != '+') { 470 item->redtype = items[n].type; 471 break; 472 } 473 } 474 } 475 } 476 477 dir->items = items; 478 dir->nitems = nitems; 479 dir->printoff = dir->curline = 0; 480 481 return dir; 482 } 483 484 static char * 485 getrawitem(struct cnx *c) 486 { 487 char *raw, *buf; 488 size_t bn, bs; 489 ssize_t n; 490 491 raw = buf = NULL; 492 bn = bs = n = 0; 493 494 do { 495 bs -= n; 496 buf += n; 497 498 if (buf - raw >= 5) { 499 if (strncmp(buf-5, "\r\n.\r\n", 5) == 0) { 500 buf[-3] = '\0'; 501 break; 502 } 503 } else if (buf - raw == 3 && strncmp(buf-3, ".\r\n", 3)) { 504 buf[-3] = '\0'; 505 break; 506 } 507 508 if (bs < 1) { 509 raw = xreallocarray(raw, ++bn, BUFSIZ); 510 buf = raw + (bn-1) * BUFSIZ; 511 bs = BUFSIZ; 512 } 513 514 } while ((n = ioread(c, buf, bs)) > 0); 515 516 *buf = '\0'; 517 518 if (n == -1) { 519 diag("Can't read socket: %s", strerror(errno)); 520 clear(&raw); 521 } 522 523 return raw; 524 } 525 526 static int 527 sendselector(struct cnx *c, const char *selector) 528 { 529 char *msg, *p; 530 size_t ln; 531 ssize_t n; 532 533 ln = strlen(selector) + 3; 534 msg = p = xmalloc(ln); 535 snprintf(msg, ln--, "%s\r\n", selector); 536 537 while ((n = iowrite(c, p, ln)) > 0) { 538 ln -= n; 539 p += n; 540 }; 541 542 free(msg); 543 if (n == -1) 544 diag("Can't send message: %s", strerror(errno)); 545 546 return n; 547 } 548 549 static int 550 connectto(const char *host, const char *port, struct cnx *c) 551 { 552 sigset_t set, oset; 553 static const struct addrinfo hints = { 554 .ai_family = AF_UNSPEC, 555 .ai_socktype = SOCK_STREAM, 556 .ai_protocol = IPPROTO_TCP, 557 }; 558 struct addrinfo *addrs, *ai; 559 int r, err; 560 561 sigemptyset(&set); 562 sigaddset(&set, SIGWINCH); 563 sigprocmask(SIG_BLOCK, &set, &oset); 564 565 if (r = getaddrinfo(host, port, &hints, &addrs)) { 566 diag("Can't resolve hostname \"%s\": %s", 567 host, gai_strerror(r)); 568 goto err; 569 } 570 571 r = -1; 572 for (ai = addrs; ai && r == -1; ai = ai->ai_next) { 573 do { 574 if ((c->sock = socket(ai->ai_family, ai->ai_socktype, 575 ai->ai_protocol)) == -1) { 576 err = errno; 577 break; 578 } 579 580 if ((r = ioconnect(c, ai, host)) < 0) { 581 err = errno; 582 ioclose(c); 583 } 584 } while (r == CONN_RETRY); 585 } 586 587 freeaddrinfo(addrs); 588 589 if (r == CONN_ERROR) 590 ioconnerr(c, host, port, err); 591 err: 592 sigprocmask(SIG_SETMASK, &oset, NULL); 593 594 return r; 595 } 596 597 static int 598 download(Item *item, int dest) 599 { 600 char buf[BUFSIZ]; 601 struct cnx c = { 0 }; 602 ssize_t r, w; 603 604 if (item->tag == NULL) { 605 if (connectto(item->host, item->port, &c) < 0 || 606 sendselector(&c, item->selector) == -1) 607 return 0; 608 } else { 609 if ((c.sock = open(item->tag, O_RDONLY)) == -1) { 610 printf("Can't open source file %s: %s", 611 item->tag, strerror(errno)); 612 errno = 0; 613 return 0; 614 } 615 } 616 617 w = 0; 618 while ((r = ioread(&c, buf, BUFSIZ)) > 0) { 619 while ((w = write(dest, buf, r)) > 0) 620 r -= w; 621 } 622 623 if (r == -1 || w == -1) { 624 printf("Error downloading file %s: %s", 625 item->selector, strerror(errno)); 626 errno = 0; 627 } 628 629 close(dest); 630 ioclose(&c); 631 632 return (r == 0 && w == 0); 633 } 634 635 static void 636 downloaditem(Item *item) 637 { 638 char *file, *path, *tag; 639 mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; 640 int dest; 641 642 if (file = strrchr(item->selector, '/')) 643 ++file; 644 else 645 file = item->selector; 646 647 if (!(path = uiprompt("Download to [%s] (^D cancel): ", file))) 648 return; 649 650 if (!path[0]) 651 path = xstrdup(file); 652 653 if (tag = item->tag) { 654 if (access(tag, R_OK) == -1) { 655 clear(&item->tag); 656 } else if (!strcmp(tag, path)) { 657 goto cleanup; 658 } 659 } 660 661 if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { 662 diag("Can't open destination file %s: %s", 663 path, strerror(errno)); 664 errno = 0; 665 goto cleanup; 666 } 667 668 if (!download(item, dest)) 669 goto cleanup; 670 671 if (item->tag) 672 goto cleanup; 673 674 item->tag = path; 675 676 return; 677 cleanup: 678 free(path); 679 return; 680 } 681 682 static int 683 fetchitem(Item *item) 684 { 685 struct cnx c; 686 char *raw; 687 688 if (connectto(item->host, item->port, &c) < 0 || 689 sendselector(&c, item->selector) == -1) 690 return 0; 691 692 raw = getrawitem(&c); 693 ioclose(&c); 694 695 if (raw == NULL || !*raw) { 696 diag("Empty response from server"); 697 clear(&raw); 698 } 699 700 return ((item->raw = raw) != NULL); 701 } 702 703 static void 704 pipeuri(char *cmd, char *msg, char *uri) 705 { 706 FILE *sel; 707 708 if ((sel = popen(cmd, "w")) == NULL) { 709 diag("URI not %s\n", msg); 710 return; 711 } 712 713 fputs(uri, sel); 714 pclose(sel); 715 diag("%s \"%s\"", msg, uri); 716 } 717 718 static void 719 execuri(char *cmd, char *msg, char *uri) 720 { 721 switch (fork()) { 722 case -1: 723 diag("Couldn't fork."); 724 return; 725 case 0: 726 parent = 0; 727 dup2(devnullfd, 1); 728 dup2(devnullfd, 2); 729 if (execlp(cmd, cmd, uri, NULL) == -1) 730 _exit(1); 731 default: 732 if (modalplumber) { 733 while (waitpid(-1, NULL, 0) != -1) 734 ; 735 } 736 } 737 738 diag("%s \"%s\"", msg, uri); 739 } 740 741 static void 742 plumbitem(Item *item) 743 { 744 char *file, *path, *tag; 745 mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; 746 int dest, plumbitem; 747 748 if (file = strrchr(item->selector, '/')) 749 ++file; 750 else 751 file = item->selector; 752 753 path = uiprompt("Download %s to (^D cancel, <empty> plumb): ", 754 file); 755 if (!path) 756 return; 757 758 if ((tag = item->tag) && access(tag, R_OK) == -1) { 759 clear(&item->tag); 760 tag = NULL; 761 } 762 763 plumbitem = path[0] ? 0 : 1; 764 765 if (!path[0]) { 766 clear(&path); 767 if (!tag) { 768 if (asprintf(&path, "%s/%s", tmpdir, file) == -1) 769 die("Can't generate tmpdir path: %s/%s: %s", 770 tmpdir, file, strerror(errno)); 771 } 772 } 773 774 if (path && (!tag || strcmp(tag, path))) { 775 if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { 776 diag("Can't open destination file %s: %s", 777 path, strerror(errno)); 778 errno = 0; 779 goto cleanup; 780 } 781 if (!download(item, dest) || tag) 782 goto cleanup; 783 } 784 785 if (!tag) 786 item->tag = path; 787 788 if (plumbitem) 789 execuri(plumber, "Plumbed", item->tag); 790 791 return; 792 cleanup: 793 free(path); 794 return; 795 } 796 797 void 798 yankitem(Item *item) 799 { 800 itemuri(item, intbuf, sizeof(intbuf)); 801 pipeuri(yanker, "Yanked", intbuf); 802 } 803 804 static int 805 dig(Item *entry, Item *item) 806 { 807 char *plumburi = NULL; 808 int t; 809 810 if (item->raw) /* already in cache */ 811 return item->type; 812 if (!item->entry) 813 item->entry = entry ? entry : item; 814 815 t = item->redtype ? item->redtype : item->type; 816 switch (t) { 817 case 'h': /* fallthrough */ 818 if (!strncmp(item->selector, "URL:", 4)) { 819 execuri(plumber, "Plumbed", item->selector+4); 820 return 0; 821 } 822 case '0': 823 if (!fetchitem(item)) 824 return 0; 825 break; 826 case '1': 827 case '7': 828 if (!fetchitem(item) || !(item->dat = molddiritem(item->raw))) 829 return 0; 830 break; 831 case '4': 832 case '5': 833 case '6': 834 case '9': 835 downloaditem(item); 836 return 0; 837 case '8': 838 if (asprintf(&plumburi, "telnet://%s%s%s:%s", 839 item->selector, item->selector ? "@" : "", 840 item->host, item->port) == -1) 841 return 0; 842 execuri(plumber, "Plumbed", plumburi); 843 free(plumburi); 844 return 0; 845 case 'T': 846 if (asprintf(&plumburi, "tn3270://%s%s%s:%s", 847 item->selector, item->selector ? "@" : "", 848 item->host, item->port) == -1) 849 return 0; 850 execuri(plumburi, "Plumbed", plumburi); 851 free(plumburi); 852 return 0; 853 default: 854 if (t >= '0' && t <= 'Z') { 855 diag("Type %c (%s) not supported", t, typedisplay(t)); 856 return 0; 857 } 858 case 'g': 859 case 'I': 860 plumbitem(item); 861 case 'i': 862 return 0; 863 } 864 865 return item->type; 866 } 867 868 static char * 869 searchselector(Item *item) 870 { 871 char *pexp, *exp, *tag, *selector = item->selector; 872 size_t n = strlen(selector); 873 874 if ((tag = item->tag) && !strncmp(tag, selector, n)) 875 pexp = tag + n+1; 876 else 877 pexp = ""; 878 879 if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp))) 880 return NULL; 881 882 if (exp[0] && strcmp(exp, pexp)) { 883 n += strlen(exp) + 2; 884 tag = xmalloc(n); 885 snprintf(tag, n, "%s\t%s", selector, exp); 886 } 887 888 free(exp); 889 return tag; 890 } 891 892 static int 893 searchitem(Item *entry, Item *item) 894 { 895 char *sel, *selector; 896 897 if (!(sel = searchselector(item))) 898 return 0; 899 900 if (sel != item->tag) 901 clearitem(item); 902 if (!item->dat) { 903 selector = item->selector; 904 item->selector = item->tag = sel; 905 dig(entry, item); 906 item->selector = selector; 907 } 908 return (item->dat != NULL); 909 } 910 911 static void 912 printout(Item *hole) 913 { 914 char t = 0; 915 916 if (!hole) 917 return; 918 919 switch (hole->redtype ? hole->redtype : (t = hole->type)) { 920 case '0': 921 if (dig(hole, hole)) 922 fputs(hole->raw, stdout); 923 return; 924 case '1': 925 case '7': 926 if (dig(hole, hole)) 927 printdir(hole); 928 return; 929 default: 930 if (t >= '0' && t <= 'Z') { 931 diag("Type %c (%s) not supported", t, typedisplay(t)); 932 return; 933 } 934 case '4': 935 case '5': 936 case '6': 937 case '9': 938 case 'g': 939 case 'I': 940 download(hole, 1); 941 case '2': 942 case '3': 943 case '8': 944 case 'T': 945 return; 946 } 947 } 948 949 static void 950 delve(Item *hole) 951 { 952 Item *entry = NULL; 953 954 while (hole) { 955 switch (hole->redtype ? hole->redtype : hole->type) { 956 case 'h': 957 case '0': 958 if (dig(entry, hole)) 959 displaytextitem(hole); 960 break; 961 case '1': 962 case '+': 963 if (dig(entry, hole) && hole->dat) 964 entry = hole; 965 break; 966 case '7': 967 if (searchitem(entry, hole)) 968 entry = hole; 969 break; 970 case 0: 971 diag("Couldn't get %s:%s/%c%s", hole->host, 972 hole->port, hole->type, hole->selector); 973 break; 974 case '4': 975 case '5': 976 case '6': /* TODO decode? */ 977 case '8': 978 case '9': 979 case 'g': 980 case 'I': 981 case 'T': 982 default: 983 dig(entry, hole); 984 break; 985 } 986 987 if (!entry) 988 return; 989 990 do { 991 uidisplay(entry); 992 hole = uiselectitem(entry); 993 } while (hole == entry); 994 } 995 } 996 997 static Item * 998 moldentry(char *url) 999 { 1000 Item *entry; 1001 char *p, *host = url, *port = "70", *gopherpath = "1"; 1002 int parsed, ipv6; 1003 1004 host = ioparseurl(url); 1005 1006 if (*host == '[') { 1007 ipv6 = 1; 1008 ++host; 1009 } else { 1010 ipv6 = 0; 1011 } 1012 1013 for (parsed = 0, p = host; !parsed && *p; ++p) { 1014 switch (*p) { 1015 case ']': 1016 if (ipv6) { 1017 *p = '\0'; 1018 ipv6 = 0; 1019 } 1020 continue; 1021 case ':': 1022 if (!ipv6) { 1023 *p = '\0'; 1024 port = p+1; 1025 } 1026 continue; 1027 case '/': 1028 *p = '\0'; 1029 parsed = 1; 1030 continue; 1031 } 1032 } 1033 1034 if (*host == '\0' || *port == '\0' || ipv6) 1035 die("Can't parse url"); 1036 1037 if (*p != '\0') 1038 gopherpath = p; 1039 1040 entry = xcalloc(sizeof(Item)); 1041 entry->type = gopherpath[0]; 1042 entry->username = entry->selector = ++gopherpath; 1043 if (entry->type == '7') { 1044 if (p = strstr(gopherpath, "%09")) { 1045 memmove(p+1, p+3, strlen(p+3)+1); 1046 *p = '\t'; 1047 } 1048 if (p || (p = strchr(gopherpath, '\t'))) { 1049 asprintf(&entry->tag, "%s", gopherpath); 1050 *p = '\0'; 1051 } 1052 } 1053 entry->host = host; 1054 entry->port = port; 1055 entry->entry = entry; 1056 1057 return entry; 1058 } 1059 1060 static void 1061 cleanup(void) 1062 { 1063 clearitem(mainentry); 1064 if (parent) 1065 rmdir(tmpdir); 1066 free(mainentry); 1067 free(mainurl); 1068 if (interactive) 1069 uicleanup(); 1070 } 1071 1072 static void 1073 sighandler(int signo) 1074 { 1075 exit(128 + signo); 1076 } 1077 1078 static void 1079 setup(void) 1080 { 1081 struct sigaction sa; 1082 int fd; 1083 1084 setlocale(LC_CTYPE, ""); 1085 setenv("PAGER", "more", 0); 1086 atexit(cleanup); 1087 /* reopen stdin in case we're reading from a pipe */ 1088 if ((fd = open("/dev/tty", O_RDONLY)) == -1) 1089 die("open: /dev/tty: %s", strerror(errno)); 1090 if (dup2(fd, 0) == -1) 1091 die("dup2: /dev/tty, stdin: %s", strerror(errno)); 1092 close(fd); 1093 if ((devnullfd = open("/dev/null", O_WRONLY)) == -1) 1094 die("open: /dev/null: %s", strerror(errno)); 1095 1096 sigemptyset(&sa.sa_mask); 1097 sa.sa_flags = SA_RESTART; 1098 sa.sa_handler = sighandler; 1099 sigaction(SIGINT, &sa, NULL); 1100 sigaction(SIGHUP, &sa, NULL); 1101 sigaction(SIGTERM, &sa, NULL); 1102 1103 sa.sa_handler = SIG_IGN; 1104 sigaction(SIGCHLD, &sa, NULL); 1105 1106 if (!mkdtemp(tmpdir)) 1107 die("mkdir: %s: %s", tmpdir, strerror(errno)); 1108 if (interactive = isatty(1)) { 1109 uisetup(); 1110 diag = uistatus; 1111 sa.sa_handler = uisigwinch; 1112 sigaction(SIGWINCH, &sa, NULL); 1113 } else { 1114 diag = stddiag; 1115 } 1116 iosetup(); 1117 } 1118 1119 int 1120 main(int argc, char *argv[]) 1121 { 1122 if (argc != 2) 1123 usage(); 1124 1125 setup(); 1126 1127 mainurl = xstrdup(argv[1]); 1128 mainentry = moldentry(mainurl); 1129 1130 if (interactive) 1131 delve(mainentry); 1132 else 1133 printout(mainentry); 1134 1135 exit(0); 1136 }