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