summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2019-11-23 16:32:29 +0100
committertomsmeding <tom.smeding@gmail.com>2019-11-23 16:33:44 +0100
commit8b91af88666b2289daaf7ca655ab039c8ad9488a (patch)
treea0bd6ea83ca38b3277839a3035ea0c1896fe0137 /plugins
parent6c1ec053b7cdc3ef173ea302cb9e804377f50aaf (diff)
Static file server plugin
Diffstat (limited to 'plugins')
-rw-r--r--plugins/static/static.c185
1 files changed, 185 insertions, 0 deletions
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);
+}