aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthe little girl <yui@blekksprut.net>2013-06-24 13:32:33 +0200
committerthe little girl <yui@blekksprut.net>2013-06-24 13:32:33 +0200
commit98f3fb0cd359abcf7a6ecfcf84d6b8cf44204437 (patch)
treedfcc30d19e08f1d81be77b51826494e585b1a78f
downloadukulele-98f3fb0cd359abcf7a6ecfcf84d6b8cf44204437.tar.xz
wiki.c
-rw-r--r--Makefile2
-rw-r--r--wiki.c208
-rw-r--r--wiki.css15
3 files changed, 225 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..65b2c57
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+wiki.cgi: wiki.c
+ ${CC} -std=c11 -Os -o wiki.cgi wiki.c -lmarxup
diff --git a/wiki.c b/wiki.c
new file mode 100644
index 0000000..b3e8e9b
--- /dev/null
+++ b/wiki.c
@@ -0,0 +1,208 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <glob.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <marxup.h>
+
+static char *wikititle = "a wiki";
+static char *links[][2] = {
+ { "home", "home" },
+ { "planer", "planer" },
+ 0
+};
+
+static char *script;
+static char *base;
+
+static const char *badchars = "\\?%:|\"<>. ";
+
+int writable(char *path) {
+ struct stat s;
+ if(!stat(path, &s)) {
+ if(S_ISREG(s.st_mode))
+ return !access(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 !access(dir, W_OK);
+ }
+ return 0;
+}
+
+void head(char *title, char *link, char *what) {
+ printf("Content-Type: text/html; charset=utf-8\n\n");
+ printf("<!doctype html>\n");
+ printf("<title>%s · %s</title>\n", wikititle, title);
+ printf("<link rel='stylesheet' href='%s/wiki.css'>\n", base);
+ if(link && what)
+ printf("<h1><a data-text='%s' href='%s' >%s</a></h1>", what, link, title);
+ else
+ printf("<h1>%s</h1>", title);
+}
+
+void foot(int file) {
+ printf("<hr>\n");
+ printf("<p>");
+ for(int i = 0; *links[i]; i++)
+ printf("<a href='%s/%s'>%s</a> ", script, links[i][1], links[i][0]);
+ if(!file) return;
+ printf("<span style='float: right'>");
+ printf("<a href='?t'>text/plain</a>");
+ printf("</span>");
+}
+
+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;
+ return 1;
+}
+
+int redirect(char *path) {
+ printf("Status: 303\n");
+ if(*path == '?')
+ printf("Location: %s\n\n", path);
+ else
+ printf("Location: %s/%s\n\n", getenv("SCRIPT_NAME"), path);
+ return 0;
+}
+
+int unauthorized(char *path) {
+ printf("Content-Type: text/plain; charset=utf-8\n\n");
+ printf("No.\n");
+}
+
+int edit(char *path) {
+ if(!writable(path)) return unauthorized(path);
+ FILE *fp = fopen(path, "r");
+ char *title = clean(path);
+ fp ? head(title, path, "vis") : head(title, NULL, NULL);
+ char buffer[sysconf(_SC_PAGESIZE)];
+ printf("<form method='post' enctype='text/plain'>\n");
+ printf("<p><textarea name=text rows=24 cols=72>");
+ if(fp)
+ while(fgets(buffer, sizeof(buffer), fp))
+ printf(buffer);
+ printf("</textarea>\n");
+ printf("<p><input type=submit value=Update>");
+ printf("</form>");
+ foot(1);
+ if(fp) fclose(fp);
+ return 0;
+}
+
+int post(char *path) {
+ char *header, *data;
+ int clen;
+ header = getenv("CONTENT_LENGTH");
+ if(!header) return redirect(path);
+ clen = atoi(header);
+ data = calloc(1, clen + 1);
+ fread(data, clen, 1, stdin);
+ if(strncmp(data, "text=", 5))
+ return redirect(path);
+ FILE *fp = fopen(path, "w");
+ if(!fp) return redirect(path);
+ fwrite(data + 5, clen - 5, 1, fp);
+ fputc('\0', fp);
+ fclose(fp);
+ return redirect(path);
+}
+
+int list(char *raw, int dir) {
+ int len = strlen(raw);
+ char *pattern = malloc(len + 3);
+ 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)) {
+ head("ingen treff", NULL, NULL);
+ foot(0);
+ return 0;
+ }
+
+ head("treff", NULL, NULL);
+ char *path;
+ printf("<ul class='glob'>");
+ for(int i = 0; i < res.gl_pathc; i++) {
+ path = res.gl_pathv[i];
+ printf("<li><a href='%s/%s'>%s</a>", script, path, path);
+ }
+ printf("</ul>");
+ foot(0);
+ free(pattern);
+ return 0;
+}
+
+int plaintext(char *path) {
+ FILE *fp = fopen(path, "r");
+ if(fp) {
+ char buffer[sysconf(_SC_PAGESIZE)];
+ printf("Content-Type: text/plain; charset=utf-8\n\n");
+ while(fgets(buffer, sizeof(buffer), fp))
+ printf(buffer);
+ }
+ fclose(fp);
+}
+
+int view(char *path) {
+ struct stat s;
+ stat(path, &s);
+ if(S_ISDIR(s.st_mode))
+ return list(path, 1);
+ FILE *fp = fopen(path, "r");
+ if(!fp) return redirect("?e");
+ char *title = clean(path);
+ writable(path) ? head(title, "?e", "rediger") : head(title, NULL, NULL);
+ marxup(fp, stdout);
+ foot(1);
+}
+
+int main(int argc, char **argv) {
+ char *page = getenv("PATH_INFO");
+ char *method = getenv("REQUEST_METHOD");
+ char *query = getenv("QUERY_STRING");
+ setenv("MARXUP_HEADER", "2", 1);
+ script = getenv("SCRIPT_NAME");
+ if(!script) script = "/";
+ base = dirname(strdup(script));
+ chdir("pages");
+ if(!legit(page)) return redirect("home");
+ page++;
+ if(!strncmp(method, "POST", 4)) return post(page);
+ while(query && *query) {
+ switch(*query) {
+ case 'e': return edit(page);
+ case 't': return plaintext(page);
+ // case 'h': return history(page); ?
+ }
+ query++;
+ }
+ if(strchr(page, '*'))
+ return list(page, 0);
+ return view(page);
+}
+
diff --git a/wiki.css b/wiki.css
new file mode 100644
index 0000000..0ab051c
--- /dev/null
+++ b/wiki.css
@@ -0,0 +1,15 @@
+body { font: 16px/1.2 sans-serif; width: 64em; margin: 0 auto; }
+textarea { font: inherit; width: 100%; }
+h1 { text-transform: uppercase; }
+hr { clear: both; }
+img { border: none; }
+img[align=left] { margin: 0 1em 1em 0; }
+img[align=right] { margin: 0 0 1em 1em; }
+ul { list-style: inside square; }
+ul.glob { list-style: inside circle; padding: 0; text-transform: uppercase; }
+table { text-align: left; border-collapse: collapse; background: #eee; }
+th, td { padding: 4px 8px 4px 0; }
+tr:nth-child(2n) { background: #fff; }
+a { text-decoration: none; color: DeepPink; }
+a:hover, a:visited { color: Crimson; }
+h1 a:hover:after { content: " »" attr(data-text); color: #aaa; }