aboutsummaryrefslogtreecommitdiff
path: root/ukulele.c
diff options
context:
space:
mode:
Diffstat (limited to 'ukulele.c')
-rw-r--r--ukulele.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/ukulele.c b/ukulele.c
new file mode 100644
index 0000000..ba854a5
--- /dev/null
+++ b/ukulele.c
@@ -0,0 +1,332 @@
+#define _POSIX_C_SOURCE 200809L
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <glob.h>
+#include <libgen.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <marxup.h>
+
+#include "config.h"
+
+static const char *badchars = "\\?%:|\"<> ";
+
+int writable(char *path) {
+ struct stat s;
+ if(!stat(path, &s)) {
+ if(S_ISREG(s.st_mode))
+ return !eaccess(path, W_OK);
+ else if(S_ISDIR(s.st_mode))
+ return 0;
+ }
+ char *dir = dirname(strdup(path));
+ if(!stat(dir, &s)) {
+ if(S_ISDIR(s.st_mode))
+ return !eaccess(dir, W_OK);
+ }
+ return 0;
+}
+
+void head(char *page, char *link, char *what) {
+ printf("Content-Type: text/html; charset=utf-8\n\n");
+ printf("<!doctype html>\n");
+ printf("<title>%s · %s</title>\n", title, page);
+ printf("<link rel='stylesheet' type='text/css' href='%s/wiki.css'>\n", base);
+ printf("<script src='%s/wiki.js'></script>\n", base);
+ printf("<h1>");
+ if(link && what && !strcmp(what, "rediger"))
+ printf("<a data-text='%s' href='%s'>%s</a>", what, link, page);
+ else if(link && what)
+ printf("<a data-text='%s' href='%s/%s'>%s</a>", what, base, link, page);
+ else
+ printf("%s", page);
+ printf("</h1>\n");
+}
+
+void foot(int file) {
+ printf("<hr>\n");
+ printf("<p>");
+ for(int i = 0; *links[i]; i++) {
+ if(*links[i][1] == '/' || strstr(links[i][1], "://"))
+ printf("<a href='%s'>%s</a> ", links[i][1], links[i][0]);
+ else
+ printf("<a href='%s/%s'>%s</a> ", script, links[i][1], links[i][0]);
+ }
+ if(!file) return;
+ printf("<span class='f'>%s <a href='?t'>text/plain</a></span>\n", id);
+}
+
+char *clean(char *p) {
+ char *dup = strdup(basename(p));
+ char *x = dup;
+ while(*x) {
+ if(*x == '-') *x = ' ';
+ x++;
+ }
+ return dup;
+}
+
+int legit(char *page) {
+ if(!page) return 0;
+ if(!strcmp(page, "/")) return 0;
+ if(strstr(page, "..")) return 0;
+ if(strcspn(page, badchars) < strlen(page)) return 0;
+ // strlen(page)
+ return 1;
+}
+
+int problem(int status, char *message) {
+ printf("Status: %d\n", status);
+ printf("Content-Type: text/plain\n\n");
+ printf("%s\n", message);
+ return 1;
+}
+
+int redirect(char *path, char *prefix) {
+ printf("Status: 303\n");
+ switch(*path) {
+ case '?': printf("Location: %s\n\n", path); break;
+ case '#':
+ printf("Location: %s/%s?m=", script, path);
+ // skrive ut ++path
+ printf("\n");
+ default:
+ if(prefix) {
+ printf("Location: %s/%s/%s\n\n", script, prefix, path); break;
+ } else {
+ printf("Location: %s/%s\n\n", script, path); break;
+ }
+ }
+ return 0;
+}
+
+int unauthorized(int status) {
+ printf("Status: %d\n", status);
+ if(status == 401)
+ printf("WWW-Authenticate: Basic realm='%s'\n", realm);
+ printf("Content-Type: text/plain; charset=utf-8\n\n");
+ printf("No.\n");
+ return 1;
+}
+
+int edit(char *path) {
+ if(!writable(path)) return unauthorized(403);
+ FILE *fp = fopen(path, "r");
+ fp ? head(path, path, "vis") : head(path, NULL, NULL);
+ char buffer[sysconf(_SC_PAGESIZE)];
+ printf("<form method='post'>\n");
+ printf("<p class='edit'>\n");
+ if(orz)
+ printf("<a id='orz' href='#'>@picto</a><br>\n");
+ printf("<textarea name=t rows=24 cols=72>");
+ if(fp)
+ while(fgets(buffer, sizeof(buffer), fp))
+ printf("%s", buffer);
+ printf("</textarea>\n");
+ printf("<p><input type=submit value=Update>");
+ printf("</form>");
+ foot(1);
+ if(fp) fclose(fp);
+ return 0;
+}
+
+// v fæl
+int store(char *raw, int len) {
+ char *dir = dirname(strdup(raw));
+ char *id = basename(strdup(raw));
+ if(chdir(dir))
+ return problem(404, "Not found");
+ char path[strlen(loft) + strlen(id) + 256];
+ int epoch = time(NULL);
+ snprintf(path, strlen(loft) + strlen(id) + 256, "%s/%s.%d", loft, id, epoch);
+ FILE *fp = fopen(path, "w");
+ if(!fp) return redirect("fff", NULL);
+ int pos = 0;
+ unsigned int decoded;
+ char buffer[3] = { 0 };
+ for(int i = 0; i < len; i++) {
+ buffer[pos] = getchar();
+ if(buffer[pos] == '+') buffer[pos] = ' ';
+ if(buffer[pos] == '&') { buffer[pos] = '\0'; break; }
+ if(pos == 2) {
+ if(buffer[0] == '%' && isxdigit(buffer[1]) && isxdigit(buffer[2])) {
+ sscanf(buffer, "%%%2x", &decoded);
+ fprintf(fp, "%c", decoded);
+ memset(buffer, 0, 3);
+ pos = 0;
+ } else {
+ fprintf(fp, "%c", buffer[0]);
+ memmove(buffer, &buffer[1], 2);
+ buffer[2] = 0;
+ }
+ }
+ else pos++;
+ }
+ fprintf(fp, "%.3s", buffer);
+ fputc('\0', fp);
+ fclose(fp);
+ unlink(id);
+ symlink(path, id);
+ return 0;
+}
+
+int post(char *path) {
+ char *header;
+ int clen;
+ header = getenv("CONTENT_LENGTH");
+ if(!header) return redirect(path, NULL);
+ clen = atoi(header);
+ while(clen > 1)
+ if(clen--, getchar() == 't')
+ if(clen--, getchar() == '=')
+ store(path, clen);
+ return redirect(path, NULL);
+}
+
+// gjør denne penere!
+int list(char *raw, int dir, int inc) {
+ int len = strlen(raw);
+ char *pattern = malloc(len + 3);
+ if(len > 0 && raw[len - 1] == '/')
+ raw[len - 1] = '\0';
+ if(dir)
+ snprintf(pattern, len + 3, "%s/*", raw);
+ else
+ snprintf(pattern, len + 1, "%s", raw);
+ glob_t res;
+ if(glob(pattern, GLOB_MARK, NULL, &res)) {
+ if(!inc) {
+ head("ingen treff", NULL, NULL);
+ foot(0);
+ }
+ return 0;
+ }
+ if(!inc)
+ head("treff", NULL, NULL);
+ char *path;
+ printf("<ul class='glob'>");
+ for(int i = 0; i < res.gl_pathc; i++) {
+ path = res.gl_pathv[i];
+ if(*path == '.' && *path + 1 == '/') path += 2;
+ printf("<li><a href='%s/%s'>%s</a>", script, path, path);
+ }
+ printf("</ul>");
+ if(!inc)
+ foot(0);
+ free(pattern);
+ return 0;
+}
+
+int text(char *path, char *type) {
+ FILE *fp = fopen(path, "r");
+ if(fp) {
+ char buffer[sysconf(_SC_PAGESIZE)];
+ printf("Content-Type: %s; charset=utf-8\n\n", type);
+ while(fgets(buffer, sizeof(buffer), fp))
+ printf("%s", buffer);
+ }
+ fclose(fp);
+ return 0;
+}
+
+void include(FILE *out, char *path) {
+ struct stat s;
+ int plain = 0;
+ if(*path == 't')
+ plain = 1, path++;
+ while(isspace(*path)) path++;
+ if(!legit(path)) return;
+ if(*path == '/') return;
+ stat(path, &s);
+ if(S_ISDIR(s.st_mode)) {
+ list(path, 1, 1);
+ return;
+ }
+ FILE *fp = fopen(path, "r");
+ if(!fp) return;
+ if(plain) {
+ printf("<pre>");
+ int c;
+ while((c = fgetc(fp)) != EOF) {
+ switch(c) {
+ case '&': printf("&amp;"); break;
+ case '<': printf("&lt;"); break;
+ case '>': printf("&gt;"); break;
+ default: putc(c, stdout);
+ }
+ }
+ printf("</pre>");
+ } else {
+ marxup(fp, out, NULL);
+ }
+ fclose(fp);
+}
+
+void magic(FILE *out, char *line) {
+ switch(*line++) {
+ case 'i': include(out, line); break;
+ default: fprintf(out, "WHAT: %s\n", line); return;
+ }
+}
+
+int view(char *path) {
+ struct stat s;
+ stat(path, &s);
+ if(S_ISDIR(s.st_mode)) {
+ if(dirlist)
+ return list(path, 1, 0);
+ return redirect(home, path);
+ }
+ FILE *fp = fopen(path, "r");
+ if(!fp) return redirect("?e", NULL);
+ writable(path) ? head(path, "?e", "rediger") : head(path, NULL, NULL);
+ marxup(fp, stdout, magic);
+ foot(1);
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ char *page = getenv("PATH_INFO");
+ char *qstr = getenv("QUERY_STRING");
+ char *verb = getenv("REQUEST_METHOD");
+
+ if(!page && argc > 1) page = argv[1];
+ if(!qstr && argc > 2) qstr = argv[2];
+
+ if(!verb) verb = "GET";
+
+ if(!script) script = getenv("SCRIPT_NAME");
+ if(!script) script = "";
+ if(!base) base = strdup(dirname(strdup(script)));
+
+ if(authenticate && !(id = authenticate())) return unauthorized(401);
+ if(!legit(page++)) return redirect(home, NULL);
+
+ setenv("MARXUP_HEADER", "2", 1);
+ setenv("MARXUP_PREFIX", base, 1);
+ setenv("MARXUP_WIKI", "1", 1);
+
+ if(chdir(pages))
+ return problem(503, "Service unavailable");
+
+ if(!strncmp(verb, "POST", 4)) return post(page);
+ if(strchr(page, '*'))
+ return list(page, 0, 0);
+
+ while(qstr && *qstr) {
+ switch(*qstr) {
+ case 'e': return edit(page);
+ case 't': return text(page, "text/plain");
+ case 'c': return text(page, "text/css");
+ }
+ qstr++;
+ }
+ return view(page);
+}
+