tadd hiltjo posthuma's utilities - vote - simple cgi voting system for web and … | |
git clone git://src.adamsgaard.dk/vote | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 93cfbe7466a3f69bcdf8928fdef8e3dde1d732e5 | |
parent f49595b9c30a3a21c5380807654b1b43379e5b0d | |
Author: Anders Damsgaard <[email protected]> | |
Date: Sun, 27 Sep 2020 08:03:51 +0200 | |
add hiltjo posthuma's utilities | |
from git://git.codemadness.org/frontends | |
Diffstat: | |
M Makefile | 2 +- | |
A util.c | 206 +++++++++++++++++++++++++++++… | |
A util.h | 18 ++++++++++++++++++ | |
M vote.c | 68 +++++++++++++++++------------… | |
4 files changed, 263 insertions(+), 31 deletions(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
t@@ -5,7 +5,7 @@ NAME = vote | |
HERE_CFLAGS = ${CFLAGS} | |
HERE_LDFLAGS = -static ${LDFLAGS} | |
-SRC = vote.c | |
+SRC = vote.c util.c | |
OBJ = ${SRC:.c=.o} | |
all: ${NAME} | |
diff --git a/util.c b/util.c | |
t@@ -0,0 +1,206 @@ | |
+#include <sys/socket.h> | |
+#include <sys/types.h> | |
+ | |
+#include <ctype.h> | |
+#include <errno.h> | |
+#include <netdb.h> | |
+#include <stdarg.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include <time.h> | |
+#include <unistd.h> | |
+#include <wchar.h> | |
+ | |
+int | |
+uriencode(const char *s, char *buf, size_t bufsiz) | |
+{ | |
+ static char hex[] = "0123456789ABCDEF"; | |
+ char *d = buf, *e = buf + bufsiz; | |
+ unsigned char c; | |
+ | |
+ if (!bufsiz) | |
+ return 0; | |
+ | |
+ for (; *s; ++s) { | |
+ c = (unsigned char)*s; | |
+ if (d + 4 >= e) | |
+ return 0; | |
+ if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' || | |
+ c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) { | |
+ *d++ = '%'; | |
+ *d++ = hex[c >> 4]; | |
+ *d++ = hex[c & 0x0f]; | |
+ } else { | |
+ *d++ = *s; | |
+ } | |
+ } | |
+ *d = '\0'; | |
+ | |
+ return 1; | |
+} | |
+ | |
+int | |
+hexdigit(int c) | |
+{ | |
+ if (c >= '0' && c <= '9') | |
+ return c - '0'; | |
+ else if (c >= 'A' && c <= 'F') | |
+ return c - 'A' + 10; | |
+ else if (c >= 'a' && c <= 'f') | |
+ return c - 'a' + 10; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* decode until NUL separator or end of "key". */ | |
+int | |
+decodeparam(char *buf, size_t bufsiz, const char *s) | |
+{ | |
+ size_t i; | |
+ | |
+ if (!bufsiz) | |
+ return -1; | |
+ | |
+ for (i = 0; *s && *s != '&'; s++) { | |
+ switch (*s) { | |
+ case '%': | |
+ if (i + 3 >= bufsiz) | |
+ return -1; | |
+ if (!isxdigit((unsigned char)*(s+1)) || | |
+ !isxdigit((unsigned char)*(s+2))) | |
+ return -1; | |
+ buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2)); | |
+ s += 2; | |
+ break; | |
+ case '+': | |
+ if (i + 1 >= bufsiz) | |
+ return -1; | |
+ buf[i++] = ' '; | |
+ break; | |
+ default: | |
+ if (i + 1 >= bufsiz) | |
+ return -1; | |
+ buf[i++] = *s; | |
+ break; | |
+ } | |
+ } | |
+ buf[i] = '\0'; | |
+ | |
+ return i; | |
+} | |
+ | |
+char * | |
+getparam(const char *query, const char *s) | |
+{ | |
+ const char *p, *last = NULL; | |
+ size_t len; | |
+ | |
+ len = strlen(s); | |
+ for (p = query; (p = strstr(p, s)); p += len) { | |
+ if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '… | |
+ last = p + len + 1; | |
+ } | |
+ | |
+ return (char *)last; | |
+} | |
+ | |
+int | |
+friendlytime(time_t now, time_t t) | |
+{ | |
+ long long d = now - t; | |
+ | |
+ if (d < 60) { | |
+ printf("just now"); | |
+ } else if (d < 3600) { | |
+ printf("%lld minutes ago", d / 60); | |
+ } else if (d <= 24*3600) { | |
+ printf("%lld hours ago", d / 3600); | |
+ } else { | |
+ return 0; | |
+ } | |
+ return 1; | |
+} | |
+ | |
+/* Escape characters below as HTML 2.0 / XML 1.0. */ | |
+void | |
+xmlencode(const char *s) | |
+{ | |
+ for (; *s; s++) { | |
+ switch(*s) { | |
+ case '<': fputs("<", stdout); break; | |
+ case '>': fputs(">", stdout); break; | |
+ case '\'': fputs("'", stdout); break; | |
+ case '&': fputs("&", stdout); break; | |
+ case '"': fputs(""", stdout); break; | |
+ default: putchar(*s); | |
+ } | |
+ } | |
+} | |
+ | |
+/* format `len' columns of characters. If string is shorter pad the rest | |
+ * with characters `pad`. */ | |
+int | |
+utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad) | |
+{ | |
+ wchar_t wc; | |
+ size_t col = 0, i, slen, siz = 0; | |
+ int rl, w; | |
+ | |
+ if (!len) | |
+ return -1; | |
+ | |
+ slen = strlen(s); | |
+ for (i = 0; i < slen; i += rl) { | |
+ if ((rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4)) <= … | |
+ break; | |
+ if ((w = wcwidth(wc)) == -1) | |
+ continue; | |
+ if (col + w > len || (col + w == len && s[i + rl])) { | |
+ if (siz + 4 >= bufsiz) | |
+ return -1; | |
+ memcpy(&buf[siz], "\xe2\x80\xa6", 3); | |
+ siz += 3; | |
+ if (col + w == len && w > 1) | |
+ buf[siz++] = pad; | |
+ buf[siz] = '\0'; | |
+ return 0; | |
+ } | |
+ if (siz + rl + 1 >= bufsiz) | |
+ return -1; | |
+ memcpy(&buf[siz], &s[i], rl); | |
+ col += w; | |
+ siz += rl; | |
+ buf[siz] = '\0'; | |
+ } | |
+ | |
+ len -= col; | |
+ if (siz + len + 1 >= bufsiz) | |
+ return -1; | |
+ memset(&buf[siz], pad, len); | |
+ siz += len; | |
+ buf[siz] = '\0'; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* Escape characters in gopher, CR and LF are ignored */ | |
+void | |
+gophertext(FILE *fp, const char *s, size_t len) | |
+{ | |
+ size_t i; | |
+ | |
+ for (i = 0; *s && i < len; s++, i++) { | |
+ switch (*s) { | |
+ case '\r': /* ignore CR */ | |
+ case '\n': /* ignore LF */ | |
+ break; | |
+ case '\t': | |
+ fputs(" ", fp); | |
+ break; | |
+ default: | |
+ fputc(*s, fp); | |
+ break; | |
+ } | |
+ } | |
+} | |
diff --git a/util.h b/util.h | |
t@@ -0,0 +1,18 @@ | |
+#ifndef __OpenBSD__ | |
+#define pledge(p1,p2) 0 | |
+#define unveil(p1,p2) 0 | |
+#endif | |
+ | |
+#undef strlcat | |
+size_t strlcat(char *, const char *, size_t); | |
+#undef strlcpy | |
+size_t strlcpy(char *, const char *, size_t); | |
+ | |
+int decodeparam(char *buf, size_t bufsiz, const char *s); | |
+int friendlytime(time_t now, time_t t); | |
+char *getparam(const char *query, const char *s); | |
+void gophertext(FILE *fp, const char *s, size_t len); | |
+int hexdigit(int c); | |
+int uriencode(const char *s, char *buf, size_t bufsiz); | |
+int utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad); | |
+void xmlencode(const char *s); | |
diff --git a/vote.c b/vote.c | |
t@@ -5,20 +5,34 @@ | |
#include <sys/stat.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
+#include "util.h" | |
#define OUT(s) (fputs((s), stdout)) | |
#define POLLS_DIR "polls" | |
-static char poll[1024]; | |
+static char rawpoll[1024], poll[1024]; | |
void | |
-die_500() { | |
- OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
- exit(1); | |
+die(int statuscode) | |
+{ | |
+ switch(statuscode) { | |
+ case 401: | |
+ OUT("Status: 401 Bad Request\r\n\r\n"); | |
+ break; | |
+ case 500: | |
+ OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
+ break; | |
+ default: | |
+ fprintf(stderr, "unknown status code %d\n", statuscode); | |
+ OUT("Status: 500 Internal Server Error\r\n\r\n"); | |
+ } | |
+ | |
+ exit(statuscode); | |
} | |
void | |
-print_html_head() { | |
+print_html_head() | |
+{ | |
OUT("Content-type: text/html; charset=utf-8\r\n\r\n"); | |
OUT("<!DOCTYPE html>\n" | |
"<html>\n" | |
t@@ -26,39 +40,28 @@ print_html_head() { | |
} | |
void | |
-print_html_foot() { | |
+print_html_foot() | |
+{ | |
OUT("</body>\n" | |
"</html>\n"); | |
} | |
void | |
-show_poll(const char *poll_name) { | |
+show_poll(const char *poll_name) | |
+{ | |
FILE *fd; | |
if ((fd = fopen(poll_name, "r")) != NULL) { | |
fclose(fd); | |
} else { | |
fprintf(stderr, "poll_open %s: %s\n", poll_name, strerror(errn… | |
- die_500(); | |
+ die(500); | |
} | |
} | |
-/* from hiltjo posthuma's frontends */ | |
-char * | |
-getparam(const char *query, const char *s) { | |
- const char *p, *last = NULL; | |
- size_t len; | |
- | |
- len = strlen(s); | |
- for (p = query; (p = strstr(p, s)); p += len) { | |
- if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '… | |
- last = p + len + 1; | |
- } | |
- return (char *)last; | |
-} | |
- | |
void | |
-parse_query() { | |
+parse_query() | |
+{ | |
char *query, *p; | |
size_t len; | |
t@@ -66,33 +69,38 @@ parse_query() { | |
query = ""; | |
if ((p = getparam(query, "poll"))) { | |
- if ((len = strcspn(p, "&")) && len + 1 < sizeof(poll)) { | |
- memcpy(poll, p, len); | |
- poll[len] = '\0'; | |
+ if ((len = strcspn(p, "&")) && len + 1 < sizeof(rawpoll)) { | |
+ memcpy(rawpoll, p, len); | |
+ rawpoll[len] = '\0'; | |
+ } | |
+ | |
+ if (decodeparam(poll, sizeof(poll), p) == -1) { | |
+ die(401); | |
} | |
} | |
} | |
int | |
-main() { | |
+main() | |
+{ | |
struct stat sb; | |
#ifdef __OpenBSD__ | |
if (unveil(getenv("PWD"), NULL) == -1 || unveil(NULL, NULL) == -1) { | |
fprintf(stderr, "unveil: %s\n", strerror(errno)); | |
- die_500(); | |
+ die(500); | |
} | |
if (pledge("stdio cpath rpath", NULL) == -1) { | |
fprintf(stderr, "pledge: %s\n", strerror(errno)); | |
- die_500(); | |
+ die(500); | |
} | |
#endif | |
if (stat(POLLS_DIR, &sb) == -1) { | |
if (mkdir(POLLS_DIR, 0755) == -1) { | |
fprintf(stderr, "mkdir polls/ failed: %s\n", strerror(… | |
- die_500(); | |
+ die(500); | |
} | |
} | |