1
0
Fork 0
mirror of https://git.sr.ht/~sircmpwn/gmni synced 2024-06-08 15:46:03 +02:00
gmni/src/gmni.c

227 lines
4.5 KiB
C
Raw Normal View History

2020-09-20 19:06:34 +02:00
#include <assert.h>
2020-09-20 19:30:28 +02:00
#include <errno.h>
2020-09-20 19:06:34 +02:00
#include <getopt.h>
#include <netdb.h>
2020-09-20 19:30:28 +02:00
#include <openssl/bio.h>
2020-09-20 19:06:34 +02:00
#include <openssl/err.h>
#include <stdbool.h>
2020-09-20 16:17:39 +02:00
#include <stdio.h>
2020-09-20 19:06:34 +02:00
#include <stdlib.h>
2020-09-20 19:30:28 +02:00
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
2020-09-20 19:30:28 +02:00
#include <unistd.h>
2020-09-20 19:06:34 +02:00
#include "client.h"
static void
usage(char *argv_0)
{
fprintf(stderr,
"usage: %s [-46lLiIN] [-E cert] [-d input] [-D path] gemini://...\n",
2020-09-20 19:06:34 +02:00
argv_0);
}
2020-09-20 16:17:39 +02:00
int
2020-09-20 19:06:34 +02:00
main(int argc, char *argv[])
{
2020-09-20 19:30:28 +02:00
enum header_mode {
OMIT_HEADERS,
SHOW_HEADERS,
ONLY_HEADERS,
};
2020-09-20 20:09:45 +02:00
enum header_mode header_mode = OMIT_HEADERS;
enum input_mode {
INPUT_READ,
INPUT_SUPPRESS,
};
enum input_mode input_mode = INPUT_READ;
FILE *input_source = stdin;
2020-09-20 20:18:40 +02:00
bool follow_redirects = false, linefeed = true;
struct addrinfo hints = {0};
struct gemini_options opts = {
.hints = &hints,
};
2020-09-20 19:06:34 +02:00
int c;
while ((c = getopt(argc, argv, "46d:D:E:hlLiIN")) != -1) {
2020-09-20 19:06:34 +02:00
switch (c) {
case '4':
hints.ai_family = AF_INET;
2020-09-20 19:06:34 +02:00
break;
case '6':
hints.ai_family = AF_INET6;
2020-09-20 19:06:34 +02:00
break;
case 'd':
2020-09-20 20:09:45 +02:00
input_mode = INPUT_READ;
input_source = fmemopen(optarg, strlen(optarg), "r");
break;
case 'D':
input_mode = INPUT_READ;
if (strcmp(optarg, "-") == 0) {
input_source = stdin;
} else {
input_source = fopen(optarg, "r");
if (!input_source) {
fprintf(stderr, "Error: open %s: %s",
optarg, strerror(errno));
return 1;
}
}
2020-09-20 19:06:34 +02:00
break;
case 'E':
assert(0); // TODO: Client certificates
break;
2020-09-20 19:06:34 +02:00
case 'h':
usage(argv[0]);
return 0;
case 'l':
linefeed = false;
break;
2020-09-20 19:06:34 +02:00
case 'L':
2020-09-20 20:18:40 +02:00
follow_redirects = true;
2020-09-20 19:30:28 +02:00
break;
case 'i':
2020-09-20 20:09:45 +02:00
header_mode = SHOW_HEADERS;
2020-09-20 19:06:34 +02:00
break;
case 'I':
2020-09-20 20:09:45 +02:00
header_mode = ONLY_HEADERS;
input_mode = INPUT_SUPPRESS;
break;
case 'N':
input_mode = INPUT_SUPPRESS;
2020-09-20 19:06:34 +02:00
break;
default:
fprintf(stderr, "fatal: unknown flag %c\n", c);
2020-09-20 19:06:34 +02:00
return 1;
}
}
2020-09-20 19:30:28 +02:00
2020-09-20 19:06:34 +02:00
if (optind != argc - 1) {
usage(argv[0]);
return 1;
}
SSL_load_error_strings();
ERR_load_crypto_strings();
2020-09-20 20:09:45 +02:00
bool exit = false;
char *url = strdup(argv[optind]);
int ret = 0;
while (!exit) {
struct gemini_response resp;
enum gemini_result r = gemini_request(url, &opts, &resp);
2020-09-20 20:09:45 +02:00
if (r != GEMINI_OK) {
fprintf(stderr, "Error: %s\n", gemini_strerr(r, &resp));
ret = (int)r;
exit = true;
goto next;
}
char *new_url, *input = NULL;
switch (resp.status / 10) {
case 1: // INPUT
if (input_mode == INPUT_SUPPRESS) {
exit = true;
break;
}
if (fileno(input_source) != -1 &&
isatty(fileno(input_source))) {
fprintf(stderr, "%s: ", resp.meta);
}
2020-09-20 19:30:28 +02:00
2020-09-20 20:09:45 +02:00
size_t s = 0;
ssize_t n = getline(&input, &s, input_source);
2020-09-20 19:30:28 +02:00
if (n == -1) {
2020-09-20 20:09:45 +02:00
fprintf(stderr, "Error reading input: %s\n",
feof(input_source) ? "EOF" :
strerror(ferror(input_source)));
r = 1;
exit = true;
break;
2020-09-20 19:30:28 +02:00
}
2020-09-20 20:09:45 +02:00
input[n - 1] = '\0'; // Drop LF
new_url = gemini_input_url(url, input);
free(url);
url = new_url;
2020-09-20 21:14:54 +02:00
assert(url);
2020-09-20 20:09:45 +02:00
goto next;
case 3: // REDIRECT
2020-09-20 20:18:40 +02:00
free(url);
url = strdup(resp.meta);
if (!follow_redirects) {
if (header_mode == OMIT_HEADERS) {
fprintf(stderr, "REDIRECT: %d %s\n",
resp.status, resp.meta);
}
exit = true;
}
goto next;
2020-09-20 20:09:45 +02:00
case 6: // CLIENT CERTIFICATE REQUIRED
assert(0); // TODO
case 4: // TEMPORARY FAILURE
case 5: // PERMANENT FAILURE
if (header_mode == OMIT_HEADERS) {
fprintf(stderr, "%s: %d %s\n",
resp.status / 10 == 4 ?
"TEMPORARY FAILURE" : "PERMANENT FALIURE",
resp.status, resp.meta);
}
exit = true;
break;
case 2: // SUCCESS
exit = true;
break;
}
switch (header_mode) {
case ONLY_HEADERS:
printf("%d %s\n", resp.status, resp.meta);
break;
case SHOW_HEADERS:
printf("%d %s\n", resp.status, resp.meta);
/* fallthrough */
case OMIT_HEADERS:
if (resp.status / 10 != 2) {
break;
}
2020-09-20 20:18:26 +02:00
char last;
char buf[BUFSIZ];
2020-09-20 20:18:26 +02:00
for (int n = 1; n > 0;) {
2020-09-20 20:09:45 +02:00
n = BIO_read(resp.bio, buf, BUFSIZ);
if (n == -1) {
fprintf(stderr, "Error: read\n");
2020-09-20 19:30:28 +02:00
return 1;
2020-09-20 20:18:26 +02:00
} else if (n != 0) {
last = buf[n - 1];
2020-09-20 19:30:28 +02:00
}
2020-09-20 20:09:45 +02:00
ssize_t w = 0;
while (w < (ssize_t)n) {
ssize_t x = write(STDOUT_FILENO, &buf[w], n - w);
if (x == -1) {
fprintf(stderr, "Error: write: %s\n",
strerror(errno));
return 1;
}
w += x;
}
2020-09-20 19:30:28 +02:00
}
if (strncmp(resp.meta, "text/", 5) == 0
&& linefeed && last != '\n'
&& isatty(STDOUT_FILENO)) {
printf("\n");
}
2020-09-20 20:09:45 +02:00
break;
2020-09-20 19:30:28 +02:00
}
2020-09-20 20:09:45 +02:00
next:
gemini_response_finish(&resp);
2020-09-20 19:06:34 +02:00
}
2020-09-20 20:09:45 +02:00
free(url);
return ret;
2020-09-20 16:17:39 +02:00
}