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

2
http.h
View file

@ -69,6 +69,6 @@ struct response {
enum status http_send_header(int, const struct response *);
enum status http_send_status(int, enum status);
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 */

2
main.c
View file

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

8
resp.c
View file

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

6
resp.h
View file

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

3
sock.c
View file

@ -125,7 +125,8 @@ sock_set_timeout(int fd, int sec)
}
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) {
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 *);
int sock_get_uds(const char *, uid_t, gid_t);
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 */