diff --git a/src/gmnlm.c b/src/gmnlm.c

index d05180c7a74c88bd7ed8ba551a4a522bb8b74009..9bc95fad89c0f92bc5a68fdd40d272b021c3cc25 100644

--- a/src/gmnlm.c

+++ b/src/gmnlm.c

@@ -263,6 +263,167 @@ putc('\n', out);

}

}

+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;

+}

+

+static bool

+has_suffix(char *str, char *suff)

+{

+ size_t suffl = strlen(suff);

+ size_t strl = strlen(str);

+ if (strl < suffl) {

+ return false;

+ }

+ return strcmp(&str[strl - suffl], suff) == 0;

+}

+

+static enum gemini_result

+do_requests(struct browser *browser, struct gemini_response *resp)

+{

+ int nredir = 0;

+ bool requesting = true;

+ enum gemini_result res;

+ while (requesting) {

+ char *scheme;

+ CURLUcode uc = curl_url_get(browser->url,

+ CURLUPART_SCHEME, &scheme, 0);

+ assert(uc == CURLUE_OK); // Invariant

+ if (strcmp(scheme, "file") == 0) {

+ free(scheme);

+ requesting = false;

+

+ char *path;

+ uc = curl_url_get(browser->url,

+ CURLUPART_PATH, &path, 0);

+ if (uc != CURLUE_OK) {

+ resp->status = GEMINI_STATUS_BAD_REQUEST;

+ break;

+ }

+

+ FILE *fp = fopen(path, "r");

+ if (!fp) {

+ resp->status = GEMINI_STATUS_NOT_FOUND;

+ /* Make sure members of resp evaluate to false, so that

+ gemini_response_finish does not try to free them. */

+ resp->bio = NULL;

+ resp->ssl = NULL;

+ resp->ssl_ctx = NULL;

+ resp->meta = NULL;

+ resp->fd = -1;

+ free(path);

+ break;

+ }

+

+ BIO *file = BIO_new_fp(fp, BIO_CLOSE);

+ resp->bio = BIO_new(BIO_f_buffer());

+ BIO_push(resp->bio, file);

+ if (has_suffix(path, ".gmi") || has_suffix(path, ".gemini")) {

+ resp->meta = strdup("text/gemini");

+ } else if (has_suffix(path, ".txt")) {

+ resp->meta = strdup("text/plain");

+ } else {

+ resp->meta = strdup("application/x-octet-stream");

+ }

+ free(path);

+ resp->status = GEMINI_STATUS_SUCCESS;

+ resp->fd = -1;

+ resp->ssl = NULL;

+ resp->ssl_ctx = NULL;

+ return GEMINI_OK;

+ }

+ free(scheme);

+

+ res = gemini_request(browser->plain_url, &browser->opts, resp);

+ if (res != GEMINI_OK) {

+ fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp));

+ requesting = false;

+ resp->status = 70 + res;

+ break;

+ }

+

+ char *input;

+ switch (gemini_response_class(resp->status)) {

+ case GEMINI_STATUS_CLASS_INPUT:

+ input = get_input(resp, browser->tty);

+ if (!input) {

+ requesting = false;

+ break;

+ }

+ if (input[0] == '\0' && browser->history->prev) {

+ free(input);

+ browser->history = browser->history->prev;

+ set_url(browser, browser->history->url, NULL);

+ break;

+ }

+

+ char *new_url = gemini_input_url(

+ browser->plain_url, input);

+ free(input);

+ assert(new_url);

+ set_url(browser, new_url, NULL);

+ free(new_url);

+ break;

+ case GEMINI_STATUS_CLASS_REDIRECT:

+ if (++nredir >= 5) {

+ requesting = false;

+ fprintf(stderr, "Error: maximum redirects (5) exceeded\n");

+ break;

+ }

+ 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:

+ return res;

+ }

+

+ if (requesting) {

+ gemini_response_finish(resp);

+ }

+ }

+

+ return res;

+}

+

static enum prompt_result

do_prompts(const char *prompt, struct browser *browser)

