check received content length if the Content-Length header is set - 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 85d6ee233ea16724799840840cb19c955cc0842f (DIR) parent a931b2c32a110fc26258afe16cc77d1c7f5d1e44 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org> Date: Mon, 9 Aug 2021 18:54:46 +0200 check received content length if the Content-Length header is set This prevents corrupted downloads, noticed when my connection was crappy. Diffstat: M hurl.c | 58 ++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) --- (DIR) diff --git a/hurl.c b/hurl.c @@ -64,6 +64,32 @@ sighandler(int signo) } int +parse_content_length(const char *s, size_t *length) +{ + const char *p; + char *end; + long long l; + + if (!(p = strcasestr(s, "\r\nContent-Length:"))) + return -1; + + p += sizeof("\r\nContent-Length:") - 1; + p += strspn(p, " \t"); + + if (!isdigit(*p)) + return -1; + + errno = 0; + l = strtoll(p, &end, 10); + if (errno || p == end || (*end != '\0' && *end != '\r') || l < 0) + return -1; + + *length = l; + + return 0; +} + +int uri_parse(const char *s, struct uri *u) { const char *p = s; @@ -221,9 +247,9 @@ https_request(void) struct tls *t; char buf[READ_BUF_SIZ], *p; const char *errstr; - size_t n, len; + size_t bodylen, expectedlen, n, len; ssize_t r; - int fd = -1, httpok = 0, ret = 1, stdport; + int cs, fd = -1, httpok = 0, ret = 1, stdport; if (pledge("stdio dns inet rpath unveil", NULL) == -1) err(1, "pledge"); @@ -308,7 +334,9 @@ https_request(void) goto err; } *p = '\0'; /* NUL terminate header part */ + cs = parse_content_length(buf, &expectedlen); p += strlen("\r\n\r\n"); + bodylen = strlen(p); /* (partial) body after header */ if (httpok) { n = len - (p - buf); @@ -336,6 +364,7 @@ https_request(void) goto err; } len += r; + bodylen += r; if (httpok) { r = fwrite(buf, 1, r, stdout); @@ -349,10 +378,15 @@ https_request(void) break; } if (config_maxresponsesiz && len >= config_maxresponsesiz) { - fprintf(stderr, "tls_read: response too big: %zu >= %zu\n", + 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; + } ret = 0; err: @@ -368,9 +402,9 @@ int http_request(void) { char buf[READ_BUF_SIZ], *p; - size_t n, len; + size_t bodylen, expectedlen, n, len; ssize_t r; - int fd = -1, httpok = 0, ret = 1, stdport; + int cs, fd = -1, httpok = 0, ret = 1, stdport; if (pledge("stdio dns inet", NULL) == -1) err(1, "pledge"); @@ -426,7 +460,9 @@ http_request(void) goto err; } *p = '\0'; /* NUL terminate header part */ + cs = parse_content_length(buf, &expectedlen); p += strlen("\r\n\r\n"); + bodylen = strlen(p); /* (partial) body after header */ if (httpok) { n = len - (p - buf); @@ -451,6 +487,7 @@ http_request(void) goto err; } len += r; + bodylen += r; if (httpok) { r = fwrite(buf, 1, r, stdout); @@ -464,10 +501,15 @@ http_request(void) break; } if (config_maxresponsesiz && len >= config_maxresponsesiz) { - fprintf(stderr, "read: response too big: %zu >= %zu\n", + 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; + } ret = 0; err: @@ -534,7 +576,7 @@ gopher_request(void) break; } if (config_maxresponsesiz && len >= config_maxresponsesiz) { - fprintf(stderr, "read: response too big: %zu >= %zu\n", + fprintf(stderr, "response too big: %zu >= %zu\n", len, config_maxresponsesiz); goto err; } @@ -633,7 +675,7 @@ gophers_request(void) break; } if (config_maxresponsesiz && len >= config_maxresponsesiz) { - fprintf(stderr, "read: response too big: %zu >= %zu\n", + fprintf(stderr, "response too big: %zu >= %zu\n", len, config_maxresponsesiz); goto err; }