#include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BUFFER_SIZE #define BUFFER_SIZE 4096 #endif struct mimedef { struct mimedef *next; char *target; char *recipe; }; static ssize_t mimedef_print(struct mimedef *); static struct mimedef *mimedef_parse(const char *); static void mimedef_append(struct mimedef **, struct mimedef *); static int mimedef_merge(struct mimedef *); static int strappend(char **, const char *, size_t, int); static void *memlcpy(void *, size_t, const void *, size_t); int main(int argc, char *argv[]) { char mimestr[GEMINI_META_MAX + 1]; struct gemini gemini; struct mimedef *mime = NULL; struct mimedef *tmime; const char *drecipe = "$PAGER"; FILE *fp; char *b; char *p; uintmax_t bs = BUFFER_SIZE; ssize_t r; size_t i; size_t j; int lflag = 0; int c; gemini_envinit(&gemini); if ((p = getenv("GEMINI_TOFU_WRITE")) != NULL && *p != '\0') gemini.flags |= GEMINI_TOFU_WRITE; while ((c = getopt(argc, argv, "b:c:d:f:h:k:lm:p:r:t:w")) != -1) switch (c) { case 'b': errno = 0; bs = strtoumax(optarg, &p, 0); if (p == optarg || *p != '\0') err(EX_USAGE, "Invalid number: %s", optarg); break; case 'c': gemini.certfile = optarg; break; case 'd': drecipe = optarg; break; case 'f': if ((fp = fopen(optarg, "r")) == NULL) { if (errno == 0 || errno == ENOENT) break; err(EX_NOINPUT, "%s", optarg); } for (j = 1; (r = getdelim(&p, &i, '\n', fp)) > 0; j++) { errno = 0; if ((tmime = mimedef_parse(p)) == NULL && errno != 0) err(EX_DATAERR, "%s:%zu", optarg, j); mimedef_append(&mime, tmime); } break; case 'h': gemini.proxy = optarg; break; case 'k': gemini.keyfile = optarg; break; case 'p': for (i = 0, gemini.port = 0; optarg[i] >= '0' && optarg[i] <= '9'; i++) gemini.port = gemini.port * 10 + optarg[i] - '0'; break; case 'r': for (i = 0, gemini.maxredirects = 0; gemini.maxredirects <= (INT_MAX - 9) / 10 && optarg[i] >= '0' && optarg[i] <= '9'; gemini.maxredirects = gemini.maxredirects * 10 + (optarg[i] - '0'), i++) /* do nothing */; if (i == 0 || optarg[i] != '\0') warnx("Invalid number: %s", optarg); break; case 'l': lflag = 1; break; case 'm': if ((tmime = mimedef_parse(optarg)) == NULL) err(EX_USAGE, "Invalid MIME-def: %s", optarg); mimedef_append(&mime, tmime); break; case 't': gemini.tofufile = optarg; break; case 'w': gemini.flags |= GEMINI_TOFU_WRITE; break; default: goto usage; } argc -= optind; argv += optind; if ((b = malloc(bs)) == NULL) err(EX_OSERR, "Could not allocate the buffer"); if (getenv("PAGER") == NULL) (void)setenv("PAGER", "more", 1); if (mimedef_merge(mime) != 0) err(EX_OSERR, "Could not merge the MIME-defs"); if (lflag) return (mimedef_print(mime) >= 0 ? 0 : EX_IOERR); for (i = 0; i < argc; i++) { if (gemini_connect(&gemini, argv[i]) != 0) { warn("Failed to connect to %s", argv[i]); continue; } if ((r = gemini_read(&gemini, b, bs)) < 0) { warn("Gemini read error"); gemini_reset(&gemini); continue; } if (gemini.status / 10 != 2) { warnx("Gemini Error %02hd: %s", gemini.status, gemini.meta); gemini_reset(&gemini); continue; } for (i = 0; i < gemini.metalen && isspace(gemini.meta[i]); i++) /* do nothing */; for (j = i; j < gemini.metalen && !isspace(gemini.meta[j]) && gemini.meta[j] != ';'; j++) /* do nothing */; if (j > i) (void)memlcpy(mimestr, sizeof(mimestr), gemini.meta + i, j - i); else (void)strncpy(mimestr, "text/gemini", sizeof(mimestr)); for (tmime = mime; tmime != NULL && strcmp(tmime->target, mimestr) != 0; tmime = tmime->next) /* do nothing */; while (tmime != NULL && tmime->recipe == NULL) tmime = tmime->next; (void)setenv("mime", mimestr, 1); fp = popen(tmime != NULL ? tmime->recipe : drecipe, "w"); do { (void)fwrite(b, 1, r, fp); } while ((r = gemini_read(&gemini, b, bs)) > 0); (void)pclose(fp); gemini_reset(&gemini); } gemini_fini(&gemini); return (0); err: gemini_fini(&gemini); return (EX_SOFTWARE); usage: (void)fprintf(stderr, "usage: gemini-pipe [-lw] [-c cert-file] " "[-d default-handler]\n" " [-f mimedef-file] [-h proxy-host] " "[-k key-file]\n" " [-m mime-def] [-p port] " "[-r max-redirects]\n" " [-t tofu-file] URL...\n"); return (EX_USAGE); } ssize_t mimedef_print(struct mimedef *mime) { struct mimedef *t; ssize_t r; size_t i; size_t j; size_t l; for (t = mime, l = 0; t != NULL; t = t->next) { if ((r = printf("%s\n", t->target)) < 0) return (r); l += r; if (t->recipe == NULL) continue; for (i = 0; t->recipe[i] != '\0'; i = j + (t->recipe[j] == '\n')) { for (j = i; t->recipe[j] != '\0' && t->recipe[j] != '\n'; j++) /* do nothing */; if (j - i > INT_MAX) return (-1); if ((r = printf("\t%.*s\n", (int)(j - i), t->recipe + i)) < 0) return (r); l += r; } } return (l); } struct mimedef * mimedef_parse(const char *s) { struct mimedef *mime = NULL; struct mimedef *tmime; char *target = NULL; char *recipe = NULL; size_t i = 0; size_t j = 0; for (;;) { for (; isspace(s[i]) && !isblank(s[i]); i++) /* do nothing */; if (s[i] == '\0') break; if (isblank(s[i])) { /* recipe */ if (target == NULL) goto err; for (i++, j = i; s[j] != '\0' && s[j] != '\n'; j++) /* do nothing */; if (strappend(&recipe, s + i, j - i, '\n') != 0) goto err; } else { /* target */ if (target != NULL) { if ((tmime = malloc(sizeof(*tmime))) == NULL) goto err; tmime->next = NULL; tmime->target = target; tmime->recipe = recipe; mimedef_append(&mime, tmime); target = NULL; recipe = NULL; } for (j = i; s[j] != '\0' && !isspace(s[j]); j++) /* do nothing */; if (strappend(&target, s + i, j - i, '\0') != 0) goto err; } i = j; } if (target != NULL) { if ((tmime = malloc(sizeof(*tmime))) == NULL) goto err; tmime->next = NULL; tmime->target = target; tmime->recipe = recipe; mimedef_append(&mime, tmime); } return (mime); err: if (target != NULL) free(target); if (recipe != NULL) free(recipe); while (mime != NULL) { tmime = mime; mime = mime->next; free(tmime->target); free(tmime->recipe); free(tmime); } return (NULL); } void mimedef_append(struct mimedef **dstp, struct mimedef *src) { struct mimedef *dst; if (*dstp == NULL) { *dstp = src; return; } for (dst = *dstp; dst->next != NULL; dst = dst->next) /* do nothing */; dst->next = src; } int mimedef_merge(struct mimedef *mime) { struct mimedef *a; struct mimedef *b; struct mimedef *c; struct mimedef *p; for (a = mime; a != NULL; a = a->next) { for (b = a->next, p = a; b != NULL;) { c = b->next; if (strcmp(a->target, b->target) == 0) { if (strappend(&a->recipe, b->recipe, strlen(b->recipe), '\n') != 0) return (-1); free(b->target); free(b->recipe); free(b); p->next = c; } else p = b; b = c; } } return (0); } int strappend(char **dstp, const char *src, size_t l, int end) { char *dst; size_t dl = 0; dst = *dstp; if (dst == NULL) dst = malloc(l + 2); else dst = realloc(dst, (dl = strlen(dst)) + l + 2); if (dst == NULL) return (-1); (void)memcpy(dst + dl, src, l); dl += l; if (l > 0 && dst[l - 1] != end) dst[l++] = end; dst[l] = '\0'; *dstp = dst; return (0); } void * memlcpy(void *dst, size_t ds, const void *src, size_t ss) { if (ds == 0) return (dst); if (ss > ds - 1) ss = ds - 1; (void)memcpy(dst, src, ss); ((char *)dst)[ss] = '\0'; return (dst); }