summaryrefslogtreecommitdiff
path: root/src/multipart-replace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/multipart-replace.c')
-rw-r--r--src/multipart-replace.c168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/multipart-replace.c b/src/multipart-replace.c
new file mode 100644
index 0000000..9a5e268
--- /dev/null
+++ b/src/multipart-replace.c
@@ -0,0 +1,168 @@
+/* Copyright 2016 Luke Shumaker */
+
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "multipart-replace.h"
+#include "util.h"
+
+#define error(stat, errnum, ...) do { error(0, errnum, __VA_ARGS__); goto end; } while(0)
+
+static
+char *boundary_line(const char *old) {
+ size_t len = strlen(old);
+ char *new = xrealloc(NULL, len+5);
+ new[0] = '-';
+ new[1] = '-';
+ strcpy(&new[2], old);
+ new[2+len+0] = '\r';
+ new[2+len+1] = '\n';
+ new[2+len+2] = '\0';
+ return new;
+}
+
+static
+ssize_t safe_atoi(char *str) {
+ while (*str == ' ')
+ str++;
+ size_t len = 0;
+ while ('0' <= str[len] && str[len] <= '9')
+ len++;
+ size_t i = len;
+ while (str[i] == ' ')
+ i++;
+ if (len < 1 || strcmp(&str[i], "\r\n") != 0)
+ return -1;
+ return atoi(str);
+}
+
+void multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *_boundary) {
+ FILE *stream = fdopen(fd, "r");
+ char *boundary = boundary_line(_boundary);
+
+ char *line_buf = NULL;
+ size_t line_cap = 0;
+ ssize_t line_len = 0;
+ ssize_t content_length = -1;
+
+ bool first = true;
+ while (running) {
+ content_length = -1;
+ /* scan for the first non-empty line */
+ do {
+ line_len = getline(&line_buf, &line_cap, stream);
+ if (line_len < 0)
+ error(EXIT_FAILURE, ferror(stream), "source hung up");
+ } while (strcmp(line_buf, "\r\n") == 0);
+ /* make sure it matches the boundary separator */
+ if (first) {
+ while (strcmp(line_buf, boundary) != 0) {
+ line_len = getline(&line_buf, &line_cap, stream);
+ if (line_len < 0)
+ error(EXIT_FAILURE, ferror(stream), "source hung up");
+ }
+ first = false;
+ } else {
+ if (strcmp(line_buf, boundary) != 0)
+ error(EXIT_FAILURE, 0, "line does not match boundary: \"%s\"", line_buf);
+ }
+ /* read the frame header (MIME headers) */
+ s->back->len = 0;
+ do {
+ line_len = getline(&line_buf, &line_cap, stream);
+ if (line_len < 0)
+ error(EXIT_FAILURE, ferror(stream), "source hung up");
+ /* append the line to the frame contents */
+ if ((ssize_t)s->back->cap < s->back->len + line_len)
+ s->back->buf = xrealloc(s->back->buf, s->back->cap = s->back->len + line_len);
+ memcpy(&s->back->buf[s->back->len], line_buf, line_len);
+ s->back->len += line_len;
+ /* parse the Content-length (if applicable) */
+ if (strncasecmp(line_buf, "Content-length:", strlen("Content-length:")) == 0) {
+ content_length = safe_atoi(&line_buf[strlen("Content-length:")]);
+ }
+ } while (strcmp(line_buf, "\r\n") != 0);
+ /* make sure that it included a Content-length header */
+ if (content_length < 0)
+ error(EXIT_FAILURE, 0, "did not get frame length");
+ /* read the frame contents */
+ if ((ssize_t)s->back->cap < s->back->len + content_length)
+ s->back->buf = xrealloc(s->back->buf, s->back->cap = s->back->len + content_length);
+ if (fread(&s->back->buf[s->back->len], content_length, 1, stream) != 1)
+ error(EXIT_FALURE, ferror(stream), "fread(%zd)", s->back->len);
+ s->back->len += content_length;
+
+ /* swap the frames */
+ pthread_rwlock_wrlock(&s->frontlock);
+ struct frame *tmp = s->front;
+ s->front = s->back;
+ s->back = tmp;
+ s->framecount++;
+ pthread_rwlock_unlock(&s->frontlock);
+ }
+ end:
+ free(boundary);
+ fclose(stream);
+}
+
+void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *_boundary) {
+ FILE *stream = fdopen(fd, "w");
+ struct frame myframe = { 0 };
+ long lastframe = 0;
+ char *boundary = boundary_line(_boundary);
+ size_t boundary_len = strlen(boundary);
+
+ pthread_rwlock_rdlock(&s->frontlock);
+ while (running) {
+ /* get the most recent frame (copy `s->front` to `myframe`) */
+ if (myframe.cap < (size_t)s->front->len)
+ myframe.buf = xrealloc(myframe.buf, myframe.cap = s->front->len);
+ memcpy(myframe.buf, s->front->buf, myframe.len = s->front->len);
+ lastframe = s->framecount;
+
+ pthread_rwlock_unlock(&s->frontlock);
+
+ /* send the frame to the client */
+ if (fwrite(boundary, boundary_len, 1, stream) < 1)
+ error(EXIT_FAILURE, ferror(stream), "fwrite(boundary)");
+ if (fwrite(myframe.buf, myframe.len, 1, stream) < 1)
+ error(EXIT_FAILURE, ferror(stream), "fwrite(frame.buf)");
+ /* send a blank line for pleasantness */
+ if (fwrite("\r\n", 2, 1, stream) < 1)
+ error(EXIT_FAILURE, ferror(stream), "fwrite(\"\\r\\n\")");
+ fflush(stream);
+
+ /* poll until there's a new frame */
+ pthread_rwlock_rdlock(&s->frontlock);
+ while (s->framecount == lastframe && running) {
+ pthread_rwlock_unlock(&s->frontlock);
+ usleep(30000); /* a bit over 30 FPS */
+ pthread_rwlock_rdlock(&s->frontlock);
+ }
+ }
+ pthread_rwlock_unlock(&s->frontlock);
+ end:
+ free(boundary);
+ free(myframe.buf);
+ fclose(stream);
+}
+
+void init_multipart_replace_stream(struct multipart_replace_stream *s) {
+ ZERO(s->a);
+ ZERO(s->b);
+ s->front = &s->a;
+ s->back = &s->b;
+ pthread_rwlock_init(&s->frontlock, NULL);
+ s->framecount = 0;
+}
+
+void destroy_multipart_replace_stream(struct multipart_replace_stream *s) {
+ free(s->a.buf);
+ free(s->b.buf);
+ pthread_rwlock_destroy(&s->frontlock);
+}