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