Refactor http_send_response() into http_prepare_response()

The function http_send_response() did too much. It not only took
the request fields and built them together into a response, it
delegated too little and many functions were "hacked" into it, for
instance shady directory-changes for vhosts and hand-construction
of response structs.

The preparations for a rework were already made in previous commits,
including a tighter focus on the response-struct itself. Instead of
doing everything locally in the http_send_response() function, the
new http_prepare_response() only really takes the request-struct and
builds a response-struct. The response-struct is expanded such that
it's possible to do the data-sending simply with the response-struct
itself and not any other magic parameters that just drop out of the
function.

Another matter are the http_send_status()-calls. Because the
aforementioned function is so central, this refactoring has included
many areas. Instead of calling http_send_status() in every error-case,
which makes little sense now given we first delegate everything through
a response struct, errors are just sent as a return value and caught
centrally (in serve() in main.c), which centralizes the error handling
a bit.

It might look a bit strange now and it might not be clear in which
direction this is going, but subsequent commits will hopefully give
clarity in this regard.

Signed-off-by: Laslo Hunhold <dev@frign.de>
This commit is contained in:
Laslo Hunhold 2020-08-22 23:20:00 +02:00
parent a5163d0813
commit 58d0f44e03
No known key found for this signature in database
GPG key ID: 69576BD24CFCB980
7 changed files with 297 additions and 266 deletions

261
http.c
View file

