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;

}