libgeminiclient/gemini-cat.c

182 lines
3.8 KiB
C

#include <ctype.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>
#ifdef LIBBSD
#include <bsd/readpassphrase.h>
#else
#include <readpassphrase.h>
#endif
#include <tls.h>
#include <libgeminiclient.h>
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 4096
#endif
static ssize_t stripcntrl(char *, size_t, const char *, size_t);
int
main(int argc, char *argv[])
{
char b[BUFFER_SIZE];
struct gemini gemini = GEMINI_INITIALIZER;
const char *query = NULL;
char *p;
ssize_t r;
size_t i;
int prompt = 0;
int rawout;
int c;
gemini_envinit(&gemini);
if ((p = getenv("GEMINI_TOFU_WRITE")) != NULL && *p != '\0')
gemini.flags |= GEMINI_TOFU_WRITE;
rawout = !isatty(STDOUT_FILENO);
#ifdef __OpenBSD__
if (pledge("stdio cpath rpath wpath flock inet dns tty", NULL)
!= 0)
err(EX_NOPERM, "Failed pledge(2)");
#endif
while ((c = getopt(argc, argv, "RSc:h:ik:p:r:st:w")) != -1)
switch (c) {
case 'R':
rawout = 1;
break;
case 'S':
rawout = 0;
break;
case 'c':
gemini.certfile = optarg;
break;
case 'h':
gemini.proxy = optarg;
break;
case 'i':
prompt = 1;
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 's':
prompt = 2;
break;
case 't':
gemini.tofufile = optarg;
break;
case 'w':
gemini.flags |= GEMINI_TOFU_WRITE;
break;
default:
goto usage;
}
argc -= optind;
argv += optind;
#ifdef __OpenBSD__
if (!(gemini.flags & GEMINI_TOFU_WRITE) &&
pledge("stdio rpath flock inet dns tty", NULL) != 0)
err(EX_NOPERM, "Failed pledge(2)");
#endif
for (i = 0; i < argc; i += (query == NULL)) {
if (gemini_connect_query(&gemini, argv[i], query)
!= 0) {
warn("Failed to connect to %s", argv[i]);
goto err;
}
#ifdef __OpenBSD__
if (i == 0 &&
pledge("stdio rpath flock inet dns tty", NULL) != 0)
err(EX_NOPERM, "Failed pledge(2)");
#endif
while ((r = gemini_read(&gemini, b, sizeof(b))) > 0) {
if (!rawout)
r = stripcntrl(b, sizeof(b), b, r);
(void)fwrite(b, 1, r, stdout);
}
if (r < 0) {
warn("Gemini read error");
goto err;
}
query = NULL;
switch (gemini.status / 10) {
case 1:
if (prompt == 0) {
warnx("User-input is not enabled");
break;
}
if (readpassphrase(gemini.meta, b, sizeof(b),
prompt > 1 ? RPP_ECHO_OFF : RPP_ECHO_ON)
== NULL) {
warn("Could not get user-input");
goto err;
}
query = prompt > 0 ? b : NULL;
break;
case 2:
break;
case 4:
break;
case 5:
break;
case 6:
warnx("Gemini certificate error %02hd: %s",
gemini.status, gemini.meta);
break;
default:
warnx("Gemini error %02hd: %s", gemini.status,
gemini.meta);
break;
}
gemini_reset(&gemini);
}
gemini_fini(&gemini);
return (0);
err:
gemini_fini(&gemini);
return (EX_SOFTWARE);
usage:
(void)fprintf(stderr, "usage: gemini-cat [-RSisw] "
"[-c cert-file] [-h proxy-host] [-k key-file]\n"
" [-p port] [-r max-redirects] "
"[-t tofu-file] URL...\n");
return (EX_USAGE);
}
ssize_t
stripcntrl(char *dst, size_t dl, const char *src, size_t sl)
{
size_t i;
size_t j;
for (i = 0, j = 0; i < sl && j < dl; i++)
if (!iscntrl(src[i]) || isspace(src[i]))
dst[j++] = src[i];
return (j);
}