--- /dev/null
+#ifdef __linux__
+ #include <sys/epoll.h>
+#else
+ #include <sys/event.h>
+#endif
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <MagickWand/MagickWand.h>
+
+#include "config.h"
+
+#define NUM_IMAGES (sizeof(imgpaths) / sizeof(imgpaths[0]))
+
+typedef struct {
+ struct sockaddr_in addr;
+ int sock;
+} ThreadArgs;
+
+typedef struct {
+ ThreadArgs clients[MAX_QUEUE];
+ int head;
+ int tail;
+ int count;
+ pthread_mutex_t lock;
+ pthread_cond_t note;
+} ClientQueue;
+
+typedef struct {
+ unsigned char *data;
+ size_t size;
+} ImageBuffer;
+
+static void seedinit(void);
+static int imgloadbuff(const char *, ImageBuffer *);
+static int imgpreload(void);
+static char *b64e(const unsigned char *, size_t, size_t *);
+static char *fontpreload(void);
+static double randrange(double, double);
+static unsigned long randnext(void);
+static unsigned char *imggen(const unsigned char *, size_t,
+ const char *, size_t *,
+ double, double, double);
+static void terminatepath(char *, size_t);
+static char *getheader(const char *, const char *);
+static void handleclient(ThreadArgs *);
+static void *worker(void *);
+
+static ClientQueue queue;
+static ImageBuffer imgbuffs[NUM_IMAGES];
+static char *fonturi;
+/* TODO: remove compiler-specific __thread */
+static __thread char buffer[BUFFER_SIZE];
+static __thread char method[16], path[256], header[512];
+static unsigned long randstate = 0;
+static pthread_mutex_t random_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void
+seedinit(void)
+{
+ int fd;
+
+ /* Try urandom first, random as a fallback */
+ if ((fd = open("/dev/urandom", O_RDONLY)) < 0) {
+ fd = open("/dev/random", O_RDONLY);
+ }
+
+ if (fd < 0) {
+ warn("open random device");
+ randstate = 12345; /* Fallback seed */
+ return;
+ }
+
+ if (read(fd, &randstate, sizeof(randstate)) <
+ (ssize_t)sizeof(randstate)) {
+ warn("read random device");
+ randstate = 12345; /* Fallback seed */
+ }
+
+ close(fd);
+ printf("Random seed initialized: %lu\n", randstate);
+
+ return;
+}
+
+int
+imgloadbuff(const char *path, ImageBuffer *ibuffer)
+{
+ int fd;
+ ssize_t bytesr;
+ struct stat st;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ warn("open image file");
+ return -1;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ warn("fstat");
+ close(fd);
+ return -1;
+ }
+
+ ibuffer->size = st.st_size;
+ ibuffer->data = malloc(ibuffer->size);
+ if (!ibuffer->data) {
+ warnx("failed to allocate image buffer");
+ close(fd);
+ return -1;
+ }
+
+ bytesr = read(fd, ibuffer->data, ibuffer->size);
+ if (bytesr != (ssize_t)ibuffer->size) {
+ warn("read image file");
+ free(ibuffer->data);
+ ibuffer->data = NULL;
+ ibuffer->size = 0;
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ printf("Loaded image: %s (%zu bytes)\n", path, ibuffer->size);
+
+ return 0;
+}
+
+
+int
+imgpreload(void)
+{
+ for (int i = 0; i < (int)NUM_IMAGES; i++) {
+ if (imgloadbuff(imgpaths[i], &imgbuffs[i]) < 0)
+ {
+ warnx("failed to load image %d: %s", i,
+ imgpaths[i]);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+char *
+b64e(const unsigned char *data, size_t ilen, size_t *olen)
+{
+ char *edata;
+ uint32_t octet_a, octet_b, octet_c, triple;
+ static const char etable[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ *olen = 4 * ((ilen + 2) / 3);
+
+ edata = malloc(*olen + 1);
+ if (!edata) return NULL;
+
+ for (size_t i = 0, j = 0; i < ilen;) {
+ octet_a = i < ilen ? data[i++] : 0;
+ octet_b = i < ilen ? data[i++] : 0;
+ octet_c = i < ilen ? data[i++] : 0;
+
+ triple = (octet_a << 0x10) + (octet_b << 0x08) +
+ octet_c;
+
+ edata[j++] = etable[(triple >> 3 * 6) & 0x3F];
+ edata[j++] = etable[(triple >> 2 * 6) & 0x3F];
+ edata[j++] = (i > ilen + 1) ? '=' : etable[(triple >> 1 * 6) &
+ 0x3F];
+ edata[j++] = (i > ilen) ? '=' : etable[triple & 0x3F];
+ }
+
+ edata[*olen] = '\0';
+
+ return edata;
+}
+
+char *
+fontpreload(void)
+{
+ size_t fontsz, b64sz, readsz;
+ FILE *file;
+ char *b64font, *inlineuri;
+ unsigned char *fontbuffer;
+
+ file = fopen(fontpath, "rb");
+ if (!file)
+ err(1, "open font file");
+
+ fseek(file, 0, SEEK_END);
+ fontsz = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ fontbuffer = malloc(fontsz);
+ if (!fontbuffer)
+ errx(1, "failed to allocate raw font");
+
+ readsz = fread(fontbuffer, 1, fontsz, file);
+ if (readsz < fontsz || ferror(file))
+ err(1, "read font file");
+
+ fclose(file);
+
+ b64font = b64e(fontbuffer, fontsz, &b64sz);
+ free(fontbuffer);
+
+ inlineuri = malloc(b64sz + 8);
+ if (!inlineuri)
+ errx(1, "failed to allocate base64 font");
+
+ sprintf(inlineuri, "inline:%s", b64font);
+ printf("Loaded font from: %s\n", fontpath);
+
+ return inlineuri;
+}
+
+/* Linear congruential generator */
+unsigned long
+randnext(void)
+{
+ unsigned long result;
+
+ pthread_mutex_lock(&random_mutex);
+ randstate = (randstate * 1103515245 + 12345) & 0x7fffffff;
+ result = randstate;
+ pthread_mutex_unlock(&random_mutex);
+
+ return result;
+}
+
+double
+randrange(double min, double max)
+{
+ unsigned long rand_val;
+ double normalized;
+
+ rand_val = randnext();
+ normalized = (double)rand_val / 0x7fffffff;
+
+ return min + (normalized * (max - min));
+}
+
+unsigned char *
+imggen(const unsigned char *imgdata, size_t imgsz,
+ const char *msg, size_t *output_size,
+ double hue, double saturation, double brightness)
+{
+ size_t data_size = 0;
+ unsigned char *result = NULL;
+
+ MagickWand *wand = NewMagickWand();
+ DrawingWand *dw = NewDrawingWand();
+ PixelWand *pw = NewPixelWand();
+
+ if (!MagickReadImageBlob(wand, imgdata, imgsz)) {
+ fprintf(stderr, "failed to read image from buffer");
+ goto cleanup;
+ }
+
+ MagickModulateImage(wand, brightness, saturation, hue);
+
+ PixelSetRed(pw, randrange(0.0, 1.0));
+ PixelSetGreen(pw, randrange(0.0, 1.0));
+ PixelSetBlue(pw, randrange(0.0, 1.0));
+ PixelSetAlpha(pw, 1.0); /* Fully opaque */
+
+ DrawSetFillColor(dw, pw);
+ DrawSetFont(dw, fonturi);
+ DrawSetFontSize(dw, (int)randrange(8.0, 200.0));
+ DrawSetGravity(dw, CenterGravity);
+
+ MagickAnnotateImage(wand, dw, saturation / 4, hue / 4, hue, msg);
+ result = MagickGetImageBlob(wand, &data_size);
+
+cleanup:
+ *output_size = data_size;
+ DestroyMagickWand(wand);
+ DestroyDrawingWand(dw);
+ DestroyPixelWand(pw);
+
+ return result;
+}
+
+void
+terminatepath(char *path, size_t len)
+{
+ char *current = path;
+
+ while (*current && *current != '\r' && current < path + len)
+ current++;
+
+ *current = '\0';
+
+ return;
+}
+
+char *
+getheader(const char *headerbuff, const char *find)
+{
+ if (!headerbuff) {
+ return NULL;
+ }
+
+ const size_t tlen = strlen(find);
+ char *current = (char *)headerbuff;
+
+ while (*current) {
+ if (strncasecmp(current, find, tlen) == 0) {
+ char *vptr, *endptr;
+
+ vptr = current + tlen;
+ while (*vptr == ' ' || *vptr == '\t') {
+ vptr++;
+ }
+
+ endptr = strchr(vptr, '\r');
+ if (endptr) *endptr = '\0';
+
+ return vptr;
+ }
+
+ current = strchr(current, '\n');
+ if (!current) {
+ break;
+ }
+ current++;
+ }
+
+ return NULL;
+}
+
+void
+handleclient(ThreadArgs *args)
+{
+ int clientsock;
+ ssize_t recbytes;
+ static const char response_404[] = "HTTP/1.1 404 Not Found\r\n"
+ "Content-Type: text/html\r\n"
+ "Content-Length: 48\r\n\r\n"
+ "<html><body><h1>404 Not Found</h1></body></html>";
+
+ clientsock = args->sock;
+
+ recbytes = recv(clientsock, buffer, BUFFER_SIZE - 1, 0);
+ if (recbytes <= 0) {
+ close(clientsock);
+ return;
+ }
+
+ buffer[recbytes] = '\0';
+
+ /* Parse HTTP request */
+ /* method is unused, for now */
+ terminatepath(path, 256);
+
+ sscanf(buffer, "%15s %255s", method, path);
+
+ if (strncmp(path, PATH, sizeof(PATH) - 1) == 0) {
+ int imgid = (int)randrange(0.0, NUM_IMAGES);
+ size_t imgsz = 0;
+ double hue, saturation, brightness;
+ char hip[INET_ADDRSTRLEN];
+ char *hhip;
+ unsigned char *imgdata;
+
+ hue = randrange(10.0, 150.0);
+ saturation = randrange(10.0, 200.0);
+ brightness = randrange(30.0, 150.0);
+
+ inet_ntop(AF_INET, &(args->addr.sin_addr), hip, sizeof(hip));
+ hhip = getheader(buffer, "X-Forwarded-For:");
+ if (!hhip) hhip = hip;
+
+ imgdata = imggen(imgbuffs[imgid].data,
+ imgbuffs[imgid].size,
+ path + sizeof(PATH) - 1, &imgsz,
+ hue, saturation, brightness);
+
+ printf("{ \"forwarded-for\": \"%s\", \"host\": \"%s\", \"image\": \"%d\", "
+ "\"path\": \"%s\", \"size\": \"%zu\", \"hue\": \"%.2f\", "
+ "\"saturation\": \"%.2f\", \"brightness\": \"%.2f\" }\n",
+ hhip, hip, imgid, path + sizeof(PATH) - 1, imgsz, hue, saturation, brightness);
+
+ if (imgdata) {
+ snprintf(header, sizeof(header),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: image/jpeg\r\n"
+ "Content-Length: %zu\r\n"
+ "Connection: close\r\n\r\n",
+ imgsz);
+
+ send(clientsock, header, strlen(header), 0);
+ send(clientsock, imgdata, imgsz, 0);
+ MagickRelinquishMemory(imgdata);
+ } else {
+ fprintf(stderr, "failed to process image %d\n", imgid);
+ send(clientsock, response_404, sizeof(response_404), 0);
+ }
+ } else {
+ send(clientsock, response_404, sizeof(response_404), 0);
+ }
+
+ close(clientsock);
+ return;
+}
+
+void *
+worker(void *unused)
+{
+ (void)unused;
+ ThreadArgs args;
+
+ for (;;) {
+ pthread_mutex_lock(&queue.lock);
+
+ while (queue.count == 0) {
+ pthread_cond_wait(&queue.note, &queue.lock);
+ }
+
+ args.sock = queue.clients[queue.head].sock;
+ args.addr = queue.clients[queue.head].addr;
+ queue.head = (queue.head + 1) % MAX_QUEUE;
+ queue.count--;
+
+ pthread_mutex_unlock(&queue.lock);
+
+ handleclient(&args);
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, efd, opt, serversock;
+ const char *progname = argv[0];
+ pthread_t threads[NUM_THREADS];
+ struct sockaddr_in server_addr = { 0 };
+
+#ifdef __linux__
+ struct epoll_event event, events[1];
+#else
+ struct kevent kev, kevs[10];
+#endif
+
+ while ((c = getopt(argc, argv, "d")) >= 0) {
+ switch (c) {
+ case 'd':
+ if (daemon(0, 0) < 0) err(1, "daemon");
+ break;
+ default:
+ fprintf(stderr, "usage: %s [-d]\n", progname);
+ exit(2);
+ }
+ }
+
+ MagickWandGenesis();
+ seedinit();
+
+ serversock = socket(AF_INET, SOCK_STREAM, 0);
+ if (serversock < 0)
+ err(1, "socket");
+
+ if (imgpreload() < 0)
+ errx(1, "failed to preload images");
+
+ fonturi = fontpreload();
+ if (!fonturi)
+ errx(1, "failed to preload the font");
+
+ opt = 1;
+ setsockopt(serversock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = INADDR_ANY;
+ server_addr.sin_port = htons(PORT);
+
+ if (bind(serversock, (struct sockaddr *) &server_addr,
+ sizeof(server_addr)) < 0)
+ err(1, "bind");
+
+ if (listen(serversock, 1024) < 0)
+ err(1, "listen");
+
+ printf("Server listening on port %d\n", PORT);
+ printf("Access images at: http://localhost:%d" PATH "\n", PORT);
+
+ pthread_mutex_init(&queue.lock, NULL);
+ pthread_cond_init(&queue.note, NULL);
+
+ for (int i = 0; i < NUM_THREADS; i++) {
+ pthread_create(&threads[i], NULL, worker, NULL);
+ pthread_detach(threads[i]);
+ }
+
+#ifdef __linux__
+ if ((efd = epoll_create1(0)) < 0)
+ err(1, "epoll_create1");
+
+ event.events = EPOLLIN;
+ event.data.fd = serversock;
+ epoll_ctl(efd, EPOLL_CTL_ADD, serversock, &event);
+#else
+ if ((efd = kqueue()) < 0)
+ err(1, "kqueue");
+
+ EV_SET(&kev, serversock, EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(efd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "kevent");
+#endif
+
+
+ for (;;) {
+ int nfds;
+
+#ifdef __linux__
+ if ((nfds = epoll_wait(efd, events, 1 , -1)) < 0) {
+ perror("epoll_wait");
+ continue;
+ }
+
+
+ if (events[0].events & EPOLLIN) {
+#else
+ if ((nfds = kevent(efd, NULL, 0, kevs, 10, NULL)) < 0) {
+ perror("kevent");
+ continue;
+ }
+
+ for (int i = 0; i < nfds; i++) {
+ if(kevs[i].ident == (uintptr_t)serversock) {
+#endif
+ /*
+ * This goes inside the for loop above in case of kqueue/kevent
+ */
+ ThreadArgs args;
+ socklen_t clientaddrlen = sizeof(args.addr);
+
+ args.sock = accept(serversock, (struct sockaddr *)&args.addr,
+ &clientaddrlen);
+
+ if (args.sock < 0) {
+ perror("accept");
+ continue;
+ }
+
+ pthread_mutex_lock(&queue.lock);
+
+ if (queue.count >= MAX_QUEUE) {
+ close(args.sock);
+ fprintf(stderr, "Client queue full, rejecting\n");
+ } else {
+ queue.clients[queue.tail].sock = args.sock;
+ queue.clients[queue.tail].addr = args.addr;
+ queue.tail = (queue.tail + 1) % MAX_QUEUE;
+ queue.count++;
+ pthread_cond_signal(&queue.note);
+ }
+
+ pthread_mutex_unlock(&queue.lock);
+ }
+#ifndef __linux__
+ } /* closing the kevent for loop */
+#endif
+ }
+ close(efd);
+ close(serversock);
+ MagickWandTerminus();
+
+ return 0;
+}