#include #include #include #include #include #include #include #include #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); }