diff options
-rw-r--r-- | FEATURES | 2 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | hittpd.c | 162 |
4 files changed, 120 insertions, 48 deletions
diff --git a/FEATURES b/FEATURES index 5524dd8..8afda8a 100644 --- a/FEATURES +++ b/FEATURES @@ -6,7 +6,7 @@ Features of hittpd: - HTTP 1.0 and HTTP 1.1 support - Ranges (basic variants only) - If-Modified-Since -- Keep-Alive +- Keep-Alive (but no pipelining) - IPv6 - sendfile(2) on Linux - Virtual Hosts diff --git a/LICENSE b/LICENSE index 20471fb..cb12f14 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020 Leah Neukirchen <leah@vuxu.org> +Copyright 2020, 2021, 2022 Leah Neukirchen <leah@vuxu.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/Makefile b/Makefile index 4203428..2253be0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ALL=hittpd OBJ=hittpd.o http-parser/http_parser.o -CFLAGS=-g -O2 -Wall -Wno-switch -Wextra -Wwrite-strings +CFLAGS=-g -O2 -Wall -Wno-switch -Wextra -Wwrite-strings -Werror=incompatible-pointer-types CPPFLAGS=-Ihttp-parser DESTDIR= diff --git a/hittpd.c b/hittpd.c index 1f943de..7a979a4 100644 --- a/hittpd.c +++ b/hittpd.c @@ -1,6 +1,6 @@ /* hittpd - efficient, no-frills HTTP 1.1 server */ -/* Copyright 2020 Leah Neukirchen <leah@vuxu.org> +/* Copyright 2020, 2021, 2022 Leah Neukirchen <leah@vuxu.org> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -93,6 +93,7 @@ char mimetypes[] = ":.ico=image/x-icon"; const char *default_mimetype = "application/octet-stream"; +const char index_html[] = "index.html"; char default_vhost[] = "_default"; char default_port[] = "80"; @@ -105,6 +106,15 @@ int only_public = 0; int reuse_port = 0; const char *custom_mimetypes = ""; +time_t now; +char timestamp[64]; + +struct pollfd client[MAX_CLIENTS]; +struct http_parser parsers[MAX_CLIENTS]; +struct conn_data datas[MAX_CLIENTS]; + +volatile sig_atomic_t stop; + static int on_url(http_parser *p, const char *s, size_t l) { @@ -140,8 +150,8 @@ on_header_field(http_parser *p, const char *s, size_t l) int scan_int64(const char **s, int64_t *u) { const char *t = *s; - long x; - for (x = 0; *t && (unsigned)(*t)-'0' < 10 && x <= LLONG_MAX/10 - 1; t++) + int64_t x; + for (x = 0; *t && (unsigned)(*t)-'0' < 10 && x <= INT64_MAX/10 - 1; t++) x = x * 10 + ((*t)-'0'); if (t != *s) { *s = t; @@ -245,9 +255,6 @@ content_length(struct conn_data *data) return data->last - data->first; } -time_t now; -char timestamp[64]; - void accesslog(http_parser *p, int status) { @@ -256,14 +263,18 @@ accesslog(http_parser *p, int status) struct conn_data *data = p->data; - char buf[64]; - strftime(buf, 64, "[%d/%b/%Y:%H:%M:%S %z]", localtime(&now)); + if (!data->path) + return; + + char logtimestamp[64]; + strftime(logtimestamp, sizeof logtimestamp, + "[%d/%b/%Y:%H:%M:%S %z]", localtime(&now)); // REMOTEHOST - - [DD/MON/YYYY:HH:MM:SS -TZ] "METHOD PATH" STATUS BYTES // ? REFERER USER_AGENT printf("%s - - %s \"%s ", peername(data->fd), - buf, + logtimestamp, http_method_str(p->method)); for (char *s = data->path; *s; s++) @@ -342,6 +353,11 @@ send_error(http_parser *p, int status, const char *msg) char content[512]; snprintf(content, sizeof content, "%03d %s\r\n", status, msg); + if (p->http_major == 0) { + p->http_major = 1; + p->http_minor = 0; + } + send_response(p, status, msg, "", content); return 0; @@ -448,31 +464,46 @@ send_ok(http_parser *p, time_t modified, const char *mimetype, off_t filesize) (intmax_t)data->last - 1, (intmax_t)filesize, lastmod); - send_response(p, 216, "Partial Content", headers, 0); + send_response(p, 206, "Partial Content", headers, 0); } } +static const char * +getmime(const char *mime, char *ext) +{ + size_t extlen = strlen(ext); + const char *c = mime; + + do { + if (*c == ':') + c++; + if (strncmp(c, ext, extlen) == 0 && c[extlen] == '=') + return c + extlen + 1; + } while ((c = strchr(c, ':'))); + + return 0; +} + const char * mimetype(char *ext) { - static char type[16]; + static char type[256]; // RFC 6838 if (!ext) return default_mimetype; - char *x = strstr(custom_mimetypes, ext); + const char *x = getmime(custom_mimetypes, ext); if (!x) - x = strstr(mimetypes, ext); - - if (x && x[-1] == ':' && x[strlen(ext)] == '=') { - char *t = type; - for (char *c = x + strlen(ext) + 1; *c && *c != ':'; ) - *t++ = *c++; - *t = 0; - return type; - } + x = getmime(mimetypes, ext); + if (!x) + return default_mimetype; - return default_mimetype; + char *t = type; + char *e = type + sizeof type - 1; + for (const char *c = x; t < e && *c && *c != ':'; ) + *t++ = *c++; + *t = 0; + return type; } static inline int @@ -493,6 +524,7 @@ on_message_complete(http_parser *p) { struct conn_data *data = p->data; data->state = SENDING; + http_parser_pause(p, 1); // can't do more than one request at a time if (p->http_major == 0 && p->http_minor == 9) return send_error(p, 400, "Bad Request"); @@ -599,7 +631,11 @@ on_message_complete(http_parser *p) { if (S_ISDIR(st.st_mode)) { int x; if (path[strlen(path)-1] == '/' && - (x = openat(stream_fd, "index.html", O_RDONLY)) >= 0) { + (x = openat(stream_fd, index_html, O_RDONLY)) >= 0) { + if (strlen(path) > PATH_MAX - sizeof index_html) { + return send_error(p, 413, "Payload Too Large"); + } + strcat(path, index_html); close(stream_fd); stream_fd = x; if (fstat(stream_fd, &st) < 0) @@ -651,6 +687,9 @@ on_message_complete(http_parser *p) { if (fstatat(stream_fd, file, &ist, AT_SYMLINK_NOFOLLOW) < 0) continue; + if (only_public && !(ist.st_mode & S_IROTH)) + continue; + fprintf(stream, "<a href=\""); print_urlencoded(stream, file); fprintf(stream, "%s\">", @@ -662,14 +701,14 @@ on_message_complete(http_parser *p) { int len = strlen(file) + !!S_ISDIR(ist.st_mode); fprintf(stream, "%-*.*s ", 48 - len, 48 - len, ""); - char timestamp[64]; - strftime(timestamp, sizeof timestamp, + char filetimestamp[64]; + strftime(filetimestamp, sizeof filetimestamp, "%Y-%m-%d %H:%M", localtime(&ist.st_mtime)); if (S_ISDIR(ist.st_mode)) - fprintf(stream, " %s %12s\n", timestamp, "-"); + fprintf(stream, " %s %12s\n", filetimestamp, "-"); else - fprintf(stream, " %s %12jd\n", timestamp, + fprintf(stream, " %s %12jd\n", filetimestamp, (intmax_t)ist.st_size); } @@ -728,6 +767,11 @@ file: if (data->last > st.st_size) data->last = st.st_size; + if (data->first == data->last) { + send_rns(p, st.st_size); + return 0; + } + send_ok(p, st.st_mtime, mimetype(ext), st.st_size); // XXX send short file directly? @@ -742,10 +786,6 @@ static http_parser_settings settings = { .on_url = on_url, }; -struct pollfd client[MAX_CLIENTS]; -struct http_parser parsers[MAX_CLIENTS]; -struct conn_data datas[MAX_CLIENTS]; - void close_connection(int i) { @@ -759,6 +799,7 @@ close_connection(int i) free(datas[i].host); datas[i] = (struct conn_data){ 0 }; + datas[i].stream_fd = -1; } void @@ -778,6 +819,11 @@ finish_response(int i) datas[i].ims = 0; datas[i].host = 0; + datas[i].off = 0; + datas[i].first = 0; + datas[i].last = -1; + datas[i].state = NONE; + client[i].events = POLLRDNORM; if (parsers[i].flags & F_CONNECTION_CLOSE) @@ -816,7 +862,21 @@ write_client(int i) ssize_t w = 0; if (data->stream_fd >= 0) { -#ifndef __linux__ +#ifdef __linux__ + w = sendfile(sockfd, data->stream_fd, + &(data->off), data->last - data->off); + if (data->off == data->last) { + finish_response(i); + return; + } else if (w == 0) { + close_connection(i); // file was truncated! + return; + } else if (w > 0) { + return; + } + + /* use default code when sendfile failed with w < 0 */ +#endif char buf[16*4096]; ssize_t n = pread(data->stream_fd, buf, sizeof buf, data->off); if (n < 0) { @@ -834,14 +894,6 @@ write_client(int i) else if (w == 0) close_connection(i); // file was truncated! } -#else - w = sendfile(sockfd, data->stream_fd, - &(data->off), data->last - data->off); - if (data->off == data->last) - finish_response(i); - else if (w == 0) - close_connection(i); // file was truncated! -#endif } else if (data->buf) { if (data->off == data->last) { finish_response(i); @@ -882,7 +934,24 @@ read_client(int i) } else if (n == 0) { close_connection(i); } else { - http_parser_execute(&parsers[i], &settings, buf, n); + size_t r = http_parser_execute(&parsers[i], &settings, buf, n); + + if ((size_t)n == r && + HTTP_PARSER_ERRNO(&parsers[i]) == HPE_PAUSED) { + // we handled a complete request, we can reuse + // the parser + http_parser_pause(&parsers[i], 0); + } else if (HTTP_PARSER_ERRNO(&parsers[i]) > 0) { + send_error(&parsers[i], 400, "Bad Request"); + close_connection(i); + } else { + // the read data was longer than a single request + // drop the rest and make sure we close the connection, + // so the client tries without pipelining + // (this is conformant) + http_parser_pause(&parsers[i], 0); + parsers[i].flags |= F_CONNECTION_CLOSE; + } if (parsers[i].http_errno) { printf("err=%s\n", @@ -902,8 +971,6 @@ read_client(int i) } } -sig_atomic_t stop; - void do_stop(int sig) { @@ -1108,8 +1175,13 @@ main(int argc, char *argv[]) accept_client(i, connfd); break; } - if (i == MAX_CLIENTS) + if (i == MAX_CLIENTS) { printf("too many clients\n"); + int connfd = accept(listenfd, 0, 0); + if (connfd >= 0) + close(connfd); + continue; + } if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) @@ -1131,7 +1203,7 @@ main(int argc, char *argv[]) if (--nready <= 0) break; /* no more readable descriptors */ } - else if (client[i].revents & (POLLRDNORM | POLLERR)) { + else if (client[i].revents & (POLLRDNORM | POLLHUP | POLLERR)) { read_client(i); datas[i].deadline = now + TIMEOUT; |