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");