wdvi

network DVI viewer
Log | Files | Refs

commit bfc895c4f59721bd3924873636a9a280a3d716c4
parent 8ac1c0ae3263f199c44aed632261c0f387eef740
Author: Kyle Milz <krwmilz@gmail.com>
Date:   Mon, 20 Sep 2021 16:29:40 +0000

cope with http response body being sent with headers

Some http servers send the HTTP response body in the same TLS record as the
headers, handle this case by finding where the header ends and writing
the extra data to the dvi FILE*.

Diffstat:
Mhttp.c | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 102 insertions(+), 40 deletions(-)

diff --git a/http.c b/http.c @@ -32,6 +32,7 @@ #include "util.h" /* xmalloc() */ #define SSL_BUF_SIZE 16 * 1024 +#define HTTP_DELIM "\r\n" struct http_headers { @@ -227,6 +228,10 @@ tls_connect(int server, struct url *url) } +/* + * Creates an HTTP GET request and writes it to tls. + * Returns 0 on success and -1 on failure. + */ static int send_request(SSL *tls, struct url *url) { @@ -235,9 +240,9 @@ send_request(SSL *tls, struct url *url) int ret; /* RFC7230 3.1.1 */ - req_fmt = "GET /%s HTTP/1.1\r\n" - "Host: %s\r\n" - "\r\n"; + req_fmt = "GET /%s HTTP/1.1" HTTP_DELIM + "Host: %s" HTTP_DELIM + HTTP_DELIM; if (asprintf(&req, req_fmt, url->document, url->hostname) == -1) err(1, "asprintf"); @@ -246,7 +251,7 @@ send_request(SSL *tls, struct url *url) warning_popup_long("%s: %s", "OK", NULL, url->hostname, SSL_strerror(tls, ret)); free(req); - return 1; + return -1; } free(req); @@ -257,109 +262,166 @@ send_request(SSL *tls, struct url *url) /* * Parse a buffer that supposedly contains HTTP headers. * - * Return a pointer to an allocated struct http_headers that the caller must - * free. + * On success: + * - *hdr_len contains the length of the header found in hdr + * - return a pointer to an allocated struct http_headers that the caller + * must free + * + * Return NULL on failure. */ static struct http_headers * -parse_headers(char *hdr, int hdr_len) +parse_headers(char *hdr, int *hdr_len) { struct http_headers *res; + char *hdr_end; char *line; + if ((hdr_end = strstr(hdr, HTTP_DELIM HTTP_DELIM)) == NULL) + return NULL; + *hdr_len = hdr_end - hdr + strlen(HTTP_DELIM HTTP_DELIM); + if ((res = calloc(1, sizeof(struct http_headers))) == NULL) err(1, NULL); - if ((line = strtok(hdr, "\r\n")) == NULL) + /* Let our string processing functions below know when they're done */ + *(hdr_end + strlen(HTTP_DELIM)) = '\0'; + + if ((line = strtok(hdr, HTTP_DELIM)) == NULL) goto free_res; - /* First line of response is 'HTTP/1.1 200 OK' for example. */ + /* Status line of response is 'HTTP/1.1 200 OK' for example. */ if (sscanf(line, "%15s %i %31[^\n]", res->ver, &res->stat, res->stat_text) != 3) goto free_res; /* We only care about Content-Length right now. */ - while ((line = strtok(NULL, "\r\n")) != NULL) { - if (sscanf(line, "Content-Length: %u", &res->cont_len) == 1) - return res; - } + while ((line = strtok(NULL, HTTP_DELIM)) != NULL) + sscanf(line, "Content-Length: %u", &res->cont_len); + + return res; free_res: free(res); return NULL; } + +/* + * Read tls for the response sent by the server after url was requested. + * The read is split in two parts: + * 1) the first gets the header, parses it and sizes recieve buffers + * 2) read at max Content-Length: into the receive buffers + * + * Returns a FILE* containing the response body on success or NULL on failure. + */ static FILE * read_response(SSL *tls, struct url *url) { - char *buf; - struct http_headers *resp; - int nbytes = 0; - int total_bytes = 0; - FILE *dvi_file = NULL; + char *buf; + struct http_headers *resp; + int nbytes; + int bytes_left; + int hdr_len; + FILE *dvi = NULL; if ((buf = calloc(1, SSL_BUF_SIZE)) == NULL) err(1, "calloc"); - /* First read yields the HTTP header response in its entirety. */ + /* + * The first read should yield AT LEAST the HTTP header. The following + * code does not handle the case where it does not. + */ if ((nbytes = SSL_read(tls, buf, SSL_BUF_SIZE)) < 1) { warning_popup_long("SSL_read: %s: %s", "OK", NULL, url->hostname, SSL_strerror(tls, nbytes)); goto free_buf; } - /* We must free returned response. */ - if ((resp = parse_headers(buf, nbytes)) == NULL) { + if ((resp = parse_headers(buf, &hdr_len)) == NULL) { warning_popup_long("%s: Malformed HTTP header", "OK", NULL, url->hostname); goto free_buf; } + /* + * Do not handle anything other than 200. We must have + * Content-Length: as well. + */ if (resp->stat != 200) { warning_popup_long("%s: %i %s", "OK", NULL, url->orig_url, resp->stat, resp->stat_text); goto free_resp; } + else if (resp->cont_len < 1) { + warning_popup_long("%s: Content-Length invalid", "OK", NULL, + url->orig_url); + goto free_resp; + } - /* XDvi does all its processing with FILE streams. */ - if ((dvi_file = fmemopen(NULL, resp->cont_len, "r+")) == NULL) + /* + * XDvi is set up to do all its processing on FILE's, so we use that + * here to minimize changes over there. + */ + if ((dvi = fmemopen(NULL, resp->cont_len, "r+")) == NULL) err(1, "fmemopen"); - /* Second (or more) reads are the HTTP body response. */ - while (total_bytes < resp->cont_len) { + /* Write out any remaining data in buf from the first SSL_read(). */ + if (hdr_len < nbytes) { + bytes_left = nbytes - hdr_len; + + /* Check for buffer overflow. */ + if (bytes_left > resp->cont_len) { + warnx("truncating extra http data"); + bytes_left = resp->cont_len; + } + + if (fwrite(buf + hdr_len, bytes_left, 1, dvi) < 1) + err(1, "fwrite"); + } + + /* Second (or more) reads are the HTTP response body. */ + while (ftell(dvi) < resp->cont_len) { if ((nbytes = SSL_read(tls, buf, SSL_BUF_SIZE)) < 1) { warning_popup_long("SSL_read: %s: %s", "OK", NULL, url->hostname, SSL_strerror(tls, nbytes)); - fclose(dvi_file); - dvi_file = NULL; + fclose(dvi); + dvi = NULL; goto free_resp; } - if (fwrite(buf, 1, nbytes, dvi_file) < nbytes) - err(1, "fwrite"); + /* Check for buffer overflow. */ + if (ftell(dvi) + nbytes > resp->cont_len) { + warnx("truncating extra http data"); + nbytes = resp->cont_len - ftell(dvi); + } - total_bytes += nbytes; + if (fwrite(buf, nbytes, 1, dvi) < 1) + err(1, "fwrite"); } - if (fseek(dvi_file, 0L, SEEK_SET) < 0) - err(1, "fseek"); - - if (total_bytes != resp->cont_len) + if (ftell(dvi) != resp->cont_len) warning_popup_long("HTTP body len: expected %i but read %i\n", - "OK", NULL, resp->cont_len, nbytes); + "OK", NULL, resp->cont_len, ftell(dvi)); + + if (fseek(dvi, 0L, SEEK_SET) < 0) + err(1, "fseek"); free_resp: free(resp); free_buf: free(buf); - return dvi_file; + return dvi; } + /* - * Gets the dest_url[] using a tls connection to the host, then writes the - * returned dvi document into a memory FILE, which it returns. + * Parses the url, resolves and connects to the host, creates a tls + * connection to the host, sends an HTTP GET request then reads the + * response. * - * Returns a FILE * pointer on success or NULL pointer on failure. + * Returns a FILE* buffer containing a DVI file on success, or NULL on + * failure. */ FILE * http_get(const char dest_url[]) @@ -378,7 +440,7 @@ http_get(const char dest_url[]) if ((tls = tls_connect(server, url)) == NULL) goto free_server; - if (send_request(tls, url)) + if (send_request(tls, url) != 0) goto free_tls; if ((dvi = read_response(tls, url)) == NULL)