refactor read/write logic and merge logic for handling HTTP[s] and Gopher[s] - hurl - Gopher/HTTP/HTTPS file grabber
(HTM) git clone git://git.codemadness.org/hurl
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit 545a1dd587466312d41d6378bc4df29aef3e5c4c
(DIR) parent 1bd2344632ecac436c229ddbc4d41f9715fff46f
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sun, 4 Aug 2024 13:02:53 +0200
refactor read/write logic and merge logic for handling HTTP[s] and Gopher[s]
* Make the interface for the read/write functions for unencrypted and encrypted
TLS connection the same.
* Handle the protocol (HTTP or Gopher) itself in one place.
Diffstat:
M LICENSE | 2 +-
M hurl.c | 287 +++++++++----------------------
2 files changed, 79 insertions(+), 210 deletions(-)
---
(DIR) diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,6 @@
ISC License
-Copyright (c) 2018-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
+Copyright (c) 2018-2024 Hiltjo Posthuma <hiltjo@codemadness.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
(DIR) diff --git a/hurl.c b/hurl.c
@@ -64,22 +64,25 @@ static struct tls_config *tls_config;
/* protocol handlers */
int gopher_request(void);
-int gophers_request(void);
int http_request(void);
-int https_request(void);
-static const struct {
+struct handler {
int (*handler)(void); /* function / handler / callback */
const char *proto; /* protocol / scheme, "gopher://" */
const char *port; /* default port */
int usetls; /* setup TLS (=1) or plain connection (=0) */
-} handlers[] = {
+};
+
+static const struct handler handlers[] = {
{ .handler = gopher_request, .proto = "gopher://", .port = "70", .usetls = 0 },
- { .handler = gophers_request, .proto = "gophers://", .port = "70", .usetls = 1 },
+ { .handler = gopher_request, .proto = "gophers://", .port = "70", .usetls = 1 },
{ .handler = http_request, .proto = "http://", .port = "80", .usetls = 0 },
- { .handler = https_request, .proto = "https://", .port = "443", .usetls = 1 },
+ { .handler = http_request, .proto = "https://", .port = "443", .usetls = 1 },
};
+ssize_t (*readbuf)(char *, size_t);
+ssize_t (*writebuf)(const char *, size_t);
+
void
sighandler(int signo)
{
@@ -266,7 +269,7 @@ edial(const char *host, const char *port)
}
void
-setupplain(void)
+setup_plain(void)
{
if (pledge("stdio dns inet", NULL) == -1)
err(1, "pledge");
@@ -275,7 +278,7 @@ setupplain(void)
}
void
-setuptls(void)
+setup_tls(void)
{
if (tls_init())
errx(1, "tls_init failed");
@@ -311,52 +314,38 @@ setuptls(void)
errx(1, "tls_connect: %s", tls_error(t));
}
-int
-https_request(void)
+ssize_t
+tls_writebuf(const char *buf, size_t buflen)
{
- char buf[READ_BUF_SIZ], *p;
const char *errstr;
- size_t bodylen, expectedlen, n, len;
- ssize_t r;
- int cs, httpok = 0, ret = 1, stdport;
-
- stdport = u.port[0] == '\0' || strcmp(u.port, "443") == 0;
-
- /* create and send HTTP header */
- r = snprintf(buf, sizeof(buf),
- "GET %s%s%s HTTP/1.0\r\n"
- "Host: %s%s%s\r\n"
- "Connection: close\r\n"
- "%s%s"
- "\r\n",
- u.path[0] ? u.path : "/",
- u.query[0] ? "?" : "", u.query,
- u.host,
- stdport ? "" : ":",
- stdport ? "" : u.port,
- config_headers, config_headers[0] ? "\r\n" : "");
- if (r < 0 || (size_t)r >= sizeof(buf)) {
- fprintf(stderr, "not writing header because it is truncated");
- goto err;
- }
+ const char *p;
+ size_t len;
+ ssize_t r, written = 0;
- for (len = r, p = buf; len > 0; ) {
+ for (len = buflen, p = buf; len > 0; ) {
r = tls_write(t, p, len);
if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
continue;
} else if (r == -1) {
- fprintf(stderr, "tls_write: %s\n", tls_error(t));
- goto err;
+ errstr = tls_error(t);
+ fprintf(stderr, "tls_write: %s\n", errstr ? errstr : "");
+ return -1;
}
p += r;
len -= r;
+ written += r;
}
+ return written;
+}
+
+ssize_t
+tls_readbuf(char *buf, size_t bufsiz)
+{
+ const char *errstr;
+ ssize_t r, len;
- /* NOTE: HTTP header must fit in the buffer */
- for (len = 0; len < sizeof(buf);) {
- /* NOTE: buffer size is -1 to NUL terminate the buffer for a
- string comparison. */
- r = tls_read(t, &buf[len], sizeof(buf) - len - 1);
+ for (len = 0; bufsiz > 0;) {
+ r = tls_read(t, buf + len, bufsiz);
if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
continue;
} else if (r == 0) {
@@ -364,78 +353,41 @@ https_request(void)
} else if (r == -1) {
errstr = tls_error(t);
fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
- goto err;
+ return -1;
}
len += r;
+ bufsiz -= r;
}
- buf[len] = '\0';
+ return len;
+}
- if (!strncmp(buf, "HTTP/1.0 200 ", sizeof("HTTP/1.0 200 ") - 1) ||
- !strncmp(buf, "HTTP/1.1 200 ", sizeof("HTTP/1.1 200 ") - 1))
- httpok = 1;
+ssize_t
+plain_writebuf(const char *buf, size_t buflen)
+{
+ ssize_t r;
- if (!(p = strstr(buf, "\r\n\r\n"))) {
- fprintf(stderr, "no HTTP header found or header too big\n");
- goto err;
- }
- *p = '\0'; /* NUL terminate header part */
- cs = parse_content_length(buf, &expectedlen);
- p += strlen("\r\n\r\n");
- bodylen = len - (p - buf); /* (partial) body after header */
+ if ((r = write(sock, buf, buflen)) == -1)
+ fprintf(stderr, "write: %s\n", strerror(errno));
+ return r;
+}
- if (httpok) {
- n = len - (p - buf);
- fwrite(p, 1, n, stdout);
- if (ferror(stdout)) {
- fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
- goto err;
- }
- } else {
- /* if not 200 OK print header */
- fputs(buf, stderr);
- fputs("\r\n\r\n", stderr);
- /* NOTE: we are nice and keep reading (not closing) until the server is done. */
- }
+ssize_t
+plain_readbuf(char *buf, size_t bufsiz)
+{
+ ssize_t r, len;
- while (1) {
- r = tls_read(t, &buf, sizeof(buf));
- if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
- continue;
- } else if (r == 0) {
+ for (len = 0; bufsiz > 0;) {
+ r = read(sock, buf + len, bufsiz);
+ if (r == 0) {
break;
} else if (r == -1) {
- errstr = tls_error(t);
- fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
- goto err;
+ fprintf(stderr, "read: %s\n", strerror(errno));
+ return -1;
}
len += r;
- bodylen += r;
-
- if (httpok) {
- fwrite(buf, 1, r, stdout);
- if (ferror(stdout)) {
- fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
- goto err;
- }
- }
-
- if (config_maxresponsesiz && len >= config_maxresponsesiz)
- break;
- }
- if (config_maxresponsesiz && len >= config_maxresponsesiz) {
- fprintf(stderr, "response too big: %zu >= %zu\n",
- len, config_maxresponsesiz);
- goto err;
- }
- if (cs != -1 && expectedlen != bodylen) {
- fprintf(stderr, "Content-Length mismatch: %zu expected != %zu received\n",
- expectedlen, bodylen);
- goto err;
+ bufsiz -= r;
}
- ret = 0;
-
-err:
- return httpok ? ret : 2;
+ return len;
}
int
@@ -446,7 +398,7 @@ http_request(void)
ssize_t r;
int cs, httpok = 0, ret = 1, stdport;
- stdport = u.port[0] == '\0' || strcmp(u.port, "80") == 0;
+ stdport = u.port[0] == '\0' || strcmp(u.port, t ? "443" : "80") == 0;
/* create and send HTTP header */
r = snprintf(buf, sizeof(buf),
@@ -465,23 +417,16 @@ http_request(void)
fprintf(stderr, "not writing header because it is truncated");
goto err;
}
- if (write(sock, buf, r) == -1) {
- fprintf(stderr, "write: %s\n", strerror(errno));
+
+ if ((r = writebuf(buf, r)) == -1)
goto err;
- }
- /* NOTE: HTTP header must fit in the buffer */
- for (len = 0; len < sizeof(buf); len += r) {
- /* NOTE: buffer size is -1 to NUL terminate the buffer for a
- string comparison. */
- if ((r = read(sock, &buf[len], sizeof(buf) - len - 1)) == 0)
- break;
- if (r == -1) {
- fprintf(stderr, "read: %s\n", strerror(errno));
- goto err;
- }
- }
- buf[len] = '\0';
+ /* NOTE: HTTP header must fit in the buffer, buffer size is -1 to NUL
+ terminate the buffer for a string comparison. */
+ if ((r = readbuf(buf, sizeof(buf) - 1)) == -1)
+ goto err;
+ len = r;
+ buf[len] = '\0'; /* NUL terminate buffer */
if (!strncmp(buf, "HTTP/1.0 200 ", sizeof("HTTP/1.0 200 ") - 1) ||
!strncmp(buf, "HTTP/1.1 200 ", sizeof("HTTP/1.1 200 ") - 1))
@@ -510,14 +455,7 @@ http_request(void)
/* NOTE: we are nice and keep reading (not closing) until the server is done. */
}
- while (1) {
- r = read(sock, &buf, sizeof(buf));
- if (r == 0)
- break;
- if (r == -1) {
- fprintf(stderr, "read: %s\n", strerror(errno));
- goto err;
- }
+ while ((r = readbuf(buf, sizeof(buf))) > 0) {
len += r;
bodylen += r;
@@ -532,6 +470,8 @@ http_request(void)
if (config_maxresponsesiz && len >= config_maxresponsesiz)
break;
}
+ if (r == -1)
+ goto err;
if (config_maxresponsesiz && len >= config_maxresponsesiz) {
fprintf(stderr, "response too big: %zu >= %zu\n",
len, config_maxresponsesiz);
@@ -573,19 +513,10 @@ gopher_request(void)
goto err;
}
- if (write(sock, buf, strlen(buf)) == -1) {
- fprintf(stderr, "write: %s\n", strerror(errno));
+ if ((r = writebuf(buf, r)) == -1)
goto err;
- }
- while (1) {
- r = read(sock, &buf, sizeof(buf));
- if (r == 0) {
- break;
- } else if (r == -1) {
- fprintf(stderr, "read: %s\n", strerror(errno));
- goto err;
- }
+ while ((r = readbuf(buf, sizeof(buf))) > 0) {
len += r;
fwrite(buf, 1, r, stdout);
@@ -597,76 +528,9 @@ gopher_request(void)
if (config_maxresponsesiz && len >= config_maxresponsesiz)
break;
}
- if (config_maxresponsesiz && len >= config_maxresponsesiz) {
- fprintf(stderr, "response too big: %zu >= %zu\n",
- len, config_maxresponsesiz);
- goto err;
- }
- ret = 0;
-
-err:
- return ret;
-}
-
-int
-gophers_request(void)
-{
- char buf[READ_BUF_SIZ], *p;
- const char *errstr, *path;
- size_t len = 0;
- ssize_t r;
- int ret = 1;
-
- /* create and send path, skip type part, empty path is allowed,
- see RFC 4266 The gopher URI Scheme - section 2.1 */
- path = u.path;
- if (*path == '/') {
- path++;
- if (*path)
- path++; /* skip type */
- }
-
- r = snprintf(buf, sizeof(buf), "%s%s%s\r\n",
- path, u.query[0] ? "?" : "", u.query);
- if (r < 0 || (size_t)r >= sizeof(buf)) {
- fprintf(stderr, "not writing header because it is truncated");
+ if (r == -1)
goto err;
- }
- for (len = r, p = buf; len > 0; ) {
- r = tls_write(t, p, len);
- if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
- continue;
- } else if (r == -1) {
- fprintf(stderr, "tls_write: %s\n", tls_error(t));
- goto err;
- }
- p += r;
- len -= r;
- }
-
- while (1) {
- r = tls_read(t, &buf, sizeof(buf));
- if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
- continue;
- } else if (r == 0) {
- break;
- } else if (r == -1) {
- errstr = tls_error(t);
- fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
- goto err;
- }
- len += r;
-
- fwrite(buf, 1, r, stdout);
- if (ferror(stdout)) {
- fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
- goto err;
- }
-
- if (config_maxresponsesiz && len >= config_maxresponsesiz)
- break;
- }
if (config_maxresponsesiz && len >= config_maxresponsesiz) {
fprintf(stderr, "response too big: %zu >= %zu\n",
len, config_maxresponsesiz);
@@ -744,10 +608,15 @@ main(int argc, char **argv)
strcpy(u.port, handlers[i].port); /* default port if unset */
/* setup TLS or plain connection */
- if (handlers[i].usetls)
- setuptls();
- else
- setupplain();
+ if (handlers[i].usetls) {
+ setup_tls();
+ readbuf = tls_readbuf;
+ writebuf = tls_writebuf;
+ } else {
+ setup_plain();
+ readbuf = plain_readbuf;
+ writebuf = plain_writebuf;
+ }
if (pledge("stdio", NULL) == -1)
err(1, "pledge");