Ensure const-correctness where possible and refactor parse_range()

I know that the effect of 'const' on compiler optimizations is smaller
than many believe, but it provides a good insight to the caller which
parameters are not modified and simplifies parallelization, in case
that is desired at a later point.

Throughout processing, the big structs mostly remained unmodified, with
the exception of parse_range(), which added a null-byte in the "Range"-
header to simplify its parsing. This commit refactors parse_range()
such that it won't modify this string anymore.

Additionally, the parser was made even stricter: Usually, strtoll()
(which is wrapped by strtonum()) allows whitespace and plus and minus
signs before the number, which is not part of the specification. The
stricter parser also better differentiates now between invalid requests
and range-lists. In that context, the switch in http_send_response()
was replaced for better readability.

Signed-off-by: Laslo Hunhold <dev@frign.de>
This commit is contained in:
Laslo Hunhold 2020-08-05 18:28:21 +02:00
parent 90d5179ea0
commit d105c28aad
No known key found for this signature in database
GPG key ID: 69576BD24CFCB980
7 changed files with 87 additions and 58 deletions

122
http.c
View file

@ -126,11 +126,11 @@ http_send_status(int fd, enum status s)
} }
static void static void
decode(char src[PATH_MAX], char dest[PATH_MAX]) decode(const char src[PATH_MAX], char dest[PATH_MAX])
{ {
size_t i; size_t i;
uint8_t n; uint8_t n;
char *s; const char *s;
for (s = src, i = 0; *s; s++, i++) { for (s = src, i = 0; *s; s++, i++) {
if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) { if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) {
@ -325,10 +325,10 @@ http_get_request(int fd, struct request *req)
} }
static void static void
encode(char src[PATH_MAX], char dest[PATH_MAX]) encode(const char src[PATH_MAX], char dest[PATH_MAX])
{ {
size_t i; size_t i;
char *s; const char *s;
for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) { for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) {
if (iscntrl(*s) || (unsigned char)*s > 127) { if (iscntrl(*s) || (unsigned char)*s > 127) {
@ -396,59 +396,83 @@ squash:
} }
static enum status static enum status
parse_range(char *s, off_t size, off_t *lower, off_t *upper) parse_range(const char *str, off_t size, off_t *lower, off_t *upper)
{ {
char *p, *q; char first[FIELD_MAX], last[FIELD_MAX];
const char *err; const char *p, *q, *r, *err;
/* default to the complete range */ /* default to the complete range */
*lower = 0; *lower = 0;
*upper = size - 1; *upper = size - 1;
/* done if no range-string is given */ /* done if no range-string is given */
if (s == NULL || *s == '\0') { if (str == NULL || *str == '\0') {
return 0; return 0;
} }
/* skip opening statement */ /* skip opening statement */
if (strncmp(s, "bytes=", sizeof("bytes=") - 1)) { if (strncmp(str, "bytes=", sizeof("bytes=") - 1)) {
return S_BAD_REQUEST; return S_BAD_REQUEST;
} }
p = s + (sizeof("bytes=") - 1); p = str + (sizeof("bytes=") - 1);
/* find hyphen and replace with \0 */ /* check string (should only contain numbers and a hyphen) */
if (!(q = strchr(p, '-'))) { for (r = p, q = NULL; *r != '\0'; r++) {
if (*r < '0' || *r > '9') {
if (*r == '-') {
if (q != NULL) {
/* we have already seen a hyphen */
return S_BAD_REQUEST;
} else {
/* place q after the hyphen */
q = r + 1;
}
} else if (*r == ',' && r > p) {
/*
* we refuse to accept range-lists out
* of spite towards this horrible part
* of the spec
*/
return S_RANGE_NOT_SATISFIABLE;
} else {
return S_BAD_REQUEST;
}
}
}
if (q == NULL) {
/* the input string must contain a hyphen */
return S_BAD_REQUEST; return S_BAD_REQUEST;
} }
*(q++) = '\0'; r = q + strlen(q);
/* /*
* byte-range=first\0last... * byte-range=first-last\0
* ^ ^ * ^ ^ ^
* | | * | | |
* p q * p q r
*/ */
/* /* copy 'first' and 'last' to their respective arrays */
* make sure we only have a single range, and not a comma if ((size_t)((q - 1) - p + 1) > sizeof(first) ||
* separated list, which we will refuse to accept out of spite (size_t)(r - q + 1) > sizeof(last)) {
* towards this horrible part of the spec return S_REQUEST_TOO_LARGE;
*/
if (strchr(q, ',')) {
return S_RANGE_NOT_SATISFIABLE;
} }
memcpy(first, p, (q - 1) - p);
first[(q - 1) - p] = '\0';
memcpy(last, q, r - q);
last[r - q] = '\0';
if (p[0] != '\0') { if (first[0] != '\0') {
/* /*
* Range has format "first-last" or "first-", * range has format "first-last" or "first-",
* i.e. return bytes 'first' to 'last' (or the * i.e. return bytes 'first' to 'last' (or the
* last byte if 'last' is not given), * last byte if 'last' is not given),
* inclusively, and byte-numbering beginning at 0 * inclusively, and byte-numbering beginning at 0
*/ */
*lower = strtonum(p, 0, LLONG_MAX, &err); *lower = strtonum(first, 0, LLONG_MAX, &err);
if (!err) { if (!err) {
if (q[0] != '\0') { if (last[0] != '\0') {
*upper = strtonum(q, 0, LLONG_MAX, &err); *upper = strtonum(last, 0, LLONG_MAX, &err);
} else { } else {
*upper = size - 1; *upper = size - 1;
} }
@ -466,6 +490,11 @@ parse_range(char *s, off_t size, off_t *lower, off_t *upper)
/* adjust upper limit to be at most the last byte */ /* adjust upper limit to be at most the last byte */
*upper = MIN(*upper, size - 1); *upper = MIN(*upper, size - 1);
} else { } else {
/* last must not also be empty */
if (last[0] == '\0') {
return S_BAD_REQUEST;
}
/* /*
* Range has format "-num", i.e. return the 'num' * Range has format "-num", i.e. return the 'num'
* last bytes * last bytes
@ -475,7 +504,7 @@ parse_range(char *s, off_t size, off_t *lower, off_t *upper)
* use upper as a temporary storage for 'num', * use upper as a temporary storage for 'num',
* as we know 'upper' is size - 1 * as we know 'upper' is size - 1
*/ */
*upper = strtonum(q, 0, LLONG_MAX, &err); *upper = strtonum(last, 0, LLONG_MAX, &err);
if (err) { if (err) {
return S_BAD_REQUEST; return S_BAD_REQUEST;
} }
@ -492,9 +521,6 @@ parse_range(char *s, off_t size, off_t *lower, off_t *upper)
*upper = size - 1; *upper = size - 1;
} }
/* reset \0 to the hyphen */
*(q - 1) = '-';
return 0; return 0;
} }
@ -502,8 +528,9 @@ parse_range(char *s, off_t size, off_t *lower, off_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, struct request *req) http_send_response(int fd, const struct request *req)
{ {
enum status returnstatus;
struct in6_addr addr; struct in6_addr addr;
struct response res = { 0 }; struct response res = { 0 };
struct stat st; struct stat st;
@ -686,21 +713,22 @@ http_send_response(int fd, struct request *req)
} }
/* range */ /* range */
switch (parse_range(req->field[REQ_RANGE], st.st_size, &lower, &upper)) { if ((returnstatus = parse_range(req->field[REQ_RANGE],
case S_RANGE_NOT_SATISFIABLE: st.st_size, &lower, &upper))) {
res.status = S_RANGE_NOT_SATISFIABLE; if (returnstatus == 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, S_INTERNAL_SERVER_ERROR); return http_send_status(fd,
S_INTERNAL_SERVER_ERROR);
}
return http_send_header(fd, &res);
} else {
return http_send_status(fd, returnstatus);
} }
return http_send_header(fd, &res);
case S_BAD_REQUEST:
return http_send_status(fd, S_BAD_REQUEST);
default:
;
} }
/* mime */ /* mime */

2
http.h
View file

@ -69,6 +69,6 @@ struct response {
enum status http_send_header(int, const struct response *); 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);
int http_get_request(int, struct request *); int http_get_request(int, struct request *);
enum status http_send_response(int, struct request *); enum status http_send_response(int, const struct request *);
#endif /* HTTP_H */ #endif /* HTTP_H */

2
main.c
View file

@ -23,7 +23,7 @@
static char *udsname; static char *udsname;
static void static void
serve(int infd, struct sockaddr_storage *in_sa) serve(int infd, const struct sockaddr_storage *in_sa)
{ {
struct request req; struct request req;
time_t t; time_t t;

8
resp.c
View file

@ -39,7 +39,7 @@ suffix(int t)
} }
static void static void
html_escape(char *src, char *dst, size_t dst_siz) html_escape(const char *src, char *dst, size_t dst_siz)
{ {
const struct { const struct {
char c; char c;
@ -84,7 +84,7 @@ html_escape(char *src, char *dst, size_t dst_siz)
} }
enum status enum status
resp_dir(int fd, char *name, struct request *req) resp_dir(int fd, const char *name, const struct request *req)
{ {
enum status sendstatus; enum status sendstatus;
struct dirent **e; struct dirent **e;
@ -155,8 +155,8 @@ cleanup:
} }
enum status enum status
resp_file(int fd, char *name, struct request *req, struct stat *st, char *mime, resp_file(int fd, const char *name, const struct request *req,
off_t lower, off_t upper) const struct stat *st, const char *mime, off_t lower, off_t upper)
{ {
FILE *fp; FILE *fp;
enum status sendstatus; enum status sendstatus;

6
resp.h
View file

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

3
sock.c
View file

@ -125,7 +125,8 @@ sock_set_timeout(int fd, int sec)
} }
int int
sock_get_inaddr_str(struct sockaddr_storage *in_sa, char *str, size_t len) sock_get_inaddr_str(const struct sockaddr_storage *in_sa, char *str,
size_t len)
{ {
switch (in_sa->ss_family) { switch (in_sa->ss_family) {
case AF_INET: case AF_INET:

2
sock.h
View file

@ -10,6 +10,6 @@ int sock_get_ips(const char *, const char *);
void sock_rem_uds(const char *); void sock_rem_uds(const char *);
int sock_get_uds(const char *, uid_t, gid_t); int sock_get_uds(const char *, uid_t, gid_t);
int sock_set_timeout(int, int); int sock_set_timeout(int, int);
int sock_get_inaddr_str(struct sockaddr_storage *, char *, size_t); int sock_get_inaddr_str(const struct sockaddr_storage *, char *, size_t);
#endif /* SOCK_H */ #endif /* SOCK_H */