cgmnlm

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

commit 9b1a618b4211a029c352c72f6d273e3085c8457d
parent abcb9caf86020a7cdd9f502fe01eb5db3c70c685
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun, 20 Sep 2020 13:30:28 -0400

Implement reading response body & meta

Diffstat:
Minclude/client.h | 4++++
Msrc/client.c | 42++++++++++++++++++++++++++++++++++++++++++
Msrc/gmnic.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
3 files changed, 96 insertions(+), 30 deletions(-)

diff --git a/include/client.h b/include/client.h @@ -46,6 +46,7 @@ enum gemini_result { // use SSL_get_error(resp->ssl, resp->status) to get details GEMINI_ERR_SSL, GEMINI_ERR_IO, + GEMINI_ERR_PROTOCOL, }; // Requests the specified URL via the gemini protocol. If options is non-NULL, @@ -64,4 +65,7 @@ enum gemini_result gemini_request(const char *url, // gemini_response_finish. 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); + #endif diff --git a/src/client.c b/src/client.c @@ -2,6 +2,7 @@ #include <errno.h> #include <netdb.h> #include <openssl/bio.h> +#include <openssl/err.h> #include <openssl/ssl.h> #include <stdlib.h> #include <string.h> @@ -168,6 +169,21 @@ gemini_request(const char *url, struct gemini_options *options, goto cleanup; } + if (r < 3 || strcmp(&buf[r - 2], "\r\n") != 0) { + res = GEMINI_ERR_PROTOCOL; + goto cleanup; + } + + char *endptr; + resp->status = (int)strtol(buf, &endptr, 10); + if (*endptr != ' ' || resp->status <= 10 || resp->status >= 70) { + res = GEMINI_ERR_PROTOCOL; + goto cleanup; + } + resp->meta = calloc(r - 5 /* 2 digits, space, and CRLF */ + 1 /* NUL */, 1); + strncpy(resp->meta, &endptr[1], r - 5); + resp->meta[r - 5] = '\0'; + cleanup: curl_url_cleanup(uri); return res; @@ -188,3 +204,29 @@ gemini_response_finish(struct gemini_response *resp) SSL_CTX_free(resp->ssl_ctx); free(resp->meta); } + +const char * +gemini_strerr(enum gemini_result r, struct gemini_response *resp) +{ + switch (r) { + case GEMINI_OK: + return "OK"; + case GEMINI_ERR_OOM: + return "Out of memory"; + case GEMINI_ERR_INVALID_URL: + return "Invalid URL"; + case GEMINI_ERR_RESOLVE: + return gai_strerror(resp->status); + case GEMINI_ERR_CONNECT: + return strerror(errno); + case GEMINI_ERR_SSL: + return ERR_error_string( + SSL_get_error(resp->ssl, resp->status), + NULL); + case GEMINI_ERR_IO: + return "I/O error"; + case GEMINI_ERR_PROTOCOL: + return "Protocol error"; + } + assert(0); +} diff --git a/src/gmnic.c b/src/gmnic.c @@ -1,9 +1,13 @@ #include <assert.h> +#include <errno.h> #include <getopt.h> +#include <openssl/bio.h> #include <openssl/err.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <unistd.h> #include "client.h" static void @@ -17,11 +21,15 @@ usage(char *argv_0) int main(int argc, char *argv[]) { - bool headers = false, follow_redirect = false; - char *certificate = NULL, *input = NULL; + enum header_mode { + OMIT_HEADERS, + SHOW_HEADERS, + ONLY_HEADERS, + }; + enum header_mode headers = OMIT_HEADERS; int c; - while ((c = getopt(argc, argv, "46C:d:hLI")) != -1) { + while ((c = getopt(argc, argv, "46C:d:hLiI")) != -1) { switch (c) { case '4': assert(0); // TODO @@ -30,25 +38,29 @@ main(int argc, char *argv[]) assert(0); // TODO break; case 'C': - certificate = optarg; + assert(0); // TODO: Client certificates break; case 'd': - input = optarg; + assert(0); // TODO: Input break; case 'h': usage(argv[0]); return 0; case 'L': - follow_redirect = true; + assert(0); // TODO: Follow redirects + break; + case 'i': + headers = SHOW_HEADERS; break; case 'I': - headers = true; + headers = ONLY_HEADERS; break; default: fprintf(stderr, "fatal: unknown flag %c", c); return 1; } } + if (optind != argc - 1) { usage(argv[0]); return 1; @@ -59,33 +71,41 @@ main(int argc, char *argv[]) struct gemini_response resp; enum gemini_result r = gemini_request(argv[optind], NULL, &resp); - switch (r) { - case GEMINI_OK: - printf("OK\n"); - break; - case GEMINI_ERR_OOM: - printf("OOM\n"); - break; - case GEMINI_ERR_INVALID_URL: - printf("INVALID_URL\n"); - break; - case GEMINI_ERR_RESOLVE: - printf("RESOLVE\n"); - break; - case GEMINI_ERR_CONNECT: - printf("CONNECT\n"); + if (r != GEMINI_OK) { + fprintf(stderr, "Error: %s\n", gemini_strerr(r, &resp)); + gemini_response_finish(&resp); + return (int)r; + } + + switch (headers) { + case ONLY_HEADERS: + printf("%d %s\n", resp.status, resp.meta); break; - case GEMINI_ERR_SSL: - fprintf(stderr, "SSL error: %s\n", ERR_error_string( - SSL_get_error(resp.ssl, resp.status), NULL)); + 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); + if (n == -1) { + fprintf(stderr, "Error: read\n"); + return 1; + } + 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; } gemini_response_finish(&resp); - - (void)headers; - (void)follow_redirect; - (void)certificate; - (void)input; return 0; }