sacc

sacc (saccomys): simple gopher client.
Log | Files | Refs | LICENSE

sacc.c (16859B)


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