Refactor range-parsing into a separate function

The method http_send_response() is already long enough and this
separation of concerns both helps shorten it a bit, improves
readability and reduces the chance of programming errors.

Signed-off-by: Laslo Hunhold <dev@frign.de>
This commit is contained in:
Laslo Hunhold 2020-08-04 16:31:08 +02:00
parent 5a7994bc61
commit 26c593ade1
No known key found for this signature in database
GPG key ID: 69576BD24CFCB980

203
http.c
View file

@ -342,6 +342,109 @@ squash:
return 0; return 0;
} }
static enum status
parse_range(char *s, off_t size, off_t *lower, off_t *upper)
{
char *p, *q;
const char *err;
/* default to the complete range */
*lower = 0;
*upper = size - 1;
/* done if no range-string is given */
if (s == NULL || *s == '\0') {
return 0;
}
/* skip opening statement */
if (strncmp(s, "bytes=", sizeof("bytes=") - 1)) {
return S_BAD_REQUEST;
}
p = s + (sizeof("bytes=") - 1);
/* find hyphen and replace with \0 */
if (!(q = strchr(p, '-'))) {
return S_BAD_REQUEST;
}
*(q++) = '\0';
/*
* byte-range=first\0last...
* ^ ^
* | |
* 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;
}
if (p[0] != '\0') {
/*
* 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);
if (!err) {
if (q[0] != '\0') {
*upper = strtonum(q, 0, LLONG_MAX, &err);
} else {
*upper = size - 1;
}
}
if (err) {
/* one of the strtonum()'s failed */
return S_BAD_REQUEST;
}
/* check ranges */
if (*lower > *upper || *lower >= size) {
return S_RANGE_NOT_SATISFIABLE;
}
/* adjust upper limit to be at most the last byte */
*upper = MIN(*upper, size - 1);
} else {
/*
* Range has format "-num", i.e. return the 'num'
* last bytes
*/
/*
* use upper as a temporary storage for 'num',
* as we know 'upper' is size - 1
*/
*upper = strtonum(q, 0, LLONG_MAX, &err);
if (err) {
return S_BAD_REQUEST;
}
/* determine lower */
if (*upper > size) {
/* more bytes requested than we have */
*lower = 0;
} else {
*lower = size - *upper;
}
/* set upper to the correct value */
*upper = size - 1;
}
/* reset \0 to the hyphen */
*(q - 1) = '-';
return 0;
}
#undef RELPATH #undef RELPATH
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
@ -355,8 +458,8 @@ http_send_response(int fd, struct request *r)
off_t lower, upper; off_t lower, upper;
int hasport, ipv6host; int hasport, ipv6host;
static char realtarget[PATH_MAX], tmptarget[PATH_MAX], t[TIMESTAMP_LEN]; static char realtarget[PATH_MAX], tmptarget[PATH_MAX], t[TIMESTAMP_LEN];
char *p, *q, *mime; char *p, *mime;
const char *vhostmatch, *targethost, *err; const char *vhostmatch, *targethost;
/* make a working copy of the target */ /* make a working copy of the target */
memcpy(realtarget, r->target, sizeof(realtarget)); memcpy(realtarget, r->target, sizeof(realtarget));
@ -546,96 +649,8 @@ http_send_response(int fd, struct request *r)
} }
/* range */ /* range */
lower = 0; switch (parse_range(r->field[REQ_RANGE], st.st_size, &lower, &upper)) {
upper = st.st_size - 1; case S_RANGE_NOT_SATISFIABLE:
if (r->field[REQ_RANGE][0]) {
/* parse field */
p = r->field[REQ_RANGE];
err = NULL;
if (strncmp(p, "bytes=", sizeof("bytes=") - 1)) {
return http_send_status(fd, S_BAD_REQUEST);
}
p += sizeof("bytes=") - 1;
if (!(q = strchr(p, '-'))) {
return http_send_status(fd, S_BAD_REQUEST);
}
*(q++) = '\0';
/*
* byte-range=first\0last...
* ^ ^
* | |
* 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, ',')) {
goto not_satisfiable;
}
if (p[0] != '\0') {
/*
* 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);
if (!err) {
if (q[0] != '\0') {
upper = strtonum(q, 0, LLONG_MAX,
&err);
} else {
upper = st.st_size - 1;
}
}
if (err) {
/* one of the strtonum()'s failed */
return http_send_status(fd, S_BAD_REQUEST);
}
/* check ranges */
if (lower > upper || lower >= st.st_size) {
goto not_satisfiable;
}
/* adjust upper limit to be at most the last byte */
upper = MIN(upper, st.st_size - 1);
} else {
/*
* Range has format "-num", i.e. return the 'num'
* last bytes
*/
/*
* use upper as a temporary storage for 'num',
* as we know 'upper' is st.st_size - 1
*/
upper = strtonum(q, 0, LLONG_MAX, &err);
if (err) {
return http_send_status(fd, S_BAD_REQUEST);
}
/* determine lower */
if (upper > st.st_size) {
/* more bytes requested than we have */
lower = 0;
} else {
lower = st.st_size - upper;
}
/* set upper to the correct value */
upper = st.st_size - 1;
}
goto satisfiable;
not_satisfiable:
if (dprintf(fd, if (dprintf(fd,
"HTTP/1.1 %d %s\r\n" "HTTP/1.1 %d %s\r\n"
"Date: %s\r\n" "Date: %s\r\n"
@ -649,7 +664,9 @@ not_satisfiable:
return S_REQUEST_TIMEOUT; return S_REQUEST_TIMEOUT;
} }
return S_RANGE_NOT_SATISFIABLE; return S_RANGE_NOT_SATISFIABLE;
satisfiable: case S_BAD_REQUEST:
return http_send_status(fd, S_BAD_REQUEST);
default:
; ;
} }