Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
ab42879bc6
9 changed files with 257 additions and 196 deletions
10
Makefile
10
Makefile
|
@ -4,15 +4,15 @@
|
||||||
|
|
||||||
include config.mk
|
include config.mk
|
||||||
|
|
||||||
COMPONENTS = util sock http resp
|
COMPONENTS = data http sock util
|
||||||
|
|
||||||
all: quark
|
all: quark
|
||||||
|
|
||||||
util.o: util.c util.h config.mk
|
data.o: data.c data.h util.h http.h config.mk
|
||||||
sock.o: sock.c sock.h util.h config.mk
|
http.o: http.c http.h util.h http.h data.h config.h config.mk
|
||||||
http.o: http.c http.h util.h http.h resp.h config.h config.mk
|
|
||||||
resp.o: resp.c resp.h util.h http.h config.mk
|
|
||||||
main.o: main.c util.h sock.h http.h arg.h config.h config.mk
|
main.o: main.c util.h sock.h http.h arg.h config.h config.mk
|
||||||
|
sock.o: sock.c sock.h util.h config.mk
|
||||||
|
util.o: util.c util.h config.mk
|
||||||
|
|
||||||
quark: $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
|
quark: $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
|
||||||
$(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)
|
$(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "resp.h"
|
#include "data.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -38,10 +38,55 @@ suffix(int t)
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
enum status
|
static void
|
||||||
resp_dir(int fd, const struct response *res)
|
html_escape(const char *src, char *dst, size_t dst_siz)
|
||||||
{
|
{
|
||||||
enum status ret;
|
const struct {
|
||||||
|
char c;
|
||||||
|
char *s;
|
||||||
|
} escape[] = {
|
||||||
|
{ '&', "&" },
|
||||||
|
{ '<', "<" },
|
||||||
|
{ '>', ">" },
|
||||||
|
{ '"', """ },
|
||||||
|
{ '\'', "'" },
|
||||||
|
};
|
||||||
|
size_t i, j, k, esclen;
|
||||||
|
|
||||||
|
for (i = 0, j = 0; src[i] != '\0'; i++) {
|
||||||
|
for (k = 0; k < LEN(escape); k++) {
|
||||||
|
if (src[i] == escape[k].c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k == LEN(escape)) {
|
||||||
|
/* no escape char at src[i] */
|
||||||
|
if (j == dst_siz - 1) {
|
||||||
|
/* silent truncation */
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
dst[j++] = src[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* escape char at src[i] */
|
||||||
|
esclen = strlen(escape[k].s);
|
||||||
|
|
||||||
|
if (j >= dst_siz - esclen) {
|
||||||
|
/* silent truncation */
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
memcpy(&dst[j], escape[k].s, esclen);
|
||||||
|
j += esclen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst[j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
data_send_dirlisting(int fd, const struct response *res)
|
||||||
|
{
|
||||||
|
enum status ret = 0;
|
||||||
struct dirent **e;
|
struct dirent **e;
|
||||||
size_t i;
|
size_t i;
|
||||||
int dirlen;
|
int dirlen;
|
||||||
|
@ -52,6 +97,17 @@ resp_dir(int fd, const struct response *res)
|
||||||
return S_FORBIDDEN;
|
return S_FORBIDDEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* listing header (we use esc because sizeof(esc) >= PATH_MAX) */
|
||||||
|
html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc)));
|
||||||
|
if (dprintf(fd,
|
||||||
|
"<!DOCTYPE html>\n<html>\n\t<head>"
|
||||||
|
"<title>Index of %s</title></head>\n"
|
||||||
|
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
||||||
|
esc) < 0) {
|
||||||
|
ret = S_REQUEST_TIMEOUT;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/* listing */
|
/* listing */
|
||||||
for (i = 0; i < (size_t)dirlen; i++) {
|
for (i = 0; i < (size_t)dirlen; i++) {
|
||||||
/* skip hidden files, "." and ".." */
|
/* skip hidden files, "." and ".." */
|
||||||
|
@ -87,7 +143,22 @@ cleanup:
|
||||||
}
|
}
|
||||||
|
|
||||||
enum status
|
enum status
|
||||||
resp_file(int fd, const struct response *res)
|
data_send_error(int fd, const struct response *res)
|
||||||
|
{
|
||||||
|
if (dprintf(fd,
|
||||||
|
"<!DOCTYPE html>\n<html>\n\t<head>\n"
|
||||||
|
"\t\t<title>%d %s</title>\n\t</head>\n\t<body>\n"
|
||||||
|
"\t\t<h1>%d %s</h1>\n\t</body>\n</html>\n",
|
||||||
|
res->status, status_str[res->status],
|
||||||
|
res->status, status_str[res->status]) < 0) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
data_send_file(int fd, const struct response *res)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
enum status ret = 0;
|
enum status ret = 0;
|
11
data.h
Normal file
11
data.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/* See LICENSE file for copyright and license details. */
|
||||||
|
#ifndef DATA_H
|
||||||
|
#define DATA_H
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
|
||||||
|
enum status data_send_dirlisting(int, const struct response *);
|
||||||
|
enum status data_send_error(int, const struct response *);
|
||||||
|
enum status data_send_file(int, const struct response *);
|
||||||
|
|
||||||
|
#endif /* DATA_H */
|
192
http.c
192
http.c
|
@ -17,8 +17,8 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "data.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "resp.h"
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
const char *req_field_str[] = {
|
const char *req_field_str[] = {
|
||||||
|
@ -58,10 +58,16 @@ const char *res_field_str[] = {
|
||||||
[RES_CONTENT_TYPE] = "Content-Type",
|
[RES_CONTENT_TYPE] = "Content-Type",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum status (* const body_fct[])(int, const struct response *) = {
|
||||||
|
[RESTYPE_ERROR] = data_send_error,
|
||||||
|
[RESTYPE_FILE] = data_send_file,
|
||||||
|
[RESTYPE_DIRLISTING] = data_send_dirlisting,
|
||||||
|
};
|
||||||
|
|
||||||
enum status
|
enum status
|
||||||
http_send_header(int fd, const struct response *res)
|
http_send_header(int fd, const struct response *res)
|
||||||
{
|
{
|
||||||
char t[FIELD_MAX], esc[PATH_MAX];
|
char t[FIELD_MAX];
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
if (timestamp(t, sizeof(t), time(NULL))) {
|
if (timestamp(t, sizeof(t), time(NULL))) {
|
||||||
|
@ -89,52 +95,7 @@ http_send_header(int fd, const struct response *res)
|
||||||
return S_REQUEST_TIMEOUT;
|
return S_REQUEST_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* listing header */
|
return 0;
|
||||||
if (res->type == RESTYPE_DIRLISTING) {
|
|
||||||
html_escape(res->uri, esc, sizeof(esc));
|
|
||||||
if (dprintf(fd,
|
|
||||||
"<!DOCTYPE html>\n<html>\n\t<head>"
|
|
||||||
"<title>Index of %s</title></head>\n"
|
|
||||||
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
|
||||||
esc) < 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum status
|
|
||||||
http_send_status(int fd, enum status s)
|
|
||||||
{
|
|
||||||
enum status sendstatus;
|
|
||||||
|
|
||||||
struct response res = {
|
|
||||||
.status = s,
|
|
||||||
.field[RES_CONTENT_TYPE] = "text/html; charset=utf-8",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (s == S_METHOD_NOT_ALLOWED) {
|
|
||||||
if (esnprintf(res.field[RES_ALLOW],
|
|
||||||
sizeof(res.field[RES_ALLOW]), "%s",
|
|
||||||
"Allow: GET, HEAD")) {
|
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((sendstatus = http_send_header(fd, &res)) != s) {
|
|
||||||
return sendstatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dprintf(fd,
|
|
||||||
"<!DOCTYPE html>\n<html>\n\t<head>\n"
|
|
||||||
"\t\t<title>%d %s</title>\n\t</head>\n\t<body>\n"
|
|
||||||
"\t\t<h1>%d %s</h1>\n\t</body>\n</html>\n",
|
|
||||||
s, status_str[s], s, status_str[s]) < 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -547,11 +508,11 @@ parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
|
||||||
#undef RELPATH
|
#undef RELPATH
|
||||||
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
||||||
|
|
||||||
enum status
|
void
|
||||||
http_prepare_response(const struct request *req, struct response *res,
|
http_prepare_response(const struct request *req, struct response *res,
|
||||||
const struct server *srv)
|
const struct server *srv)
|
||||||
{
|
{
|
||||||
enum status returnstatus;
|
enum status s;
|
||||||
struct in6_addr addr;
|
struct in6_addr addr;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
struct tm tm = { 0 };
|
struct tm tm = { 0 };
|
||||||
|
@ -568,7 +529,8 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
/* make a working copy of the URI and normalize it */
|
/* make a working copy of the URI and normalize it */
|
||||||
memcpy(realuri, req->uri, sizeof(realuri));
|
memcpy(realuri, req->uri, sizeof(realuri));
|
||||||
if (normabspath(realuri)) {
|
if (normabspath(realuri)) {
|
||||||
return S_BAD_REQUEST;
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* match vhost */
|
/* match vhost */
|
||||||
|
@ -583,13 +545,15 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == srv->vhost_len) {
|
if (i == srv->vhost_len) {
|
||||||
return S_NOT_FOUND;
|
s = S_NOT_FOUND;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if we have a vhost prefix, prepend it to the URI */
|
/* if we have a vhost prefix, prepend it to the URI */
|
||||||
if (vhost->prefix &&
|
if (vhost->prefix &&
|
||||||
prepend(realuri, LEN(realuri), vhost->prefix)) {
|
prepend(realuri, LEN(realuri), vhost->prefix)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,7 +571,8 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
/* swap out URI prefix */
|
/* swap out URI prefix */
|
||||||
memmove(realuri, realuri + len, strlen(realuri) + 1);
|
memmove(realuri, realuri + len, strlen(realuri) + 1);
|
||||||
if (prepend(realuri, LEN(realuri), srv->map[i].to)) {
|
if (prepend(realuri, LEN(realuri), srv->map[i].to)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -615,19 +580,22 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
|
|
||||||
/* normalize URI again, in case we introduced dirt */
|
/* normalize URI again, in case we introduced dirt */
|
||||||
if (normabspath(realuri)) {
|
if (normabspath(realuri)) {
|
||||||
return S_BAD_REQUEST;
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stat the relative path derived from the URI */
|
/* stat the relative path derived from the URI */
|
||||||
if (stat(RELPATH(realuri), &st) < 0) {
|
if (stat(RELPATH(realuri), &st) < 0) {
|
||||||
return (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
|
s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
/* append '/' to URI if not present */
|
/* append '/' to URI if not present */
|
||||||
len = strlen(realuri);
|
len = strlen(realuri);
|
||||||
if (len + 1 + 1 > PATH_MAX) {
|
if (len + 1 + 1 > PATH_MAX) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
if (len > 0 && realuri[len - 1] != '/') {
|
if (len > 0 && realuri[len - 1] != '/') {
|
||||||
realuri[len] = '/';
|
realuri[len] = '/';
|
||||||
|
@ -641,7 +609,8 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
*/
|
*/
|
||||||
if (strstr(realuri, "/.") && strncmp(realuri,
|
if (strstr(realuri, "/.") && strncmp(realuri,
|
||||||
"/.well-known/", sizeof("/.well-known/") - 1)) {
|
"/.well-known/", sizeof("/.well-known/") - 1)) {
|
||||||
return S_FORBIDDEN;
|
s = S_FORBIDDEN;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -670,7 +639,8 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
* honor that later when we fill the "Location"-field */
|
* honor that later when we fill the "Location"-field */
|
||||||
if ((ipv6host = inet_pton(AF_INET6, targethost,
|
if ((ipv6host = inet_pton(AF_INET6, targethost,
|
||||||
&addr)) < 0) {
|
&addr)) < 0) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write location to response struct */
|
/* write location to response struct */
|
||||||
|
@ -681,18 +651,20 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
targethost,
|
targethost,
|
||||||
ipv6host ? "]" : "", hasport ? ":" : "",
|
ipv6host ? "]" : "", hasport ? ":" : "",
|
||||||
hasport ? srv->port : "", tmpuri)) {
|
hasport ? srv->port : "", tmpuri)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* write relative redirection URI to response struct */
|
/* write relative redirection URI to response struct */
|
||||||
if (esnprintf(res->field[RES_LOCATION],
|
if (esnprintf(res->field[RES_LOCATION],
|
||||||
sizeof(res->field[RES_LOCATION]),
|
sizeof(res->field[RES_LOCATION]),
|
||||||
"%s", tmpuri)) {
|
"%s", tmpuri)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* the URI is well-formed, we can now write the URI into
|
* the URI is well-formed, we can now write the URI into
|
||||||
|
@ -701,11 +673,13 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
* into the actual response-path
|
* into the actual response-path
|
||||||
*/
|
*/
|
||||||
if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) {
|
if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
if (esnprintf(res->path, sizeof(res->path), "%s%s",
|
if (esnprintf(res->path, sizeof(res->path), "%s%s",
|
||||||
vhost ? vhost->dir : "", RELPATH(req->uri))) {
|
vhost ? vhost->dir : "", RELPATH(req->uri))) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -716,7 +690,8 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
*/
|
*/
|
||||||
if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
|
if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
|
||||||
req->uri, srv->docindex)) {
|
req->uri, srv->docindex)) {
|
||||||
return S_REQUEST_TOO_LARGE;
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stat the docindex, which must be a regular file */
|
/* stat the docindex, which must be a regular file */
|
||||||
|
@ -730,14 +705,16 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
||||||
sizeof(res->field[RES_CONTENT_TYPE]),
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
||||||
"%s", "text/html; charset=utf-8")) {
|
"%s", "text/html; charset=utf-8")) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return;
|
||||||
} else {
|
} else {
|
||||||
/* reject */
|
/* reject */
|
||||||
return (!S_ISREG(st.st_mode) || errno == EACCES) ?
|
s = (!S_ISREG(st.st_mode) || errno == EACCES) ?
|
||||||
S_FORBIDDEN : S_NOT_FOUND;
|
S_FORBIDDEN : S_NOT_FOUND;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -747,32 +724,33 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
/* parse field */
|
/* parse field */
|
||||||
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
|
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
|
||||||
"%a, %d %b %Y %T GMT", &tm)) {
|
"%a, %d %b %Y %T GMT", &tm)) {
|
||||||
return S_BAD_REQUEST;
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* compare with last modification date of the file */
|
/* compare with last modification date of the file */
|
||||||
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
|
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
|
||||||
res->status = S_NOT_MODIFIED;
|
res->status = S_NOT_MODIFIED;
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* range */
|
/* range */
|
||||||
if ((returnstatus = parse_range(req->field[REQ_RANGE], st.st_size,
|
if ((s = parse_range(req->field[REQ_RANGE], st.st_size,
|
||||||
&(res->file.lower),
|
&(res->file.lower), &(res->file.upper)))) {
|
||||||
&(res->file.upper)))) {
|
if (s == S_RANGE_NOT_SATISFIABLE) {
|
||||||
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 S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return;
|
||||||
} else {
|
} else {
|
||||||
return returnstatus;
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,31 +776,79 @@ http_prepare_response(const struct request *req, struct response *res,
|
||||||
if (esnprintf(res->field[RES_ACCEPT_RANGES],
|
if (esnprintf(res->field[RES_ACCEPT_RANGES],
|
||||||
sizeof(res->field[RES_ACCEPT_RANGES]),
|
sizeof(res->field[RES_ACCEPT_RANGES]),
|
||||||
"%s", "bytes")) {
|
"%s", "bytes")) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esnprintf(res->field[RES_CONTENT_LENGTH],
|
if (esnprintf(res->field[RES_CONTENT_LENGTH],
|
||||||
sizeof(res->field[RES_CONTENT_LENGTH]),
|
sizeof(res->field[RES_CONTENT_LENGTH]),
|
||||||
"%zu", res->file.upper - res->file.lower + 1)) {
|
"%zu", res->file.upper - res->file.lower + 1)) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
if (req->field[REQ_RANGE][0] != '\0') {
|
if (req->field[REQ_RANGE][0] != '\0') {
|
||||||
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 %zd-%zd/%zu", res->file.lower,
|
"bytes %zd-%zd/%zu", res->file.lower,
|
||||||
res->file.upper, st.st_size)) {
|
res->file.upper, st.st_size)) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
||||||
sizeof(res->field[RES_CONTENT_TYPE]),
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
||||||
"%s", mime)) {
|
"%s", mime)) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
if (timestamp(res->field[RES_LAST_MODIFIED],
|
if (timestamp(res->field[RES_LAST_MODIFIED],
|
||||||
sizeof(res->field[RES_LAST_MODIFIED]),
|
sizeof(res->field[RES_LAST_MODIFIED]),
|
||||||
st.st_mtim.tv_sec)) {
|
st.st_mtim.tv_sec)) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
err:
|
||||||
|
http_prepare_error_response(req, res, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return 0;
|
||||||
|
|
14
http.h
14
http.h
|
@ -3,6 +3,7 @@
|
||||||
#define HTTP_H
|
#define HTTP_H
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
@ -82,17 +83,20 @@ struct response {
|
||||||
} file;
|
} file;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern enum status (* const body_fct[])(int, const struct response *);
|
||||||
|
|
||||||
enum conn_state {
|
enum conn_state {
|
||||||
C_VACANT,
|
C_VACANT,
|
||||||
C_RECV_HEADER,
|
C_RECV_HEADER,
|
||||||
C_SEND_HEADER,
|
C_SEND_HEADER,
|
||||||
C_SEND_DATA,
|
C_SEND_BODY,
|
||||||
NUM_CONN_STATES,
|
NUM_CONN_STATES,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct connection {
|
struct connection {
|
||||||
enum conn_state state;
|
enum conn_state state;
|
||||||
int fd;
|
int fd;
|
||||||
|
struct sockaddr_storage ia;
|
||||||
char header[HEADER_MAX]; /* general req/res-header buffer */
|
char header[HEADER_MAX]; /* general req/res-header buffer */
|
||||||
size_t off; /* general offset (header/file/dir) */
|
size_t off; /* general offset (header/file/dir) */
|
||||||
struct request req;
|
struct request req;
|
||||||
|
@ -103,7 +107,11 @@ 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);
|
||||||
enum status http_recv_header(int, char *, size_t, size_t *);
|
enum status http_recv_header(int, char *, size_t, size_t *);
|
||||||
enum status http_parse_header(const char *, struct request *);
|
enum status http_parse_header(const char *, struct request *);
|
||||||
enum status http_prepare_response(const struct request *, struct response *,
|
void http_prepare_response(const struct request *, struct response *,
|
||||||
const struct server *);
|
const struct server *);
|
||||||
|
void http_prepare_error_response(const struct request *,
|
||||||
|
struct response *, enum status);
|
||||||
|
enum status http_send_body(int, const struct response *,
|
||||||
|
const struct request *);
|
||||||
|
|
||||||
#endif /* HTTP_H */
|
#endif /* HTTP_H */
|
||||||
|
|
86
main.c
86
main.c
|
@ -16,7 +16,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "resp.h"
|
#include "data.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "sock.h"
|
#include "sock.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -24,52 +24,57 @@
|
||||||
static char *udsname;
|
static char *udsname;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv)
|
logmsg(const struct connection *c)
|
||||||
{
|
{
|
||||||
struct connection c = { .fd = infd };
|
char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
|
||||||
time_t t;
|
|
||||||
enum status status;
|
|
||||||
char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
|
|
||||||
char tstmp[21];
|
char tstmp[21];
|
||||||
|
|
||||||
|
/* create timestamp */
|
||||||
|
if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
|
||||||
|
gmtime(&(time_t){time(NULL)}))) {
|
||||||
|
warn("strftime: Exceeded buffer capacity");
|
||||||
|
/* continue anyway (we accept the truncation) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generate address-string */
|
||||||
|
if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
|
||||||
|
warn("sock_get_inaddr_str: Couldn't generate adress-string");
|
||||||
|
inaddr_str[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr_str, c->res.status,
|
||||||
|
c->req.field[REQ_HOST], c->req.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
serve(struct connection *c, const struct server *srv)
|
||||||
|
{
|
||||||
|
enum status s;
|
||||||
|
|
||||||
/* set connection timeout */
|
/* set connection timeout */
|
||||||
if (sock_set_timeout(c.fd, 30)) {
|
if (sock_set_timeout(c->fd, 30)) {
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle request */
|
/* handle request */
|
||||||
if ((status = http_recv_header(c.fd, c.header, LEN(c.header), &c.off)) ||
|
if ((s = http_recv_header(c->fd, c->header, LEN(c->header), &c->off)) ||
|
||||||
(status = http_parse_header(c.header, &c.req)) ||
|
(s = http_parse_header(c->header, &c->req))) {
|
||||||
(status = http_prepare_response(&c.req, &c.res, srv))) {
|
http_prepare_error_response(&c->req, &c->res, s);
|
||||||
status = http_send_status(c.fd, status);
|
|
||||||
} else {
|
} else {
|
||||||
status = http_send_header(c.fd, &c.res);
|
http_prepare_response(&c->req, &c->res, srv);
|
||||||
|
|
||||||
/* send data */
|
|
||||||
if (c.res.type == RESTYPE_FILE) {
|
|
||||||
resp_file(c.fd, &c.res);
|
|
||||||
} else if (c.res.type == RESTYPE_DIRLISTING) {
|
|
||||||
resp_dir(c.fd, &c.res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write output to log */
|
if ((s = http_send_header(c->fd, &c->res)) ||
|
||||||
t = time(NULL);
|
(s = http_send_body(c->fd, &c->res, &c->req))) {
|
||||||
if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
|
c->res.status = s;
|
||||||
gmtime(&t))) {
|
|
||||||
warn("strftime: Exceeded buffer capacity");
|
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
if (sock_get_inaddr_str(in_sa, inaddr, LEN(inaddr))) {
|
|
||||||
goto cleanup;
|
logmsg(c);
|
||||||
}
|
|
||||||
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status,
|
|
||||||
c.req.field[REQ_HOST], c.req.uri);
|
|
||||||
cleanup:
|
cleanup:
|
||||||
/* clean up and finish */
|
/* clean up and finish */
|
||||||
shutdown(c.fd, SHUT_RD);
|
shutdown(c->fd, SHUT_RD);
|
||||||
shutdown(c.fd, SHUT_WR);
|
shutdown(c->fd, SHUT_WR);
|
||||||
close(c.fd);
|
close(c->fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -192,10 +197,8 @@ main(int argc, char *argv[])
|
||||||
struct server srv = {
|
struct server srv = {
|
||||||
.docindex = "index.html",
|
.docindex = "index.html",
|
||||||
};
|
};
|
||||||
struct sockaddr_storage in_sa;
|
|
||||||
size_t i;
|
size_t i;
|
||||||
socklen_t in_sa_len;
|
int insock, status = 0;
|
||||||
int insock, status = 0, infd;
|
|
||||||
const char *err;
|
const char *err;
|
||||||
char *tok[4];
|
char *tok[4];
|
||||||
|
|
||||||
|
@ -370,9 +373,10 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
/* accept incoming connections */
|
/* accept incoming connections */
|
||||||
while (1) {
|
while (1) {
|
||||||
in_sa_len = sizeof(in_sa);
|
struct connection c = { 0 };
|
||||||
if ((infd = accept(insock, (struct sockaddr *)&in_sa,
|
|
||||||
&in_sa_len)) < 0) {
|
if ((c.fd = accept(insock, (struct sockaddr *)&c.ia,
|
||||||
|
&(socklen_t){sizeof(c.ia)})) < 0) {
|
||||||
warn("accept:");
|
warn("accept:");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -380,7 +384,7 @@ main(int argc, char *argv[])
|
||||||
/* fork and handle */
|
/* fork and handle */
|
||||||
switch (fork()) {
|
switch (fork()) {
|
||||||
case 0:
|
case 0:
|
||||||
serve(infd, &in_sa, &srv);
|
serve(&c, &srv);
|
||||||
exit(0);
|
exit(0);
|
||||||
break;
|
break;
|
||||||
case -1:
|
case -1:
|
||||||
|
@ -388,7 +392,7 @@ main(int argc, char *argv[])
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
/* close the connection in the parent */
|
/* close the connection in the parent */
|
||||||
close(infd);
|
close(c.fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
13
resp.h
13
resp.h
|
@ -1,13 +0,0 @@
|
||||||
/* See LICENSE file for copyright and license details. */
|
|
||||||
#ifndef RESP_H
|
|
||||||
#define RESP_H
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "http.h"
|
|
||||||
|
|
||||||
enum status resp_dir(int, const struct response *);
|
|
||||||
enum status resp_file(int, const struct response *);
|
|
||||||
|
|
||||||
#endif /* RESP_H */
|
|
45
util.c
45
util.c
|
@ -123,51 +123,6 @@ prepend(char *str, size_t size, const char *prefix)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
html_escape(const char *src, char *dst, size_t dst_siz)
|
|
||||||
{
|
|
||||||
const struct {
|
|
||||||
char c;
|
|
||||||
char *s;
|
|
||||||
} escape[] = {
|
|
||||||
{ '&', "&" },
|
|
||||||
{ '<', "<" },
|
|
||||||
{ '>', ">" },
|
|
||||||
{ '"', """ },
|
|
||||||
{ '\'', "'" },
|
|
||||||
};
|
|
||||||
size_t i, j, k, esclen;
|
|
||||||
|
|
||||||
for (i = 0, j = 0; src[i] != '\0'; i++) {
|
|
||||||
for (k = 0; k < LEN(escape); k++) {
|
|
||||||
if (src[i] == escape[k].c) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (k == LEN(escape)) {
|
|
||||||
/* no escape char at src[i] */
|
|
||||||
if (j == dst_siz - 1) {
|
|
||||||
/* silent truncation */
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
dst[j++] = src[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* escape char at src[i] */
|
|
||||||
esclen = strlen(escape[k].s);
|
|
||||||
|
|
||||||
if (j >= dst_siz - esclen) {
|
|
||||||
/* silent truncation */
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
memcpy(&dst[j], escape[k].s, esclen);
|
|
||||||
j += esclen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dst[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
#define INVALID 1
|
#define INVALID 1
|
||||||
#define TOOSMALL 2
|
#define TOOSMALL 2
|
||||||
#define TOOLARGE 3
|
#define TOOLARGE 3
|
||||||
|
|
1
util.h
1
util.h
|
@ -52,7 +52,6 @@ void eunveil(const char *, const char *);
|
||||||
int timestamp(char *, size_t, time_t);
|
int timestamp(char *, size_t, time_t);
|
||||||
int esnprintf(char *, size_t, const char *, ...);
|
int esnprintf(char *, size_t, const char *, ...);
|
||||||
int prepend(char *, size_t, const char *);
|
int prepend(char *, size_t, const char *);
|
||||||
void html_escape(const char *, char *, size_t);
|
|
||||||
|
|
||||||
void *reallocarray(void *, size_t, size_t);
|
void *reallocarray(void *, size_t, size_t);
|
||||||
long long strtonum(const char *, long long, long long, const char **);
|
long long strtonum(const char *, long long, long long, const char **);
|
||||||
|
|
Loading…
Reference in a new issue