-
Notifications
You must be signed in to change notification settings - Fork 0
/
files.c
201 lines (180 loc) · 6.25 KB
/
files.c
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#define _GNU_SOURCE /* strcasestr */
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include "purr.h"
#include "mmap_file.h"
struct strip_header_info {
struct mmap_file *output;
char *header;
int counter, header_counter;
int content_size;
bool no_strip, debug;
enum connection_type type;
};
const int header_separator_len[] = { [HTTP_CONN] = 4, [GEMINI_CONN] = 2 };
const char *header_separator[] = { [HTTP_CONN] = "\r\n\r\n", [GEMINI_CONN] = "\r\n" };
static size_t fwrite_strip(const uint8_t *buf, int rlen, struct strip_header_info *st)
{
const char *separator = header_separator[st->type];
const int len = header_separator_len[st->type];
int i = 0;
if (st->counter != len) {
for (; i < rlen; i++) {
// state machine to detect the HTTP or Gemini header separator
if (buf[i] == separator[st->counter]) {
st->counter++;
} else {
st->counter = 0;
if (buf[i] == separator[st->counter]) {
st->counter++;
}
}
if (st->header_counter < HEADER_MAX_LEN - 1) {
// protect from buffer overflow
// the header buffer is calloc'd, so no need to null-terminate it manually
st->header[st->header_counter++] = buf[i];
}
if (st->counter == len) {
// eat last matching char
i++;
break;
}
}
}
// no_strip mode -> show header in stdout
// debug mode -> show header in stderr
// otherwise -> hide header
if (st->no_strip) {
write_into_mmap(st->output, buf, i);
} else if (st->debug) {
fwrite(buf, 1, i, stderr);
}
return write_into_mmap(st->output, buf + i, rlen - i);
}
size_t ssl_to_mmap(struct transmission_information ti)
{
size_t rv = 0;
struct strip_header_info st =
{.output = ti.file,
.header = calloc(HEADER_MAX_LEN, 1),
.debug = ti.debug, .no_strip = ti.no_strip,
.type = ti.type};
if (st.header == NULL) {
perror("allocation failure");
goto early_out;
}
size_t transmission_size = 0;
bool parsed_header = false;
while (1) {
uint8_t tmp[512];
int rlen;
if (ti.ssl) {
rlen = br_sslio_read(ti.ioc, tmp, sizeof tmp);
} else {
rlen = socket_read(&ti.socket, tmp, sizeof tmp);
}
if (rlen < 0) {
break;
}
rv += fwrite_strip(tmp, rlen, &st);
// check if header is done
if (st.counter == header_separator_len[ti.type] && !parsed_header) {
parsed_header = true;
if (ti.type == HTTP_CONN) {
// http headers need to be parsed for content length
const char *needle = "content-length: ";
char *length = strcasestr(st.header, needle);
if (length == NULL) {
fputs("header didn't contain content-length field\n", stderr);
rv = 0;
goto early_out;
}
transmission_size = atoll(length + strlen(needle));
if (transmission_size == 0) {
fputs("couldn't parse content-length\n", stderr);
rv = 0;
goto early_out;
}
} else if (ti.type == GEMINI_CONN) {
// gemini headers can be checked for information
// <STATUS: 2 chars><SPACE><META>
char first = st.header[0], second = st.header[1], *meta = st.header + 2;
if (first < '1' || first > '6' || second < '0' || second > '9' || !isblank(*meta)) {
fputs("out-of-spec header!\n", stderr);
goto early_out;
}
// eat all the whitespaces - known to have at least one;
// breaks if whitespace isn't the same
meta = strrchr(st.header, *meta) + 1;
if (ti.header_callback) {
ti.header_callback(first, meta);
}
switch (first) {
case '1':
fputs("INPUT not supported\n", stderr);
rv = 0;
goto early_out;
break;
case '2':
if (ti.debug) fprintf(stderr, "success code: %c mime: %s\n", second, meta);
break;
case '3':
fprintf(stderr, "redirect code: %c url: %s\n", second, meta);
rv = 0;
goto early_out;
break;
case '4':
fprintf(stderr, "temp failure code: %c msg: %s\n", second, meta);
rv = 0;
goto early_out;
break;
case '5':
fprintf(stderr, "perm failure code: %c msg: %s\n", second, meta);
rv = 0;
goto early_out;
break;
case '6':
fprintf(stderr, "client cert code: %c msg: %s\n", second, meta);
rv = 0;
goto early_out;
break;
}
}
}
if (transmission_size) {
// limit transmission size
if (rv >= transmission_size) {
rv = transmission_size;
break;
}
}
}
early_out:
free(st.header);
return rv;
}
size_t mmap_to_ssl(struct transmission_information ti)
{
size_t rv = 0;
while (1) {
uint8_t tmp[NET_BLOCK_SIZE];
int wlen = read_from_mmap(ti.file, tmp, NET_BLOCK_SIZE);
int err = 0;
if (ti.ssl) {
err = br_sslio_write_all(ti.ioc, tmp, wlen);
} else {
err = (int)fwrite(tmp, 1, wlen, ti.socket_write_stream) != wlen;
}
if (err == 0) {
rv += wlen;
}
if (wlen < NET_BLOCK_SIZE) {
break;
}
}
return rv;
}