diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | plugins/static/static.c | 185 |
2 files changed, 186 insertions, 0 deletions
@@ -3,3 +3,4 @@ cserver *.dSYM *.dylib *.so +compile_commands.json diff --git a/plugins/static/static.c b/plugins/static/static.c new file mode 100644 index 0000000..011a92f --- /dev/null +++ b/plugins/static/static.c @@ -0,0 +1,185 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include "plugin.h" +#include "util.h" + + +static char *static_file_dir = NULL; + + +static bool path_allowed(const char *path) { + char last = '\0'; + for (size_t i = 0; path[i]; i++) { + // no unprintable characters + if (path[i] < 32 || path[i] >= 127) return false; + + // no hidden files or magic directory items like ".." + if (last == '/' && path[i] == '.') return false; + + last = path[i]; + } + + return true; +} + +// Returns pointer to internal buffer, don't free +static const char* make_relative_path(const char *path) { + static char *rel = NULL; + static size_t rel_cap = 0; + + size_t necessary = strlen(static_file_dir) + 1 + strlen(path) + 1; + if (necessary >= rel_cap) { + rel_cap = necessary; + rel = realloc(rel, rel_cap); + assert(rel); + } + + sprintf(rel, "%s/%s", static_file_dir, path); + + return rel; +} + +// If an error occurs, returns -1 and sets errno +static ssize_t query_file_size(const char *path) { + struct stat st; + memset(&st, 0, sizeof st); + if (stat(path, &st) < 0) { + return -1; + } + if (st.st_mode & S_IFREG) { + return st.st_size; + } else { + errno = EISDIR; + return -1; + } +} + +struct Buffer { + char *buf; + size_t cap, len; +}; + +static struct Buffer buffer_make(size_t capacity) { + struct Buffer buffer = (struct Buffer){malloc(capacity), capacity + 1, 0}; + assert(buffer.buf); + return buffer; +} + +static void buffer_free(struct Buffer buffer) { + free(buffer.buf); +} + +static void buffer_append_mem(struct Buffer *buffer, const char *str, size_t len) { + if (buffer->len + len >= buffer->cap) { + do buffer->cap *= 2; while (buffer->len + len >= buffer->cap); + buffer->buf = realloc(buffer->buf, buffer->cap); + assert(buffer->buf); + } + memcpy(buffer->buf + buffer->len, str, len); + buffer->len += len; + buffer->buf[buffer->len] = 0; +} + +static void buffer_append_str(struct Buffer *buffer, const char *str) { + buffer_append_mem(buffer, str, strlen(str)); +} + +// returns whether successful +static bool buffer_append_file(struct Buffer *buffer, const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) return false; + + char block[4096]; + while (true) { + size_t nr = fread(block, 1, sizeof block, f); + if (nr > 0) buffer_append_mem(buffer, block, nr); + if (nr < sizeof block) break; + } + + bool has_error = ferror(f); + fclose(f); + return !has_error; +} + +static struct Buffer build_response_headers(const char *status, const char *content_type, size_t body_len) { + struct Buffer buffer = buffer_make(200 + body_len); + + buffer_append_str(&buffer, "HTTP/1.1 "); + buffer_append_str(&buffer, status); + buffer_append_str(&buffer, "\r\n"); + + if (content_type) { + buffer_append_str(&buffer, "Content-Type: "); + buffer_append_str(&buffer, content_type); + buffer_append_str(&buffer, "\r\n"); + } + + char tempbuf[128]; // size_t is at most 20 chars long + sprintf(tempbuf, "Content-Length: %zu\r\n", body_len); + buffer_append_str(&buffer, tempbuf); + + buffer_append_str(&buffer, "Server: cserver\r\n\r\n"); + + return buffer; +} + +static void send_404(int sock) { + const char *body = "404 Not found"; + struct Buffer buffer = build_response_headers("404 Not found", NULL, strlen(body)); + buffer_append_str(&buffer, body); + sendall(sock, buffer.buf, buffer.len); +} + +static void send_500(int sock) { + const char *body = "500 Internal server error"; + struct Buffer buffer = build_response_headers("500 Internal server error", NULL, strlen(body)); + buffer_append_str(&buffer, body); + sendall(sock, buffer.buf, buffer.len); +} + +static Handler_ret_t connection_handler(int sock, Headers *headers) { + if (!path_allowed(headers->url)) { + send_404(sock); + return HR_HANDLED; + } + + const char *path = make_relative_path(headers->url); + + ssize_t size = query_file_size(path); + if (size < 0) { + if (errno == ENOENT || errno == EISDIR) { + send_404(sock); + return HR_HANDLED; + } else { + send_500(sock); + return HR_HANDLED; + } + } + + struct Buffer buffer = build_response_headers("200 OK", "text/plain; charset=UTF-8", size); + if (!buffer_append_file(&buffer, path)) { + buffer_free(buffer); + send_500(sock); + return HR_HANDLED; + } + + sendall(sock, buffer.buf, buffer.len); + buffer_free(buffer); + return HR_HANDLED; +} + +void plugin_register_yourself(register_callback_t callback) { + static_file_dir = getenv("STATIC_DIR"); + if (!static_file_dir || access(static_file_dir, R_OK | X_OK) < 0) { + fprintf(stderr, "cserver: static: Environment variable $STATIC_DIR not set or directory not readable\n"); + exit(1); + } + + callback("static", &connection_handler); +} |