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