@ -61,7 +61,7 @@ const char *res_field_str[] = {
enum status enum status
http_send_header(int fd, const struct response *res) http_send_header(int fd, const struct response *res)
{ {
char t[FIELD_MAX]; char t[FIELD_MAX], esc[PATH_MAX];
size_t i; size_t i;
if (timestamp(t, sizeof(t), time(NULL))) { if (timestamp(t, sizeof(t), time(NULL))) {
@ -89,6 +89,18 @@ http_send_header(int fd, const struct response *res)
return S_REQUEST_TIMEOUT; return S_REQUEST_TIMEOUT;
} }
/* listing header */
if (res->type == RESTYPE_DIRLISTING) {
html_escape(res->uri, esc, sizeof(esc));
if (dprintf(fd,
"<!DOCTYPE html>\n<html>\n\t<head>"
"<title>Index of %s</title></head>\n"
"\t<body>\n\t\t<a href=\"..\">..</a>",
esc) < 0) {
return S_REQUEST_TIMEOUT;
}
}
return res->status; return res->status;
} }
@ -536,93 +548,90 @@ parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
enum status enum status
http_send_response(int fd, const struct request *req, const struct server *s) http_prepare_response(const struct request *req, struct response *res,
const struct server *s)
{ {
enum status returnstatus; enum status returnstatus;
struct in6_addr addr; struct in6_addr addr;
struct response res = { 0 };
struct stat st; struct stat st;
struct tm tm = { 0 }; struct tm tm = { 0 };
struct vhost *vhost;
size_t len, i; size_t len, i;
size_t lower, upper;
int hasport, ipv6host; int hasport, ipv6host;
static char realtarget[PATH_MAX], tmptarget[PATH_MAX]; static char realuri[PATH_MAX], tmpuri[PATH_MAX];
char *p, *mime; char *p, *mime;
const char *vhostmatch, *targethost; const char *targethost;
/* make a working copy of the target */ /* empty all response fields */
memcpy(realtarget, req->target, sizeof(realtarget)); memset(res, 0, sizeof(*res));
/* make a working copy of the URI and normalize it */
memcpy(realuri, req->target, sizeof(realuri));
if (normabspath(realuri)) {
return S_BAD_REQUEST;
}
/* match vhost */ /* match vhost */
vhostmatch = NULL; vhost = NULL;
if (s->vhost) { if (s->vhost) {
for (i = 0; i < s->vhost_len; i++) { for (i = 0; i < s->vhost_len; i++) {
/* switch to vhost directory if there is a match */ if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST],
if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], 0, 0, NULL, 0)) {
NULL, 0)) { /* we have a matching vhost */
if (chdir(s->vhost[i].dir) < 0) { vhost = &(s->vhost[i]);
return http_send_status(fd, (errno == EACCES) ?
S_FORBIDDEN : S_NOT_FOUND);
}
vhostmatch = s->vhost[i].chost;
break; break;
} }
} }
if (i == s->vhost_len) { if (i == s->vhost_len) {
return http_send_status(fd, S_NOT_FOUND); return S_NOT_FOUND;
} }
/* if we have a vhost prefix, prepend it to the target */ /* if we have a vhost prefix, prepend it to the URI */
if (s->vhost[i].prefix) { if (vhost->prefix &&
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", prepend(realuri, LEN(realuri), vhost->prefix)) {
s->vhost[i].prefix, realtarget)) { return S_REQUEST_TOO_LARGE;
return http_send_status(fd, S_REQUEST_TOO_LARGE);
}
memcpy(realtarget, tmptarget, sizeof(realtarget));
} }
} }
/* apply target prefix mapping */ /* apply target prefix mapping */
for (i = 0; i < s->map_len; i++) { for (i = 0; i < s->map_len; i++) {
len = strlen(s->map[i].from); len = strlen(s->map[i].from);
if (!strncmp(realtarget, s->map[i].from, len)) { if (!strncmp(realuri, s->map[i].from, len)) {
/* match canonical host if vhosts are enabled and /* match canonical host if vhosts are enabled and
* the mapping specifies a canonical host */ * the mapping specifies a canonical host */
if (s->vhost && s->map[i].chost && if (s->vhost && s->map[i].chost &&
strcmp(s->map[i].chost, vhostmatch)) { strcmp(s->map[i].chost, vhost->chost)) {
continue; continue;
} }
/* swap out target prefix */ /* swap out target prefix */
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", memmove(realuri, realuri + len, strlen(realuri) + 1);
s->map[i].to, realtarget + len)) { if (prepend(realuri, LEN(realuri), s->map[i].to)) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return S_REQUEST_TOO_LARGE;
} }
memcpy(realtarget, tmptarget, sizeof(realtarget));
break; break;
} }
} }
/* normalize target */ /* normalize URI again, in case we introduced dirt */
if (normabspath(realtarget)) { if (normabspath(realuri)) {
return http_send_status(fd, S_BAD_REQUEST); return S_BAD_REQUEST;
} }
/* stat the target */ /* stat the relative path derived from the URI */
if (stat(RELPATH(realtarget), &st) < 0) { if (stat(RELPATH(realuri), &st) < 0) {
return http_send_status(fd, (errno == EACCES) ? return (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
S_FORBIDDEN : S_NOT_FOUND);
} }
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
/* add / to target if not present */ /* append '/' to URI if not present */
len = strlen(realtarget); len = strlen(realuri);
if (len >= PATH_MAX - 2) { if (len + 1 + 1 > PATH_MAX) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return S_REQUEST_TOO_LARGE;
} }
if (len && realtarget[len - 1] != '/') { if (len > 0 && realuri[len - 1] != '/') {
realtarget[len] = '/'; realuri[len] = '/';
realtarget[len + 1] = '\0'; realuri[len + 1] = '\0';
} }
} }
@ -630,24 +639,27 @@ http_send_response(int fd, const struct request *req, const struct server *s)
* reject hidden target, except if it is a well-known URI * reject hidden target, except if it is a well-known URI
* according to RFC 8615 * according to RFC 8615
*/ */
if (strstr(realtarget, "/.") && strncmp(realtarget, if (strstr(realuri, "/.") && strncmp(realuri,
"/.well-known/", sizeof("/.well-known/") - 1)) { "/.well-known/", sizeof("/.well-known/") - 1)) {
return http_send_status(fd, S_FORBIDDEN); return S_FORBIDDEN;
} }
/* redirect if targets differ, host is non-canonical or we prefixed */ /*
if (strcmp(req->target, realtarget) || (s->vhost && vhostmatch && * redirect if the original URI and the "real" URI differ or if
strcmp(req->field[REQ_HOST], vhostmatch))) { * the requested host is non-canonical
res.status = S_MOVED_PERMANENTLY; */
if (strcmp(req->target, realuri) || (s->vhost && vhost &&
strcmp(req->field[REQ_HOST], vhost->chost))) {
res->status = S_MOVED_PERMANENTLY;
/* encode realtarget */ /* encode realuri */
encode(realtarget, tmptarget); encode(realuri, tmpuri);
/* determine target location */ /* determine target location */
if (s->vhost) { if (s->vhost) {
/* absolute redirection URL */ /* absolute redirection URL */
targethost = req->field[REQ_HOST][0] ? vhostmatch ? targethost = req->field[REQ_HOST][0] ? vhost->chost ?
vhostmatch : req->field[REQ_HOST] : s->host ? vhost->chost : req->field[REQ_HOST] : s->host ?
s->host : "localhost"; s->host : "localhost";
/* do we need to add a port to the Location? */ /* do we need to add a port to the Location? */
@ -658,53 +670,74 @@ http_send_response(int fd, const struct request *req, const struct server *s)
* honor that later when we fill the "Location"-field */ * honor that later when we fill the "Location"-field */
if ((ipv6host = inet_pton(AF_INET6, targethost, if ((ipv6host = inet_pton(AF_INET6, targethost,
&addr)) < 0) { &addr)) < 0) {
return http_send_status(fd, return S_INTERNAL_SERVER_ERROR;
S_INTERNAL_SERVER_ERROR);
} }
/* write location to response struct */ /* write location to response struct */
if (esnprintf(res.field[RES_LOCATION], if (esnprintf(res->field[RES_LOCATION],
sizeof(res.field[RES_LOCATION]), sizeof(res->field[RES_LOCATION]),
"//%s%s%s%s%s%s", "//%s%s%s%s%s%s",
ipv6host ? "[" : "", ipv6host ? "[" : "",
targethost, targethost,
ipv6host ? "]" : "", hasport ? ":" : "", ipv6host ? "]" : "", hasport ? ":" : "",
hasport ? s->port : "", tmptarget)) { hasport ? s->port : "", tmpuri)) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return S_REQUEST_TOO_LARGE;
} }
} else { } else {
/* write relative redirection URL to response struct */ /* write relative redirection URI to response struct */
if (esnprintf(res.field[RES_LOCATION], if (esnprintf(res->field[RES_LOCATION],
sizeof(res.field[RES_LOCATION]), sizeof(res->field[RES_LOCATION]),
tmptarget)) { "%s", tmpuri)) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return S_REQUEST_TOO_LARGE;
} }
} }
return http_send_header(fd, &res); return 0;
} else {
/*
* the URI is well-formed, we can now write the URI into
* the response-URI and corresponding relative path
* (optionally including the vhost servedir as a prefix)
* into the actual response-path
*/
if (esnprintf(res->uri, sizeof(res->uri), "%s", req->target)) {
return S_REQUEST_TOO_LARGE;
}
if (esnprintf(res->path, sizeof(res->path), "%s%s",
vhost ? vhost->dir : "", RELPATH(req->target))) {
return S_REQUEST_TOO_LARGE;
}
} }
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
/* append docindex to target */ /*
if (esnprintf(realtarget, sizeof(realtarget), "%s%s", * check if the directory index exists by appending it to
* the URI
*/
if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
req->target, s->docindex)) { req->target, s->docindex)) {
return http_send_status(fd, S_REQUEST_TOO_LARGE); return S_REQUEST_TOO_LARGE;
} }
/* stat the docindex, which must be a regular file */ /* stat the docindex, which must be a regular file */
if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) { if (stat(RELPATH(tmpuri), &st) < 0 || !S_ISREG(st.st_mode)) {
if (s->listdirs) { if (s->listdirs) {
/* remove index suffix and serve dir */ /* serve directory listing */
realtarget[strlen(realtarget) - res->type = RESTYPE_DIRLISTING;
strlen(s->docindex)] = '\0'; res->status = (access(res->path, R_OK)) ?
return resp_dir(fd, RELPATH(realtarget), req); S_FORBIDDEN : S_OK;
if (esnprintf(res->field[RES_CONTENT_TYPE],
sizeof(res->field[RES_CONTENT_TYPE]),
"%s", "text/html; charset=utf-8")) {
return S_INTERNAL_SERVER_ERROR;
}
return 0;
} else { } else {
/* reject */ /* reject */
if (!S_ISREG(st.st_mode) || errno == EACCES) { return (!S_ISREG(st.st_mode) || errno == EACCES) ?
return http_send_status(fd, S_FORBIDDEN); S_FORBIDDEN : S_NOT_FOUND;
} else {
return http_send_status(fd, S_NOT_FOUND);
}
} }
} }
} }
@ -714,39 +747,39 @@ http_send_response(int fd, const struct request *req, const struct server *s)
/* parse field */ /* parse field */
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE], if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
"%a, %d %b %Y %T GMT", &tm)) { "%a, %d %b %Y %T GMT", &tm)) {
return http_send_status(fd, S_BAD_REQUEST); return S_BAD_REQUEST;
} }
/* compare with last modification date of the file */ /* compare with last modification date of the file */
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) { if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
res.status = S_NOT_MODIFIED; res->status = S_NOT_MODIFIED;
return http_send_header(fd, &res); return 0;
} }
} }
/* range */ /* range */
if ((returnstatus = parse_range(req->field[REQ_RANGE], if ((returnstatus = parse_range(req->field[REQ_RANGE], st.st_size,
st.st_size, &lower, &upper))) { &(res->file.lower),
&(res->file.upper)))) {
if (returnstatus == S_RANGE_NOT_SATISFIABLE) { if (returnstatus == S_RANGE_NOT_SATISFIABLE) {
res.status = S_RANGE_NOT_SATISFIABLE; res->status = S_RANGE_NOT_SATISFIABLE;
if (esnprintf(res.field[RES_CONTENT_RANGE], if (esnprintf(res->field[RES_CONTENT_RANGE],
sizeof(res.field[RES_CONTENT_RANGE]), sizeof(res->field[RES_CONTENT_RANGE]),
"bytes */%zu", st.st_size)) { "bytes */%zu", st.st_size)) {
return http_send_status(fd, return S_INTERNAL_SERVER_ERROR;
S_INTERNAL_SERVER_ERROR);
} }
return http_send_header(fd, &res); return 0;
} else { } else {
return http_send_status(fd, returnstatus); return returnstatus;
} }
} }
/* mime */ /* mime */
mime = "application/octet-stream"; mime = "application/octet-stream";
if ((p = strrchr(realtarget, '.'))) { if ((p = strrchr(realuri, '.'))) {
for (i = 0; i < sizeof(mimes) / sizeof(*mimes); i++) { for (i = 0; i < LEN(mimes); i++) {
if (!strcmp(mimes[i].ext, p + 1)) { if (!strcmp(mimes[i].ext, p + 1)) {
mime = mimes[i].type; mime = mimes[i].type;
break; break;
@ -754,5 +787,43 @@ http_send_response(int fd, const struct request *req, const struct server *s)
} }
} }
return resp_file(fd, RELPATH(realtarget), req, &st, mime, lower, upper); /* fill response struct */
res->type = RESTYPE_FILE;
/* check if file is readable */
res->status = (access(res->path, R_OK)) ? S_FORBIDDEN :
(req->field[REQ_RANGE][0] != '\0') ?
S_PARTIAL_CONTENT : S_OK;
if (esnprintf(res->field[RES_ACCEPT_RANGES],
sizeof(res->field[RES_ACCEPT_RANGES]),
"%s", "bytes")) {
return S_INTERNAL_SERVER_ERROR;
}
if (esnprintf(res->field[RES_CONTENT_LENGTH],
sizeof(res->field[RES_CONTENT_LENGTH]),
"%zu", res->file.upper - res->file.lower + 1)) {
return S_INTERNAL_SERVER_ERROR;
}
if (req->field[REQ_RANGE][0] != '\0') {
if (esnprintf(res->field[RES_CONTENT_RANGE],
sizeof(res->field[RES_CONTENT_RANGE]),
"bytes %zd-%zd/%zu", res->file.lower,
res->file.upper, st.st_size)) {
return S_INTERNAL_SERVER_ERROR;
}
}
if (esnprintf(res->field[RES_CONTENT_TYPE],
sizeof(res->field[RES_CONTENT_TYPE]),
"%s", mime)) {
return S_INTERNAL_SERVER_ERROR;
}
if (timestamp(res->field[RES_LAST_MODIFIED],
sizeof(res->field[RES_LAST_MODIFIED]),
st.st_mtim.tv_sec)) {
return S_INTERNAL_SERVER_ERROR;
}
return 0;
} }