{

@@ -678,6 +839,9 @@

static bool

display_response(struct browser *browser, struct gemini_response *resp)

{

+ if (gemini_response_class(resp->status) != GEMINI_STATUS_CLASS_SUCCESS) {

+ return false;

+ }

if (strcmp(resp->meta, "text/gemini") == 0

|| strncmp(resp->meta, "text/gemini;", 12) == 0) {

return display_gemini(browser, resp);

@@ -688,170 +852,6 @@ }

assert(0); // TODO: Deal with other mimetypes

}

-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;

-}

-

-static bool

-has_suffix(char *str, char *suff)

-{

- size_t suffl = strlen(suff);

- size_t strl = strlen(str);

- if (strl < suffl) {

- return false;

- }

- return strcmp(&str[strl - suffl], suff) == 0;

-}

-

-// Returns true to skip prompting

-static bool

-do_requests(struct browser *browser, struct gemini_response *resp)

-{

- int nredir = 0;

- bool requesting = true;

- while (requesting) {

- char *scheme;

- CURLUcode uc = curl_url_get(browser->url,

- CURLUPART_SCHEME, &scheme, 0);

- assert(uc == CURLUE_OK); // Invariant

- if (strcmp(scheme, "file") == 0) {

- free(scheme);

- requesting = false;

-

- char *path;

- uc = curl_url_get(browser->url,

- CURLUPART_PATH, &path, 0);

- if (uc != CURLUE_OK) {

- resp->status = GEMINI_STATUS_BAD_REQUEST;

- break;

- }

-

- FILE *fp = fopen(path, "r");

- if (!fp) {

- resp->status = GEMINI_STATUS_NOT_FOUND;

- /* Make sure members of resp evaluate to false, so that

- gemini_response_finish does not try to free them. */

- resp->bio = NULL;

- resp->ssl = NULL;

- resp->ssl_ctx = NULL;

- resp->meta = NULL;

- resp->fd = -1;

- free(path);

- break;

- }

-

- BIO *file = BIO_new_fp(fp, BIO_CLOSE);

- resp->bio = BIO_new(BIO_f_buffer());

- BIO_push(resp->bio, file);

- if (has_suffix(path, ".gmi") || has_suffix(path, ".gemini")) {

- resp->meta = strdup("text/gemini");

- } else if (has_suffix(path, ".txt")) {

- resp->meta = strdup("text/plain");

- } else {

- resp->meta = strdup("application/x-octet-stream");

- }

- free(path);

- resp->status = GEMINI_STATUS_SUCCESS;

- resp->fd = -1;

- resp->ssl = NULL;

- resp->ssl_ctx = NULL;

- return display_response(browser, resp);

- }

- free(scheme);

-

- enum gemini_result res = gemini_request(browser->plain_url,

- &browser->opts, resp);

- if (res != GEMINI_OK) {

- fprintf(stderr, "Error: %s\n", gemini_strerr(res, resp));

- requesting = false;

- resp->status = 70 + res;

- break;

- }

-

- char *input;

- switch (gemini_response_class(resp->status)) {

- case GEMINI_STATUS_CLASS_INPUT:

- input = get_input(resp, browser->tty);

- if (!input) {

- requesting = false;

- break;

- }

- if (input[0] == '\0' && browser->history->prev) {

- free(input);

- browser->history = browser->history->prev;

- set_url(browser, browser->history->url, NULL);

- break;

- }

-

- char *new_url = gemini_input_url(

- browser->plain_url, input);

- free(input);

- assert(new_url);

- set_url(browser, new_url, NULL);

- free(new_url);

- break;

- case GEMINI_STATUS_CLASS_REDIRECT:

- if (++nredir >= 5) {

- requesting = false;

- fprintf(stderr, "Error: maximum redirects (5) exceeded\n");

- 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;

- return display_response(browser, resp);

- }

-

- if (requesting) {

- gemini_response_finish(resp);

- }

- }

-

- return false;

-}

-

static enum tofu_action

tofu_callback(enum tofu_error error, const char *fingerprint,

struct known_host *khost, void *data)

@@ -1001,7 +1001,8 @@ struct gemini_response resp;

browser.running = true;

while (browser.running) {

static char prompt[4096];

- bool skip_prompt = do_requests(&browser, &resp);

+ bool skip_prompt = do_requests(&browser, &resp) == GEMINI_OK

+ && display_response(&browser, &resp);

if (browser.meta) {

free(browser.meta);

}