Recently Written · git

sacc

Mirror of git://bitreich.org/sacc, the sacc gopher client from bitreich.org.

git clone https://github.com/equwal/sacc

Log | Files | Refs


sacc.c (19147 bytes)

1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <locale.h>
7 #include <netdb.h>
8 #include <netinet/in.h>
9 #include <signal.h>
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <wchar.h>
16 #include <sys/socket.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 
21 #include "version.h"
22 #include "common.h"
23 #include "io.h"
24 
25 enum {
26 	TXT,
27 	DIR,
28 	CSO,
29 	ERR,
30 	MAC,
31 	DOS,
32 	UUE,
33 	IND,
34 	TLN,
35 	BIN,
36 	MIR,
37 	IBM,
38 	GIF,
39 	IMG,
40 	URL,
41 	INF,
42 	UNK,
43 	BRK,
44 };
45 
46 #define NEED_CONF
47 #include "config.h"
48 #undef NEED_CONF
49 
50 void (*diag)(char *, ...);
51 
52 int interactive;
53 const char ident[] = "@(#) sacc(omys): " VERSION;
54 
55 static char intbuf[256]; /* 256B ought to be enough for any URI */
56 static char *mainurl;
57 static Item *mainentry;
58 static int devnullfd;
59 static int parent = 1;
60 
61 static void
62 stddiag(char *fmt, ...)
63 {
64 	va_list arg;
65 
66 	va_start(arg, fmt);
67 	vfprintf(stderr, fmt, arg);
68 	va_end(arg);
69 	fputc('\n', stderr);
70 }
71 
72 void
73 die(const char *fmt, ...)
74 {
75 	va_list arg;
76 
77 	va_start(arg, fmt);
78 	vfprintf(stderr, fmt, arg);
79 	va_end(arg);
80 	fputc('\n', stderr);
81 
82 	exit(1);
83 }
84 
85 #ifdef NEED_ASPRINTF
86 int
87 asprintf(char **s, const char *fmt, ...)
88 {
89 	va_list ap;
90 	int n;
91 
92 	va_start(ap, fmt);
93 	n = vsnprintf(NULL, 0, fmt, ap);
94 	va_end(ap);
95 
96 	if (n == INT_MAX || !(*s = malloc(++n)))
97 		return -1;
98 
99 	va_start(ap, fmt);
100 	vsnprintf(*s, n, fmt, ap);
101 	va_end(ap);
102 
103 	return n;
104 }
105 #endif /* NEED_ASPRINTF */
106 
107 #ifdef NEED_STRCASESTR
108 char *
109 strcasestr(const char *h, const char *n)
110 {
111 	size_t i;
112 
113 	if (!n[0])
114 		return (char *)h;
115 
116 	for (; *h; ++h) {
117 		for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
118 		            tolower((unsigned char)h[i]); ++i)
119 			;
120 		if (n[i] == '\0')
121 			return (char *)h;
122 	}
123 
124 	return NULL;
125 }
126 #endif /* NEED_STRCASESTR */
127 
128 /* print `len' columns of characters. */
129 size_t
130 mbsprint(const char *s, size_t len)
131 {
132 	wchar_t wc;
133 	size_t col = 0, i, slen;
134 	const char *p;
135 	int rl, pl, w;
136 
137 	if (!len)
138 		return col;
139 
140 	slen = strlen(s);
141 	for (i = 0; i < slen; i += rl) {
142 		rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4);
143 		if (rl == -1) {
144 			/* reset state */
145 			mbtowc(NULL, NULL, 0);
146 			p = "\xef\xbf\xbd"; /* replacement character */
147 			pl = 3;
148 			rl = w = 1;
149 		} else {
150 			if ((w = wcwidth(wc)) == -1)
151 				continue;
152 			pl = rl;
153 			p = s + i;
154 		}
155 		if (col + w > len || (col + w == len && s[i + rl])) {
156 			fputs("\xe2\x80\xa6", stdout); /* ellipsis */
157 			col++;
158 			break;
159 		}
160 		fwrite(p, 1, pl, stdout);
161 		col += w;
162 	}
163 	return col;
164 }
165 
166 static void *
167 xreallocarray(void *m, size_t n, size_t s)
168 {
169 	void *nm;
170 
171 	if (n == 0 || s == 0) {
172 		free(m);
173 		return NULL;
174 	}
175 	if (s && n > (size_t)-1/s)
176 		die("realloc: overflow");
177 	if (!(nm = realloc(m, n * s)))
178 		die("realloc: %s", strerror(errno));
179 
180 	return nm;
181 }
182 
183 static void *
184 xmalloc(const size_t n)
185 {
186 	void *m = malloc(n);
187 
188 	if (!m)
189 		die("malloc: %s", strerror(errno));
190 
191 	return m;
192 }
193 
194 static void *
195 xcalloc(size_t n)
196 {
197 	char *m = calloc(1, n);
198 
199 	if (!m)
200 		die("calloc: %s", strerror(errno));
201 
202 	return m;
203 }
204 
205 static char *
206 xstrdup(const char *str)
207 {
208 	char *s;
209 
210 	if (!(s = strdup(str)))
211 		die("strdup: %s", strerror(errno));
212 
213 	return s;
214 }
215 
216 static void
217 usage(void)
218 {
219 	die("usage: sacc URL");
220 }
221 
222 static void
223 clearitem(Item *item)
224 {
225 	Dir *dir;
226 	Item *items;
227 	char *tag;
228 	size_t i;
229 
230 	if (!item)
231 		return;
232 
233 	if (dir = item->dat) {
234 		items = dir->items;
235 		for (i = 0; i < dir->nitems; ++i)
236 			clearitem(&items[i]);
237 		free(items);
238 		clear(&item->dat);
239 	}
240 
241 	if (parent && (tag = item->tag) &&
242 	    !strncmp(tag, tmpdir, strlen(tmpdir)))
243 		unlink(tag);
244 
245 	clear(&item->tag);
246 	clear(&item->raw);
247 }
248 
249 const char *
250 typedisplay(char t)
251 {
252 	switch (t) {
253 	case '0':
254 		return typestr[TXT];
255 	case '1':
256 		return typestr[DIR];
257 	case '2':
258 		return typestr[CSO];
259 	case '3':
260 		return typestr[ERR];
261 	case '4':
262 		return typestr[MAC];
263 	case '5':
264 		return typestr[DOS];
265 	case '6':
266 		return typestr[UUE];
267 	case '7':
268 		return typestr[IND];
269 	case '8':
270 		return typestr[TLN];
271 	case '9':
272 		return typestr[BIN];
273 	case '+':
274 		return typestr[MIR];
275 	case 'T':
276 		return typestr[IBM];
277 	case 'g':
278 		return typestr[GIF];
279 	case 'I':
280 		return typestr[IMG];
281 	case 'h':
282 		return typestr[URL];
283 	case 'i':
284 		return typestr[INF];
285 	default:
286 		/* "Characters '0' through 'Z' are reserved." (ASCII) */
287 		if (t >= '0' && t <= 'Z')
288 			return typestr[BRK];
289 		else
290 			return typestr[UNK];
291 	}
292 }
293 
294 int
295 itemuri(Item *item, char *buf, size_t bsz)
296 {
297 	int n;
298 
299 	switch (item->type) {
300 	case '8':
301 		n = snprintf(buf, bsz, "telnet://%s@%s:%s",
302 		             item->selector, item->host, item->port);
303 		break;
304 	case 'T':
305 		n = snprintf(buf, bsz, "tn3270://%s@%s:%s",
306 		             item->selector, item->host, item->port);
307 		break;
308 	case 'h': /* fallthrough */
309 		if (!strncmp(item->selector, "URL:", 4)) {
310 			n = snprintf(buf, bsz, "%s", item->selector+4);
311 			break;
312 		}
313 	default:
314 		n = snprintf(buf, bsz, "gopher://%s", item->host);
315 
316 		if (n < bsz-1 && strcmp(item->port, "70"))
317 			n += snprintf(buf+n, bsz-n, ":%s", item->port);
318 		if (n < bsz-1) {
319 			n += snprintf(buf+n, bsz-n, "/%c%s",
320 			              item->type, item->selector);
321 		}
322 		if (n < bsz-1 && item->type == '7' && item->tag) {
323 			n += snprintf(buf+n, bsz-n, "%%09%s",
324 			              item->tag + strlen(item->selector));
325 		}
326 		break;
327 	}
328 
329 	return n;
330 }
331 
332 static void
333 printdir(Item *item)
334 {
335 	Dir *dir;
336 	Item *items;
337 	size_t i, nitems;
338 
339 	if (!item || !(dir = item->dat))
340 		return;
341 
342 	items = dir->items;
343 	nitems = dir->nitems;
344 
345 	for (i = 0; i < nitems; ++i) {
346 		printf("%s%s\n",
347 		       typedisplay(items[i].type), items[i].username);
348 	}
349 }
350 
351 static void
352 displaytextitem(Item *item)
353 {
354 	struct sigaction sa;
355 	FILE *pagerin;
356 	int pid, wpid;
357 
358 	sigemptyset(&sa.sa_mask);
359 	sa.sa_flags = SA_RESTART;
360 	sa.sa_handler = SIG_DFL;
361 	sigaction(SIGWINCH, &sa, NULL);
362 
363 	uicleanup();
364 
365 	switch (pid = fork()) {
366 	case -1:
367 		diag("Couldn't fork.");
368 		return;
369 	case 0:
370 		parent = 0;
371 		if (!(pagerin = popen("$PAGER", "w")))
372 			_exit(1);
373 		fputs(item->raw, pagerin);
374 		exit(pclose(pagerin));
375 	default:
376 		while ((wpid = wait(NULL)) >= 0 && wpid != pid)
377 			;
378 	}
379 	uisetup();
380 
381 	sa.sa_handler = uisigwinch;
382 	sigaction(SIGWINCH, &sa, NULL);
383 	uisigwinch(SIGWINCH); /* force redraw */
384 }
385 
386 static char *
387 pickfield(char **raw, const char *sep)
388 {
389 	char c, *r, *f = *raw;
390 
391 	for (r = *raw; (c = *r) && !strchr(sep, c); ++r) {
392 		if (c == '\n')
393 			goto skipsep;
394 	}
395 
396 	*r++ = '\0';
397 skipsep:
398 	*raw = r;
399 
400 	return f;
401 }
402 
403 static char *
404 invaliditem(char *raw)
405 {
406 	char c;
407 	int tabs;
408 
409 	for (tabs = 0; (c = *raw) && c != '\n'; ++raw) {
410 		if (c == '\t')
411 			++tabs;
412 	}
413 	if (tabs < 3) {
414 		*raw++ = '\0';
415 		return raw;
416 	}
417 
418 	return NULL;
419 }
420 
421 static void
422 molditem(Item *item, char **raw)
423 {
424 	char *next;
425 
426 	if (!*raw)
427 		return;
428 
429 	if ((next = invaliditem(*raw))) {
430 		item->username = *raw;
431 		*raw = next;
432 		return;
433 	}
434 
435 	item->type = *raw[0]++;
436 	item->username = pickfield(raw, "\t");
437 	item->selector = pickfield(raw, "\t");
438 	item->host = pickfield(raw, "\t");
439 	item->port = pickfield(raw, "\t\r");
440 	while (*raw[0] != '\n')
441 		++*raw;
442 	*raw[0]++ = '\0';
443 }
444 
445 static Dir *
446 molddiritem(char *raw)
447 {
448 	Item *item, *items = NULL;
449 	char *nl, *p;
450 	Dir *dir;
451 	size_t i, n, nitems;
452 
453 	for (nl = raw, nitems = 0; p = strchr(nl, '\n'); nl = p+1)
454 		++nitems;
455 
456 	if (!nitems) {
457 		diag("Couldn't parse dir item");
458 		return NULL;
459 	}
460 
461 	dir = xmalloc(sizeof(Dir));
462 	items = xreallocarray(items, nitems, sizeof(Item));
463 	memset(items, 0, nitems * sizeof(Item));
464 
465 	for (i = 0; i < nitems; ++i) {
466 		item = &items[i];
467 		molditem(item, &raw);
468 		if (item->type == '+') {
469 			for (n = i - 1; n < (size_t)-1; --n) {
470 				if (items[n].type != '+') {
471 					item->redtype = items[n].type;
472 					break;
473 				}
474 			}
475 		}
476 	}
477 
478 	dir->items = items;
479 	dir->nitems = nitems;
480 	dir->printoff = dir->curline = 0;
481 
482 	return dir;
483 }
484 
485 static char *
486 getrawitem(struct cnx *c)
487 {
488 	char *raw, *buf;
489 	size_t bn, bs;
490 	ssize_t n;
491 
492 	raw = buf = NULL;
493 	bn = bs = n = 0;
494 
495 	do {
496 		bs -= n;
497 		buf += n;
498 
499 		if (buf - raw >= 5) {
500 			if (strncmp(buf-5, "\r\n.\r\n", 5) == 0) {
501 				buf[-3] = '\0';
502 				break;
503 			}
504 		} else if (buf - raw == 3 && strncmp(buf-3, ".\r\n", 3)) {
505 			buf[-3] = '\0';
506 			break;
507 		}
508 
509 		if (bs < 1) {
510 			raw = xreallocarray(raw, ++bn, BUFSIZ);
511 			buf = raw + (bn-1) * BUFSIZ;
512 			bs = BUFSIZ;
513 		}
514 
515 	} while ((n = ioread(c, buf, bs)) > 0);
516 
517 	*buf = '\0';
518 
519 	if (n == -1) {
520 		diag("Can't read socket: %s", strerror(errno));
521 		clear(&raw);
522 	}
523 
524 	return raw;
525 }
526 
527 static int
528 sendselector(struct cnx *c, const char *selector)
529 {
530 	char *msg, *p;
531 	size_t ln;
532 	ssize_t n;
533 
534 	ln = strlen(selector) + 3;
535 	msg = p = xmalloc(ln);
536 	snprintf(msg, ln--, "%s\r\n", selector);
537 
538 	while (ln && (n = iowrite(c, p, ln)) > 0) {
539 		ln -= n;
540 		p += n;
541 	}
542 
543 	free(msg);
544 	if (n == -1)
545 		diag("Can't send message: %s", strerror(errno));
546 
547 	return n;
548 }
549 
550 static int
551 connectto(const char *host, const char *port, struct cnx *c)
552 {
553 	sigset_t set, oset;
554 	static const struct addrinfo hints = {
555 	    .ai_family = AF_UNSPEC,
556 	    .ai_socktype = SOCK_STREAM,
557 	    .ai_protocol = IPPROTO_TCP,
558 	};
559 	struct addrinfo *addrs, *ai;
560 	int r, err;
561 
562 	sigemptyset(&set);
563 	sigaddset(&set, SIGWINCH);
564 	sigprocmask(SIG_BLOCK, &set, &oset);
565 
566 	if (r = getaddrinfo(host, port, &hints, &addrs)) {
567 		diag("Can't resolve hostname \"%s\": %s",
568 		     host, gai_strerror(r));
569 		goto err;
570 	}
571 
572 	r = -1;
573 	for (ai = addrs; ai && r == -1; ai = ai->ai_next) {
574 		do {
575 			if ((c->sock = socket(ai->ai_family, ai->ai_socktype,
576 			                      ai->ai_protocol)) == -1) {
577 				err = errno;
578 				break;
579 			}
580 
581 			if ((r = ioconnect(c, ai, host)) < 0) {
582 				err = errno;
583 				ioclose(c);
584 			}
585 		} while (r == CONN_RETRY);
586 	}
587 
588 	freeaddrinfo(addrs);
589 
590 	if (r == CONN_ERROR)
591 		ioconnerr(c, host, port, err);
592 err:
593 	sigprocmask(SIG_SETMASK, &oset, NULL);
594 
595 	return r;
596 }
597 
598 static int
599 download(Item *item, int dest)
600 {
601 	char buf[BUFSIZ];
602 	struct cnx c = { 0 };
603 	ssize_t r, w;
604 
605 	if (item->tag == NULL) {
606 		if (connectto(item->host, item->port, &c) < 0 ||
607 		    sendselector(&c, item->selector) == -1)
608 			return 0;
609 	} else {
610 		if ((c.sock = open(item->tag, O_RDONLY)) == -1) {
611 			printf("Can't open source file %s: %s",
612 			       item->tag, strerror(errno));
613 			errno = 0;
614 			return 0;
615 		}
616 	}
617 
618 	w = 0;
619 	while ((r = ioread(&c, buf, BUFSIZ)) > 0) {
620 		while ((w = write(dest, buf, r)) > 0)
621 			r -= w;
622 	}
623 
624 	if (r == -1 || w == -1) {
625 		printf("Error downloading file %s: %s",
626 		       item->selector, strerror(errno));
627 		errno = 0;
628 	}
629 
630 	close(dest);
631 	ioclose(&c);
632 
633 	return (r == 0 && w == 0);
634 }
635 
636 static void
637 downloaditem(Item *item)
638 {
639 	char *file, *path, *tag;
640 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
641 	int dest;
642 
643 	if (file = strrchr(item->selector, '/'))
644 		++file;
645 	else
646 		file = item->selector;
647 
648 	if (!(path = uiprompt("Download to [%s] (^D cancel): ", file)))
649 		return;
650 
651 	if (!path[0])
652 		path = xstrdup(file);
653 
654 	if (tag = item->tag) {
655 		if (access(tag, R_OK) == -1) {
656 			clear(&item->tag);
657 		} else if (!strcmp(tag, path)) {
658 			goto cleanup;
659 		}
660 	}
661 
662 	if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) {
663 		diag("Can't open destination file %s: %s",
664 		     path, strerror(errno));
665 		errno = 0;
666 		goto cleanup;
667 	}
668 
669 	if (!download(item, dest))
670 		goto cleanup;
671 
672 	if (item->tag)
673 		goto cleanup;
674 
675 	item->tag = path;
676 
677 	return;
678 cleanup:
679 	free(path);
680 	return;
681 }
682 
683 static int
684 fetchitem(Item *item)
685 {
686 	struct cnx c;
687 	char *raw;
688 
689 	if (connectto(item->host, item->port, &c) < 0 ||
690 	    sendselector(&c, item->selector) == -1)
691 		return 0;
692 
693 	raw = getrawitem(&c);
694 	ioclose(&c);
695 
696 	if (raw == NULL || !*raw) {
697 		diag("Empty response from server");
698 		clear(&raw);
699 	}
700 
701 	return ((item->raw = raw) != NULL);
702 }
703 
704 static void
705 pipeuri(char *cmd, char *msg, char *uri)
706 {
707 	FILE *sel;
708 
709 	if ((sel = popen(cmd, "w")) == NULL) {
710 		diag("URI not %s\n", msg);
711 		return;
712 	}
713 
714 	fputs(uri, sel);
715 	pclose(sel);
716 	diag("%s \"%s\"", msg, uri);
717 }
718 
719 static void
720 execuri(char *cmd, char *msg, char *uri)
721 {
722 	switch (fork()) {
723 	case -1:
724 		diag("Couldn't fork.");
725 		return;
726 	case 0:
727 		parent = 0;
728 		dup2(devnullfd, 1);
729 		dup2(devnullfd, 2);
730 		if (execlp(cmd, cmd, uri, NULL) == -1)
731 			_exit(1);
732 	default:
733 		if (modalplumber) {
734 			while (waitpid(-1, NULL, 0) != -1)
735 				;
736 		}
737 	}
738 
739 	diag("%s \"%s\"", msg, uri);
740 }
741 
742 static void
743 plumbitem(Item *item)
744 {
745 	char *file, *path, *tag;
746 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
747 	int dest, plumbitem;
748 
749 	if (file = strrchr(item->selector, '/'))
750 		++file;
751 	else
752 		file = item->selector;
753 
754 	path = uiprompt("Download %s to (^D cancel, <empty> plumb): ",
755 	                file);
756 	if (!path)
757 		return;
758 
759 	if ((tag = item->tag) && access(tag, R_OK) == -1) {
760 		clear(&item->tag);
761 		tag = NULL;
762 	}
763 
764 	plumbitem = path[0] ? 0 : 1;
765 
766 	if (!path[0]) {
767 		clear(&path);
768 		if (!tag) {
769 			if (asprintf(&path, "%s/%s", tmpdir, file) == -1)
770 				die("Can't generate tmpdir path: %s/%s: %s",
771 				    tmpdir, file, strerror(errno));
772 		}
773 	}
774 
775 	if (path && (!tag || strcmp(tag, path))) {
776 		if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) {
777 			diag("Can't open destination file %s: %s",
778 			     path, strerror(errno));
779 			errno = 0;
780 			goto cleanup;
781 		}
782 		if (!download(item, dest) || tag)
783 			goto cleanup;
784 	}
785 
786 	if (!tag)
787 		item->tag = path;
788 
789 	if (plumbitem)
790 		execuri(plumber, "Plumbed", item->tag);
791 
792 	return;
793 cleanup:
794 	free(path);
795 	return;
796 }
797 
798 void
799 yankitem(Item *item)
800 {
801 	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 }