11
http.h
View file

@ -3,7 +3,6 @@
#define HTTP_H #define HTTP_H
#include <limits.h> #include <limits.h>
#include <sys/stat.h>
#include "util.h" #include "util.h"
@ -67,8 +66,9 @@ enum res_field {
extern const char *res_field_str[]; extern const char *res_field_str[];
enum res_type { enum res_type {
RESTYPE_ERROR,
RESTYPE_FILE, RESTYPE_FILE,
RESTYPE_DIR, RESTYPE_DIRLISTING,
NUM_RES_TYPES, NUM_RES_TYPES,
}; };
@ -76,10 +76,9 @@ struct response {
enum res_type type; enum res_type type;
enum status status; enum status status;
char field[NUM_RES_FIELDS][FIELD_MAX]; char field[NUM_RES_FIELDS][FIELD_MAX];
char uri[PATH_MAX];
char path[PATH_MAX]; char path[PATH_MAX];
struct stat st;
struct { struct {
char *mime;
size_t lower; size_t lower;
size_t upper; size_t upper;
} file; } file;
@ -106,7 +105,7 @@ enum status http_send_header(int, const struct response *);
enum status http_send_status(int, enum status); enum status http_send_status(int, enum status);
enum status http_recv_header(int, char *, size_t, size_t *); enum status http_recv_header(int, char *, size_t, size_t *);
enum status http_parse_header(const char *, struct request *); enum status http_parse_header(const char *, struct request *);
enum status http_send_response(int fd, const struct request *, enum status http_prepare_response(const struct request *, struct response *,
const struct server *); const struct server *);
#endif /* HTTP_H */ #endif /* HTTP_H */

16
main.c
View file

@ -16,6 +16,7 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include "resp.h"
#include "http.h" #include "http.h"
#include "sock.h" #include "sock.h"
#include "util.h" #include "util.h"
@ -37,12 +38,19 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *s)
} }
/* handle request */ /* handle request */
if ((status = http_recv_header(c.fd, c.header, LEN(c.header), &c.off))) { if ((status = http_recv_header(c.fd, c.header, LEN(c.header), &c.off)) ||
status = http_send_status(c.fd, status); (status = http_parse_header(c.header, &c.req)) ||
} else if ((status = http_parse_header(c.header, &c.req))) { (status = http_prepare_response(&c.req, &c.res, s))) {
status = http_send_status(c.fd, status); status = http_send_status(c.fd, status);
} else { } else {
status = http_send_response(c.fd, &c.req, s); status = http_send_header(c.fd, &c.res);
/* send data */
if (c.res.type == RESTYPE_FILE) {
resp_file(c.fd, &c.res);
} else if (c.res.type == RESTYPE_DIRLISTING) {
resp_dir(c.fd, &c.res);
}
} }
/* write output to log */ /* write output to log */

