diff options
author | the little girl <yui@blekksprut.net> | 2013-06-24 13:32:33 +0200 |
---|---|---|
committer | the little girl <yui@blekksprut.net> | 2013-06-24 13:32:33 +0200 |
commit | 98f3fb0cd359abcf7a6ecfcf84d6b8cf44204437 (patch) | |
tree | dfcc30d19e08f1d81be77b51826494e585b1a78f | |
download | ukulele-98f3fb0cd359abcf7a6ecfcf84d6b8cf44204437.tar.xz |
wiki.c
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | wiki.c | 208 | ||||
-rw-r--r-- | wiki.css | 15 |
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 @@ -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; } |