7 #include <sys/socket.h>
20 #include <arpa/inet.h>
21 #include <netinet/in.h>
22 #include <MagickWand/MagickWand.h>
26 #define NUM_IMAGES (sizeof(imgpaths) / sizeof(imgpaths[0]))
29 struct sockaddr_in addr
;
34 ThreadArgs clients
[MAX_QUEUE
];
47 static void seedinit(void);
48 static int imgloadbuff(const char *, ImageBuffer
*);
49 static int imgpreload(void);
50 static char *b64e(const unsigned char *, size_t, size_t *);
51 static char *fontpreload(void);
52 static double randrange(double, double);
53 static unsigned long randnext(void);
54 static unsigned char *imggen(const unsigned char *, size_t,
55 const char *, size_t *,
56 double, double, double);
57 static void terminatepath(char *, size_t);
58 static char *getheader(const char *, const char *);
59 static void handleclient(ThreadArgs
*);
60 static void *worker(void *);
62 static ClientQueue queue
;
63 static ImageBuffer imgbuffs
[NUM_IMAGES
];
65 /* TODO: remove compiler-specific __thread */
66 static __thread
char buffer
[BUFFER_SIZE
];
67 static __thread
char method
[16], path
[256], header
[512];
68 static unsigned long randstate
= 0;
69 static pthread_mutex_t random_mutex
= PTHREAD_MUTEX_INITIALIZER
;
76 /* Try urandom first, random as a fallback */
77 if ((fd
= open("/dev/urandom", O_RDONLY
)) < 0) {
78 fd
= open("/dev/random", O_RDONLY
);
82 warn("open random device");
83 randstate
= 12345; /* Fallback seed */
87 if (read(fd
, &randstate
, sizeof(randstate
)) <
88 (ssize_t
)sizeof(randstate
)) {
89 warn("read random device");
90 randstate
= 12345; /* Fallback seed */
94 printf("Random seed initialized: %lu\n", randstate
);
100 imgloadbuff(const char *path
, ImageBuffer
*ibuffer
)
106 fd
= open(path
, O_RDONLY
);
108 warn("open image file");
112 if (fstat(fd
, &st
) < 0) {
118 ibuffer
->size
= st
.st_size
;
119 ibuffer
->data
= malloc(ibuffer
->size
);
120 if (!ibuffer
->data
) {
121 warnx("failed to allocate image buffer");
126 bytesr
= read(fd
, ibuffer
->data
, ibuffer
->size
);
127 if (bytesr
!= (ssize_t
)ibuffer
->size
) {
128 warn("read image file");
130 ibuffer
->data
= NULL
;
137 printf("Loaded image: %s (%zu bytes)\n", path
, ibuffer
->size
);
146 for (int i
= 0; i
< (int)NUM_IMAGES
; i
++) {
147 if (imgloadbuff(imgpaths
[i
], &imgbuffs
[i
]) < 0)
149 warnx("failed to load image %d: %s", i
,
159 b64e(const unsigned char *data
, size_t ilen
, size_t *olen
)
162 uint32_t octet_a
, octet_b
, octet_c
, triple
;
163 static const char etable
[] =
164 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
165 *olen
= 4 * ((ilen
+ 2) / 3);
167 edata
= malloc(*olen
+ 1);
168 if (!edata
) return NULL
;
170 for (size_t i
= 0, j
= 0; i
< ilen
;) {
171 octet_a
= i
< ilen
? data
[i
++] : 0;
172 octet_b
= i
< ilen
? data
[i
++] : 0;
173 octet_c
= i
< ilen
? data
[i
++] : 0;
175 triple
= (octet_a
<< 0x10) + (octet_b
<< 0x08) +
178 edata
[j
++] = etable
[(triple
>> 3 * 6) & 0x3F];
179 edata
[j
++] = etable
[(triple
>> 2 * 6) & 0x3F];
180 edata
[j
++] = (i
> ilen
+ 1) ? '=' : etable
[(triple
>> 1 * 6) &
182 edata
[j
++] = (i
> ilen
) ? '=' : etable
[triple
& 0x3F];
193 size_t fontsz
, b64sz
, readsz
;
195 char *b64font
, *inlineuri
;
196 unsigned char *fontbuffer
;
198 file
= fopen(fontpath
, "rb");
200 err(1, "open font file");
202 fseek(file
, 0, SEEK_END
);
203 fontsz
= ftell(file
);
204 fseek(file
, 0, SEEK_SET
);
206 fontbuffer
= malloc(fontsz
);
208 errx(1, "failed to allocate raw font");
210 readsz
= fread(fontbuffer
, 1, fontsz
, file
);
211 if (readsz
< fontsz
|| ferror(file
))
212 err(1, "read font file");
216 b64font
= b64e(fontbuffer
, fontsz
, &b64sz
);
219 inlineuri
= malloc(b64sz
+ 8);
221 errx(1, "failed to allocate base64 font");
223 sprintf(inlineuri
, "inline:%s", b64font
);
224 printf("Loaded font from: %s\n", fontpath
);
229 /* Linear congruential generator */
233 unsigned long result
;
235 pthread_mutex_lock(&random_mutex
);
236 randstate
= (randstate
* 1103515245 + 12345) & 0x7fffffff;
238 pthread_mutex_unlock(&random_mutex
);
244 randrange(double min
, double max
)
246 unsigned long rand_val
;
249 rand_val
= randnext();
250 normalized
= (double)rand_val
/ 0x7fffffff;
252 return min
+ (normalized
* (max
- min
));
256 imggen(const unsigned char *imgdata
, size_t imgsz
,
257 const char *msg
, size_t *output_size
,
258 double hue
, double saturation
, double brightness
)
260 size_t data_size
= 0;
261 unsigned char *result
= NULL
;
263 MagickWand
*wand
= NewMagickWand();
264 DrawingWand
*dw
= NewDrawingWand();
265 PixelWand
*pw
= NewPixelWand();
267 if (!MagickReadImageBlob(wand
, imgdata
, imgsz
)) {
268 fprintf(stderr
, "failed to read image from buffer");
272 MagickModulateImage(wand
, brightness
, saturation
, hue
);
274 PixelSetRed(pw
, randrange(0.0, 1.0));
275 PixelSetGreen(pw
, randrange(0.0, 1.0));
276 PixelSetBlue(pw
, randrange(0.0, 1.0));
277 PixelSetAlpha(pw
, 1.0); /* Fully opaque */
279 DrawSetFillColor(dw
, pw
);
280 DrawSetFont(dw
, fonturi
);
281 DrawSetFontSize(dw
, (int)randrange(8.0, 200.0));
282 DrawSetGravity(dw
, CenterGravity
);
284 MagickAnnotateImage(wand
, dw
, saturation
/ 4, hue
/ 4, hue
, msg
);
285 result
= MagickGetImageBlob(wand
, &data_size
);
288 *output_size
= data_size
;
289 DestroyMagickWand(wand
);
290 DestroyDrawingWand(dw
);
291 DestroyPixelWand(pw
);
297 terminatepath(char *path
, size_t len
)
299 char *current
= path
;
301 while (*current
&& *current
!= '\r' && current
< path
+ len
)
310 getheader(const char *headerbuff
, const char *find
)
316 const size_t tlen
= strlen(find
);
317 char *current
= (char *)headerbuff
;
320 if (strncasecmp(current
, find
, tlen
) == 0) {
323 vptr
= current
+ tlen
;
324 while (*vptr
== ' ' || *vptr
== '\t') {
328 endptr
= strchr(vptr
, '\r');
329 if (endptr
) *endptr
= '\0';
334 current
= strchr(current
, '\n');
345 handleclient(ThreadArgs
*args
)
349 static const char response_404
[] = "HTTP/1.1 404 Not Found\r\n"
350 "Content-Type: text/html\r\n"
351 "Content-Length: 48\r\n\r\n"
352 "<html><body><h1>404 Not Found</h1></body></html>";
354 clientsock
= args
->sock
;
356 recbytes
= recv(clientsock
, buffer
, BUFFER_SIZE
- 1, 0);
362 buffer
[recbytes
] = '\0';
364 /* Parse HTTP request */
365 /* method is unused, for now */
366 terminatepath(path
, 256);
368 sscanf(buffer
, "%15s %255s", method
, path
);
370 if (strncmp(path
, PATH
, sizeof(PATH
) - 1) == 0) {
371 int imgid
= (int)randrange(0.0, NUM_IMAGES
);
373 double hue
, saturation
, brightness
;
374 char hip
[INET_ADDRSTRLEN
];
376 unsigned char *imgdata
;
378 hue
= randrange(10.0, 150.0);
379 saturation
= randrange(10.0, 200.0);
380 brightness
= randrange(30.0, 150.0);
382 inet_ntop(AF_INET
, &(args
->addr
.sin_addr
), hip
, sizeof(hip
));
383 hhip
= getheader(buffer
, "X-Forwarded-For:");
384 if (!hhip
) hhip
= hip
;
386 imgdata
= imggen(imgbuffs
[imgid
].data
,
387 imgbuffs
[imgid
].size
,
388 path
+ sizeof(PATH
) - 1, &imgsz
,
389 hue
, saturation
, brightness
);
391 printf("{ \"forwarded-for\": \"%s\", \"host\": \"%s\", \"image\": \"%d\", "
392 "\"path\": \"%s\", \"size\": \"%zu\", \"hue\": \"%.2f\", "
393 "\"saturation\": \"%.2f\", \"brightness\": \"%.2f\" }\n",
394 hhip
, hip
, imgid
, path
+ sizeof(PATH
) - 1, imgsz
, hue
, saturation
, brightness
);
397 snprintf(header
, sizeof(header
),
398 "HTTP/1.1 200 OK\r\n"
399 "Content-Type: image/jpeg\r\n"
400 "Content-Length: %zu\r\n"
401 "Connection: close\r\n\r\n",
404 send(clientsock
, header
, strlen(header
), 0);
405 send(clientsock
, imgdata
, imgsz
, 0);
406 MagickRelinquishMemory(imgdata
);
408 fprintf(stderr
, "failed to process image %d\n", imgid
);
409 send(clientsock
, response_404
, sizeof(response_404
), 0);
412 send(clientsock
, response_404
, sizeof(response_404
), 0);
426 pthread_mutex_lock(&queue
.lock
);
428 while (queue
.count
== 0) {
429 pthread_cond_wait(&queue
.note
, &queue
.lock
);
432 args
.sock
= queue
.clients
[queue
.head
].sock
;
433 args
.addr
= queue
.clients
[queue
.head
].addr
;
434 queue
.head
= (queue
.head
+ 1) % MAX_QUEUE
;
437 pthread_mutex_unlock(&queue
.lock
);
446 main(int argc
, char *argv
[])
448 int c
, efd
, opt
, serversock
;
449 const char *progname
= argv
[0];
450 pthread_t threads
[NUM_THREADS
];
451 struct sockaddr_in server_addr
= { 0 };
454 struct epoll_event event
, events
[1];
456 struct kevent kev
, kevs
[10];
459 while ((c
= getopt(argc
, argv
, "d")) >= 0) {
462 if (daemon(0, 0) < 0) err(1, "daemon");
465 fprintf(stderr
, "usage: %s [-d]\n", progname
);
473 serversock
= socket(AF_INET
, SOCK_STREAM
, 0);
477 if (imgpreload() < 0)
478 errx(1, "failed to preload images");
480 fonturi
= fontpreload();
482 errx(1, "failed to preload the font");
485 setsockopt(serversock
, SOL_SOCKET
, SO_REUSEADDR
, &opt
, sizeof(opt
));
487 server_addr
.sin_family
= AF_INET
;
488 server_addr
.sin_addr
.s_addr
= INADDR_ANY
;
489 server_addr
.sin_port
= htons(PORT
);
491 if (bind(serversock
, (struct sockaddr
*) &server_addr
,
492 sizeof(server_addr
)) < 0)
495 if (listen(serversock
, 1024) < 0)
498 printf("Server listening on port %d\n", PORT
);
499 printf("Access images at: http://localhost:%d" PATH
"\n", PORT
);
501 pthread_mutex_init(&queue
.lock
, NULL
);
502 pthread_cond_init(&queue
.note
, NULL
);
504 for (int i
= 0; i
< NUM_THREADS
; i
++) {
505 pthread_create(&threads
[i
], NULL
, worker
, NULL
);
506 pthread_detach(threads
[i
]);
510 if ((efd
= epoll_create1(0)) < 0)
511 err(1, "epoll_create1");
513 event
.events
= EPOLLIN
;
514 event
.data
.fd
= serversock
;
515 epoll_ctl(efd
, EPOLL_CTL_ADD
, serversock
, &event
);
517 if ((efd
= kqueue()) < 0)
520 EV_SET(&kev
, serversock
, EVFILT_READ
, EV_ADD
, 0, 0, NULL
);
521 if (kevent(efd
, &kev
, 1, NULL
, 0, NULL
) < 0)
530 if ((nfds
= epoll_wait(efd
, events
, 1 , -1)) < 0) {
531 perror("epoll_wait");
536 if (events
[0].events
& EPOLLIN
) {
538 if ((nfds
= kevent(efd
, NULL
, 0, kevs
, 10, NULL
)) < 0) {
543 for (int i
= 0; i
< nfds
; i
++) {
544 if(kevs
[i
].ident
== (uintptr_t)serversock
) {
547 * This goes inside the for loop above in case of kqueue/kevent
550 socklen_t clientaddrlen
= sizeof(args
.addr
);
552 args
.sock
= accept(serversock
, (struct sockaddr
*)&args
.addr
,
560 pthread_mutex_lock(&queue
.lock
);
562 if (queue
.count
>= MAX_QUEUE
) {
564 fprintf(stderr
, "Client queue full, rejecting\n");
566 queue
.clients
[queue
.tail
].sock
= args
.sock
;
567 queue
.clients
[queue
.tail
].addr
= args
.addr
;
568 queue
.tail
= (queue
.tail
+ 1) % MAX_QUEUE
;
570 pthread_cond_signal(&queue
.note
);
573 pthread_mutex_unlock(&queue
.lock
);
576 } /* closing the kevent for loop */
581 MagickWandTerminus();