208
resp.c
View file

@ -38,202 +38,94 @@ suffix(int t)
return ""; return "";
} }
static void
html_escape(const char *src, char *dst, size_t dst_siz)
{
const struct {
char c;
char *s;
} escape[] = {
{ '&', "&amp;" },
{ '<', "&lt;" },
{ '>', "&gt;" },
{ '"', "&quot;" },
{ '\'', "&#x27;" },
};
size_t i, j, k, esclen;
for (i = 0, j = 0; src[i] != '\0'; i++) {
for (k = 0; k < LEN(escape); k++) {
if (src[i] == escape[k].c) {
break;
}
}
if (k == LEN(escape)) {
/* no escape char at src[i] */
if (j == dst_siz - 1) {
/* silent truncation */
break;
} else {
dst[j++] = src[i];
}
} else {
/* escape char at src[i] */
esclen = strlen(escape[k].s);
if (j >= dst_siz - esclen) {
/* silent truncation */
break;
} else {
memcpy(&dst[j], escape[k].s, esclen);
j += esclen;
}
}
}
dst[j] = '\0';
}
enum status enum status
resp_dir(int fd, const char *name, const struct request *req) resp_dir(int fd, const struct response *res)
{ {
enum status sendstatus; enum status ret;
struct dirent **e; struct dirent **e;
struct response res = {
.status = S_OK,
.field[RES_CONTENT_TYPE] = "text/html; charset=utf-8",
};
size_t i; size_t i;
int dirlen; int dirlen;
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
/* read directory */ /* read directory */
if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) { if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) {
return http_send_status(fd, S_FORBIDDEN); return S_FORBIDDEN;
} }
/* send header as late as possible */ /* listing */
if ((sendstatus = http_send_header(fd, &res)) != res.status) { for (i = 0; i < (size_t)dirlen; i++) {
res.status = sendstatus; /* skip hidden files, "." and ".." */
if (e[i]->d_name[0] == '.') {
continue;
}
/* entry line */
html_escape(e[i]->d_name, esc, sizeof(esc));
if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
esc,
(e[i]->d_type == DT_DIR) ? "/" : "",
esc,
suffix(e[i]->d_type)) < 0) {
ret = S_REQUEST_TIMEOUT;
goto cleanup;
}
}
/* listing footer */
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
ret = S_REQUEST_TIMEOUT;
goto cleanup; goto cleanup;
} }
if (req->method == M_GET) {
/* listing header */
html_escape(name, esc, sizeof(esc));
if (dprintf(fd,
"<!DOCTYPE html>\n<html>\n\t<head>"
"<title>Index of %s</title></head>\n"
"\t<body>\n\t\t<a href=\"..\">..</a>",
esc) < 0) {
res.status = S_REQUEST_TIMEOUT;
goto cleanup;
}
/* listing */
for (i = 0; i < (size_t)dirlen; i++) {
/* skip hidden files, "." and ".." */
if (e[i]->d_name[0] == '.') {
continue;
}
/* entry line */
html_escape(e[i]->d_name, esc, sizeof(esc));
if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
esc,
(e[i]->d_type == DT_DIR) ? "/" : "",
esc,
suffix(e[i]->d_type)) < 0) {
res.status = S_REQUEST_TIMEOUT;
goto cleanup;
}
}
/* listing footer */
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
res.status = S_REQUEST_TIMEOUT;
goto cleanup;
}
}
cleanup: cleanup:
while (dirlen--) { while (dirlen--) {
free(e[dirlen]); free(e[dirlen]);
} }
free(e); free(e);
return res.status; return ret;
} }
enum status enum status
resp_file(int fd, const char *name, const struct request *req, resp_file(int fd, const struct response *res)
const struct stat *st, const char *mime, size_t lower,
size_t upper)
{ {
FILE *fp; FILE *fp;
enum status sendstatus; enum status ret = 0;
struct response res = {
.status = (req->field[REQ_RANGE][0] != '\0') ?
S_PARTIAL_CONTENT : S_OK,
.field[RES_ACCEPT_RANGES] = "bytes",
};
ssize_t bread, bwritten; ssize_t bread, bwritten;
size_t remaining; size_t remaining;
static char buf[BUFSIZ], *p; static char buf[BUFSIZ], *p;
/* open file */ /* open file */
if (!(fp = fopen(name, "r"))) { if (!(fp = fopen(res->path, "r"))) {
res.status = http_send_status(fd, S_FORBIDDEN); ret = S_FORBIDDEN;
goto cleanup; goto cleanup;
} }
/* seek to lower bound */ /* seek to lower bound */
if (fseek(fp, lower, SEEK_SET)) { if (fseek(fp, res->file.lower, SEEK_SET)) {
res.status = http_send_status(fd, S_INTERNAL_SERVER_ERROR); ret = S_INTERNAL_SERVER_ERROR;
goto cleanup; goto cleanup;
} }
/* build header */ /* write data until upper bound is hit */
if (esnprintf(res.field[RES_CONTENT_LENGTH], remaining = res->file.upper - res->file.lower + 1;
sizeof(res.field[RES_CONTENT_LENGTH]),
"%zu", upper - lower + 1)) { while ((bread = fread(buf, 1, MIN(sizeof(buf),
return http_send_status(fd, S_INTERNAL_SERVER_ERROR); remaining), fp))) {
} if (bread < 0) {
if (req->field[REQ_RANGE][0] != '\0') { ret = S_INTERNAL_SERVER_ERROR;
if (esnprintf(res.field[RES_CONTENT_RANGE], goto cleanup;
sizeof(res.field[RES_CONTENT_RANGE]),
"bytes %zd-%zd/%zu", lower, upper,
st->st_size)) {
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
} }
} remaining -= bread;
if (esnprintf(res.field[RES_CONTENT_TYPE], p = buf;
sizeof(res.field[RES_CONTENT_TYPE]), while (bread > 0) {
"%s", mime)) { bwritten = write(fd, p, bread);
return http_send_status(fd, S_INTERNAL_SERVER_ERROR); if (bwritten <= 0) {
} ret = S_REQUEST_TIMEOUT;
if (timestamp(res.field[RES_LAST_MODIFIED],
sizeof(res.field[RES_LAST_MODIFIED]),
st->st_mtim.tv_sec)) {
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
}
/* send header as late as possible */
if ((sendstatus = http_send_header(fd, &res)) != res.status) {
res.status = sendstatus;
goto cleanup;
}
if (req->method == M_GET) {
/* write data until upper bound is hit */
remaining = upper - lower + 1;
while ((bread = fread(buf, 1, MIN(sizeof(buf),
remaining), fp))) {
if (bread < 0) {
res.status = S_INTERNAL_SERVER_ERROR;
goto cleanup; goto cleanup;
} }
remaining -= bread; bread -= bwritten;
p = buf; p += bwritten;
while (bread > 0) {
bwritten = write(fd, p, bread);
if (bwritten <= 0) {
res.status = S_REQUEST_TIMEOUT;
goto cleanup;
}
bread -= bwritten;
p += bwritten;
}
} }
} }
cleanup: cleanup:
@ -241,5 +133,5 @@ cleanup:
fclose(fp); fclose(fp);
} }
return res.status; return ret;
} }

