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("<", 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)) <= 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);
}
}