2018-02-04 20:27:33 +00:00
|
|
|
/* See LICENSE file for copyright and license details. */
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
2018-02-12 19:29:51 +00:00
|
|
|
#include <netinet/in.h>
|
2018-02-04 20:27:33 +00:00
|
|
|
#include <regex.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2018-03-01 14:18:20 +00:00
|
|
|
#include <strings.h>
|
2018-02-12 19:29:51 +00:00
|
|
|
#include <sys/socket.h>
|
2018-02-04 20:27:33 +00:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
2018-03-01 14:18:20 +00:00
|
|
|
#include <time.h>
|
2018-02-04 20:27:33 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
2020-08-28 22:42:54 +00:00
|
|
|
#include "data.h"
|
2018-02-04 20:27:33 +00:00
|
|
|
#include "http.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
const char *req_field_str[] = {
|
2020-08-05 13:46:03 +00:00
|
|
|
[REQ_HOST] = "Host",
|
|
|
|
[REQ_RANGE] = "Range",
|
|
|
|
[REQ_IF_MODIFIED_SINCE] = "If-Modified-Since",
|
2018-02-04 20:27:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const char *req_method_str[] = {
|
|
|
|
[M_GET] = "GET",
|
|
|
|
[M_HEAD] = "HEAD",
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *status_str[] = {
|
|
|
|
[S_OK] = "OK",
|
|
|
|
[S_PARTIAL_CONTENT] = "Partial Content",
|
|
|
|
[S_MOVED_PERMANENTLY] = "Moved Permanently",
|
|
|
|
[S_NOT_MODIFIED] = "Not Modified",
|
|
|
|
[S_BAD_REQUEST] = "Bad Request",
|
|
|
|
[S_FORBIDDEN] = "Forbidden",
|
|
|
|
[S_NOT_FOUND] = "Not Found",
|
|
|
|
[S_METHOD_NOT_ALLOWED] = "Method Not Allowed",
|
|
|
|
[S_REQUEST_TIMEOUT] = "Request Time-out",
|
|
|
|
[S_RANGE_NOT_SATISFIABLE] = "Range Not Satisfiable",
|
|
|
|
[S_REQUEST_TOO_LARGE] = "Request Header Fields Too Large",
|
|
|
|
[S_INTERNAL_SERVER_ERROR] = "Internal Server Error",
|
|
|
|
[S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
|
|
|
|
};
|
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
const char *res_field_str[] = {
|
|
|
|
[RES_ACCEPT_RANGES] = "Accept-Ranges",
|
|
|
|
[RES_ALLOW] = "Allow",
|
|
|
|
[RES_LOCATION] = "Location",
|
|
|
|
[RES_LAST_MODIFIED] = "Last-Modified",
|
|
|
|
[RES_CONTENT_LENGTH] = "Content-Length",
|
|
|
|
[RES_CONTENT_RANGE] = "Content-Range",
|
|
|
|
[RES_CONTENT_TYPE] = "Content-Type",
|
|
|
|
};
|
|
|
|
|
2020-08-28 22:42:54 +00:00
|
|
|
enum status (* const body_fct[])(int, const struct response *) = {
|
|
|
|
[RESTYPE_ERROR] = data_send_error,
|
|
|
|
[RESTYPE_FILE] = data_send_file,
|
|
|
|
[RESTYPE_DIRLISTING] = data_send_dirlisting,
|
|
|
|
};
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
enum status
|
2020-08-05 11:41:44 +00:00
|
|
|
http_send_header(int fd, const struct response *res)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
2020-08-28 22:42:54 +00:00
|
|
|
char t[FIELD_MAX];
|
2020-08-05 11:41:44 +00:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (timestamp(t, sizeof(t), time(NULL))) {
|
|
|
|
return S_INTERNAL_SERVER_ERROR;
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
2020-08-05 11:41:44 +00:00
|
|
|
"Connection: close\r\n",
|
|
|
|
res->status, status_str[res->status], t) < 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_RES_FIELDS; i++) {
|
|
|
|
if (res->field[i][0] != '\0') {
|
|
|
|
if (dprintf(fd, "%s: %s\r\n", res_field_str[i],
|
|
|
|
res->field[i]) < 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dprintf(fd, "\r\n") < 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
2020-08-28 21:16:47 +00:00
|
|
|
return 0;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2020-08-05 16:28:21 +00:00
|
|
|
decode(const char src[PATH_MAX], char dest[PATH_MAX])
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
uint8_t n;
|
2020-08-05 16:28:21 +00:00
|
|
|
const char *s;
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
for (s = src, i = 0; *s; s++, i++) {
|
2019-01-10 21:02:23 +00:00
|
|
|
if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) {
|
2018-02-04 20:27:33 +00:00
|
|
|
dest[i] = n;
|
|
|
|
s += 2;
|
|
|
|
} else {
|
|
|
|
dest[i] = *s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dest[i] = '\0';
|
|
|
|
}
|
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
enum status
|
|
|
|
http_recv_header(int fd, char *h, size_t hsiz, size_t *off)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
2020-08-22 09:05:20 +00:00
|
|
|
ssize_t r;
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
if (h == NULL || off == NULL || *off > hsiz) {
|
|
|
|
return S_INTERNAL_SERVER_ERROR;
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
while (1) {
|
|
|
|
if ((r = read(fd, h + *off, hsiz - *off)) <= 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-22 09:05:20 +00:00
|
|
|
*off += r;
|
|
|
|
|
|
|
|
/* check if we are done (header terminated) */
|
|
|
|
if (*off >= 4 && !memcmp(h + *off - 4, "\r\n\r\n", 4)) {
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-08-22 09:05:20 +00:00
|
|
|
|
|
|
|
/* buffer is full or read over, but header is not terminated */
|
|
|
|
if (r == 0 || *off == hsiz) {
|
|
|
|
return S_REQUEST_TOO_LARGE;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
/* header is complete, remove last \r\n and null-terminate */
|
|
|
|
h[*off - 2] = '\0';
|
|
|
|
|
|
|
|
/* set *off to 0 to indicate we are finished */
|
|
|
|
*off = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
enum status
|
|
|
|
http_parse_header(const char *h, struct request *req)
|
|
|
|
{
|
|
|
|
struct in6_addr addr;
|
|
|
|
size_t i, mlen;
|
|
|
|
const char *p, *q;
|
|
|
|
char *m, *n;
|
|
|
|
|
|
|
|
/* empty all fields */
|
|
|
|
memset(req, 0, sizeof(*req));
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* parse request line
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* METHOD */
|
|
|
|
for (i = 0; i < NUM_REQ_METHODS; i++) {
|
|
|
|
mlen = strlen(req_method_str[i]);
|
|
|
|
if (!strncmp(req_method_str[i], h, mlen)) {
|
2020-08-05 13:43:29 +00:00
|
|
|
req->method = i;
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == NUM_REQ_METHODS) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_METHOD_NOT_ALLOWED;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* a single space must follow the method */
|
|
|
|
if (h[mlen] != ' ') {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* basis for next step */
|
|
|
|
p = h + mlen + 1;
|
|
|
|
|
|
|
|
/* TARGET */
|
|
|
|
if (!(q = strchr(p, ' '))) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
if (q - p + 1 > PATH_MAX) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_REQUEST_TOO_LARGE;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-22 21:37:08 +00:00
|
|
|
memcpy(req->uri, p, q - p);
|
|
|
|
req->uri[q - p] = '\0';
|
|
|
|
decode(req->uri, req->uri);
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
/* basis for next step */
|
|
|
|
p = q + 1;
|
|
|
|
|
|
|
|
/* HTTP-VERSION */
|
|
|
|
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
p += sizeof("HTTP/") - 1;
|
|
|
|
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
|
|
|
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_VERSION_NOT_SUPPORTED;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
p += sizeof("1.*") - 1;
|
|
|
|
|
|
|
|
/* check terminator */
|
|
|
|
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* basis for next step */
|
|
|
|
p += sizeof("\r\n") - 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* parse request-fields
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* match field type */
|
|
|
|
for (; *p != '\0';) {
|
|
|
|
for (i = 0; i < NUM_REQ_FIELDS; i++) {
|
|
|
|
if (!strncasecmp(p, req_field_str[i],
|
|
|
|
strlen(req_field_str[i]))) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == NUM_REQ_FIELDS) {
|
|
|
|
/* unmatched field, skip this line */
|
|
|
|
if (!(q = strstr(p, "\r\n"))) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
p = q + (sizeof("\r\n") - 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
p += strlen(req_field_str[i]);
|
|
|
|
|
|
|
|
/* a single colon must follow the field name */
|
|
|
|
if (*p != ':') {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* skip whitespace */
|
|
|
|
for (++p; *p == ' ' || *p == '\t'; p++)
|
|
|
|
;
|
|
|
|
|
|
|
|
/* extract field content */
|
|
|
|
if (!(q = strstr(p, "\r\n"))) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_BAD_REQUEST;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
if (q - p + 1 > FIELD_MAX) {
|
2020-08-22 09:05:20 +00:00
|
|
|
return S_REQUEST_TOO_LARGE;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-22 09:05:20 +00:00
|
|
|
memcpy(req->field[i], p, q - p);
|
|
|
|
req->field[i][q - p] = '\0';
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
/* go to next line */
|
|
|
|
p = q + (sizeof("\r\n") - 1);
|
|
|
|
}
|
|
|
|
|
2018-04-02 22:55:52 +00:00
|
|
|
/*
|
|
|
|
* clean up host
|
|
|
|
*/
|
|
|
|
|
2020-08-22 09:05:20 +00:00
|
|
|
m = strrchr(req->field[REQ_HOST], ':');
|
|
|
|
n = strrchr(req->field[REQ_HOST], ']');
|
2018-04-02 22:55:52 +00:00
|
|
|
|
|
|
|
/* strip port suffix but don't interfere with IPv6 bracket notation
|
|
|
|
* as per RFC 2732 */
|
2020-08-22 09:05:20 +00:00
|
|
|
if (m && (!n || m > n)) {
|
2018-04-02 22:55:52 +00:00
|
|
|
/* port suffix must not be empty */
|
2020-08-22 09:05:20 +00:00
|
|
|
if (*(m + 1) == '\0') {
|
|
|
|
return S_BAD_REQUEST;
|
2018-04-02 22:55:52 +00:00
|
|
|
}
|
2020-08-22 09:05:20 +00:00
|
|
|
*m = '\0';
|
2018-04-02 22:55:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* strip the brackets from the IPv6 notation and validate the address */
|
2020-08-22 09:05:20 +00:00
|
|
|
if (n) {
|
2018-04-02 22:55:52 +00:00
|
|
|
/* brackets must be on the outside */
|
2020-08-22 09:05:20 +00:00
|
|
|
if (req->field[REQ_HOST][0] != '[' || *(n + 1) != '\0') {
|
|
|
|
return S_BAD_REQUEST;
|
2018-04-02 22:55:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* remove the right bracket */
|
2020-08-22 09:05:20 +00:00
|
|
|
*n = '\0';
|
|
|
|
m = req->field[REQ_HOST] + 1;
|
2018-04-02 22:55:52 +00:00
|
|
|
|
|
|
|
/* validate the contained IPv6 address */
|
2020-08-22 09:05:20 +00:00
|
|
|
if (inet_pton(AF_INET6, m, &addr) != 1) {
|
|
|
|
return S_BAD_REQUEST;
|
2018-04-02 22:55:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* copy it into the host field */
|
2020-08-22 09:05:20 +00:00
|
|
|
memmove(req->field[REQ_HOST], m, n - m + 1);
|
2018-04-02 22:55:52 +00:00
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2020-08-05 16:28:21 +00:00
|
|
|
encode(const char src[PATH_MAX], char dest[PATH_MAX])
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
|
|
|
size_t i;
|
2020-08-05 16:28:21 +00:00
|
|
|
const char *s;
|
2018-02-04 20:27:33 +00:00
|
|
|
|
|
|
|
for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) {
|
|
|
|
if (iscntrl(*s) || (unsigned char)*s > 127) {
|
|
|
|
i += snprintf(dest + i, PATH_MAX - i, "%%%02X",
|
|
|
|
(unsigned char)*s);
|
|
|
|
} else {
|
|
|
|
dest[i] = *s;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dest[i] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
normabspath(char *path)
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
int last = 0;
|
|
|
|
char *p, *q;
|
|
|
|
|
|
|
|
/* require and skip first slash */
|
|
|
|
if (path[0] != '/') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
p = path + 1;
|
|
|
|
|
|
|
|
/* get length of path */
|
|
|
|
len = strlen(p);
|
|
|
|
|
|
|
|
for (; !last; ) {
|
|
|
|
/* bound path component within (p,q) */
|
|
|
|
if (!(q = strchr(p, '/'))) {
|
|
|
|
q = strchr(p, '\0');
|
|
|
|
last = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p == q || (q - p == 1 && p[0] == '.')) {
|
|
|
|
/* "/" or "./" */
|
|
|
|
goto squash;
|
|
|
|
} else if (q - p == 2 && p[0] == '.' && p[1] == '.') {
|
|
|
|
/* "../" */
|
|
|
|
if (p != path + 1) {
|
|
|
|
/* place p right after the previous / */
|
|
|
|
for (p -= 2; p > path && *p != '/'; p--);
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
goto squash;
|
|
|
|
} else {
|
|
|
|
/* move on */
|
|
|
|
p = q + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
squash:
|
|
|
|
/* squash (p,q) into void */
|
|
|
|
if (last) {
|
|
|
|
*p = '\0';
|
|
|
|
len = p - path;
|
|
|
|
} else {
|
|
|
|
memmove(p, q + 1, len - ((q + 1) - path) + 2);
|
|
|
|
len -= (q + 1) - p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-08-04 14:31:08 +00:00
|
|
|
static enum status
|
2020-08-05 16:59:55 +00:00
|
|
|
parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
|
2020-08-04 14:31:08 +00:00
|
|
|
{
|
2020-08-05 16:28:21 +00:00
|
|
|
char first[FIELD_MAX], last[FIELD_MAX];
|
|
|
|
const char *p, *q, *r, *err;
|
2020-08-04 14:31:08 +00:00
|
|
|
|
|
|
|
/* default to the complete range */
|
|
|
|
*lower = 0;
|
|
|
|
*upper = size - 1;
|
|
|
|
|
|
|
|
/* done if no range-string is given */
|
2020-08-05 16:28:21 +00:00
|
|
|
if (str == NULL || *str == '\0') {
|
2020-08-04 14:31:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* skip opening statement */
|
2020-08-05 16:28:21 +00:00
|
|
|
if (strncmp(str, "bytes=", sizeof("bytes=") - 1)) {
|
2020-08-04 14:31:08 +00:00
|
|
|
return S_BAD_REQUEST;
|
|
|
|
}
|
2020-08-05 16:28:21 +00:00
|
|
|
p = str + (sizeof("bytes=") - 1);
|
2020-08-04 14:31:08 +00:00
|
|
|
|
2020-08-05 16:28:21 +00:00
|
|
|
/* 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 */
|
2020-08-04 14:31:08 +00:00
|
|
|
return S_BAD_REQUEST;
|
|
|
|
}
|
2020-08-05 16:28:21 +00:00
|
|
|
r = q + strlen(q);
|
2020-08-04 14:31:08 +00:00
|
|
|
|
|
|
|
/*
|
2020-08-05 16:28:21 +00:00
|
|
|
* byte-range=first-last\0
|
|
|
|
* ^ ^ ^
|
|
|
|
* | | |
|
|
|
|
* p q r
|
2020-08-04 14:31:08 +00:00
|
|
|
*/
|
|
|
|
|
2020-08-05 16:28:21 +00:00
|
|
|
/* 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;
|
2020-08-04 14:31:08 +00:00
|
|
|
}
|
2020-08-05 16:28:21 +00:00
|
|
|
memcpy(first, p, (q - 1) - p);
|
|
|
|
first[(q - 1) - p] = '\0';
|
|
|
|
memcpy(last, q, r - q);
|
|
|
|
last[r - q] = '\0';
|
2020-08-04 14:31:08 +00:00
|
|
|
|
2020-08-05 16:28:21 +00:00
|
|
|
if (first[0] != '\0') {
|
2020-08-04 14:31:08 +00:00
|
|
|
/*
|
2020-08-05 16:28:21 +00:00
|
|
|
* range has format "first-last" or "first-",
|
2020-08-04 14:31:08 +00:00
|
|
|
* i.e. return bytes 'first' to 'last' (or the
|
|
|
|
* last byte if 'last' is not given),
|
|
|
|
* inclusively, and byte-numbering beginning at 0
|
|
|
|
*/
|
2020-08-05 16:59:55 +00:00
|
|
|
*lower = strtonum(first, 0, SIZE_MAX, &err);
|
2020-08-04 14:31:08 +00:00
|
|
|
if (!err) {
|
2020-08-05 16:28:21 +00:00
|
|
|
if (last[0] != '\0') {
|
2020-08-05 16:59:55 +00:00
|
|
|
*upper = strtonum(last, 0, SIZE_MAX, &err);
|
2020-08-04 14:31:08 +00:00
|
|
|
} 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 {
|
2020-08-05 16:28:21 +00:00
|
|
|
/* last must not also be empty */
|
|
|
|
if (last[0] == '\0') {
|
|
|
|
return S_BAD_REQUEST;
|
|
|
|
}
|
|
|
|
|
2020-08-04 14:31:08 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2020-08-05 16:59:55 +00:00
|
|
|
*upper = strtonum(last, 0, SIZE_MAX, &err);
|
2020-08-04 14:31:08 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
#undef RELPATH
|
|
|
|
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
|
|
|
|
2020-08-28 20:48:32 +00:00
|
|
|
void
|
2020-08-22 21:20:00 +00:00
|
|
|
http_prepare_response(const struct request *req, struct response *res,
|
2020-08-23 09:02:38 +00:00
|
|
|
const struct server *srv)
|
2018-02-04 20:27:33 +00:00
|
|
|
{
|
2020-08-28 20:48:32 +00:00
|
|
|
enum status s;
|
2020-08-05 11:41:44 +00:00
|
|
|
struct in6_addr addr;
|
2018-02-04 20:27:33 +00:00
|
|
|
struct stat st;
|
2020-07-23 14:54:21 +00:00
|
|
|
struct tm tm = { 0 };
|
2020-08-22 21:20:00 +00:00
|
|
|
struct vhost *vhost;
|
2018-02-04 20:27:33 +00:00
|
|
|
size_t len, i;
|
|
|
|
int hasport, ipv6host;
|
2020-08-22 21:20:00 +00:00
|
|
|
static char realuri[PATH_MAX], tmpuri[PATH_MAX];
|
2020-08-04 14:31:08 +00:00
|
|
|
char *p, *mime;
|
2020-08-22 21:20:00 +00:00
|
|
|
const char *targethost;
|
|
|
|
|
|
|
|
/* empty all response fields */
|
|
|
|
memset(res, 0, sizeof(*res));
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* make a working copy of the URI and normalize it */
|
2020-08-22 21:37:08 +00:00
|
|
|
memcpy(realuri, req->uri, sizeof(realuri));
|
2020-08-22 21:20:00 +00:00
|
|
|
if (normabspath(realuri)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_BAD_REQUEST;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
2018-02-27 10:36:24 +00:00
|
|
|
|
2018-02-04 20:27:33 +00:00
|
|
|
/* match vhost */
|
2020-08-22 21:20:00 +00:00
|
|
|
vhost = NULL;
|
2020-08-23 09:02:38 +00:00
|
|
|
if (srv->vhost) {
|
|
|
|
for (i = 0; i < srv->vhost_len; i++) {
|
|
|
|
if (!regexec(&(srv->vhost[i].re), req->field[REQ_HOST],
|
2020-08-22 21:20:00 +00:00
|
|
|
0, NULL, 0)) {
|
|
|
|
/* we have a matching vhost */
|
2020-08-23 09:02:38 +00:00
|
|
|
vhost = &(srv->vhost[i]);
|
2018-02-04 20:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-08-23 09:02:38 +00:00
|
|
|
if (i == srv->vhost_len) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_NOT_FOUND;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2018-02-27 10:36:24 +00:00
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* if we have a vhost prefix, prepend it to the URI */
|
|
|
|
if (vhost->prefix &&
|
|
|
|
prepend(realuri, LEN(realuri), vhost->prefix)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-02-27 10:36:24 +00:00
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 21:37:08 +00:00
|
|
|
/* apply URI prefix mapping */
|
2020-08-23 09:02:38 +00:00
|
|
|
for (i = 0; i < srv->map_len; i++) {
|
|
|
|
len = strlen(srv->map[i].from);
|
|
|
|
if (!strncmp(realuri, srv->map[i].from, len)) {
|
2019-02-23 23:40:46 +00:00
|
|
|
/* match canonical host if vhosts are enabled and
|
|
|
|
* the mapping specifies a canonical host */
|
2020-08-23 09:02:38 +00:00
|
|
|
if (srv->vhost && srv->map[i].chost &&
|
|
|
|
strcmp(srv->map[i].chost, vhost->chost)) {
|
2018-02-27 11:43:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-22 21:37:08 +00:00
|
|
|
/* swap out URI prefix */
|
2020-08-22 21:20:00 +00:00
|
|
|
memmove(realuri, realuri + len, strlen(realuri) + 1);
|
2020-08-23 09:02:38 +00:00
|
|
|
if (prepend(realuri, LEN(realuri), srv->map[i].to)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-02-27 11:43:05 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* normalize URI again, in case we introduced dirt */
|
|
|
|
if (normabspath(realuri)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_BAD_REQUEST;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* stat the relative path derived from the URI */
|
|
|
|
if (stat(RELPATH(realuri), &st) < 0) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
2020-08-22 21:20:00 +00:00
|
|
|
/* append '/' to URI if not present */
|
|
|
|
len = strlen(realuri);
|
|
|
|
if (len + 1 + 1 > PATH_MAX) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-22 21:20:00 +00:00
|
|
|
if (len > 0 && realuri[len - 1] != '/') {
|
|
|
|
realuri[len] = '/';
|
|
|
|
realuri[len + 1] = '\0';
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement RFC 8615 (Well-Known URIs) and refine access errors
We generally rejected any URI that had a path component beginning
with a '.', i.e. a hidden file. RFC 8615 specifies the well-known URI,
which is used, for instance, with the "http-01" challenge type in
acme-client(1) and will probably see more usage in the future.
To support it, we move the hidden target check after the stat(), so we
don't have to worry about canonicalization of dir-URIs (i.e. missing
trailing '/'). This changes the behaviour a bit, as now quark won't
only send out a 403 whenever a hidden target is requested, but only
if it actually exists, and a 404 otherwise.
Given the earlier call to normabspath() ensures that our path begins
with a '/', we don't need the first check "realtarget[0] == '.'"
anymore, so it can be removed.
Thanks to Robert Russell <robertrussell.72001@gmail.com> for reporting
the lack of support of the RFC 8615 in quark.
Signed-off-by: Laslo Hunhold <dev@frign.de>
2020-08-17 08:33:55 +00:00
|
|
|
/*
|
2020-08-22 21:37:08 +00:00
|
|
|
* reject hidden targets, except if it is a well-known URI
|
Implement RFC 8615 (Well-Known URIs) and refine access errors
We generally rejected any URI that had a path component beginning
with a '.', i.e. a hidden file. RFC 8615 specifies the well-known URI,
which is used, for instance, with the "http-01" challenge type in
acme-client(1) and will probably see more usage in the future.
To support it, we move the hidden target check after the stat(), so we
don't have to worry about canonicalization of dir-URIs (i.e. missing
trailing '/'). This changes the behaviour a bit, as now quark won't
only send out a 403 whenever a hidden target is requested, but only
if it actually exists, and a 404 otherwise.
Given the earlier call to normabspath() ensures that our path begins
with a '/', we don't need the first check "realtarget[0] == '.'"
anymore, so it can be removed.
Thanks to Robert Russell <robertrussell.72001@gmail.com> for reporting
the lack of support of the RFC 8615 in quark.
Signed-off-by: Laslo Hunhold <dev@frign.de>
2020-08-17 08:33:55 +00:00
|
|
|
* according to RFC 8615
|
|
|
|
*/
|
2020-08-22 21:20:00 +00:00
|
|
|
if (strstr(realuri, "/.") && strncmp(realuri,
|
Implement RFC 8615 (Well-Known URIs) and refine access errors
We generally rejected any URI that had a path component beginning
with a '.', i.e. a hidden file. RFC 8615 specifies the well-known URI,
which is used, for instance, with the "http-01" challenge type in
acme-client(1) and will probably see more usage in the future.
To support it, we move the hidden target check after the stat(), so we
don't have to worry about canonicalization of dir-URIs (i.e. missing
trailing '/'). This changes the behaviour a bit, as now quark won't
only send out a 403 whenever a hidden target is requested, but only
if it actually exists, and a 404 otherwise.
Given the earlier call to normabspath() ensures that our path begins
with a '/', we don't need the first check "realtarget[0] == '.'"
anymore, so it can be removed.
Thanks to Robert Russell <robertrussell.72001@gmail.com> for reporting
the lack of support of the RFC 8615 in quark.
Signed-off-by: Laslo Hunhold <dev@frign.de>
2020-08-17 08:33:55 +00:00
|
|
|
"/.well-known/", sizeof("/.well-known/") - 1)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_FORBIDDEN;
|
|
|
|
goto err;
|
Implement RFC 8615 (Well-Known URIs) and refine access errors
We generally rejected any URI that had a path component beginning
with a '.', i.e. a hidden file. RFC 8615 specifies the well-known URI,
which is used, for instance, with the "http-01" challenge type in
acme-client(1) and will probably see more usage in the future.
To support it, we move the hidden target check after the stat(), so we
don't have to worry about canonicalization of dir-URIs (i.e. missing
trailing '/'). This changes the behaviour a bit, as now quark won't
only send out a 403 whenever a hidden target is requested, but only
if it actually exists, and a 404 otherwise.
Given the earlier call to normabspath() ensures that our path begins
with a '/', we don't need the first check "realtarget[0] == '.'"
anymore, so it can be removed.
Thanks to Robert Russell <robertrussell.72001@gmail.com> for reporting
the lack of support of the RFC 8615 in quark.
Signed-off-by: Laslo Hunhold <dev@frign.de>
2020-08-17 08:33:55 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/*
|
|
|
|
* redirect if the original URI and the "real" URI differ or if
|
|
|
|
* the requested host is non-canonical
|
|
|
|
*/
|
2020-08-23 09:02:38 +00:00
|
|
|
if (strcmp(req->uri, realuri) || (srv->vhost && vhost &&
|
2020-08-22 21:20:00 +00:00
|
|
|
strcmp(req->field[REQ_HOST], vhost->chost))) {
|
|
|
|
res->status = S_MOVED_PERMANENTLY;
|
2020-08-05 11:41:44 +00:00
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* encode realuri */
|
|
|
|
encode(realuri, tmpuri);
|
2018-02-04 20:27:33 +00:00
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
/* determine target location */
|
2020-08-23 09:02:38 +00:00
|
|
|
if (srv->vhost) {
|
2018-07-02 16:43:06 +00:00
|
|
|
/* absolute redirection URL */
|
2020-08-22 21:20:00 +00:00
|
|
|
targethost = req->field[REQ_HOST][0] ? vhost->chost ?
|
2020-08-23 09:02:38 +00:00
|
|
|
vhost->chost : req->field[REQ_HOST] :
|
|
|
|
srv->host ? srv->host : "localhost";
|
2018-07-02 16:43:06 +00:00
|
|
|
|
|
|
|
/* do we need to add a port to the Location? */
|
2020-08-23 09:02:38 +00:00
|
|
|
hasport = srv->port && strcmp(srv->port, "80");
|
2018-07-02 16:43:06 +00:00
|
|
|
|
|
|
|
/* RFC 2732 specifies to use brackets for IPv6-addresses
|
|
|
|
* in URLs, so we need to check if our host is one and
|
|
|
|
* honor that later when we fill the "Location"-field */
|
|
|
|
if ((ipv6host = inet_pton(AF_INET6, targethost,
|
2020-08-05 11:41:44 +00:00
|
|
|
&addr)) < 0) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2018-07-02 16:43:06 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 11:41:44 +00:00
|
|
|
/* write location to response struct */
|
2020-08-22 21:20:00 +00:00
|
|
|
if (esnprintf(res->field[RES_LOCATION],
|
|
|
|
sizeof(res->field[RES_LOCATION]),
|
2020-08-05 11:41:44 +00:00
|
|
|
"//%s%s%s%s%s%s",
|
|
|
|
ipv6host ? "[" : "",
|
|
|
|
targethost,
|
|
|
|
ipv6host ? "]" : "", hasport ? ":" : "",
|
2020-08-23 09:02:38 +00:00
|
|
|
hasport ? srv->port : "", tmpuri)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-07-02 16:43:06 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-08-22 21:20:00 +00:00
|
|
|
/* write relative redirection URI to response struct */
|
|
|
|
if (esnprintf(res->field[RES_LOCATION],
|
|
|
|
sizeof(res->field[RES_LOCATION]),
|
|
|
|
"%s", tmpuri)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-07-02 16:43:06 +00:00
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 20:48:32 +00:00
|
|
|
return;
|
2020-08-22 21:20:00 +00:00
|
|
|
} 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
|
|
|
|
*/
|
2020-08-22 21:37:08 +00:00
|
|
|
if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
if (esnprintf(res->path, sizeof(res->path), "%s%s",
|
2020-08-22 21:37:08 +00:00
|
|
|
vhost ? vhost->dir : "", RELPATH(req->uri))) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
2020-08-22 21:20:00 +00:00
|
|
|
/*
|
|
|
|
* check if the directory index exists by appending it to
|
|
|
|
* the URI
|
|
|
|
*/
|
|
|
|
if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
|
2020-08-23 09:02:38 +00:00
|
|
|
req->uri, srv->docindex)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_REQUEST_TOO_LARGE;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* stat the docindex, which must be a regular file */
|
2020-08-22 21:20:00 +00:00
|
|
|
if (stat(RELPATH(tmpuri), &st) < 0 || !S_ISREG(st.st_mode)) {
|
2020-08-23 09:02:38 +00:00
|
|
|
if (srv->listdirs) {
|
2020-08-22 21:20:00 +00:00
|
|
|
/* serve directory listing */
|
|
|
|
res->type = RESTYPE_DIRLISTING;
|
|
|
|
res->status = (access(res->path, R_OK)) ?
|
|
|
|
S_FORBIDDEN : S_OK;
|
|
|
|
|
|
|
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
|
|
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
|
|
|
"%s", "text/html; charset=utf-8")) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 20:48:32 +00:00
|
|
|
return;
|
2018-02-04 20:27:33 +00:00
|
|
|
} else {
|
|
|
|
/* reject */
|
2020-08-28 20:48:32 +00:00
|
|
|
s = (!S_ISREG(st.st_mode) || errno == EACCES) ?
|
|
|
|
S_FORBIDDEN : S_NOT_FOUND;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* modified since */
|
2020-08-05 13:46:03 +00:00
|
|
|
if (req->field[REQ_IF_MODIFIED_SINCE][0]) {
|
2018-02-04 20:27:33 +00:00
|
|
|
/* parse field */
|
2020-08-05 13:46:03 +00:00
|
|
|
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
|
|
|
|
"%a, %d %b %Y %T GMT", &tm)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_BAD_REQUEST;
|
|
|
|
goto err;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* compare with last modification date of the file */
|
2020-07-23 14:48:34 +00:00
|
|
|
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
|
2020-08-22 21:20:00 +00:00
|
|
|
res->status = S_NOT_MODIFIED;
|
2020-08-28 20:48:32 +00:00
|
|
|
return;
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* range */
|
2020-08-28 20:48:32 +00:00
|
|
|
if ((s = parse_range(req->field[REQ_RANGE], st.st_size,
|
|
|
|
&(res->file.lower), &(res->file.upper)))) {
|
|
|
|
if (s == S_RANGE_NOT_SATISFIABLE) {
|
2020-08-22 21:20:00 +00:00
|
|
|
res->status = S_RANGE_NOT_SATISFIABLE;
|
2020-08-05 16:28:21 +00:00
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
if (esnprintf(res->field[RES_CONTENT_RANGE],
|
|
|
|
sizeof(res->field[RES_CONTENT_RANGE]),
|
2020-08-05 16:28:21 +00:00
|
|
|
"bytes */%zu", st.st_size)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-05 16:28:21 +00:00
|
|
|
}
|
2020-08-05 11:41:44 +00:00
|
|
|
|
2020-08-28 20:48:32 +00:00
|
|
|
return;
|
2020-08-05 16:28:21 +00:00
|
|
|
} else {
|
2020-08-28 20:48:32 +00:00
|
|
|
goto err;
|
2020-07-23 16:16:08 +00:00
|
|
|
}
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* mime */
|
|
|
|
mime = "application/octet-stream";
|
2020-08-22 21:20:00 +00:00
|
|
|
if ((p = strrchr(realuri, '.'))) {
|
|
|
|
for (i = 0; i < LEN(mimes); i++) {
|
2018-02-04 20:27:33 +00:00
|
|
|
if (!strcmp(mimes[i].ext, p + 1)) {
|
|
|
|
mime = mimes[i].type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 21:20:00 +00:00
|
|
|
/* 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")) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (esnprintf(res->field[RES_CONTENT_LENGTH],
|
|
|
|
sizeof(res->field[RES_CONTENT_LENGTH]),
|
|
|
|
"%zu", res->file.upper - res->file.lower + 1)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
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)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
|
|
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
|
|
|
"%s", mime)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
if (timestamp(res->field[RES_LAST_MODIFIED],
|
|
|
|
sizeof(res->field[RES_LAST_MODIFIED]),
|
|
|
|
st.st_mtim.tv_sec)) {
|
2020-08-28 20:48:32 +00:00
|
|
|
s = S_INTERNAL_SERVER_ERROR;
|
|
|
|
goto err;
|
2020-08-22 21:20:00 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 20:48:32 +00:00
|
|
|
return;
|
|
|
|
err:
|
|
|
|
http_prepare_error_response(req, res, s);
|
2018-02-04 20:27:33 +00:00
|
|
|
}
|
2020-08-28 20:32:47 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
http_prepare_error_response(const struct request *req,
|
|
|
|
struct response *res, enum status s)
|
|
|
|
{
|
|
|
|
/* used later */
|
|
|
|
(void)req;
|
|
|
|
|
|
|
|
/* empty all response fields */
|
|
|
|
memset(res, 0, sizeof(*res));
|
|
|
|
|
|
|
|
res->type = RESTYPE_ERROR;
|
|
|
|
res->status = s;
|
|
|
|
|
|
|
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
|
|
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
|
|
|
"text/html; charset=utf-8")) {
|
|
|
|
res->status = S_INTERNAL_SERVER_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res->status == S_METHOD_NOT_ALLOWED) {
|
|
|
|
if (esnprintf(res->field[RES_ALLOW],
|
|
|
|
sizeof(res->field[RES_ALLOW]),
|
|
|
|
"Allow: GET, HEAD")) {
|
|
|
|
res->status = S_INTERNAL_SERVER_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-28 22:42:54 +00:00
|
|
|
|
|
|
|
enum status
|
|
|
|
http_send_body(int fd, const struct response *res,
|
|
|
|
const struct request *req)
|
|
|
|
{
|
|
|
|
enum status s;
|
|
|
|
|
|
|
|
if (req->method == M_GET && (s = body_fct[res->type](fd, res))) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|