2020-09-21 00:31:33 +02:00
|
|
|
#include <assert.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <openssl/bio.h>
|
|
|
|
#include <openssl/err.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include "gmni.h"
|
|
|
|
#include "url.h"
|
|
|
|
|
|
|
|
struct link {
|
|
|
|
char *url;
|
|
|
|
struct link *next;
|
|
|
|
};
|
|
|
|
|
2020-09-21 01:01:14 +02:00
|
|
|
struct history {
|
|
|
|
char *url;
|
|
|
|
struct history *prev, *next;
|
|
|
|
};
|
|
|
|
|
2020-09-21 01:33:43 +02:00
|
|
|
struct browser {
|
2020-09-21 02:11:48 +02:00
|
|
|
bool pagination, unicode;
|
2020-09-21 01:33:43 +02:00
|
|
|
struct gemini_options opts;
|
|
|
|
|
|
|
|
FILE *tty;
|
2020-09-21 02:01:31 +02:00
|
|
|
char *plain_url;
|
2020-09-21 01:33:43 +02:00
|
|
|
struct Curl_URL *url;
|
|
|
|
struct link *links;
|
|
|
|
struct history *history;
|
2020-09-21 02:01:31 +02:00
|
|
|
bool running;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum prompt_result {
|
|
|
|
PROMPT_AGAIN,
|
|
|
|
PROMPT_MORE,
|
|
|
|
PROMPT_QUIT,
|
|
|
|
PROMPT_ANSWERED,
|
2020-09-21 01:33:43 +02:00
|
|
|
};
|
|
|
|
|
2020-09-21 00:31:33 +02:00
|
|
|
static void
|
|
|
|
usage(const char *argv_0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "usage: %s [gemini://...]\n", argv_0);
|
|
|
|
}
|
|
|
|
|
2020-09-21 01:01:14 +02:00
|
|
|
static void
|
|
|
|
history_free(struct history *history)
|
|
|
|
{
|
|
|
|
if (!history) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
history_free(history->next);
|
|
|
|
free(history);
|
|
|
|
}
|
|
|
|
|
2020-09-21 00:31:33 +02:00
|
|
|
static bool
|
2020-09-21 01:33:43 +02:00
|
|
|
set_url(struct browser *browser, char *new_url, struct history **history)
|
2020-09-21 00:31:33 +02:00
|
|
|
{
|
2020-09-21 01:01:14 +02:00
|
|
|
if (history) {
|
|
|
|
struct history *next = calloc(1, sizeof(struct history));
|
|
|
|
next->url = strdup(new_url);
|
|
|
|
next->prev = *history;
|
|
|
|
if (*history) {
|
|
|
|
if ((*history)->next) {
|
|
|
|
history_free((*history)->next);
|
|
|
|
}
|
|
|
|
(*history)->next = next;
|
|
|
|
}
|
|
|
|
*history = next;
|
|
|
|
}
|
2020-09-21 01:33:43 +02:00
|
|
|
if (curl_url_set(browser->url, CURLUPART_URL, new_url, 0) != CURLUE_OK) {
|
2020-09-21 00:31:33 +02:00
|
|
|
fprintf(stderr, "Error: invalid URL\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
static enum prompt_result
|
|
|
|
do_prompts(const char *prompt, struct browser *browser)
|
|
|
|
{
|
|
|
|
fprintf(browser->tty, "%s", prompt);
|
|
|
|
|
|
|
|
size_t l = 0;
|
|
|
|
char *in = NULL;
|
|
|
|
ssize_t n = getline(&in, &l, browser->tty);
|
|
|
|
if (n == -1 && feof(browser->tty)) {
|
|
|
|
return PROMPT_QUIT;
|
|
|
|
}
|
|
|
|
if (strcmp(in, "\n") == 0) {
|
|
|
|
return PROMPT_MORE;
|
|
|
|
}
|
|
|
|
if (strcmp(in, "q\n") == 0) {
|
|
|
|
return PROMPT_QUIT;
|
|
|
|
}
|
|
|
|
if (strcmp(in, "b\n") == 0) {
|
|
|
|
if (!browser->history->prev) {
|
|
|
|
fprintf(stderr, "At beginning of history\n");
|
|
|
|
return PROMPT_AGAIN;
|
|
|
|
}
|
|
|
|
browser->history = browser->history->prev;
|
|
|
|
set_url(browser, browser->history->url, NULL);
|
|
|
|
return PROMPT_ANSWERED;
|
|
|
|
}
|
|
|
|
if (strcmp(in, "f\n") == 0) {
|
|
|
|
if (!browser->history->next) {
|
|
|
|
fprintf(stderr, "At end of history\n");
|
|
|
|
return PROMPT_AGAIN;
|
|
|
|
}
|
|
|
|
browser->history = browser->history->next;
|
|
|
|
set_url(browser, browser->history->url, NULL);
|
|
|
|
return PROMPT_ANSWERED;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct link *link = browser->links;
|
|
|
|
char *endptr;
|
|
|
|
int linksel = (int)strtol(in, &endptr, 10);
|
|
|
|
if (endptr[0] == '\n' && linksel >= 0) {
|
|
|
|
while (linksel > 0 && link) {
|
|
|
|
link = link->next;
|
|
|
|
--linksel;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!link) {
|
|
|
|
fprintf(stderr, "Error: no such link.\n");
|
|
|
|
} else {
|
|
|
|
set_url(browser, link->url, &browser->history);
|
|
|
|
return PROMPT_ANSWERED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(in);
|
|
|
|
|
|
|
|
return PROMPT_AGAIN;
|
|
|
|
}
|
|
|
|
|
2020-09-21 00:31:33 +02:00
|
|
|
static char *
|
|
|
|
trim_ws(char *in)
|
|
|
|
{
|
2020-09-21 03:22:40 +02:00
|
|
|
while (*in && isspace(*in)) ++in;
|
2020-09-21 00:31:33 +02:00
|
|
|
return in;
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:52:18 +02:00
|
|
|
static int
|
|
|
|
wrap(FILE *f, char *s, struct winsize *ws, int *row, int *col)
|
|
|
|
{
|
|
|
|
if (!s[0]) {
|
|
|
|
fprintf(f, "\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
for (int i = 0; s[i]; ++i) {
|
|
|
|
switch (s[i]) {
|
|
|
|
case '\n':
|
|
|
|
assert(0); // Not supposed to happen
|
|
|
|
case '\t':
|
|
|
|
*col = *col + (8 - *col % 8);
|
|
|
|
break;
|
|
|
|
default:
|
2020-09-21 03:02:17 +02:00
|
|
|
if (iscntrl(s[i])) {
|
|
|
|
s[i] = '.';
|
|
|
|
}
|
2020-09-21 02:52:18 +02:00
|
|
|
*col += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*col >= ws->ws_col) {
|
|
|
|
int j = i--;
|
|
|
|
while (&s[i] != s && !isspace(s[i])) --i;
|
|
|
|
if (&s[i] == s) {
|
|
|
|
i = j;
|
|
|
|
}
|
|
|
|
char c = s[i];
|
|
|
|
s[i] = 0;
|
|
|
|
int n = fprintf(f, "%s\n", s);
|
|
|
|
s[i] = c;
|
|
|
|
*row += 1;
|
|
|
|
*col = 0;
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fprintf(f, "%s\n", s) - 1;
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
static bool
|
2020-09-21 01:33:43 +02:00
|
|
|
display_gemini(struct browser *browser, struct gemini_response *resp)
|
2020-09-21 00:31:33 +02:00
|
|
|
{
|
|
|
|
int nlinks = 0;
|
|
|
|
struct gemini_parser p;
|
|
|
|
gemini_parser_init(&p, resp->bio);
|
|
|
|
|
|
|
|
struct winsize ws;
|
2020-09-21 01:33:43 +02:00
|
|
|
ioctl(fileno(browser->tty), TIOCGWINSZ, &ws);
|
2020-09-21 00:31:33 +02:00
|
|
|
|
2020-09-21 02:52:18 +02:00
|
|
|
char *text = NULL;
|
2020-09-21 00:31:33 +02:00
|
|
|
int row = 0, col = 0;
|
|
|
|
struct gemini_token tok;
|
2020-09-21 01:33:43 +02:00
|
|
|
struct link **next = &browser->links;
|
2020-09-21 02:52:18 +02:00
|
|
|
while (text != NULL || gemini_parser_next(&p, &tok) == 0) {
|
2020-09-21 00:31:33 +02:00
|
|
|
switch (tok.token) {
|
|
|
|
case GEMINI_TEXT:
|
2020-09-21 03:22:40 +02:00
|
|
|
col += fprintf(browser->tty, " ");
|
2020-09-21 02:52:18 +02:00
|
|
|
if (text == NULL) {
|
|
|
|
text = tok.text;
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
break;
|
|
|
|
case GEMINI_LINK:
|
2020-09-21 03:22:40 +02:00
|
|
|
if (text == NULL) {
|
|
|
|
col += fprintf(browser->tty, "%d) ", nlinks++);
|
|
|
|
text = trim_ws(tok.link.text ? tok.link.text : tok.link.url);
|
|
|
|
*next = calloc(1, sizeof(struct link));
|
|
|
|
(*next)->url = strdup(trim_ws(tok.link.url));
|
|
|
|
next = &(*next)->next;
|
|
|
|
} else {
|
|
|
|
col += fprintf(browser->tty, " ");
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
break;
|
|
|
|
case GEMINI_PREFORMATTED:
|
|
|
|
continue; // TODO
|
|
|
|
case GEMINI_HEADING:
|
2020-09-21 03:22:40 +02:00
|
|
|
if (text == NULL) {
|
|
|
|
for (int n = tok.heading.level; n; --n) {
|
|
|
|
col += fprintf(browser->tty, "#");
|
|
|
|
}
|
|
|
|
switch (tok.heading.level) {
|
|
|
|
case 1:
|
|
|
|
col += fprintf(browser->tty, " ");
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
col += fprintf(browser->tty, " ");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
text = trim_ws(tok.heading.title);
|
|
|
|
} else {
|
|
|
|
col += fprintf(browser->tty, " ");
|
2020-09-21 02:42:46 +02:00
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
break;
|
|
|
|
case GEMINI_LIST_ITEM:
|
2020-09-21 03:22:40 +02:00
|
|
|
if (text == NULL) {
|
|
|
|
col += fprintf(browser->tty, " %s ",
|
|
|
|
browser->unicode ? "•" : "*");
|
|
|
|
text = trim_ws(tok.list_item);
|
|
|
|
} else {
|
|
|
|
col += fprintf(browser->tty, " ");
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
break;
|
|
|
|
case GEMINI_QUOTE:
|
2020-09-21 03:22:40 +02:00
|
|
|
if (text == NULL) {
|
|
|
|
col += fprintf(browser->tty, " %s ",
|
|
|
|
browser->unicode ? "|" : "|");
|
|
|
|
text = trim_ws(tok.quote_text);
|
|
|
|
} else {
|
|
|
|
col += fprintf(browser->tty, " ");
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-09-21 03:22:40 +02:00
|
|
|
if (text) {
|
|
|
|
int w = wrap(browser->tty, text, &ws, &row, &col);
|
|
|
|
text += w;
|
|
|
|
if (text[0] && row < ws.ws_row - 4) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!text[0]) {
|
|
|
|
text = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 00:31:33 +02:00
|
|
|
while (col >= ws.ws_col) {
|
|
|
|
col -= ws.ws_col;
|
|
|
|
++row;
|
|
|
|
}
|
2020-09-21 03:22:40 +02:00
|
|
|
++row; col = 0;
|
2020-09-21 00:31:33 +02:00
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
if (browser->pagination && row >= ws.ws_row - 4) {
|
|
|
|
char prompt[4096];
|
|
|
|
snprintf(prompt, sizeof(prompt), "\n%s at %s\n"
|
2020-09-21 03:29:40 +02:00
|
|
|
"[Enter]: read more; [N]: follow Nth link; %s%s[q]uit\n"
|
|
|
|
"(more) => ", resp->meta, browser->plain_url,
|
|
|
|
browser->history->prev ? "[b]ack; " : "",
|
|
|
|
browser->history->next ? "[f]orward; " : "");
|
2020-09-21 02:01:31 +02:00
|
|
|
enum prompt_result result = PROMPT_AGAIN;
|
|
|
|
while (result == PROMPT_AGAIN) {
|
|
|
|
result = do_prompts(prompt, browser);
|
2020-09-21 00:31:33 +02:00
|
|
|
}
|
2020-09-21 02:01:31 +02:00
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
case PROMPT_AGAIN:
|
|
|
|
case PROMPT_MORE:
|
|
|
|
break;
|
|
|
|
case PROMPT_QUIT:
|
|
|
|
browser->running = false;
|
|
|
|
return true;
|
|
|
|
case PROMPT_ANSWERED:
|
|
|
|
return true;
|
2020-09-21 00:31:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
row = col = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gemini_parser_finish(&p);
|
2020-09-21 02:01:31 +02:00
|
|
|
return false;
|
2020-09-21 00:31:33 +02:00
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
static bool
|
2020-09-21 01:33:43 +02:00
|
|
|
display_plaintext(struct browser *browser, struct gemini_response *resp)
|
2020-09-21 01:09:55 +02:00
|
|
|
{
|
2020-09-21 01:33:43 +02:00
|
|
|
// TODO: Strip ANSI escape sequences
|
2020-09-21 01:09:55 +02:00
|
|
|
struct winsize ws;
|
|
|
|
int row = 0, col = 0;
|
2020-09-21 01:33:43 +02:00
|
|
|
ioctl(fileno(browser->tty), TIOCGWINSZ, &ws);
|
2020-09-21 01:09:55 +02:00
|
|
|
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
int n;
|
|
|
|
while ((n = BIO_read(resp->bio, buf, sizeof(buf)) != 0)) {
|
|
|
|
while (n) {
|
2020-09-21 01:33:43 +02:00
|
|
|
n -= fwrite(buf, 1, n, browser->tty);
|
2020-09-21 01:09:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 01:33:43 +02:00
|
|
|
(void)row; (void)col; // TODO: generalize pagination
|
2020-09-21 02:01:31 +02:00
|
|
|
return false;
|
2020-09-21 01:09:55 +02:00
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
static bool
|
2020-09-21 01:33:43 +02:00
|
|
|
display_response(struct browser *browser, struct gemini_response *resp)
|
2020-09-21 01:09:55 +02:00
|
|
|
{
|
|
|
|
if (strcmp(resp->meta, "text/gemini") == 0
|
|
|
|
|| strncmp(resp->meta, "text/gemini;", 12) == 0) {
|
2020-09-21 02:01:31 +02:00
|
|
|
return display_gemini(browser, resp);
|
2020-09-21 01:09:55 +02:00
|
|
|
}
|
|
|
|
if (strncmp(resp->meta, "text/", 5) == 0) {
|
2020-09-21 02:01:31 +02:00
|
|
|
return display_plaintext(browser, resp);
|
2020-09-21 01:09:55 +02:00
|
|
|
}
|
2020-09-21 02:01:31 +02:00
|
|
|
assert(0); // TODO: Deal with other mimetypes
|
2020-09-21 01:09:55 +02:00
|
|
|
}
|
|
|
|
|
2020-09-21 01:40:42 +02:00
|
|
|
static char *
|
|
|
|
get_input(const struct gemini_response *resp, FILE *source)
|
|
|
|
{
|
|
|
|
int r = 0;
|
|
|
|
struct termios attrs;
|
|
|
|
bool tty = fileno(source) != -1 && isatty(fileno(source));
|
|
|
|
char *input = NULL;
|
|
|
|
if (tty) {
|
|
|
|
fprintf(stderr, "%s: ", resp->meta);
|
|
|
|
if (resp->status == GEMINI_STATUS_SENSITIVE_INPUT) {
|
|
|
|
r = tcgetattr(fileno(source), &attrs);
|
|
|
|
struct termios new_attrs;
|
|
|
|
r = tcgetattr(fileno(source), &new_attrs);
|
|
|
|
if (r != -1) {
|
|
|
|
new_attrs.c_lflag &= ~ECHO;
|
|
|
|
tcsetattr(fileno(source), TCSANOW, &new_attrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
size_t s = 0;
|
|
|
|
ssize_t n = getline(&input, &s, source);
|
|
|
|
if (n == -1) {
|
|
|
|
fprintf(stderr, "Error reading input: %s\n",
|
|
|
|
feof(source) ? "EOF" : strerror(ferror(source)));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
input[n - 1] = '\0'; // Drop LF
|
|
|
|
if (tty && resp->status == GEMINI_STATUS_SENSITIVE_INPUT && r != -1) {
|
|
|
|
attrs.c_lflag &= ~ECHO;
|
|
|
|
tcsetattr(fileno(source), TCSANOW, &attrs);
|
|
|
|
}
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
// Returns true to skip prompting
|
|
|
|
static bool
|
2020-09-21 01:33:43 +02:00
|
|
|
do_requests(struct browser *browser, struct gemini_response *resp)
|
2020-09-21 00:31:33 +02:00
|
|
|
{
|
2020-09-21 01:33:43 +02:00
|
|
|
int nredir = 0;
|
|
|
|
bool requesting = true;
|
|
|
|
while (requesting) {
|
|
|
|
CURLUcode uc = curl_url_get(browser->url,
|
2020-09-21 02:01:31 +02:00
|
|
|
CURLUPART_URL, &browser->plain_url, 0);
|
2020-09-21 01:33:43 +02:00
|
|
|
assert(uc == CURLUE_OK); // Invariant
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
enum gemini_result res = gemini_request(browser->plain_url,
|
|
|
|
&browser->opts, resp);
|
2020-09-21 01:33:43 +02:00
|
|
|
if (res != GEMINI_OK) {
|
|
|
|
fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp));
|
|
|
|
requesting = false;
|
|
|
|
break;
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
|
2020-09-21 01:40:42 +02:00
|
|
|
char *input;
|
2020-09-21 01:33:43 +02:00
|
|
|
switch (gemini_response_class(resp->status)) {
|
|
|
|
case GEMINI_STATUS_CLASS_INPUT:
|
2020-09-21 01:40:42 +02:00
|
|
|
input = get_input(resp, browser->tty);
|
|
|
|
if (!input) {
|
|
|
|
requesting = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
char *new_url = gemini_input_url(
|
|
|
|
browser->plain_url, input);
|
2020-09-21 01:40:42 +02:00
|
|
|
assert(new_url);
|
|
|
|
set_url(browser, new_url, NULL);
|
|
|
|
break;
|
2020-09-21 01:33:43 +02:00
|
|
|
case GEMINI_STATUS_CLASS_REDIRECT:
|
|
|
|
if (++nredir >= 5) {
|
|
|
|
requesting = false;
|
|
|
|
fprintf(stderr, "Error: maximum redirects (5) exceeded");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "Following redirect to %s\n", resp->meta);
|
|
|
|
set_url(browser, resp->meta, NULL);
|
|
|
|
break;
|
|
|
|
case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED:
|
|
|
|
assert(0); // TODO
|
|
|
|
case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE:
|
|
|
|
case GEMINI_STATUS_CLASS_PERMANENT_FAILURE:
|
|
|
|
requesting = false;
|
|
|
|
fprintf(stderr, "Server returned %s %d %s\n",
|
|
|
|
resp->status / 10 == 4 ?
|
|
|
|
"TEMPORARY FAILURE" : "PERMANENT FALIURE",
|
|
|
|
resp->status, resp->meta);
|
|
|
|
break;
|
|
|
|
case GEMINI_STATUS_CLASS_SUCCESS:
|
|
|
|
requesting = false;
|
2020-09-21 02:01:31 +02:00
|
|
|
return display_response(browser, resp);
|
2020-09-21 01:33:43 +02:00
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
|
2020-09-21 01:33:43 +02:00
|
|
|
if (requesting) {
|
|
|
|
gemini_response_finish(resp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
return false;
|
2020-09-21 01:33:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
struct browser browser = {
|
|
|
|
.pagination = true,
|
2020-09-21 02:11:48 +02:00
|
|
|
.unicode = true,
|
2020-09-21 01:33:43 +02:00
|
|
|
.url = curl_url(),
|
|
|
|
.tty = fopen("/dev/tty", "w+"),
|
|
|
|
};
|
2020-09-21 00:31:33 +02:00
|
|
|
|
|
|
|
int c;
|
2020-09-21 02:11:48 +02:00
|
|
|
while ((c = getopt(argc, argv, "hPU")) != -1) {
|
2020-09-21 00:31:33 +02:00
|
|
|
switch (c) {
|
|
|
|
case 'h':
|
|
|
|
usage(argv[0]);
|
|
|
|
return 0;
|
2020-09-21 02:11:48 +02:00
|
|
|
case 'P':
|
|
|
|
browser.pagination = false;
|
|
|
|
break;
|
|
|
|
case 'U':
|
|
|
|
browser.unicode = false;
|
|
|
|
break;
|
2020-09-21 00:31:33 +02:00
|
|
|
default:
|
|
|
|
fprintf(stderr, "fatal: unknown flag %c\n", c);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind == argc - 1) {
|
2020-09-21 01:33:43 +02:00
|
|
|
set_url(&browser, argv[optind], &browser.history);
|
2020-09-21 00:41:30 +02:00
|
|
|
} else {
|
2020-09-21 00:31:33 +02:00
|
|
|
usage(argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_load_error_strings();
|
|
|
|
ERR_load_crypto_strings();
|
2020-09-21 01:33:43 +02:00
|
|
|
browser.opts.ssl_ctx = SSL_CTX_new(TLS_method());
|
2020-09-21 00:31:33 +02:00
|
|
|
|
|
|
|
struct gemini_response resp;
|
2020-09-21 02:01:31 +02:00
|
|
|
browser.running = true;
|
|
|
|
while (browser.running) {
|
2020-09-21 00:31:33 +02:00
|
|
|
static char prompt[4096];
|
2020-09-21 02:01:31 +02:00
|
|
|
if (do_requests(&browser, &resp)) {
|
|
|
|
// Skip prompts
|
|
|
|
goto next;
|
|
|
|
}
|
2020-09-21 00:31:33 +02:00
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
snprintf(prompt, sizeof(prompt), "\n%s at %s\n"
|
2020-09-21 03:29:40 +02:00
|
|
|
"[N]: follow Nth link; %s%s[q]uit\n"
|
2020-09-21 01:09:55 +02:00
|
|
|
"=> ",
|
|
|
|
resp.status == GEMINI_STATUS_SUCCESS ? resp.meta : "",
|
2020-09-21 03:29:40 +02:00
|
|
|
browser.plain_url,
|
|
|
|
browser.history->prev ? "[b]ack; " : "",
|
|
|
|
browser.history->next ? "[f]orward; " : "");
|
2020-09-21 00:31:33 +02:00
|
|
|
gemini_response_finish(&resp);
|
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
enum prompt_result result = PROMPT_AGAIN;
|
|
|
|
while (result == PROMPT_AGAIN || result == PROMPT_MORE) {
|
|
|
|
result = do_prompts(prompt, &browser);
|
|
|
|
}
|
|
|
|
switch (result) {
|
|
|
|
case PROMPT_AGAIN:
|
|
|
|
case PROMPT_MORE:
|
|
|
|
assert(0);
|
|
|
|
case PROMPT_QUIT:
|
|
|
|
browser.running = false;
|
|
|
|
break;
|
|
|
|
case PROMPT_ANSWERED:
|
|
|
|
break;
|
|
|
|
}
|
2020-09-21 01:01:14 +02:00
|
|
|
|
2020-09-21 02:01:31 +02:00
|
|
|
next:;
|
2020-09-21 01:33:43 +02:00
|
|
|
struct link *link = browser.links;
|
2020-09-21 01:01:14 +02:00
|
|
|
while (link) {
|
|
|
|
struct link *next = link->next;
|
|
|
|
free(link->url);
|
|
|
|
free(link);
|
|
|
|
link = next;
|
|
|
|
}
|
2020-09-21 01:33:43 +02:00
|
|
|
browser.links = NULL;
|
2020-09-21 00:31:33 +02:00
|
|
|
}
|
|
|
|
|
2020-09-21 01:33:43 +02:00
|
|
|
history_free(browser.history);
|
|
|
|
SSL_CTX_free(browser.opts.ssl_ctx);
|
|
|
|
curl_url_cleanup(browser.url);
|
2020-09-21 00:31:33 +02:00
|
|
|
return 0;
|
|
|
|
}
|