| 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); | |
| } | |
| } | |