sacc.c - sacc - sacc(omys), simple console gopher client
git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
Log
Files
Refs
Tags
LICENSE
---
sacc.c (19147B)
---
     1 /* See LICENSE file for copyright and license details. */
     2 #include 
     3 #include 
     4 #include 
     5 #include 
     6 #include 
     7 #include 
     8 #include 
     9 #include 
    10 #include 
    11 #include 
    12 #include 
    13 #include 
    14 #include 
    15 #include 
    16 #include 
    17 #include 
    18 #include 
    19 #include 
    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,  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         if (item->type == 0)
   802                 return;
   803 
   804         itemuri(item, intbuf, sizeof(intbuf));
   805         pipeuri(yanker, "Yanked", intbuf);
   806 }
   807 
   808 static int
   809 dig(Item *entry, Item *item)
   810 {
   811         char *plumburi = NULL;
   812         int t;
   813 
   814         if (item->raw) /* already in cache */
   815                 return item->type;
   816         if (!item->entry)
   817                 item->entry = entry ? entry : item;
   818 
   819         t = item->redtype ? item->redtype : item->type;
   820         switch (t) {
   821         case 'h': /* fallthrough */
   822                 if (!strncmp(item->selector, "URL:", 4)) {
   823                         execuri(plumber, "Plumbed", item->selector+4);
   824                         return 0;
   825                 }
   826         case '0':
   827                 if (!fetchitem(item))
   828                         return 0;
   829                 break;
   830         case '1':
   831         case '7':
   832                 if (!fetchitem(item) || !(item->dat = molddiritem(item->raw)))
   833                         return 0;
   834                 break;
   835         case '4':
   836         case '5':
   837         case '6':
   838         case '9':
   839                 downloaditem(item);
   840                 return 0;
   841         case '8':
   842                 if (asprintf(&plumburi, "telnet://%s%s%s:%s",
   843                              item->selector, item->selector ? "@" : "",
   844                              item->host, item->port) == -1)
   845                         return 0;
   846                 execuri(plumber, "Plumbed", plumburi);
   847                 free(plumburi);
   848                 return 0;
   849         case 'T':
   850                 if (asprintf(&plumburi, "tn3270://%s%s%s:%s",
   851                              item->selector, item->selector ? "@" : "",
   852                              item->host, item->port) == -1)
   853                         return 0;
   854                 execuri(plumber, "Plumbed", plumburi);
   855                 free(plumburi);
   856                 return 0;
   857         default:
   858                 if (t >= '0' && t <= 'Z') {
   859                         diag("Type %c (%s) not supported", t, typedisplay(t));
   860                         return 0;
   861                 }
   862         case 'g':
   863         case 'I':
   864                 plumbitem(item);
   865         case 'i':
   866                 return 0;
   867         }
   868 
   869         return item->type;
   870 }
   871 
   872 static char *
   873 searchselector(Item *item)
   874 {
   875         char *pexp, *exp, *tag, *selector = item->selector;
   876         size_t n = strlen(selector);
   877 
   878         if ((tag = item->tag) && !strncmp(tag, selector, n))
   879                 pexp = tag + n+1;
   880         else
   881                 pexp = "";
   882 
   883         if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp)))
   884                 return NULL;
   885 
   886         if (exp[0] && strcmp(exp, pexp)) {
   887                 n += strlen(exp) + 2;
   888                 tag = xmalloc(n);
   889                 snprintf(tag, n, "%s\t%s", selector, exp);
   890         }
   891 
   892         free(exp);
   893         return tag;
   894 }
   895 
   896 static int
   897 searchitem(Item *entry, Item *item)
   898 {
   899         char *sel, *selector;
   900 
   901         if (!(sel = searchselector(item)))
   902                 return 0;
   903 
   904         if (sel != item->tag)
   905                 clearitem(item);
   906         if (!item->dat) {
   907                 selector = item->selector;
   908                 item->selector = item->tag = sel;
   909                 dig(entry, item);
   910                 item->selector = selector;
   911         }
   912         return (item->dat != NULL);
   913 }
   914 
   915 static void
   916 printout(Item *hole)
   917 {
   918         char t = 0;
   919 
   920         if (!hole)
   921                 return;
   922 
   923         switch (hole->redtype ? hole->redtype : (t = hole->type)) {
   924         case '0':
   925                 if (dig(hole, hole))
   926                         fputs(hole->raw, stdout);
   927                 return;
   928         case '1':
   929         case '7':
   930                 if (dig(hole, hole))
   931                         printdir(hole);
   932                 return;
   933         default:
   934                 if (t >= '0' && t <= 'Z') {
   935                         diag("Type %c (%s) not supported", t, typedisplay(t));
   936                         return;
   937                 }
   938         case '4':
   939         case '5':
   940         case '6':
   941         case '9':
   942         case 'g':
   943         case 'I':
   944                 download(hole, 1);
   945         case '2':
   946         case '3':
   947         case '8':
   948         case 'T':
   949                 return;
   950         }
   951 }
   952 
   953 static void
   954 delve(Item *hole)
   955 {
   956         Item *entry = NULL;
   957 
   958         while (hole) {
   959                 switch (hole->redtype ? hole->redtype : hole->type) {
   960                 case 'h':
   961                 case '0':
   962                         if (dig(entry, hole))
   963                                 displaytextitem(hole);
   964                         break;
   965                 case '1':
   966                 case '+':
   967                         if (dig(entry, hole) && hole->dat)
   968                                 entry = hole;
   969                         break;
   970                 case '7':
   971                         if (searchitem(entry, hole))
   972                                 entry = hole;
   973                         break;
   974                 case 0:
   975                         diag("Couldn't get %s", hole->username);
   976                         break;
   977                 case '4':
   978                 case '5':
   979                 case '6': /* TODO decode? */
   980                 case '8':
   981                 case '9':
   982                 case 'g':
   983                 case 'I':
   984                 case 'T':
   985                 default:
   986                         dig(entry, hole);
   987                         break;
   988                 }
   989 
   990                 if (!entry)
   991                         return;
   992 
   993                 do {
   994                         uidisplay(entry);
   995                         hole = uiselectitem(entry);
   996                 } while (hole == entry);
   997         }
   998 }
   999 
  1000 static Item *
  1001 moldentry(char *url)
  1002 {
  1003         Item *entry;
  1004         char *p, *host = url, *port = "70", *gopherpath = "1";
  1005         int parsed, ipv6;
  1006 
  1007         host = ioparseurl(url);
  1008 
  1009         if (*host == '[') {
  1010                 ipv6 = 1;
  1011                 ++host;
  1012         } else {
  1013                 ipv6 = 0;
  1014         }
  1015 
  1016         for (parsed = 0, p = host; !parsed && *p; ++p) {
  1017                 switch (*p) {
  1018                 case ']':
  1019                         if (ipv6) {
  1020                                 *p = '\0';
  1021                                 ipv6 = 0;
  1022                         }
  1023                         continue;
  1024                 case ':':
  1025                         if (!ipv6) {
  1026                                 *p = '\0';
  1027                                 port = p+1;
  1028                         }
  1029                         continue;
  1030                 case '/':
  1031                         *p = '\0';
  1032                         parsed = 1;
  1033                         continue;
  1034                 }
  1035         }
  1036 
  1037         if (*host == '\0' || *port == '\0' || ipv6)
  1038                 die("Can't parse url");
  1039 
  1040         if (*p != '\0')
  1041                 gopherpath = p;
  1042 
  1043         entry = xcalloc(sizeof(Item));
  1044         entry->type = gopherpath[0];
  1045         entry->username = entry->selector = ++gopherpath;
  1046         if (entry->type == '7') {
  1047                 if (p = strstr(gopherpath, "%09")) {
  1048                         memmove(p+1, p+3, strlen(p+3)+1);
  1049                         *p = '\t';
  1050                 }
  1051                 if (p || (p = strchr(gopherpath, '\t'))) {
  1052                         asprintf(&entry->tag, "%s", gopherpath);
  1053                         *p = '\0';
  1054                 }
  1055         }
  1056         entry->host = host;
  1057         entry->port = port;
  1058         entry->entry = entry;
  1059 
  1060         return entry;
  1061 }
  1062 
  1063 static void
  1064 cleanup(void)
  1065 {
  1066         clearitem(mainentry);
  1067         if (parent)
  1068                 rmdir(tmpdir);
  1069         free(mainentry);
  1070         free(mainurl);
  1071         if (interactive)
  1072                 uicleanup();
  1073 }
  1074 
  1075 static void
  1076 sighandler(int signo)
  1077 {
  1078         exit(128 + signo);
  1079 }
  1080 
  1081 static void
  1082 setup(void)
  1083 {
  1084         struct sigaction sa;
  1085         int fd;
  1086 
  1087         setlocale(LC_CTYPE, "");
  1088         setenv("PAGER", "more", 0);
  1089         atexit(cleanup);
  1090         /* reopen stdin in case we're reading from a pipe */
  1091         if ((fd = open("/dev/tty", O_RDONLY)) == -1)
  1092                 die("open: /dev/tty: %s", strerror(errno));
  1093         if (dup2(fd, 0) == -1)
  1094                 die("dup2: /dev/tty, stdin: %s", strerror(errno));
  1095         close(fd);
  1096         if ((devnullfd = open("/dev/null", O_WRONLY)) == -1)
  1097                 die("open: /dev/null: %s", strerror(errno));
  1098 
  1099         sigemptyset(&sa.sa_mask);
  1100         sa.sa_flags = SA_RESTART;
  1101         sa.sa_handler = sighandler;
  1102         sigaction(SIGINT, &sa, NULL);
  1103         sigaction(SIGHUP, &sa, NULL);
  1104         sigaction(SIGTERM, &sa, NULL);
  1105 
  1106         sa.sa_handler = SIG_IGN;
  1107         sigaction(SIGCHLD, &sa, NULL);
  1108 
  1109         if (!mkdtemp(tmpdir))
  1110                 die("mkdir: %s: %s", tmpdir, strerror(errno));
  1111         if (interactive = isatty(1)) {
  1112                 uisetup();
  1113                 diag = uistatus;
  1114                 sa.sa_handler = uisigwinch;
  1115                 sigaction(SIGWINCH, &sa, NULL);
  1116         } else {
  1117                 diag = stddiag;
  1118         }
  1119         iosetup();
  1120 }
  1121 
  1122 int
  1123 main(int argc, char *argv[])
  1124 {
  1125         if (argc != 2)
  1126                 usage();
  1127 
  1128         setup();
  1129 
  1130         mainurl = xstrdup(argv[1]);
  1131         mainentry = moldentry(mainurl);
  1132 
  1133         if (interactive)
  1134                 delve(mainentry);
  1135         else
  1136                 printout(mainentry);
  1137 
  1138         exit(0);
  1139 }