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:
parent
90d5179ea0
commit
d105c28aad
7 changed files with 87 additions and 58 deletions
112
http.c
112
http.c
|
@ -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;
|
return S_BAD_REQUEST;
|
||||||
|
} else {
|
||||||
|
/* place q after the hyphen */
|
||||||
|
q = r + 1;
|
||||||
}
|
}
|
||||||
*(q++) = '\0';
|
} else if (*r == ',' && r > p) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* byte-range=first\0last...
|
* we refuse to accept range-lists out
|
||||||
* ^ ^
|
* of spite towards this horrible part
|
||||||
* | |
|
* of the spec
|
||||||
* p q
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
return S_RANGE_NOT_SATISFIABLE;
|
||||||
|
} else {
|
||||||
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (q == NULL) {
|
||||||
|
/* the input string must contain a hyphen */
|
||||||
|
return S_BAD_REQUEST;
|
||||||
|
}
|
||||||
|
r = q + strlen(q);
|
||||||
|
|
||||||
if (p[0] != '\0') {
|
|
||||||
/*
|
/*
|
||||||
* Range has format "first-last" or "first-",
|
* byte-range=first-last\0
|
||||||
|
* ^ ^ ^
|
||||||
|
* | | |
|
||||||
|
* p q r
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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 (first[0] != '\0') {
|
||||||
|
/*
|
||||||
|
* 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))) {
|
||||||
|
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, S_INTERNAL_SERVER_ERROR);
|
return http_send_status(fd,
|
||||||
|
S_INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return http_send_header(fd, &res);
|
return http_send_header(fd, &res);
|
||||||
case S_BAD_REQUEST:
|
} else {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return http_send_status(fd, returnstatus);
|
||||||
default:
|
}
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mime */
|
/* mime */
|
||||||
|
|
2
http.h
2
http.h
|
@ -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
2
main.c
|
@ -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
8
resp.c
|
@ -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
6
resp.h
|
@ -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
3
sock.c
|
@ -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
2
sock.h
|
@ -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 */
|
||||||
|
|
Loading…
Reference in a new issue