Add gemini-pipe(1)
This commit is contained in:
parent
7e7e35e5d4
commit
f1bc8a9721
18
Makefile
18
Makefile
|
@ -21,20 +21,27 @@ MAGICBSD=\
|
|||
fi
|
||||
default: magic-libgeminiclient
|
||||
all: magic
|
||||
magic: magic-libgeminiclient magic-gemini-cat
|
||||
magic: magic-libgeminiclient magic-gemini-cat magic-gemini-pipe
|
||||
magic-libgeminiclient:
|
||||
@${MAGIC}; ${MAGICTLS}; ${MAKE} ${MAKEFLAGS} libgeminiclient.a
|
||||
magic-gemini-cat:
|
||||
@${MAGIC}; ${MAGICTLS}; ${MAGICBSD};\
|
||||
${MAKE} ${MAKEFLAGS} gemini-cat
|
||||
magic-gemini-pipe:
|
||||
@${MAGIC}; ${MAGICTLS};\
|
||||
${MAKE} ${MAKEFLAGS} gemini-pipe
|
||||
libgeminiclient.a: libgeminiclient.o
|
||||
${AR} ${ARFLAGS} $@ libgeminiclient.o
|
||||
libgeminiclient.o: libgeminiclient.c libgeminiclient.h
|
||||
${CC} -c ${CFLAGS} -o $@ libgeminiclient.c
|
||||
gemini-cat: gemini-cat.o libgeminiclient.a
|
||||
${CC} -o $@ gemini-cat.o libgeminiclient.a ${LDFLAGS}
|
||||
gemini-pipe: gemini-pipe.o libgeminiclient.a
|
||||
${CC} -o $@ gemini-pipe.o libgeminiclient.a ${LDFLAGS}
|
||||
gemini-cat.o: gemini-cat.c libgeminiclient.h
|
||||
${CC} -c -I. ${CFLAGS} -o $@ gemini-cat.c
|
||||
gemini-pipe.o: gemini-pipe.c libgeminiclient.h
|
||||
${CC} -c -I. ${CFLAGS} -o $@ gemini-pipe.c
|
||||
install: libgeminiclient.a libgeminiclient.h libgemeniclient.3
|
||||
install -m444 libgeminiclient.a \
|
||||
"$$DESTDIR/$${PREFIX-usr/local}/lib/"
|
||||
|
@ -54,7 +61,14 @@ install-gemini-cat: gemini-cat
|
|||
install -m755 gemini-cat "$$DESTDIR/$${PREFIX-usr/local}/bin/"
|
||||
install -m444 gemini-cat.1 \
|
||||
"$$DESTDIR/$${PREFIX:-usr/local}/$${MANDIR:-man}/man1/"
|
||||
install-gemini-pipe: gemini-cat
|
||||
install -m755 gemini-pipe "$$DESTDIR/$${PREFIX-usr/local}/bin/"
|
||||
install -m444 gemini-pipe.1 \
|
||||
"$$DESTDIR/$${PREFIX:-usr/local}/$${MANDIR:-man}/man1/"
|
||||
linstall-all: linstall-gemini-cat linstall-gemini-pipe
|
||||
linstall-gemini-cat: gemini-cat
|
||||
install -m755 gemini-cat "$$HOME/bin/"
|
||||
linstall-gemini-pipe: gemini-pipe
|
||||
install -m755 gemini-pipe "$$HOME/bin/"
|
||||
clean:
|
||||
rm -f *.[ao] gemini-cat
|
||||
rm -f *.[ao] gemini-cat gemini-pipe
|
||||
|
|
15
README
15
README
|
@ -19,6 +19,11 @@ BUILDING
|
|||
export LDFLAGS='-L/path/to/libtls -ltls'
|
||||
make gemini-cat
|
||||
|
||||
To build gemini-pipe(1) configured manually:
|
||||
export CFLAGS='-I/path/to/libtls -I/path/to/libbsd'
|
||||
export LDFLAGS='-L/path/to/libtls -ltls'
|
||||
make gemini-pipe
|
||||
|
||||
INSTALLING
|
||||
|
||||
To install libgeminiclient(3) on the system:
|
||||
|
@ -27,12 +32,18 @@ INSTALLING
|
|||
To install gemini-cat(1) on the system:
|
||||
make install-gemini-cat
|
||||
|
||||
To install gemini-pipe(1) on the system:
|
||||
make install-gemini-pipe
|
||||
|
||||
To install everything on the system:
|
||||
make install-all
|
||||
|
||||
To install gemini-cat(1) in your home-directory:
|
||||
make linstall-gemini-cat
|
||||
|
||||
To install gemini-pipe(1) in your home-directory:
|
||||
make linstall-gemini-pipe
|
||||
|
||||
MAGIC
|
||||
|
||||
The ``magic'' make(1) targets attempt to use pkg-config(1) for
|
||||
|
@ -40,4 +51,6 @@ MAGIC
|
|||
|
||||
PREPROCESSOR DEFINES
|
||||
|
||||
``BUFFER_SIZE'' The size of the buffer for gemini-cat(1) to use.
|
||||
``BUFFER_SIZE''
|
||||
The size of the buffer for gemini-cat(1) to use,
|
||||
and the default buffer size for gemini-pipe(1).
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
.Dd Jun 20, 2020
|
||||
.Dt GEMINI-PIPE 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm gemini-pipe
|
||||
.Nd plumb gemini pages based on the returned mime-type
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl w
|
||||
.Op Fl c Ar cert-file
|
||||
.Op Fl d Ar default-handler
|
||||
.Op Fl f Ar mimedef-file
|
||||
.Op Fl h Ar proxy-host
|
||||
.Op Fl k Ar key-file
|
||||
.Op Fl m Ar mimedef
|
||||
.Op Fl p Ar port
|
||||
.Op Fl r Ar max-redirects
|
||||
.Op Fl t Ar tofu-file
|
||||
.Ar URL ...
|
||||
.Sh DESCRIPTION
|
||||
Choose a pipeline to send the data received from
|
||||
.Ar URL
|
||||
based on the received mime-type.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl c Ar cert-file
|
||||
The certificate file to use for TLS.
|
||||
.It Fl d Ar default-handler
|
||||
The pipeline to use when there is no matching mimedef.
|
||||
The default is:
|
||||
.Li $PAGER
|
||||
With
|
||||
.Ev PAGER
|
||||
defaulting to
|
||||
.Xr more 1 .
|
||||
.It Fl f Ar default-handler
|
||||
Parse the file as described below.
|
||||
.It Fl h Ar proxy-host
|
||||
The host to connect through.
|
||||
.Ar proxy-host
|
||||
can also specify a port number.
|
||||
.It Fl k Ar key-file
|
||||
The key file to use for TLS.
|
||||
.It Fl m Ar mimedef
|
||||
Parse the argument as described below.
|
||||
.It Fl p Ar port
|
||||
Use
|
||||
.Ar port
|
||||
instead of the default
|
||||
.Pq 1965 .
|
||||
.It Fl r Ar max-redirects
|
||||
Set the maximum number of redirects (5 by default).
|
||||
.It Fl t Ar tofu-file
|
||||
The file to read host certificate hashes from.
|
||||
.It Fl w
|
||||
Write any new hashes to the
|
||||
.Ar tofu-file .
|
||||
.El
|
||||
.Pp
|
||||
.Sy mimedef
|
||||
strings are parsed in a manner similar to
|
||||
.Xr make 1
|
||||
files.
|
||||
The targeted mime-type is at the beginning of the line,
|
||||
and the script to run is formed from the concatenation of all of the
|
||||
lines afterwards that start with a blank (tab or space) character.
|
||||
The first line of a script can be on the same line as the mime-type,
|
||||
separated by a blank (tab or space)character.
|
||||
Examples can be found below.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width Ds
|
||||
.It Ev GEMINI_KEYFILE
|
||||
Sets the TLS key file before
|
||||
.Fl k .
|
||||
.It Ev GEMINI_CERTFILE
|
||||
Sets the TLS certificate file before
|
||||
.Fl c .
|
||||
.It Ev GEMINI_TOFUFILE
|
||||
Sets the TOFU tile before
|
||||
.Fl t .
|
||||
.It Ev GEMINI_PROXY
|
||||
Sets the proxy host before
|
||||
.Fl h .
|
||||
.It Ev GEMINI_MAX_REDIRECTS
|
||||
Sets the maximum number of redirects before
|
||||
.Fl r .
|
||||
.It Ev GEMINI_PORT
|
||||
Sets the default port number before
|
||||
.Fl p .
|
||||
.It Ev GEMINI_TOFU_WRITE
|
||||
If set to a non-empty string, it is equivalent to
|
||||
.Fl w .
|
||||
.It Ev mime
|
||||
The mime-type received.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Ex -std
|
||||
.Sh EXAMPLES
|
||||
Open an HTML file received over Gemini:
|
||||
.Dl gemini-pipe -m 'text/html lynx -stdin' //example.com/index.html
|
||||
.Pp
|
||||
An example mimedef file:
|
||||
.Bd -literal -offset indent -compact
|
||||
application/octet-stream od -tx1 | $PAGER
|
||||
text/html w3m -T "$mime"
|
||||
text/troff nroff | $PAGER
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr gemini-cat 1 ,
|
||||
.Xr libgeminiclient 3 ,
|
||||
.Xr tls_config_set_cert_file 3 ,
|
||||
.Xr tls_config_set_key_file 3
|
|
@ -0,0 +1,329 @@
|
|||
#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 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 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:m: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)
|
||||
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 '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");
|
||||
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) {
|
||||
gemini_reset(&gemini);
|
||||
warn("Gemini read error");
|
||||
continue;
|
||||
}
|
||||
if (gemini.status / 10 != 2) {
|
||||
gemini_reset(&gemini);
|
||||
warnx("Gemini error %02hd: %s",
|
||||
gemini.status, gemini.meta);
|
||||
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)strlcpy(mimestr, "text/gemini",
|
||||
sizeof(mimestr));
|
||||
for (tmime = mime; tmime != NULL &&
|
||||
strcmp(tmime->target, mimestr) != 0;
|
||||
tmime = tmime->next)
|
||||
/* do nothing */;
|
||||
(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 [-w] [-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);
|
||||
}
|
||||
|
||||
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' && !isspace(s[j]);
|
||||
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);
|
||||
}
|
Loading…
Reference in New Issue