Add connection struct - quark - quark web server | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit c1b242e405d40067c282e8116d21c6f2641e4eee | |
parent 6d2fe7f29e12190d9be062852cf3f21b7f695369 | |
Author: Laslo Hunhold <[email protected]> | |
Date: Sat, 22 Aug 2020 09:24:57 +0200 | |
Add connection struct | |
This struct contains the request and response structs, represents a state | |
and has some utility-buffers. | |
Signed-off-by: Laslo Hunhold <[email protected]> | |
Diffstat: | |
M http.c | 84 ++++++++++++++++-------------… | |
M http.h | 40 ++++++++++++++++++++++++++++-… | |
M main.c | 18 ++++++++---------- | |
3 files changed, 86 insertions(+), 56 deletions(-) | |
--- | |
diff --git a/http.c b/http.c | |
@@ -144,7 +144,7 @@ decode(const char src[PATH_MAX], char dest[PATH_MAX]) | |
} | |
int | |
-http_get_request(struct request *req) | |
+http_get_request(int fd, struct request *req) | |
{ | |
struct in6_addr addr; | |
size_t hlen, i, mlen; | |
@@ -158,8 +158,8 @@ http_get_request(struct request *req) | |
* receive header | |
*/ | |
for (hlen = 0; ;) { | |
- if ((off = read(req->fd, h + hlen, sizeof(h) - hlen)) < 0) { | |
- return http_send_status(req->fd, S_REQUEST_TIMEOUT); | |
+ if ((off = read(fd, h + hlen, sizeof(h) - hlen)) < 0) { | |
+ return http_send_status(fd, S_REQUEST_TIMEOUT); | |
} else if (off == 0) { | |
break; | |
} | |
@@ -168,13 +168,13 @@ http_get_request(struct request *req) | |
break; | |
} | |
if (hlen == sizeof(h)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO_LARGE); | |
+ return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
} | |
} | |
/* remove terminating empty line */ | |
if (hlen < 2) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
hlen -= 2; | |
@@ -194,12 +194,12 @@ http_get_request(struct request *req) | |
} | |
} | |
if (i == NUM_REQ_METHODS) { | |
- return http_send_status(req->fd, S_METHOD_NOT_ALLOWED); | |
+ return http_send_status(fd, S_METHOD_NOT_ALLOWED); | |
} | |
/* a single space must follow the method */ | |
if (h[mlen] != ' ') { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* basis for next step */ | |
@@ -207,11 +207,11 @@ http_get_request(struct request *req) | |
/* TARGET */ | |
if (!(q = strchr(p, ' '))) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
*q = '\0'; | |
if (q - p + 1 > PATH_MAX) { | |
- return http_send_status(req->fd, S_REQUEST_TOO_LARGE); | |
+ return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
} | |
memcpy(req->target, p, q - p + 1); | |
decode(req->target, req->target); | |
@@ -221,18 +221,18 @@ http_get_request(struct request *req) | |
/* HTTP-VERSION */ | |
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
p += sizeof("HTTP/") - 1; | |
if (strncmp(p, "1.0", sizeof("1.0") - 1) && | |
strncmp(p, "1.1", sizeof("1.1") - 1)) { | |
- return http_send_status(req->fd, S_VERSION_NOT_SUPPORTED); | |
+ return http_send_status(fd, S_VERSION_NOT_SUPPORTED); | |
} | |
p += sizeof("1.*") - 1; | |
/* check terminator */ | |
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* basis for next step */ | |
@@ -253,7 +253,7 @@ http_get_request(struct request *req) | |
if (i == NUM_REQ_FIELDS) { | |
/* unmatched field, skip this line */ | |
if (!(q = strstr(p, "\r\n"))) { | |
- return http_send_status(req->fd, S_BAD_REQUEST… | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
p = q + (sizeof("\r\n") - 1); | |
continue; | |
@@ -263,7 +263,7 @@ http_get_request(struct request *req) | |
/* a single colon must follow the field name */ | |
if (*p != ':') { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* skip whitespace */ | |
@@ -272,11 +272,11 @@ http_get_request(struct request *req) | |
/* extract field content */ | |
if (!(q = strstr(p, "\r\n"))) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
*q = '\0'; | |
if (q - p + 1 > FIELD_MAX) { | |
- return http_send_status(req->fd, S_REQUEST_TOO_LARGE); | |
+ return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
} | |
memcpy(req->field[i], p, q - p + 1); | |
@@ -296,7 +296,7 @@ http_get_request(struct request *req) | |
if (p && (!q || p > q)) { | |
/* port suffix must not be empty */ | |
if (*(p + 1) == '\0') { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
*p = '\0'; | |
} | |
@@ -305,7 +305,7 @@ http_get_request(struct request *req) | |
if (q) { | |
/* brackets must be on the outside */ | |
if (req->field[REQ_HOST][0] != '[' || *(q + 1) != '\0') { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* remove the right bracket */ | |
@@ -314,7 +314,7 @@ http_get_request(struct request *req) | |
/* validate the contained IPv6 address */ | |
if (inet_pton(AF_INET6, p, &addr) != 1) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* copy it into the host field */ | |
@@ -528,7 +528,7 @@ parse_range(const char *str, size_t size, size_t *lower, si… | |
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) | |
enum status | |
-http_send_response(const struct request *req, const struct server *s) | |
+http_send_response(int fd, const struct request *req, const struct server *s) | |
{ | |
enum status returnstatus; | |
struct in6_addr addr; | |
@@ -553,7 +553,7 @@ http_send_response(const struct request *req, const struct … | |
if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], … | |
NULL, 0)) { | |
if (chdir(s->vhost[i].dir) < 0) { | |
- return http_send_status(req->fd, (errn… | |
+ return http_send_status(fd, (errno == … | |
S_FORBIDDEN : … | |
} | |
vhostmatch = s->vhost[i].chost; | |
@@ -561,14 +561,14 @@ http_send_response(const struct request *req, const struc… | |
} | |
} | |
if (i == s->vhost_len) { | |
- return http_send_status(req->fd, S_NOT_FOUND); | |
+ return http_send_status(fd, S_NOT_FOUND); | |
} | |
/* if we have a vhost prefix, prepend it to the target */ | |
if (s->vhost[i].prefix) { | |
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", | |
s->vhost[i].prefix, realtarget)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO… | |
+ return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
memcpy(realtarget, tmptarget, sizeof(realtarget)); | |
} | |
@@ -588,7 +588,7 @@ http_send_response(const struct request *req, const struct … | |
/* swap out target prefix */ | |
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", | |
s->map[i].to, realtarget + len)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO… | |
+ return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
memcpy(realtarget, tmptarget, sizeof(realtarget)); | |
break; | |
@@ -597,12 +597,12 @@ http_send_response(const struct request *req, const struc… | |
/* normalize target */ | |
if (normabspath(realtarget)) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* stat the target */ | |
if (stat(RELPATH(realtarget), &st) < 0) { | |
- return http_send_status(req->fd, (errno == EACCES) ? | |
+ return http_send_status(fd, (errno == EACCES) ? | |
S_FORBIDDEN : S_NOT_FOUND); | |
} | |
@@ -610,7 +610,7 @@ http_send_response(const struct request *req, const struct … | |
/* add / to target if not present */ | |
len = strlen(realtarget); | |
if (len >= PATH_MAX - 2) { | |
- return http_send_status(req->fd, S_REQUEST_TOO_LARGE); | |
+ return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
} | |
if (len && realtarget[len - 1] != '/') { | |
realtarget[len] = '/'; | |
@@ -624,7 +624,7 @@ http_send_response(const struct request *req, const struct … | |
*/ | |
if (strstr(realtarget, "/.") && strncmp(realtarget, | |
"/.well-known/", sizeof("/.well-known/") - 1)) { | |
- return http_send_status(req->fd, S_FORBIDDEN); | |
+ return http_send_status(fd, S_FORBIDDEN); | |
} | |
/* redirect if targets differ, host is non-canonical or we prefixed */ | |
@@ -650,7 +650,7 @@ http_send_response(const struct request *req, const struct … | |
* honor that later when we fill the "Location"-field … | |
if ((ipv6host = inet_pton(AF_INET6, targethost, | |
&addr)) < 0) { | |
- return http_send_status(req->fd, | |
+ return http_send_status(fd, | |
S_INTERNAL_SERVER_ERRO… | |
} | |
@@ -662,25 +662,25 @@ http_send_response(const struct request *req, const struc… | |
targethost, | |
ipv6host ? "]" : "", hasport ? ":" : "", | |
hasport ? s->port : "", tmptarget)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO… | |
+ return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
} else { | |
/* write relative redirection URL to response struct */ | |
if (esnprintf(res.field[RES_LOCATION], | |
sizeof(res.field[RES_LOCATION]), | |
tmptarget)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO… | |
+ return http_send_status(fd, S_REQUEST_TOO_LARG… | |
} | |
} | |
- return http_send_header(req->fd, &res); | |
+ return http_send_header(fd, &res); | |
} | |
if (S_ISDIR(st.st_mode)) { | |
/* append docindex to target */ | |
if (esnprintf(realtarget, sizeof(realtarget), "%s%s", | |
req->target, s->docindex)) { | |
- return http_send_status(req->fd, S_REQUEST_TOO_LARGE); | |
+ return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
} | |
/* stat the docindex, which must be a regular file */ | |
@@ -689,13 +689,13 @@ http_send_response(const struct request *req, const struc… | |
/* remove index suffix and serve dir */ | |
realtarget[strlen(realtarget) - | |
strlen(s->docindex)] = '\0'; | |
- return resp_dir(req->fd, RELPATH(realtarget), … | |
+ return resp_dir(fd, RELPATH(realtarget), req); | |
} else { | |
/* reject */ | |
if (!S_ISREG(st.st_mode) || errno == EACCES) { | |
- return http_send_status(req->fd, S_FOR… | |
+ return http_send_status(fd, S_FORBIDDE… | |
} else { | |
- return http_send_status(req->fd, S_NOT… | |
+ return http_send_status(fd, S_NOT_FOUN… | |
} | |
} | |
} | |
@@ -706,13 +706,13 @@ http_send_response(const struct request *req, const struc… | |
/* parse field */ | |
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE], | |
"%a, %d %b %Y %T GMT", &tm)) { | |
- return http_send_status(req->fd, S_BAD_REQUEST); | |
+ return http_send_status(fd, S_BAD_REQUEST); | |
} | |
/* compare with last modification date of the file */ | |
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) { | |
res.status = S_NOT_MODIFIED; | |
- return http_send_header(req->fd, &res); | |
+ return http_send_header(fd, &res); | |
} | |
} | |
@@ -725,13 +725,13 @@ http_send_response(const struct request *req, const struc… | |
if (esnprintf(res.field[RES_CONTENT_RANGE], | |
sizeof(res.field[RES_CONTENT_RANGE]), | |
"bytes */%zu", st.st_size)) { | |
- return http_send_status(req->fd, | |
+ return http_send_status(fd, | |
S_INTERNAL_SERVER_ERRO… | |
} | |
- return http_send_header(req->fd, &res); | |
+ return http_send_header(fd, &res); | |
} else { | |
- return http_send_status(req->fd, returnstatus); | |
+ return http_send_status(fd, returnstatus); | |
} | |
} | |
@@ -746,5 +746,5 @@ http_send_response(const struct request *req, const struct … | |
} | |
} | |
- return resp_file(req->fd, RELPATH(realtarget), req, &st, mime, lower, … | |
+ return resp_file(fd, RELPATH(realtarget), req, &st, mime, lower, upper… | |
} | |
diff --git a/http.h b/http.h | |
@@ -3,6 +3,7 @@ | |
#define HTTP_H | |
#include <limits.h> | |
+#include <sys/stat.h> | |
#include "util.h" | |
@@ -27,8 +28,8 @@ enum req_method { | |
extern const char *req_method_str[]; | |
struct request { | |
- int fd; | |
- char header[HEADER_MAX]; | |
+ char header[HEADER_MAX]; /* deprecated */ | |
+ | |
enum req_method method; | |
char target[PATH_MAX]; | |
char field[NUM_REQ_FIELDS][FIELD_MAX]; | |
@@ -65,15 +66,46 @@ enum res_field { | |
extern const char *res_field_str[]; | |
+enum res_type { | |
+ RESTYPE_FILE, | |
+ RESTYPE_DIR, | |
+ NUM_RES_TYPES, | |
+}; | |
+ | |
struct response { | |
+ enum res_type type; | |
enum status status; | |
char field[NUM_RES_FIELDS][FIELD_MAX]; | |
+ char path[PATH_MAX]; | |
+ struct stat st; | |
+ struct { | |
+ char *mime; | |
+ size_t lower; | |
+ size_t upper; | |
+ } file; | |
+}; | |
+ | |
+enum conn_state { | |
+ C_VACANT, | |
+ C_RECV_HEADER, | |
+ C_SEND_HEADER, | |
+ C_SEND_DATA, | |
+ NUM_CONN_STATES, | |
+}; | |
+ | |
+struct connection { | |
+ enum conn_state state; | |
+ int fd; | |
+ char header[HEADER_MAX]; /* general req/res-header buffer */ | |
+ size_t off; /* general offset (header/file/dir) */ | |
+ struct request req; | |
+ struct response res; | |
}; | |
enum status http_send_header(int, const struct response *); | |
enum status http_send_status(int, enum status); | |
-int http_get_request(struct request *); | |
-enum status http_send_response(const struct request *, | |
+int http_get_request(int fd, struct request *); | |
+enum status http_send_response(int fd, const struct request *, | |
const struct server *); | |
#endif /* HTTP_H */ | |
diff --git a/main.c b/main.c | |
@@ -25,22 +25,20 @@ static char *udsname; | |
static void | |
serve(int infd, const struct sockaddr_storage *in_sa, const struct server *s) | |
{ | |
- struct request req; | |
+ struct connection c = { .fd = infd }; | |
time_t t; | |
enum status status; | |
char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; | |
char tstmp[21]; | |
/* set connection timeout */ | |
- if (sock_set_timeout(infd, 30)) { | |
+ if (sock_set_timeout(c.fd, 30)) { | |
goto cleanup; | |
} | |
/* handle request */ | |
- req.fd = infd; | |
- | |
- if (!(status = http_get_request(&req))) { | |
- status = http_send_response(&req, s); | |
+ if (!(status = http_get_request(c.fd, &c.req))) { | |
+ status = http_send_response(c.fd, &c.req, s); | |
} | |
/* write output to log */ | |
@@ -54,12 +52,12 @@ serve(int infd, const struct sockaddr_storage *in_sa, const… | |
goto cleanup; | |
} | |
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status, | |
- req.field[REQ_HOST], req.target); | |
+ c.req.field[REQ_HOST], c.req.target); | |
cleanup: | |
/* clean up and finish */ | |
- shutdown(infd, SHUT_RD); | |
- shutdown(infd, SHUT_WR); | |
- close(infd); | |
+ shutdown(c.fd, SHUT_RD); | |
+ shutdown(c.fd, SHUT_WR); | |
+ close(c.fd); | |
} | |
static void |