diff options
Diffstat (limited to 'src/multipart-replace.c')
-rw-r--r-- | src/multipart-replace.c | 168 |
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); +} |