Add connection struct

This struct contains the request and response structs, represents a state
and has some utility-buffers.

Signed-off-by: Laslo Hunhold <dev@frign.de>
This commit is contained in:
Laslo Hunhold 2020-08-22 09:24:57 +02:00
parent 6d2fe7f29e
commit c1b242e405
No known key found for this signature in database
GPG key ID: 69576BD24CFCB980
3 changed files with 86 additions and 56 deletions

84
http.c
View file

@ -144,7 +144,7 @@ decode(const char src[PATH_MAX], char dest[PATH_MAX])
} }
int int
http_get_request(struct request *req) http_get_request(int fd, struct request *req)
{ {
struct in6_addr addr; struct in6_addr addr;
size_t hlen, i, mlen; size_t hlen, i, mlen;
@ -158,8 +158,8 @@ http_get_request(struct request *req)
* receive header * receive header
*/ */
for (hlen = 0; ;) { for (hlen = 0; ;) {
if ((off = read(req->fd, h + hlen, sizeof(h) - hlen)) < 0) { if ((off = read(fd, h + hlen, sizeof(h) - hlen)) < 0) {
return http_send_status(req->fd, S_REQUEST_TIMEOUT); return http_send_status(fd, S_REQUEST_TIMEOUT);
} else if (off == 0) { } else if (off == 0) {
break; break;
} }
@ -168,13 +168,13 @@ http_get_request(struct request *req)
break; break;
} }
if (hlen == sizeof(h)) { 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 */ /* remove terminating empty line */
if (hlen < 2) { if (hlen < 2) {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
hlen -= 2; hlen -= 2;
@ -194,12 +194,12 @@ http_get_request(struct request *req)
} }
} }
if (i == NUM_REQ_METHODS) { 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 */ /* a single space must follow the method */
if (h[mlen] != ' ') { if (h[mlen] != ' ') {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
/* basis for next step */ /* basis for next step */
@ -207,11 +207,11 @@ http_get_request(struct request *req)
/* TARGET */ /* TARGET */
if (!(q = strchr(p, ' '))) { if (!(q = strchr(p, ' '))) {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
*q = '\0'; *q = '\0';
if (q - p + 1 > PATH_MAX) { 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); memcpy(req->target, p, q - p + 1);
decode(req->target, req->target); decode(req->target, req->target);
@ -221,18 +221,18 @@ http_get_request(struct request *req)
/* HTTP-VERSION */ /* HTTP-VERSION */
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) { 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; p += sizeof("HTTP/") - 1;
if (strncmp(p, "1.0", sizeof("1.0") - 1) && if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
strncmp(p, "1.1", sizeof("1.1") - 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; p += sizeof("1.*") - 1;
/* check terminator */ /* check terminator */
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) { 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 */ /* basis for next step */
@ -253,7 +253,7 @@ http_get_request(struct request *req)
if (i == NUM_REQ_FIELDS) { if (i == NUM_REQ_FIELDS) {
/* unmatched field, skip this line */ /* unmatched field, skip this line */
if (!(q = strstr(p, "\r\n"))) { 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); p = q + (sizeof("\r\n") - 1);
continue; continue;
@ -263,7 +263,7 @@ http_get_request(struct request *req)
/* a single colon must follow the field name */ /* a single colon must follow the field name */
if (*p != ':') { if (*p != ':') {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
/* skip whitespace */ /* skip whitespace */
@ -272,11 +272,11 @@ http_get_request(struct request *req)
/* extract field content */ /* extract field content */
if (!(q = strstr(p, "\r\n"))) { 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'; *q = '\0';
if (q - p + 1 > FIELD_MAX) { 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); memcpy(req->field[i], p, q - p + 1);
@ -296,7 +296,7 @@ http_get_request(struct request *req)
if (p && (!q || p > q)) { if (p && (!q || p > q)) {
/* port suffix must not be empty */ /* port suffix must not be empty */
if (*(p + 1) == '\0') { if (*(p + 1) == '\0') {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
*p = '\0'; *p = '\0';
} }
@ -305,7 +305,7 @@ http_get_request(struct request *req)
if (q) { if (q) {
/* brackets must be on the outside */ /* brackets must be on the outside */
if (req->field[REQ_HOST][0] != '[' || *(q + 1) != '\0') { 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 */ /* remove the right bracket */
@ -314,7 +314,7 @@ http_get_request(struct request *req)
/* validate the contained IPv6 address */ /* validate the contained IPv6 address */
if (inet_pton(AF_INET6, p, &addr) != 1) { 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 */ /* copy it into the host field */
@ -528,7 +528,7 @@ parse_range(const char *str, size_t size, size_t *lower, size_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(const struct request *req, const struct server *s) http_send_response(int fd, const struct request *req, const struct server *s)
{ {
enum status returnstatus; enum status returnstatus;
struct in6_addr addr; struct in6_addr addr;
@ -553,7 +553,7 @@ http_send_response(const struct request *req, const struct server *s)
if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], 0, if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], 0,
NULL, 0)) { NULL, 0)) {
if (chdir(s->vhost[i].dir) < 0) { if (chdir(s->vhost[i].dir) < 0) {
return http_send_status(req->fd, (errno == EACCES) ? return http_send_status(fd, (errno == EACCES) ?
S_FORBIDDEN : S_NOT_FOUND); S_FORBIDDEN : S_NOT_FOUND);
} }
vhostmatch = s->vhost[i].chost; vhostmatch = s->vhost[i].chost;
@ -561,14 +561,14 @@ http_send_response(const struct request *req, const struct server *s)
} }
} }
if (i == s->vhost_len) { 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 we have a vhost prefix, prepend it to the target */
if (s->vhost[i].prefix) { if (s->vhost[i].prefix) {
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s",
s->vhost[i].prefix, realtarget)) { s->vhost[i].prefix, realtarget)) {
return http_send_status(req->fd, S_REQUEST_TOO_LARGE); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
memcpy(realtarget, tmptarget, sizeof(realtarget)); memcpy(realtarget, tmptarget, sizeof(realtarget));
} }
@ -588,7 +588,7 @@ http_send_response(const struct request *req, const struct server *s)
/* swap out target prefix */ /* swap out target prefix */
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s",
s->map[i].to, realtarget + len)) { s->map[i].to, realtarget + len)) {
return http_send_status(req->fd, S_REQUEST_TOO_LARGE); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
memcpy(realtarget, tmptarget, sizeof(realtarget)); memcpy(realtarget, tmptarget, sizeof(realtarget));
break; break;
@ -597,12 +597,12 @@ http_send_response(const struct request *req, const struct server *s)
/* normalize target */ /* normalize target */
if (normabspath(realtarget)) { if (normabspath(realtarget)) {
return http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
/* stat the target */ /* stat the target */
if (stat(RELPATH(realtarget), &st) < 0) { 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); S_FORBIDDEN : S_NOT_FOUND);
} }
@ -610,7 +610,7 @@ http_send_response(const struct request *req, const struct server *s)
/* add / to target if not present */ /* add / to target if not present */
len = strlen(realtarget); len = strlen(realtarget);
if (len >= PATH_MAX - 2) { 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] != '/') { if (len && realtarget[len - 1] != '/') {
realtarget[len] = '/'; realtarget[len] = '/';
@ -624,7 +624,7 @@ http_send_response(const struct request *req, const struct server *s)
*/ */
if (strstr(realtarget, "/.") && strncmp(realtarget, if (strstr(realtarget, "/.") && strncmp(realtarget,
"/.well-known/", sizeof("/.well-known/") - 1)) { "/.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 */ /* redirect if targets differ, host is non-canonical or we prefixed */
@ -650,7 +650,7 @@ http_send_response(const struct request *req, const struct server *s)
* 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 http_send_status(req->fd, return http_send_status(fd,
S_INTERNAL_SERVER_ERROR); S_INTERNAL_SERVER_ERROR);
} }
@ -662,25 +662,25 @@ http_send_response(const struct request *req, const struct server *s)
targethost, targethost,
ipv6host ? "]" : "", hasport ? ":" : "", ipv6host ? "]" : "", hasport ? ":" : "",
hasport ? s->port : "", tmptarget)) { hasport ? s->port : "", tmptarget)) {
return http_send_status(req->fd, S_REQUEST_TOO_LARGE); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
} else { } else {
/* write relative redirection URL to response struct */ /* write relative redirection URL 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]),
tmptarget)) { tmptarget)) {
return http_send_status(req->fd, S_REQUEST_TOO_LARGE); return http_send_status(fd, S_REQUEST_TOO_LARGE);
} }
} }
return http_send_header(req->fd, &res); return http_send_header(fd, &res);
} }
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
/* append docindex to target */ /* append docindex to target */
if (esnprintf(realtarget, sizeof(realtarget), "%s%s", if (esnprintf(realtarget, sizeof(realtarget), "%s%s",
req->target, s->docindex)) { 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 */ /* stat the docindex, which must be a regular file */
@ -689,13 +689,13 @@ http_send_response(const struct request *req, const struct server *s)
/* remove index suffix and serve dir */ /* remove index suffix and serve dir */
realtarget[strlen(realtarget) - realtarget[strlen(realtarget) -
strlen(s->docindex)] = '\0'; strlen(s->docindex)] = '\0';
return resp_dir(req->fd, RELPATH(realtarget), req); return resp_dir(fd, RELPATH(realtarget), req);
} else { } else {
/* reject */ /* reject */
if (!S_ISREG(st.st_mode) || errno == EACCES) { if (!S_ISREG(st.st_mode) || errno == EACCES) {
return http_send_status(req->fd, S_FORBIDDEN); return http_send_status(fd, S_FORBIDDEN);
} else { } else {
return http_send_status(req->fd, S_NOT_FOUND); return http_send_status(fd, S_NOT_FOUND);
} }
} }
} }
@ -706,13 +706,13 @@ http_send_response(const struct request *req, const struct server *s)
/* 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 http_send_status(req->fd, S_BAD_REQUEST); return http_send_status(fd, S_BAD_REQUEST);
} }
/* 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 http_send_header(req->fd, &res); return http_send_header(fd, &res);
} }
} }
@ -725,13 +725,13 @@ http_send_response(const struct request *req, const struct server *s)
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(req->fd, return http_send_status(fd,
S_INTERNAL_SERVER_ERROR); S_INTERNAL_SERVER_ERROR);
} }
return http_send_header(req->fd, &res); return http_send_header(fd, &res);
} else { } 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 server *s)
} }
} }
return resp_file(req->fd, RELPATH(realtarget), req, &st, mime, lower, upper); return resp_file(fd, RELPATH(realtarget), req, &st, mime, lower, upper);
} }

40
http.h
View file

@ -3,6 +3,7 @@
#define HTTP_H #define HTTP_H
#include <limits.h> #include <limits.h>
#include <sys/stat.h>
#include "util.h" #include "util.h"
@ -27,8 +28,8 @@ enum req_method {
extern const char *req_method_str[]; extern const char *req_method_str[];
struct request { struct request {
int fd; char header[HEADER_MAX]; /* deprecated */
char header[HEADER_MAX];
enum req_method method; enum req_method method;
char target[PATH_MAX]; char target[PATH_MAX];
char field[NUM_REQ_FIELDS][FIELD_MAX]; char field[NUM_REQ_FIELDS][FIELD_MAX];
@ -65,15 +66,46 @@ enum res_field {
extern const char *res_field_str[]; extern const char *res_field_str[];
enum res_type {
RESTYPE_FILE,
RESTYPE_DIR,
NUM_RES_TYPES,
};
struct response { struct response {
enum res_type type;
enum status status; enum status status;
char field[NUM_RES_FIELDS][FIELD_MAX]; 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_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(struct request *); int http_get_request(int fd, struct request *);
enum status http_send_response(const struct request *, enum status http_send_response(int fd, const struct request *,
const struct server *); const struct server *);
#endif /* HTTP_H */ #endif /* HTTP_H */

18
main.c
View file

@ -25,22 +25,20 @@ static char *udsname;
static void static void
serve(int infd, const struct sockaddr_storage *in_sa, const struct server *s) serve(int infd, const struct sockaddr_storage *in_sa, const struct server *s)
{ {
struct request req; struct connection c = { .fd = infd };
time_t t; time_t t;
enum status status; enum status status;
char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
char tstmp[21]; char tstmp[21];
/* set connection timeout */ /* set connection timeout */
if (sock_set_timeout(infd, 30)) { if (sock_set_timeout(c.fd, 30)) {
goto cleanup; goto cleanup;
} }
/* handle request */ /* handle request */
req.fd = infd; if (!(status = http_get_request(c.fd, &c.req))) {
status = http_send_response(c.fd, &c.req, s);
if (!(status = http_get_request(&req))) {
status = http_send_response(&req, s);
} }
/* write output to log */ /* write output to log */
@ -54,12 +52,12 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *s)
goto cleanup; goto cleanup;
} }
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status, 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: cleanup:
/* clean up and finish */ /* clean up and finish */
shutdown(infd, SHUT_RD); shutdown(c.fd, SHUT_RD);
shutdown(infd, SHUT_WR); shutdown(c.fd, SHUT_WR);
close(infd); close(c.fd);
} }
static void static void