libgeminiclient/gemini-pipe.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);
}