5
resp.h
View file

@ -7,8 +7,7 @@
#include "http.h" #include "http.h"
enum status resp_dir(int, const char *, const struct request *); enum status resp_dir(int, const struct response *);
enum status resp_file(int, const char *, const struct request *, enum status resp_file(int, const struct response *);
const struct stat *, const char *, size_t, size_t);
#endif /* RESP_H */ #endif /* RESP_H */

60
util.c
View file

@ -108,6 +108,66 @@ esnprintf(char *str, size_t size, const char *fmt, ...)
return (ret < 0 || (size_t)ret >= size); return (ret < 0 || (size_t)ret >= size);
} }
int
prepend(char *str, size_t size, const char *prefix)
{
size_t len = strlen(str), prefixlen = strlen(prefix);
if (len + prefixlen + 1 > size) {
return 1;
}
memmove(str + prefixlen, str, len + 1);
memcpy(str, prefix, prefixlen);
return 0;
}
void
html_escape(const char *src, char *dst, size_t dst_siz)
{
const struct {
char c;
char *s;
} escape[] = {
{ '&', "&amp;" },
{ '<', "&lt;" },
{ '>', "&gt;" },
{ '"', "&quot;" },
{ '\'', "&#x27;" },
};
size_t i, j, k, esclen;
for (i = 0, j = 0; src[i] != '\0'; i++) {
for (k = 0; k < LEN(escape); k++) {
if (src[i] == escape[k].c) {
break;
}
}
if (k == LEN(escape)) {
/* no escape char at src[i] */
if (j == dst_siz - 1) {
/* silent truncation */
break;
} else {
dst[j++] = src[i];
}
} else {
/* escape char at src[i] */
esclen = strlen(escape[k].s);
if (j >= dst_siz - esclen) {
/* silent truncation */
break;
} else {
memcpy(&dst[j], escape[k].s, esclen);
j += esclen;
}
}
}
dst[j] = '\0';
}
#define INVALID 1 #define INVALID 1
#define TOOSMALL 2 #define TOOSMALL 2
#define TOOLARGE 3 #define TOOLARGE 3

2
util.h
View file

@ -51,6 +51,8 @@ void eunveil(const char *, const char *);
int timestamp(char *, size_t, time_t); int timestamp(char *, size_t, time_t);
int esnprintf(char *, size_t, const char *, ...); int esnprintf(char *, size_t, const char *, ...);
int prepend(char *, size_t, const char *);
void html_escape(const char *, char *, size_t);
void *reallocarray(void *, size_t, size_t); void *reallocarray(void *, size_t, size_t);
long long strtonum(const char *, long long, long long, const char **); long long strtonum(const char *, long long, long long, const char **);