tadd hiltjo posthuma's utilities - vote - simple cgi voting system for web and gopher
 (HTM) git clone git://src.adamsgaard.dk/vote
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 93cfbe7466a3f69bcdf8928fdef8e3dde1d732e5
 (DIR) parent f49595b9c30a3a21c5380807654b1b43379e5b0d
 (HTM) Author: Anders Damsgaard <anders@adamsgaard.dk>
       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(-)
       ---
 (DIR) 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}
 (DIR) 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("&lt;", stdout);   break;
       +                case '>':  fputs("&gt;", stdout);   break;
       +                case '\'': fputs("&#39;", stdout);  break;
       +                case '&':  fputs("&amp;", stdout);  break;
       +                case '"':  fputs("&quot;", 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)) <= 0)
       +                        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;
       +                }
       +        }
       +}
 (DIR) 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);
 (DIR) 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(errno));
       -                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(errno));
       -                        die_500();
       +                        die(500);
                        }
                }