Home
sacc.c - sacc - sacc(omys), simple console gopher client HTML git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/ DIR Log DIR Files DIR Refs DIR Tags DIR LICENSE --- sacc.c (19166B) --- 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': /* fallthrough */ 309 if (!strncmp(item->selector, "URL:", 4)) { 310 n = snprintf(buf, bsz, "%s", item->selector+4); 311 break; 312 } 313 default: 314 n = snprintf(buf, bsz, "gopher://%s", item->host); 315 316 if (n < bsz-1 && strcmp(item->port, "70")) 317 n += snprintf(buf+n, bsz-n, ":%s", item->port); 318 if (n < bsz-1) { 319 n += snprintf(buf+n, bsz-n, "/%c%s", 320 item->type, item->selector); 321 } 322 if (n < bsz-1 && item->type == '7' && item->tag) { 323 n += snprintf(buf+n, bsz-n, "%%09%s", 324 item->tag + strlen(item->selector)); 325 } 326 break; 327 } 328 329 return n; 330 } 331 332 static void 333 printdir(Item *item) 334 { 335 Dir *dir; 336 Item *items; 337 size_t i, nitems; 338 339 if (!item || !(dir = item->dat)) 340 return; 341 342 items = dir->items; 343 nitems = dir->nitems; 344 345 for (i = 0; i < nitems; ++i) { 346 printf("%s%s\n", 347 typedisplay(items[i].type), items[i].username); 348 } 349 } 350 351 static void 352 displaytextitem(Item *item) 353 { 354 struct sigaction sa; 355 FILE *pagerin; 356 int pid, wpid; 357 358 sigemptyset(&sa.sa_mask); 359 sa.sa_flags = SA_RESTART; 360 sa.sa_handler = SIG_DFL; 361 sigaction(SIGWINCH, &sa, NULL); 362 363 uicleanup(); 364 365 switch (pid = fork()) { 366 case -1: 367 diag("Couldn't fork."); 368 return; 369 case 0: 370 parent = 0; 371 if (!(pagerin = popen("$PAGER", "w"))) 372 _exit(1); 373 fputs(item->raw, pagerin); 374 exit(pclose(pagerin)); 375 default: 376 while ((wpid = wait(NULL)) >= 0 && wpid != pid) 377 ; 378 } 379 uisetup(); 380 381 sa.sa_handler = uisigwinch; 382 sigaction(SIGWINCH, &sa, NULL); 383 uisigwinch(SIGWINCH); /* force redraw */ 384 } 385 386 static char * 387 pickfield(char **raw, const char *sep) 388 { 389 char c, *r, *f = *raw; 390 391 for (r = *raw; (c = *r) && !strchr(sep, c); ++r) { 392 if (c == '\n') 393 goto skipsep; 394 } 395 396 *r++ = '\0'; 397 skipsep: 398 *raw = r; 399 400 return f; 401 } 402 403 static char * 404 invaliditem(char *raw) 405 { 406 char c; 407 int tabs; 408 409 for (tabs = 0; (c = *raw) && c != '\n'; ++raw) { 410 if (c == '\t') 411 ++tabs; 412 } 413 if (tabs < 3) { 414 *raw++ = '\0'; 415 return raw; 416 } 417 418 return NULL; 419 } 420 421 static void 422 molditem(Item *item, char **raw) 423 { 424 char *next; 425 426 if (!*raw) 427 return; 428 429 if ((next = invaliditem(*raw))) { 430 item->username = *raw; 431 *raw = next; 432 return; 433 } 434 435 item->type = *raw[0]++; 436 item->username = pickfield(raw, "\t"); 437 item->selector = pickfield(raw, "\t"); 438 item->host = pickfield(raw, "\t"); 439 item->port = pickfield(raw, "\t\r"); 440 while (*raw[0] != '\n') 441 ++*raw; 442 *raw[0]++ = '\0'; 443 } 444 445 static Dir * 446 molddiritem(char *raw) 447 { 448 Item *item, *items = NULL; 449 char *nl, *p; 450 Dir *dir; 451 size_t i, n, nitems; 452 453 for (nl = raw, nitems = 0; p = strchr(nl, '\n'); nl = p+1) 454 ++nitems; 455 456 if (!nitems) { 457 diag("Couldn't parse dir item"); 458 return NULL; 459 } 460 461 dir = xmalloc(sizeof(Dir)); 462 items = xreallocarray(items, nitems, sizeof(Item)); 463 memset(items, 0, nitems * sizeof(Item)); 464 465 for (i = 0; i < nitems; ++i) { 466 item = &items[i]; 467 molditem(item, &raw); 468 if (item->type == '+') { 469 for (n = i - 1; n < (size_t)-1; --n) { 470 if (items[n].type != '+') { 471 item->redtype = items[n].type; 472 break; 473 } 474 } 475 } 476 } 477 478 dir->items = items; 479 dir->nitems = nitems; 480 dir->printoff = dir->curline = 0; 481 482 return dir; 483 } 484 485 static char * 486 getrawitem(struct cnx *c) 487 { 488 char *raw, *buf; 489 size_t bn, bs; 490 ssize_t n; 491 492 raw = buf = NULL; 493 bn = bs = n = 0; 494 495 do { 496 bs -= n; 497 buf += n; 498 499 if (buf - raw >= 5) { 500 if (strncmp(buf-5, "\r\n.\r\n", 5) == 0) { 501 buf[-3] = '\0'; 502 break; 503 } 504 } else if (buf - raw == 3 && strncmp(buf-3, ".\r\n", 3)) { 505 buf[-3] = '\0'; 506 break; 507 } 508 509 if (bs < 1) { 510 raw = xreallocarray(raw, ++bn, BUFSIZ); 511 buf = raw + (bn-1) * BUFSIZ; 512 bs = BUFSIZ; 513 } 514 515 } while ((n = ioread(c, buf, bs)) > 0); 516 517 *buf = '\0'; 518 519 if (n == -1) { 520 diag("Can't read socket: %s", strerror(errno)); 521 clear(&raw); 522 } 523 524 return raw; 525 } 526 527 static int 528 sendselector(struct cnx *c, const char *selector) 529 { 530 char *msg, *p; 531 size_t ln; 532 ssize_t n; 533 534 ln = strlen(selector) + 3; 535 msg = p = xmalloc(ln); 536 snprintf(msg, ln--, "%s\r\n", selector); 537 538 while (ln && (n = iowrite(c, p, ln)) > 0) { 539 ln -= n; 540 p += n; 541 } 542 543 free(msg); 544 if (n == -1) 545 diag("Can't send message: %s", strerror(errno)); 546 547 return n; 548 } 549 550 static int 551 connectto(const char *host, const char *port, struct cnx *c) 552 { 553 sigset_t set, oset; 554 static const struct addrinfo hints = { 555 .ai_family = AF_UNSPEC, 556 .ai_socktype = SOCK_STREAM, 557 .ai_protocol = IPPROTO_TCP, 558 }; 559 struct addrinfo *addrs, *ai; 560 int r, err; 561 562 sigemptyset(&set); 563 sigaddset(&set, SIGWINCH); 564 sigprocmask(SIG_BLOCK, &set, &oset); 565 566 if (r = getaddrinfo(host, port, &hints, &addrs)) { 567 diag("Can't resolve hostname \"%s\": %s", 568 host, gai_strerror(r)); 569 goto err; 570 } 571 572 r = -1; 573 for (ai = addrs; ai && r == -1; ai = ai->ai_next) { 574 do { 575 if ((c->sock = socket(ai->ai_family, ai->ai_socktype, 576 ai->ai_protocol)) == -1) { 577 err = errno; 578 break; 579 } 580 581 if ((r = ioconnect(c, ai, host)) < 0) { 582 err = errno; 583 ioclose(c); 584 } 585 } while (r == CONN_RETRY); 586 } 587 588 freeaddrinfo(addrs); 589 590 if (r == CONN_ERROR) 591 ioconnerr(c, host, port, err); 592 err: 593 sigprocmask(SIG_SETMASK, &oset, NULL); 594 595 return r; 596 } 597 598 static int 599 download(Item *item, int dest) 600 { 601 char buf[BUFSIZ]; 602 struct cnx c = { 0 }; 603 ssize_t r, w; 604 605 if (item->tag == NULL) { 606 if (connectto(item->host, item->port, &c) < 0 || 607 sendselector(&c, item->selector) == -1) 608 return 0; 609 } else { 610 if ((c.sock = open(item->tag, O_RDONLY)) == -1) { 611 printf("Can't open source file %s: %s", 612 item->tag, strerror(errno)); 613 errno = 0; 614 return 0; 615 } 616 } 617 618 w = 0; 619 while ((r = ioread(&c, buf, BUFSIZ)) > 0) { 620 while ((w = write(dest, buf, r)) > 0) 621 r -= w; 622 } 623 624 if (r == -1 || w == -1) { 625 printf("Error downloading file %s: %s", 626 item->selector, strerror(errno)); 627 errno = 0; 628 } 629 630 close(dest); 631 ioclose(&c); 632 633 return (r == 0 && w == 0); 634 } 635 636 static void 637 downloaditem(Item *item) 638 { 639 char *file, *path, *tag; 640 mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; 641 int dest; 642 643 if (file = strrchr(item->selector, '/')) 644 ++file; 645 else 646 file = item->selector; 647 648 if (!(path = uiprompt("Download to [%s] (^D cancel): ", file))) 649 return; 650 651 if (!path[0]) 652 path = xstrdup(file); 653 654 if (tag = item->tag) { 655 if (access(tag, R_OK) == -1) { 656 clear(&item->tag); 657 } else if (!strcmp(tag, path)) { 658 goto cleanup; 659 } 660 } 661 662 if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { 663 diag("Can't open destination file %s: %s", 664 path, strerror(errno)); 665 errno = 0; 666 goto cleanup; 667 } 668 669 if (!download(item, dest)) 670 goto cleanup; 671 672 if (item->tag) 673 goto cleanup; 674 675 item->tag = path; 676 677 return; 678 cleanup: 679 free(path); 680 return; 681 } 682 683 static int 684 fetchitem(Item *item) 685 { 686 struct cnx c; 687 char *raw; 688 689 if (connectto(item->host, item->port, &c) < 0 || 690 sendselector(&c, item->selector) == -1) 691 return 0; 692 693 raw = getrawitem(&c); 694 ioclose(&c); 695 696 if (raw == NULL || !*raw) { 697 diag("Empty response from server"); 698 clear(&raw); 699 } 700 701 return ((item->raw = raw) != NULL); 702 } 703 704 static void 705 pipeuri(char *cmd, char *msg, char *uri) 706 { 707 FILE *sel; 708 709 if ((sel = popen(cmd, "w")) == NULL) { 710 diag("URI not %s\n", msg); 711 return; 712 } 713 714 fputs(uri, sel); 715 pclose(sel); 716 diag("%s \"%s\"", msg, uri); 717 } 718 719 static void 720 execuri(char *cmd, char *msg, char *uri) 721 { 722 switch (fork()) { 723 case -1: 724 diag("Couldn't fork."); 725 return; 726 case 0: 727 parent = 0; 728 dup2(devnullfd, 1); 729 dup2(devnullfd, 2); 730 if (execlp(cmd, cmd, uri, NULL) == -1) 731 _exit(1); 732 default: 733 if (modalplumber) { 734 while (waitpid(-1, NULL, 0) != -1) 735 ; 736 } 737 } 738 739 diag("%s \"%s\"", msg, uri); 740 } 741 742 static void 743 plumbitem(Item *item) 744 { 745 char *file, *path, *tag; 746 mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; 747 int dest, plumbitem; 748 749 if (file = strrchr(item->selector, '/')) 750 ++file; 751 else 752 file = item->selector; 753 754 path = uiprompt("Download %s to (^D cancel, <empty> plumb): ", 755 file); 756 if (!path) 757 return; 758 759 if ((tag = item->tag) && access(tag, R_OK) == -1) { 760 clear(&item->tag); 761 tag = NULL; 762 } 763 764 plumbitem = path[0] ? 0 : 1; 765 766 if (!path[0]) { 767 clear(&path); 768 if (!tag) { 769 if (asprintf(&path, "%s/%s", tmpdir, file) == -1) 770 die("Can't generate tmpdir path: %s/%s: %s", 771 tmpdir, file, strerror(errno)); 772 } 773 } 774 775 if (path && (!tag || strcmp(tag, path))) { 776 if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { 777 diag("Can't open destination file %s: %s", 778 path, strerror(errno)); 779 errno = 0; 780 goto cleanup; 781 } 782 if (!download(item, dest) || tag) 783 goto cleanup; 784 } 785 786 if (!tag) 787 item->tag = path; 788 789 if (plumbitem) 790 execuri(plumber, "Plumbed", item->tag); 791 792 return; 793 cleanup: 794 free(path); 795 return; 796 } 797 798 void 799 yankitem(Item *item) 800 { 801 itemuri(item, intbuf, sizeof(intbuf)); 802 pipeuri(yanker, "Yanked", intbuf); 803 } 804 805 static int 806 dig(Item *entry, Item *item) 807 { 808 char *plumburi = NULL; 809 int t; 810 811 if (item->raw) /* already in cache */ 812 return item->type; 813 if (!item->entry) 814 item->entry = entry ? entry : item; 815 816 t = item->redtype ? item->redtype : item->type; 817 switch (t) { 818 case 'h': /* fallthrough */ 819 if (!strncmp(item->selector, "URL:", 4)) { 820 execuri(plumber, "Plumbed", item->selector+4); 821 return 0; 822 } 823 case '0': 824 if (!fetchitem(item)) 825 return 0; 826 break; 827 case '1': 828 case '7': 829 if (!fetchitem(item) || !(item->dat = molddiritem(item->raw))) 830 return 0; 831 break; 832 case '4': 833 case '5': 834 case '6': 835 case '9': 836 downloaditem(item); 837 return 0; 838 case '8': 839 if (asprintf(&plumburi, "telnet://%s%s%s:%s", 840 item->selector, item->selector ? "@" : "", 841 item->host, item->port) == -1) 842 return 0; 843 execuri(plumber, "Plumbed", plumburi); 844 free(plumburi); 845 return 0; 846 case 'T': 847 if (asprintf(&plumburi, "tn3270://%s%s%s:%s", 848 item->selector, item->selector ? "@" : "", 849 item->host, item->port) == -1) 850 return 0; 851 execuri(plumber, "Plumbed", plumburi); 852 free(plumburi); 853 return 0; 854 default: 855 if (t >= '0' && t <= 'Z') { 856 diag("Type %c (%s) not supported", t, typedisplay(t)); 857 return 0; 858 } 859 case 'g': 860 case 'I': 861 plumbitem(item); 862 case 'i': 863 return 0; 864 } 865 866 return item->type; 867 } 868 869 static char * 870 searchselector(Item *item) 871 { 872 char *pexp, *exp, *tag, *selector = item->selector; 873 size_t n = strlen(selector); 874 875 if ((tag = item->tag) && !strncmp(tag, selector, n)) 876 pexp = tag + n+1; 877 else 878 pexp = ""; 879 880 if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp))) 881 return NULL; 882 883 if (exp[0] && strcmp(exp, pexp)) { 884 n += strlen(exp) + 2; 885 tag = xmalloc(n); 886 snprintf(tag, n, "%s\t%s", selector, exp); 887 } 888 889 free(exp); 890 return tag; 891 } 892 893 static int 894 searchitem(Item *entry, Item *item) 895 { 896 char *sel, *selector; 897 898 if (!(sel = searchselector(item))) 899 return 0; 900 901 if (sel != item->tag) 902 clearitem(item); 903 if (!item->dat) { 904 selector = item->selector; 905 item->selector = item->tag = sel; 906 dig(entry, item); 907 item->selector = selector; 908 } 909 return (item->dat != NULL); 910 } 911 912 static void 913 printout(Item *hole) 914 { 915 char t = 0; 916 917 if (!hole) 918 return; 919 920 switch (hole->redtype ? hole->redtype : (t = hole->type)) { 921 case '0': 922 if (dig(hole, hole)) 923 fputs(hole->raw, stdout); 924 return; 925 case '1': 926 case '7': 927 if (dig(hole, hole)) 928 printdir(hole); 929 return; 930 default: 931 if (t >= '0' && t <= 'Z') { 932 diag("Type %c (%s) not supported", t, typedisplay(t)); 933 return; 934 } 935 case '4': 936 case '5': 937 case '6': 938 case '9': 939 case 'g': 940 case 'I': 941 download(hole, 1); 942 case '2': 943 case '3': 944 case '8': 945 case 'T': 946 return; 947 } 948 } 949 950 static void 951 delve(Item *hole) 952 { 953 Item *entry = NULL; 954 955 while (hole) { 956 switch (hole->redtype ? hole->redtype : hole->type) { 957 case 'h': 958 case '0': 959 if (dig(entry, hole)) 960 displaytextitem(hole); 961 break; 962 case '1': 963 case '+': 964 if (dig(entry, hole) && hole->dat) 965 entry = hole; 966 break; 967 case '7': 968 if (searchitem(entry, hole)) 969 entry = hole; 970 break; 971 case 0: 972 diag("Couldn't get %s:%s/%c%s", hole->host, 973 hole->port, hole->type, hole->selector); 974 break; 975 case '4': 976 case '5': 977 case '6': /* TODO decode? */ 978 case '8': 979 case '9': 980 case 'g': 981 case 'I': 982 case 'T': 983 default: 984 dig(entry, hole); 985 break; 986 } 987 988 if (!entry) 989 return; 990 991 do { 992 uidisplay(entry); 993 hole = uiselectitem(entry); 994 } while (hole == entry); 995 } 996 } 997 998 static Item * 999 moldentry(char *url) 1000 { 1001 Item *entry; 1002 char *p, *host = url, *port = "70", *gopherpath = "1"; 1003 int parsed, ipv6; 1004 1005 host = ioparseurl(url); 1006 1007 if (*host == '[') { 1008 ipv6 = 1; 1009 ++host; 1010 } else { 1011 ipv6 = 0; 1012 } 1013 1014 for (parsed = 0, p = host; !parsed && *p; ++p) { 1015 switch (*p) { 1016 case ']': 1017 if (ipv6) { 1018 *p = '\0'; 1019 ipv6 = 0; 1020 } 1021 continue; 1022 case ':': 1023 if (!ipv6) { 1024 *p = '\0'; 1025 port = p+1; 1026 } 1027 continue; 1028 case '/': 1029 *p = '\0'; 1030 parsed = 1; 1031 continue; 1032 } 1033 } 1034 1035 if (*host == '\0' || *port == '\0' || ipv6) 1036 die("Can't parse url"); 1037 1038 if (*p != '\0') 1039 gopherpath = p; 1040 1041 entry = xcalloc(sizeof(Item)); 1042 entry->type = gopherpath[0]; 1043 entry->username = entry->selector = ++gopherpath; 1044 if (entry->type == '7') { 1045 if (p = strstr(gopherpath, "%09")) { 1046 memmove(p+1, p+3, strlen(p+3)+1); 1047 *p = '\t'; 1048 } 1049 if (p || (p = strchr(gopherpath, '\t'))) { 1050 asprintf(&entry->tag, "%s", gopherpath); 1051 *p = '\0'; 1052 } 1053 } 1054 entry->host = host; 1055 entry->port = port; 1056 entry->entry = entry; 1057 1058 return entry; 1059 } 1060 1061 static void 1062 cleanup(void) 1063 { 1064 clearitem(mainentry); 1065 if (parent) 1066 rmdir(tmpdir); 1067 free(mainentry); 1068 free(mainurl); 1069 if (interactive) 1070 uicleanup(); 1071 } 1072 1073 static void 1074 sighandler(int signo) 1075 { 1076 exit(128 + signo); 1077 } 1078 1079 static void 1080 setup(void) 1081 { 1082 struct sigaction sa; 1083 int fd; 1084 1085 setlocale(LC_CTYPE, ""); 1086 setenv("PAGER", "more", 0); 1087 atexit(cleanup); 1088 /* reopen stdin in case we're reading from a pipe */ 1089 if ((fd = open("/dev/tty", O_RDONLY)) == -1) 1090 die("open: /dev/tty: %s", strerror(errno)); 1091 if (dup2(fd, 0) == -1) 1092 die("dup2: /dev/tty, stdin: %s", strerror(errno)); 1093 close(fd); 1094 if ((devnullfd = open("/dev/null", O_WRONLY)) == -1) 1095 die("open: /dev/null: %s", strerror(errno)); 1096 1097 sigemptyset(&sa.sa_mask); 1098 sa.sa_flags = SA_RESTART; 1099 sa.sa_handler = sighandler; 1100 sigaction(SIGINT, &sa, NULL); 1101 sigaction(SIGHUP, &sa, NULL); 1102 sigaction(SIGTERM, &sa, NULL); 1103 1104 sa.sa_handler = SIG_IGN; 1105 sigaction(SIGCHLD, &sa, NULL); 1106 1107 if (!mkdtemp(tmpdir)) 1108 die("mkdir: %s: %s", tmpdir, strerror(errno)); 1109 if (interactive = isatty(1)) { 1110 uisetup(); 1111 diag = uistatus; 1112 sa.sa_handler = uisigwinch; 1113 sigaction(SIGWINCH, &sa, NULL); 1114 } else { 1115 diag = stddiag; 1116 } 1117 iosetup(); 1118 } 1119 1120 int 1121 main(int argc, char *argv[]) 1122 { 1123 if (argc != 2) 1124 usage(); 1125 1126 setup(); 1127 1128 mainurl = xstrdup(argv[1]); 1129 mainentry = moldentry(mainurl); 1130 1131 if (interactive) 1132 delve(mainentry); 1133 else 1134 printout(mainentry); 1135 1136 exit(0); 1137 }