commit 9743a167df9eb1006479b005e4e485b41f1c2fc7
parent d61962c084ef57650abb108343033c8c3d75426a
Author: Anton Kling <anton@kling.gg>
Date: Wed, 2 Feb 2022 15:57:39 +0100
Changed formatting again.
Diffstat:
M | smol_http.c | | | 616 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
1 file changed, 302 insertions(+), 314 deletions(-)
diff --git a/smol_http.c b/smol_http.c
@@ -48,24 +48,24 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#endif
#define COND_PERROR_EXP(condition, function_name, expression) \
- if (condition) { \
- perror(function_name); \
- expression; \
- }
+ if (condition) { \
+ perror(function_name); \
+ expression; \
+ }
#define HAS_PLEDGE (__OpenBSD__ | __SerenityOS__)
#define HAS_UNVEIL (__OpenBSD__ | __SerenityOS__)
#if HAS_PLEDGE
#define PLEDGE(promise, exec) \
- COND_PERROR_EXP(-1 == pledge(promise, exec), "pledge", exit(1))
+ COND_PERROR_EXP(-1 == pledge(promise, exec), "pledge", exit(1))
#else
#define PLEDGE(promise, exec) ;
#endif
#if HAS_UNVEIL
#define UNVEIL(path, permissions) \
- COND_PERROR_EXP(-1 == unveil(path, permissions), "unveil", exit(1))
+ COND_PERROR_EXP(-1 == unveil(path, permissions), "unveil", exit(1))
#else
#define UNVEIL(path, permissions) ;
#endif
@@ -73,339 +73,327 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#define ASSERT_NOT_REACHED assert(0)
#define COPYRIGHT_STATEMENT \
- "\
+ "\
This program is licensed under Affero GNU Public License Version 3.\n\
See https://www.gnu.org/licenses/ for more information.\n\
THIS PROGRAM COMES WITHOUT ANY WARRANTY; without even the implied\n\
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
static const struct {
- char *ext;
- char *type;
+ char *ext;
+ char *type;
} mimes[] = {
- { "xml", "application/xml; charset=utf-8" },
- { "xhtml", "application/xhtml+xml; charset=utf-8" },
- { "html", "text/html; charset=utf-8" },
- { "htm", "text/html; charset=utf-8" },
- { "css", "text/css; charset=utf-8" },
- { "txt", "text/plain; charset=utf-8" },
- { "md", "text/plain; charset=utf-8" },
- { "c", "text/plain; charset=utf-8" },
- { "h", "text/plain; charset=utf-8" },
- { "gz", "application/x-gtar" },
- { "tar", "application/tar" },
- { "pdf", "application/x-pdf" },
- { "png", "image/png" },
- { "gif", "image/gif" },
- { "jpeg", "image/jpg" },
- { "jpg", "image/jpg" },
- { "iso", "application/x-iso9660-image" },
- { "webp", "image/webp" },
- { "svg", "image/svg+xml; charset=utf-8" },
- { "flac", "audio/flac" },
- { "mp3", "audio/mpeg" },
- { "ogg", "audio/ogg" },
- { "mp4", "video/mp4" },
- { "ogv", "video/ogg" },
- { "webm", "video/webm" },
+ {"xml", "application/xml; charset=utf-8"},
+ {"xhtml", "application/xhtml+xml; charset=utf-8"},
+ {"html", "text/html; charset=utf-8"},
+ {"htm", "text/html; charset=utf-8"},
+ {"css", "text/css; charset=utf-8"},
+ {"txt", "text/plain; charset=utf-8"},
+ {"md", "text/plain; charset=utf-8"},
+ {"c", "text/plain; charset=utf-8"},
+ {"h", "text/plain; charset=utf-8"},
+ {"gz", "application/x-gtar"},
+ {"tar", "application/tar"},
+ {"pdf", "application/x-pdf"},
+ {"png", "image/png"},
+ {"gif", "image/gif"},
+ {"jpeg", "image/jpg"},
+ {"jpg", "image/jpg"},
+ {"iso", "application/x-iso9660-image"},
+ {"webp", "image/webp"},
+ {"svg", "image/svg+xml; charset=utf-8"},
+ {"flac", "audio/flac"},
+ {"mp3", "audio/mpeg"},
+ {"ogg", "audio/ogg"},
+ {"mp4", "video/mp4"},
+ {"ogv", "video/ogg"},
+ {"webm", "video/webm"},
};
-const char *const get_mime(const char *file)
-{
- for (; *file && *file++ != '.';)
- ;
- for (size_t i = 0; i < sizeof(mimes) / sizeof(mimes[0]) - 1; i++)
- if (0 == strcmp(mimes[i].ext, file))
- return mimes[i].type;
+const char *const get_mime(const char *file) {
+ for (; *file && *file++ != '.';)
+ ;
+ for (size_t i = 0; i < sizeof(mimes) / sizeof(mimes[0]) - 1; i++)
+ if (0 == strcmp(mimes[i].ext, file))
+ return mimes[i].type;
- return "application/octet-stream";
+ return "application/octet-stream";
}
-const char *const status_code_to_error_message(uint16_t status_code)
-{
- switch (status_code) {
- case 400:
- return "400 Bad Request";
- case 404:
- return "404 File Not Found";
- case 200:
- default:
- return "200 OK";
- }
+const char *const status_code_to_error_message(uint16_t status_code) {
+ switch (status_code) {
+ case 400:
+ return "400 Bad Request";
+ case 404:
+ return "404 File Not Found";
+ case 200:
+ default:
+ return "200 OK";
+ }
}
-void connection_handler(int socket_desc)
-{
- PLEDGE("stdio rpath", "");
- char recv_buffer[MAX_BUFFER];
- int status_code = 200;
-
- // We can ignore SIGPIPE as we already have checks
- // that would deal with this.
- COND_PERROR_EXP(SIG_ERR == signal(SIGPIPE, SIG_IGN), "signal",
- goto cleanup);
-
- // Ensure that we timeout should the send/recv take too long.
- struct timeval timeout;
- timeout.tv_sec = TIMEOUT_SECOND;
- timeout.tv_usec = TIMEOUT_USECOND;
- COND_PERROR_EXP(-1 == setsockopt(socket_desc, SOL_SOCKET, SO_RCVTIMEO,
- &timeout, sizeof(timeout)),
- "setsockopt", goto cleanup);
- COND_PERROR_EXP(-1 == setsockopt(socket_desc, SOL_SOCKET, SO_SNDTIMEO,
- &timeout, sizeof(timeout)),
- "setsockopt", goto cleanup);
-
- ssize_t recv_size;
- COND_PERROR_EXP(-1 == (recv_size = recv(socket_desc, recv_buffer,
- MAX_BUFFER - 1, 0)),
- "recv", goto cleanup);
- // Null terminate the request.
- recv_buffer[recv_size] = 0;
-
- char *filename = recv_buffer;
- // Get to the second argument in the buffer.
- for (; *filename && *filename++ != ' ';)
- ;
-
- // If we had only one argument then just provide 400.html.
- if (!(*filename)) {
- filename = "/400.html";
- status_code = 400;
- goto skip_filename_parse;
- }
-
- int enter_directory = 0;
- uint16_t i;
- for (i = 0; filename[i] && ' ' != filename[i] && '\n' != filename[i] &&
- '\r' != filename[i];
- i++)
- ;
-
- filename[i] = 0;
-
- struct stat statbuf;
- if (-1 == stat(filename, &statbuf)) {
- if (ENOENT == errno)
- goto not_found;
- goto cleanup;
- }
- if (S_ISDIR(statbuf.st_mode)) {
- enter_directory = 1;
- chdir(filename);
- filename = "index.html";
- }
-
- int fd;
- char *const_site_content;
+void connection_handler(int socket_desc) {
+ PLEDGE("stdio rpath", "");
+ char recv_buffer[MAX_BUFFER];
+ int status_code = 200;
+
+ // We can ignore SIGPIPE as we already have checks
+ // that would deal with this.
+ COND_PERROR_EXP(SIG_ERR == signal(SIGPIPE, SIG_IGN), "signal",
+ goto cleanup);
+
+ // Ensure that we timeout should the send/recv take too long.
+ struct timeval timeout;
+ timeout.tv_sec = TIMEOUT_SECOND;
+ timeout.tv_usec = TIMEOUT_USECOND;
+ COND_PERROR_EXP(-1 == setsockopt(socket_desc, SOL_SOCKET, SO_RCVTIMEO,
+ &timeout, sizeof(timeout)),
+ "setsockopt", goto cleanup);
+ COND_PERROR_EXP(-1 == setsockopt(socket_desc, SOL_SOCKET, SO_SNDTIMEO,
+ &timeout, sizeof(timeout)),
+ "setsockopt", goto cleanup);
+
+ ssize_t recv_size;
+ COND_PERROR_EXP(
+ -1 == (recv_size = recv(socket_desc, recv_buffer, MAX_BUFFER - 1, 0)),
+ "recv", goto cleanup);
+ // Null terminate the request.
+ recv_buffer[recv_size] = 0;
+
+ char *filename = recv_buffer;
+ // Get to the second argument in the buffer.
+ for (; *filename && *filename++ != ' ';)
+ ;
+
+ // If we had only one argument then just provide 400.html.
+ if (!(*filename)) {
+ filename = "/400.html";
+ status_code = 400;
+ goto skip_filename_parse;
+ }
+
+ int enter_directory = 0;
+ uint16_t i;
+ for (i = 0; filename[i] && ' ' != filename[i] && '\n' != filename[i] &&
+ '\r' != filename[i];
+ i++)
+ ;
+
+ filename[i] = 0;
+
+ struct stat statbuf;
+ if (-1 == stat(filename, &statbuf)) {
+ if (ENOENT == errno)
+ goto not_found;
+ goto cleanup;
+ }
+ if (S_ISDIR(statbuf.st_mode)) {
+ enter_directory = 1;
+ chdir(filename);
+ filename = "index.html";
+ }
+
+ int fd;
+ char *const_site_content;
skip_filename_parse:
redo:
- const_site_content = NULL;
- if (-1 == (fd = open(filename, O_RDONLY))) {
- if (1 == enter_directory) {
- enter_directory = 2;
- goto write;
- }
-
- if (400 == status_code) {
- const_site_content = DEFAULT_400_SITE;
- goto write;
- }
-
- if (0 == strcmp(filename, "/404.html") && 404 == status_code) {
- const_site_content = DEFAULT_404_SITE;
- goto write;
- }
-
- not_found:
- filename = "/404.html";
- status_code = 404;
- goto redo;
- }
+ const_site_content = NULL;
+ if (-1 == (fd = open(filename, O_RDONLY))) {
+ if (1 == enter_directory) {
+ enter_directory = 2;
+ goto write;
+ }
+
+ if (400 == status_code) {
+ const_site_content = DEFAULT_400_SITE;
+ goto write;
+ }
+
+ if (0 == strcmp(filename, "/404.html") && 404 == status_code) {
+ const_site_content = DEFAULT_404_SITE;
+ goto write;
+ }
+
+ not_found:
+ filename = "/404.html";
+ status_code = 404;
+ goto redo;
+ }
write:
- if (0 >
- dprintf(socket_desc,
- "HTTP/1.0 %s\r\nContent-Type: %s\r\nServer: smol_http\r\n\r\n",
- status_code_to_error_message(status_code),
- get_mime(filename))) {
- puts("dprintf error");
- goto cleanup;
- }
-
- if (const_site_content) {
- PLEDGE("stdio", NULL);
- COND_PERROR_EXP(-1 == write(socket_desc, const_site_content,
- strlen(const_site_content)),
- "write",
- /*NOP*/);
- goto cleanup;
- }
-
- // Should ./index.html be unable to be read we create a
- // directory listing.
- if (2 == enter_directory) {
- // Get the directory contents and provide that to the client.
- DIR *d;
- COND_PERROR_EXP(NULL == (d = opendir(".")), "opendir",
- goto cleanup);
-
- char current_path[PATH_MAX];
- char back_path[PATH_MAX];
- COND_PERROR_EXP(!realpath(".", current_path), "realpath",
- goto directory_cleanup)
- COND_PERROR_EXP(!realpath("..", back_path), "realpath",
- goto directory_cleanup)
- if (0 >
- dprintf(socket_desc,
- "Index of %s/<br><a href='%s'>./</a><br><a href='%s'>../</a><br>",
- current_path, current_path, back_path)) {
- puts("dprintf error");
- goto directory_cleanup;
- }
- for (struct dirent *dir; (dir = readdir(d));) {
- if (0 == strcmp(dir->d_name, ".") ||
- 0 == strcmp(dir->d_name, ".."))
- continue;
-
- char tmp_path[PATH_MAX];
- COND_PERROR_EXP(!realpath(dir->d_name, tmp_path),
- "realpath", break);
- if (0 > dprintf(socket_desc,
- "<a href='%s'>%s%s</a><br>", tmp_path,
- dir->d_name,
- (DT_DIR == dir->d_type) ? "/" : "")) {
- puts("dprintf error");
- break;
- }
- }
- directory_cleanup:
- closedir(d);
- goto cleanup;
- }
- PLEDGE("stdio", NULL);
-
- char rwbuf[4096];
- for (int l; 0 != (l = read(fd, rwbuf, sizeof(rwbuf)));) {
- COND_PERROR_EXP(-1 == l, "read", break);
- COND_PERROR_EXP(-1 == write(socket_desc, rwbuf, l), "write",
- break);
- }
-
- close(fd);
+ if (0 >
+ dprintf(socket_desc,
+ "HTTP/1.0 %s\r\nContent-Type: %s\r\nServer: smol_http\r\n\r\n",
+ status_code_to_error_message(status_code),
+ get_mime(filename))) {
+ puts("dprintf error");
+ goto cleanup;
+ }
+
+ if (const_site_content) {
+ PLEDGE("stdio", NULL);
+ COND_PERROR_EXP(-1 == write(socket_desc, const_site_content,
+ strlen(const_site_content)),
+ "write",
+ /*NOP*/);
+ goto cleanup;
+ }
+
+ // Should ./index.html be unable to be read we create a
+ // directory listing.
+ if (2 == enter_directory) {
+ // Get the directory contents and provide that to the client.
+ DIR *d;
+ COND_PERROR_EXP(NULL == (d = opendir(".")), "opendir", goto cleanup);
+
+ char current_path[PATH_MAX];
+ char back_path[PATH_MAX];
+ COND_PERROR_EXP(!realpath(".", current_path), "realpath",
+ goto directory_cleanup)
+ COND_PERROR_EXP(!realpath("..", back_path), "realpath",
+ goto directory_cleanup)
+ if (0 > dprintf(socket_desc,
+ "Index of %s/<br><a href='%s'>./</a><br><a "
+ "href='%s'>../</a><br>",
+ current_path, current_path, back_path)) {
+ puts("dprintf error");
+ goto directory_cleanup;
+ }
+ for (struct dirent *dir; (dir = readdir(d));) {
+ if (0 == strcmp(dir->d_name, ".") || 0 == strcmp(dir->d_name, ".."))
+ continue;
+
+ char tmp_path[PATH_MAX];
+ COND_PERROR_EXP(!realpath(dir->d_name, tmp_path), "realpath",
+ break);
+ if (0 > dprintf(socket_desc, "<a href='%s'>%s%s</a><br>", tmp_path,
+ dir->d_name, (DT_DIR == dir->d_type) ? "/" : "")) {
+ puts("dprintf error");
+ break;
+ }
+ }
+ directory_cleanup:
+ closedir(d);
+ goto cleanup;
+ }
+ PLEDGE("stdio", NULL);
+
+ char rwbuf[4096];
+ for (int l; 0 != (l = read(fd, rwbuf, sizeof(rwbuf)));) {
+ COND_PERROR_EXP(-1 == l, "read", break);
+ COND_PERROR_EXP(-1 == write(socket_desc, rwbuf, l), "write", break);
+ }
+
+ close(fd);
cleanup:
- PLEDGE("", NULL);
- close(socket_desc);
+ PLEDGE("", NULL);
+ close(socket_desc);
}
-int drop_root_privleges(void)
-{
- COND_PERROR_EXP(0 != seteuid(getuid()), "seteuid", return 0);
- COND_PERROR_EXP(0 != setegid(getgid()), "setegid", return 0);
- if (0 == geteuid()) {
- fprintf(stderr,
- "Error: Program can not be ran by a root user.\n");
- return 0;
- }
- return 1;
+int drop_root_privleges(void) {
+ COND_PERROR_EXP(0 != seteuid(getuid()), "seteuid", return 0);
+ COND_PERROR_EXP(0 != setegid(getgid()), "setegid", return 0);
+ if (0 == geteuid()) {
+ fprintf(stderr, "Error: Program can not be ran by a root user.\n");
+ return 0;
+ }
+ return 1;
}
-int init_server(short port, const char *website_root)
-{
- int socket_desc, new_socket;
- struct sockaddr_in server;
- struct sockaddr client;
- socklen_t c;
-
- UNVEIL(website_root, "r");
- // Disable usage of unveil()(this will also be
- // done by our pledge() call)
- UNVEIL(NULL, NULL);
- COND_PERROR_EXP(0 != chroot(website_root), "chroot", return 1);
- PLEDGE("stdio inet rpath exec id proc", "");
-
- // I am unsure if chdir("/") even can fail.
- // But I will keep this check here just in case.
- COND_PERROR_EXP(0 != chdir("/"), "chdir", return 1);
-
- COND_PERROR_EXP(-1 == (socket_desc = socket(AF_INET, SOCK_STREAM, 0)),
- "socket", return 1);
-
- server.sin_family = AF_INET;
- server.sin_addr.s_addr = INADDR_ANY;
- server.sin_port = htons(port);
-
- COND_PERROR_EXP(0 > bind(socket_desc, (struct sockaddr *)&server,
- sizeof(server)),
- "bind", return 1);
-
- // Everything that requires root privleges is done,
- // we can now drop privleges.
- if (!drop_root_privleges()) {
- fprintf(stderr, "Unable to drop privleges.\n");
- return 1;
- }
-
- PLEDGE("stdio inet rpath exec proc", NULL);
-
- COND_PERROR_EXP(0 != listen(socket_desc, 3), "listen", return 1);
-
- c = sizeof(struct sockaddr_in);
- for (; (new_socket = accept(socket_desc, &client, &c));) {
- COND_PERROR_EXP(-1 == new_socket, "accept", continue);
-
- // Create a child and handle the connection
- pid_t pid;
- COND_PERROR_EXP(-1 == (pid = fork()), "fork", continue);
- if (0 != pid) // We are the parent.
- {
- close(new_socket);
- continue;
- }
- // We are the child.
- connection_handler(new_socket);
- close(socket_desc);
- _exit(0);
- }
- close(socket_desc);
- return 0;
+int init_server(short port, const char *website_root) {
+ int socket_desc, new_socket;
+ struct sockaddr_in server;
+ struct sockaddr client;
+ socklen_t c;
+
+ UNVEIL(website_root, "r");
+ // Disable usage of unveil()(this will also be
+ // done by our pledge() call)
+ UNVEIL(NULL, NULL);
+ COND_PERROR_EXP(0 != chroot(website_root), "chroot", return 1);
+ PLEDGE("stdio inet rpath exec id proc", "");
+
+ // I am unsure if chdir("/") even can fail.
+ // But I will keep this check here just in case.
+ COND_PERROR_EXP(0 != chdir("/"), "chdir", return 1);
+
+ COND_PERROR_EXP(-1 == (socket_desc = socket(AF_INET, SOCK_STREAM, 0)),
+ "socket", return 1);
+
+ server.sin_family = AF_INET;
+ server.sin_addr.s_addr = INADDR_ANY;
+ server.sin_port = htons(port);
+
+ COND_PERROR_EXP(
+ 0 > bind(socket_desc, (struct sockaddr *)&server, sizeof(server)),
+ "bind", return 1);
+
+ // Everything that requires root privleges is done,
+ // we can now drop privleges.
+ if (!drop_root_privleges()) {
+ fprintf(stderr, "Unable to drop privleges.\n");
+ return 1;
+ }
+
+ PLEDGE("stdio inet rpath exec proc", NULL);
+
+ COND_PERROR_EXP(0 != listen(socket_desc, 3), "listen", return 1);
+
+ c = sizeof(struct sockaddr_in);
+ for (; (new_socket = accept(socket_desc, &client, &c));) {
+ COND_PERROR_EXP(-1 == new_socket, "accept", continue);
+
+ // Create a child and handle the connection
+ pid_t pid;
+ COND_PERROR_EXP(-1 == (pid = fork()), "fork", continue);
+ if (0 != pid) // We are the parent.
+ {
+ close(new_socket);
+ continue;
+ }
+ // We are the child.
+ connection_handler(new_socket);
+ close(socket_desc);
+ _exit(0);
+ }
+ close(socket_desc);
+ return 0;
}
-void usage(const char *const str)
-{
- fprintf(stderr,
- "Usage: %s [-p PORT] [-d Website root directory] -h(Print this message)\n",
- str);
- puts("---");
- puts(COPYRIGHT_STATEMENT);
+void usage(const char *const str) {
+ fprintf(stderr,
+ "Usage: %s [-p PORT] [-d Website root directory] -h(Print this "
+ "message)\n",
+ str);
+ puts("---");
+ puts(COPYRIGHT_STATEMENT);
}
-int main(int argc, char **argv)
-{
- if (0 != geteuid()) {
- fprintf(stderr, "Error: Program does not have root privleges.");
- return 1;
- }
-
- short port = DEFAULT_PORT;
- char *website_root = WEBSITE_ROOT;
- for (int ch; - 1 != (ch = getopt(argc, argv, "p:d:h"));)
- switch ((char)ch) {
- case 'p':
- if (0 == (port = atoi(optarg))) {
- usage(argv[0]);
- return 0;
- }
- break;
- case 'd':
- website_root = optarg;
- break;
- case '?':
- case ':':
- case 'h':
- usage(argv[0]);
- return 0;
- }
-
- return init_server(port, website_root);
+int main(int argc, char **argv) {
+ if (0 != geteuid()) {
+ fprintf(stderr, "Error: Program does not have root privleges.");
+ return 1;
+ }
+
+ short port = DEFAULT_PORT;
+ char *website_root = WEBSITE_ROOT;
+ for (int ch; - 1 != (ch = getopt(argc, argv, "p:d:h"));)
+ switch ((char)ch) {
+ case 'p':
+ if (0 == (port = atoi(optarg))) {
+ usage(argv[0]);
+ return 0;
+ }
+ break;
+ case 'd':
+ website_root = optarg;
+ break;
+ case '?':
+ case ':':
+ case 'h':
+ usage(argv[0]);
+ return 0;
+ }
+
+ return init_server(port, website_root);
}