diff --git a/include/client.h b/include/client.h

index f711eea91c3842ac29a884b38e0d7a0ab99cb7fb..40729073bcaa2c0a172d3375662ac91cef0ea550 100644

--- a/include/client.h

+++ b/include/client.h

@@ -68,4 +68,8 @@

// Returns a user-friendly string describing an error.

const char *gemini_strerr(enum gemini_result r, struct gemini_response *resp);

+// Returns the given URL with the input response set to the specified value.

+// The caller must free the string.

+char *gemini_input_url(const char *url, const char *input);

+

#endif

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

index 67671ccca2a25a460681bdffacb981c7f26c1407..c252c9d9ab1783dd462343ce346cc74611e9ae85 100644

--- a/src/client.c

+++ b/src/client.c

@@ -176,7 +176,7 @@ }

char *endptr;

resp->status = (int)strtol(buf, &endptr, 10);

- if (*endptr != ' ' || resp->status <= 10 || resp->status >= 70) {

+ if (*endptr != ' ' || resp->status < 10 || resp->status >= 70) {

res = GEMINI_ERR_PROTOCOL;

goto cleanup;

}

@@ -195,14 +195,25 @@ {

if (!resp) {

return;

}

+

if (resp->fd != -1) {

close(resp->fd);

+ resp->fd = -1;

}

- BIO_free(BIO_pop(resp->bio)); // ssl bio

- BIO_free(resp->bio); // buffered bio

+

+ if (resp->bio) {

+ BIO_free(BIO_pop(resp->bio)); // ssl bio

+ BIO_free(resp->bio); // buffered bio

+ resp->bio = NULL;

+ }

+

SSL_free(resp->ssl);

SSL_CTX_free(resp->ssl_ctx);

free(resp->meta);

+

+ resp->ssl = NULL;

+ resp->ssl_ctx = NULL;

+ resp->meta = NULL;

}

const char *

@@ -230,3 +241,26 @@ return "Protocol error";

}

assert(0);

}

+

+char *

+gemini_input_url(const char *url, const char *input)

+{

+ char *new_url = NULL;

+ struct Curl_URL *uri = curl_url();

+ if (!uri) {

+ return NULL;

+ }

+ if (curl_url_set(uri, CURLUPART_URL, url, 0) != CURLUE_OK) {

+ goto cleanup;

+ }

+ if (curl_url_set(uri, CURLUPART_QUERY, input, CURLU_URLENCODE) != CURLUE_OK) {

+ goto cleanup;

+ }

+ if (curl_url_get(uri, CURLUPART_URL, &new_url, 0) != CURLUE_OK) {

+ new_url = NULL;

+ goto cleanup;

+ }

+cleanup:

+ curl_url_cleanup(uri);

+ return new_url;

+}

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

index 014211dc3784ba3eadcf1885ef7bcf6fe84d8462..794b94ba2235757864acb81194b8de88220cc6da 100644

--- a/src/gmnic.c

+++ b/src/gmnic.c

@@ -26,10 +26,17 @@ OMIT_HEADERS,

SHOW_HEADERS,

ONLY_HEADERS,

};

- enum header_mode headers = OMIT_HEADERS;

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

int c;

- while ((c = getopt(argc, argv, "46C:d:hLiI")) != -1) {

+ while ((c = getopt(argc, argv, "46C:d:D:hLiIN")) != -1) {

switch (c) {

case '4':

assert(0); // TODO

@@ -41,7 +48,21 @@ case 'C':

assert(0); // TODO: Client certificates

break;

case 'd':

- assert(0); // TODO: Input

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

+ }

+ }

break;

case 'h':

usage(argv[0]);

@@ -50,10 +71,14 @@ case 'L':

assert(0); // TODO: Follow redirects

break;

case 'i':

- headers = SHOW_HEADERS;

+ header_mode = SHOW_HEADERS;

break;

case 'I':

- headers = ONLY_HEADERS;

+ header_mode = ONLY_HEADERS;

+ input_mode = INPUT_SUPPRESS;

+ break;

+ case 'N':

+ input_mode = INPUT_SUPPRESS;

break;

default:

fprintf(stderr, "fatal: unknown flag %c", c);

@@ -69,43 +94,105 @@

SSL_load_error_strings();

ERR_load_crypto_strings();

- struct gemini_response resp;

- enum gemini_result r = gemini_request(argv[optind], NULL, &resp);

- if (r != GEMINI_OK) {

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

- gemini_response_finish(&resp);

- return (int)r;

- }

+ bool exit = false;

+ char *url = strdup(argv[optind]);

+

+ int ret = 0;

+ while (!exit) {

+ struct gemini_response resp;

+ enum gemini_result r = gemini_request(url, NULL, &resp);

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

+ }

- switch (headers) {

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

- for (int n = 1; n > 0;) {

- char buf[BUFSIZ];

- n = BIO_read(resp.bio, buf, BUFSIZ);

+ size_t s = 0;

+ ssize_t n = getline(&input, &s, input_source);

if (n == -1) {

- fprintf(stderr, "Error: read\n");

- return 1;

+ fprintf(stderr, "Error reading input: %s\n",

+ feof(input_source) ? "EOF" :

+ strerror(ferror(input_source)));

+ r = 1;

+ exit = true;

+ break;

+ }

+ input[n - 1] = '\0'; // Drop LF

+

+ new_url = gemini_input_url(url, input);

+ free(url);

+ url = new_url;

+ goto next;

+ case 3: // REDIRECT

+ assert(0); // TODO

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

}

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

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

+ }

+ for (int n = 1; n > 0;) {

+ char buf[BUFSIZ];

+ n = BIO_read(resp.bio, buf, BUFSIZ);

+ if (n == -1) {

+ fprintf(stderr, "Error: read\n");

return 1;

}

- w += x;

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

+ }

}

+ break;

}

- break;

+

+next:

+ gemini_response_finish(&resp);

}

- gemini_response_finish(&resp);

- return 0;

+ (void)input_mode;

+ free(url);

+ return ret;

}