diff options
Diffstat (limited to 'ukulele.c')
-rw-r--r-- | ukulele.c | 332 |
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("&"); break; + case '<': printf("<"); break; + case '>': printf(">"); 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); +} + |