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