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 <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': | |
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_sock… | |
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, <empty> 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: … | |
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)) =… | |
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->… | |
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, typedispla… | |
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]: ", p… | |
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, typedispla… | |
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 } |