cgmnlm

colorful gemini line mode browser
git clone https://git.clttr.info/cgmnlm.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

commit 78eb57cad45aa27e83d3a5e78be0bb5ce4d631f7
parent 9b1a618b4211a029c352c72f6d273e3085c8457d
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 20 Sep 2020 14:09:45 -0400

Implement input

Diffstat:
Minclude/client.h | 4++++
Msrc/client.c | 40+++++++++++++++++++++++++++++++++++++---
Msrc/gmnic.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 163 insertions(+), 38 deletions(-)

diff --git a/include/client.h b/include/client.h @@ -68,4 +68,8 @@ void gemini_response_finish(struct gemini_response *resp); // 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 @@ -176,7 +176,7 @@ gemini_request(const char *url, struct gemini_options *options, 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 @@ gemini_response_finish(struct gemini_response *resp) if (!resp) { return; } + if (resp->fd != -1) { close(resp->fd); + resp->fd = -1; + } + + if (resp->bio) { + BIO_free(BIO_pop(resp->bio)); // ssl bio + BIO_free(resp->bio); // buffered bio + resp->bio = NULL; } - BIO_free(BIO_pop(resp->bio)); // ssl bio - BIO_free(resp->bio); // buffered bio + 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 @@ gemini_strerr(enum gemini_result r, struct gemini_response *resp) } 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 @@ -26,10 +26,17 @@ main(int argc, char *argv[]) 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 @@ main(int argc, char *argv[]) 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 @@ main(int argc, char *argv[]) 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 @@ main(int argc, char *argv[]) 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; } - 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)); + 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); + } + 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; }