tsacc.c - sacc - sacc (saccomys): simple gopher client.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
       tsacc.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 }