diff --git a/src/gmnlm.c b/src/gmnlm.c
index 273423b8261a9608c89505b4e154899806f994f4..15256bbd0dcd1c6899e8e91ba5228baf93ce289f 100644
--- a/src/gmnlm.c
+++ b/src/gmnlm.c
@@ -22,6 +22,16 @@ char *url;
struct history *prev, *next;
};
+struct browser {
+ bool pagination;
+ struct gemini_options opts;
+
+ FILE *tty;
+ struct Curl_URL *url;
+ struct link *links;
+ struct history *history;
+};
+
static void
usage(const char *argv_0)
{
@@ -39,7 +49,7 @@ free(history);
}
static bool
-set_url(struct Curl_URL *url, char *new_url, struct history **history)
+set_url(struct browser *browser, char *new_url, struct history **history)
{
if (history) {
struct history *next = calloc(1, sizeof(struct history));
@@ -53,7 +63,7 @@ (*history)->next = next;
}
*history = next;
}
- if (curl_url_set(url, CURLUPART_URL, new_url, 0) != CURLUE_OK) {
+ if (curl_url_set(browser->url, CURLUPART_URL, new_url, 0) != CURLUE_OK) {
fprintf(stderr, "Error: invalid URL\n");
return false;
}
@@ -71,27 +81,29 @@ return in;
}
static void
-display_gemini(FILE *tty, struct gemini_response *resp,
- struct link **next, bool pagination)
+display_gemini(struct browser *browser, struct gemini_response *resp)
{
+ // TODO: Strip ANSI escape sequences
int nlinks = 0;
struct gemini_parser p;
gemini_parser_init(&p, resp->bio);
struct winsize ws;
- ioctl(fileno(tty), TIOCGWINSZ, &ws);
+ ioctl(fileno(browser->tty), TIOCGWINSZ, &ws);
int row = 0, col = 0;
struct gemini_token tok;
+ struct link **next = &browser->links;
while (gemini_parser_next(&p, &tok) == 0) {
switch (tok.token) {
case GEMINI_TEXT:
// TODO: word wrap
- col += fprintf(tty, " %s\n", trim_ws(tok.text));
+ col += fprintf(browser->tty, " %s\n",
+ trim_ws(tok.text));
break;
case GEMINI_LINK:
- col += fprintf(tty, "[%d] %s\n", nlinks++, trim_ws(
- tok.link.text ? tok.link.text : tok.link.url));
+ col += fprintf(browser->tty, "[%d] %s\n", nlinks++,
+ 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;
@@ -100,17 +112,20 @@ case GEMINI_PREFORMATTED:
continue; // TODO
case GEMINI_HEADING:
for (int n = tok.heading.level; n; --n) {
- col += fprintf(tty, "#");
+ col += fprintf(browser->tty, "#");
}
- col += fprintf(tty, " %s\n", trim_ws(tok.heading.title));
+ col += fprintf(browser->tty, " %s\n",
+ trim_ws(tok.heading.title));
break;
case GEMINI_LIST_ITEM:
// TODO: Option to disable Unicode
- col += fprintf(tty, " • %s\n", trim_ws(tok.list_item));
+ col += fprintf(browser->tty, " • %s\n",
+ trim_ws(tok.list_item));
break;
case GEMINI_QUOTE:
// TODO: Option to disable Unicode
- col += fprintf(tty, " | %s\n", trim_ws(tok.quote_text));
+ col += fprintf(browser->tty, " | %s\n",
+ trim_ws(tok.quote_text));
break;
}
@@ -121,12 +136,14 @@ }
++row;
col = 0;
- if (pagination && row >= ws.ws_row - 1) {
- fprintf(tty, "[Enter for more, or q to stop] ");
+ // TODO: It would be nice if we could follow links from this
+ // prompt
+ if (browser->pagination && row >= ws.ws_row - 1) {
+ fprintf(browser->tty, "[Enter for more, or q to stop] ");
size_t n = 0;
char *l = NULL;
- if (getline(&l, &n, tty) == -1) {
+ if (getline(&l, &n, browser->tty) == -1) {
return;
}
if (strcmp(l, "q\n") == 0) {
@@ -142,52 +159,163 @@ gemini_parser_finish(&p);
}
static void
-display_plaintext(FILE *tty, struct gemini_response *resp, bool pagination)
+display_plaintext(struct browser *browser, struct gemini_response *resp)
{
+ // TODO: Strip ANSI escape sequences
struct winsize ws;
int row = 0, col = 0;
- ioctl(fileno(tty), TIOCGWINSZ, &ws);
+ ioctl(fileno(browser->tty), TIOCGWINSZ, &ws);
char buf[BUFSIZ];
int n;
while ((n = BIO_read(resp->bio, buf, sizeof(buf)) != 0)) {
while (n) {
- n -= fwrite(buf, 1, n, tty);
+ n -= fwrite(buf, 1, n, browser->tty);
}
}
- (void)pagination; (void)row; (void)col; // TODO: generalize pagination
+ (void)row; (void)col; // TODO: generalize pagination
}
static void
-display_response(FILE *tty, struct gemini_response *resp,
- struct link **next, bool pagination)
+display_response(struct browser *browser, struct gemini_response *resp)
{
if (strcmp(resp->meta, "text/gemini") == 0
|| strncmp(resp->meta, "text/gemini;", 12) == 0) {
- display_gemini(tty, resp, next, pagination);
+ display_gemini(browser, resp);
return;
}
if (strncmp(resp->meta, "text/", 5) == 0) {
- display_plaintext(tty, resp, pagination);
+ display_plaintext(browser, resp);
return;
}
}
+static char *
+do_requests(struct browser *browser, struct gemini_response *resp)
+{
+ char *plain_url;
+ int nredir = 0;
+ bool requesting = true;
+ while (requesting) {
+ CURLUcode uc = curl_url_get(browser->url,
+ CURLUPART_URL, &plain_url, 0);
+ assert(uc == CURLUE_OK); // Invariant
+
+ enum gemini_result res = gemini_request(
+ plain_url, &browser->opts, resp);
+ if (res != GEMINI_OK) {
+ fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp));
+ requesting = false;
+ break;
+ }
+
+ switch (gemini_response_class(resp->status)) {
+ case GEMINI_STATUS_CLASS_INPUT:
+ assert(0); // TODO
+ 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;
+ display_response(browser, resp);
+ break;
+ }
+
+ if (requesting) {
+ gemini_response_finish(resp);
+ }
+ }
+
+ return plain_url;
+}
+
+static bool
+do_prompts(const char *prompt, struct browser *browser)
+{
+ bool prompting = true;
+ while (prompting) {
+ 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 false;
+ }
+ if (strcmp(in, "q\n") == 0) {
+ return false;
+ }
+ if (strcmp(in, "b\n") == 0) {
+ if (!browser->history->prev) {
+ fprintf(stderr, "At beginning of history\n");
+ continue;
+ }
+ browser->history = browser->history->prev;
+ set_url(browser, browser->history->url, NULL);
+ break;
+ }
+ if (strcmp(in, "f\n") == 0) {
+ if (!browser->history->next) {
+ fprintf(stderr, "At end of history\n");
+ continue;
+ }
+ browser->history = browser->history->next;
+ set_url(browser, browser->history->url, NULL);
+ break;
+ }
+
+ 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);
+ break;
+ }
+ }
+ free(in);
+ }
+ return true;
+}
+
int
main(int argc, char *argv[])
{
- bool pagination = true;
-
- struct Curl_URL *url = curl_url();
-
- FILE *tty = fopen("/dev/tty", "w+");
+ struct browser browser = {
+ .pagination = true,
+ .url = curl_url(),
+ .tty = fopen("/dev/tty", "w+"),
+ };
int c;
while ((c = getopt(argc, argv, "hP")) != -1) {
switch (c) {
case 'P':
- pagination = false;
+ browser.pagination = false;
break;
case 'h':
usage(argv[0]);
@@ -198,9 +326,8 @@ return 1;
}
}
- struct history *history;
if (optind == argc - 1) {
- set_url(url, argv[optind], &history);
+ set_url(&browser, argv[optind], &browser.history);
} else {
usage(argv[0]);
return 1;
@@ -208,65 +335,13 @@ }
SSL_load_error_strings();
ERR_load_crypto_strings();
- struct gemini_options opts = {
- .ssl_ctx = SSL_CTX_new(TLS_method()),
- };
+ browser.opts.ssl_ctx = SSL_CTX_new(TLS_method());
bool run = true;
struct gemini_response resp;
while (run) {
- struct link *links;
static char prompt[4096];
- char *plain_url;
-
- int nredir = 0;
- bool requesting = true;
- while (requesting) {
- CURLUcode uc = curl_url_get(url, CURLUPART_URL, &plain_url, 0);
- assert(uc == CURLUE_OK); // Invariant
-
- enum gemini_result res = gemini_request(
- plain_url, &opts, &resp);
- if (res != GEMINI_OK) {
- fprintf(stderr, "Error: %s\n",
- gemini_strerr(res, &resp));
- requesting = false;
- break;
- }
-
- switch (gemini_response_class(resp.status)) {
- case GEMINI_STATUS_CLASS_INPUT:
- assert(0); // TODO
- 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(url, 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;
- display_response(tty, &resp, &links, pagination);
- break;
- }
-
- if (requesting) {
- gemini_response_finish(&resp);
- }
- }
+ char *plain_url = do_requests(&browser, &resp);
snprintf(prompt, sizeof(prompt), "\n%s%s at %s\n"
"[n]: follow Nth link; [o <url>]: open URL; "
@@ -276,74 +351,22 @@ "=> ",
resp.status == GEMINI_STATUS_SUCCESS ? " " : "",
resp.status == GEMINI_STATUS_SUCCESS ? resp.meta : "",
plain_url);
-
gemini_response_finish(&resp);
- bool prompting = true;
- while (prompting) {
- fprintf(tty, "%s", prompt);
+ run = do_prompts(prompt, &browser);
- size_t l = 0;
- char *in = NULL;
- ssize_t n = getline(&in, &l, tty);
- if (n == -1 && feof(tty)) {
- run = false;
- break;
- }
- if (strcmp(in, "q\n") == 0) {
- run = false;
- break;
- }
- if (strcmp(in, "b\n") == 0) {
- if (!history->prev) {
- fprintf(stderr, "At beginning of history\n");
- continue;
- }
- history = history->prev;
- set_url(url, history->url, NULL);
- break;
- }
- if (strcmp(in, "f\n") == 0) {
- if (!history->next) {
- fprintf(stderr, "At end of history\n");
- continue;
- }
- history = history->next;
- set_url(url, history->url, NULL);
- break;
- }
-
- struct link *link = 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(url, link->url, &history);
- break;
- }
- }
- free(in);
- }
-
- struct link *link = links;
+ struct link *link = browser.links;
while (link) {
struct link *next = link->next;
free(link->url);
free(link);
link = next;
}
- links = NULL;
+ browser.links = NULL;
}
- history_free(history);
- SSL_CTX_free(opts.ssl_ctx);
- curl_url_cleanup(url);
+ history_free(browser.history);
+ SSL_CTX_free(browser.opts.ssl_ctx);
+ curl_url_cleanup(browser.url);
return 0;
}