summaryrefslogtreecommitdiff
path: root/multipart-replace.c
blob: 5f0ca4e7c3518b9260adf1fc09db160caf76d7e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* Copyright 2016 Luke Shumaker */

#include <errno.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "multipart-replace.h"
#include "util.h"

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");
	boundary = boundary_line(boundary);

	char *line_buf = NULL;
	size_t line_cap = 0;
	ssize_t line_len = 0;
	ssize_t content_length = -1;

	while (1) {
		content_length = -1;
		/* scan for the first non-empty line */
		do {
			line_len = getline(&line_buf, &line_cap, stream);
			if (line_len < 0) {
				error(0, 0, "source hung up");
				return;
			}
		} while (strcmp(line_buf, "\r\n") == 0);
		/* make sure it matches the boundary separator */
		if (strcmp(line_buf, boundary) != 0) {
			error(0, 0, "line does not match boundary: \"%s\"", line_buf);
			return;
		}
		/* read the frame header (MIME headers) */
		do {
			line_len = getline(&line_buf, &line_cap, stream);
			if (line_len < 0) {
				error(0, 0, "source hung up");
				return;
			}
			/* 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(0, 0, "did not get frame length");
			return;
		}
		/* 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(0, ferror(stream), "fread(%zd)", s->back->len);
			return;
		}
		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);
	}
}

void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *boundary) {
	struct frame myframe = { 0 };
	long lastframe = 0;
	boundary = boundary_line(boundary);
	size_t boundary_len = strlen(boundary);

	while(1) {
		/* poll until there's a new frame */
		pthread_rwlock_rdlock(&s->frontlock);
		while (s->framecount == lastframe) {
			pthread_rwlock_unlock(&s->frontlock);
			usleep(30000); /* a bit over 30 FPS */
			pthread_rwlock_rdlock(&s->frontlock);
		}

		/* 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 (write(fd, boundary, boundary_len) < (ssize_t)boundary_len) {
			error(0, errno, "write");
			return;
		}
		if (write(fd, myframe.buf, myframe.len) < myframe.len) {
			error(0, errno, "write");
			return;
		}
		/* send a blank line for pleasantness */
		if (write(fd, "\r\n", 2) < 2) {
			error(0, errno, "write");
			return;
		}
	}
}

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);
}