/* multipart-replace.c - handle mixed/x-multipart-replace streams
*
* Copyright (C) 2016 Luke Shumaker
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#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);
}