hurl | Simple gopher and http(s) client. |
| download: http://git.y1.nz/archives/hurl.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 545a1dd587466312d41d6378bc4df29aef3e5c4c parent 1bd2344632ecac436c229ddbc4d41f9715fff46f 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(-)
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 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");
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.