372 lines
7.9 KiB
C
372 lines
7.9 KiB
C
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <err.h>
|
|
#include <sysexits.h>
|
|
|
|
#include <tls.h>
|
|
|
|
#include <libgeminiclient.h>
|
|
|
|
#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);
|
|
}
|