sacc.c - sacc - sacc(omys), simple console gopher client
 (HTM) 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 }