rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -0
data/ext/iodine/http.c
ADDED
@@ -0,0 +1,2736 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz Segev, 2016-2019
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <fio.h>
|
9
|
+
|
10
|
+
#include <http1.h>
|
11
|
+
#include <http_internal.h>
|
12
|
+
|
13
|
+
#include <ctype.h>
|
14
|
+
#include <fcntl.h>
|
15
|
+
#include <signal.h>
|
16
|
+
#include <string.h>
|
17
|
+
#include <sys/stat.h>
|
18
|
+
#include <sys/types.h>
|
19
|
+
#include <unistd.h>
|
20
|
+
|
21
|
+
#include <pthread.h>
|
22
|
+
|
23
|
+
#ifndef HAVE_TM_TM_ZONE
|
24
|
+
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
|
25
|
+
defined(__DragonFly__) || defined(__bsdi__) || defined(__ultrix) || \
|
26
|
+
(defined(__APPLE__) && defined(__MACH__)) || \
|
27
|
+
(defined(__sun) && !defined(__SVR4))
|
28
|
+
/* Known BSD systems */
|
29
|
+
#define HAVE_TM_TM_ZONE 1
|
30
|
+
#elif defined(__GLIBC__) && defined(_BSD_SOURCE)
|
31
|
+
/* GNU systems with _BSD_SOURCE */
|
32
|
+
#define HAVE_TM_TM_ZONE 1
|
33
|
+
#else
|
34
|
+
#define HAVE_TM_TM_ZONE 0
|
35
|
+
#endif
|
36
|
+
#endif
|
37
|
+
|
38
|
+
#ifndef __MINGW32__
|
39
|
+
/* *****************************************************************************
|
40
|
+
SSL/TLS patch
|
41
|
+
***************************************************************************** */
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Adds an ALPN protocol callback to the SSL/TLS context.
|
45
|
+
*
|
46
|
+
* The first protocol added will act as the default protocol to be selected.
|
47
|
+
*/
|
48
|
+
void __attribute__((weak))
|
49
|
+
fio_tls_alpn_add(void *tls, const char *protocol_name,
|
50
|
+
void (*callback)(intptr_t uuid, void *udata_connection,
|
51
|
+
void *udata_tls),
|
52
|
+
void *udata_tls, void (*on_cleanup)(void *udata_tls)) {
|
53
|
+
FIO_LOG_FATAL("HTTP SSL/TLS required but unavailable!");
|
54
|
+
exit(-1);
|
55
|
+
(void)tls;
|
56
|
+
(void)protocol_name;
|
57
|
+
(void)callback;
|
58
|
+
(void)on_cleanup;
|
59
|
+
(void)udata_tls;
|
60
|
+
}
|
61
|
+
#pragma weak fio_tls_alpn_add
|
62
|
+
#endif
|
63
|
+
|
64
|
+
/* *****************************************************************************
|
65
|
+
Small Helpers
|
66
|
+
***************************************************************************** */
|
67
|
+
static inline int hex2byte(uint8_t *dest, const uint8_t *source);
|
68
|
+
|
69
|
+
static inline void add_content_length(http_s *r, uintptr_t length) {
|
70
|
+
static uint64_t cl_hash = 0;
|
71
|
+
if (!cl_hash)
|
72
|
+
cl_hash = fiobj_hash_string("content-length", 14);
|
73
|
+
if (!fiobj_hash_get2(r->private_data.out_headers, cl_hash)) {
|
74
|
+
fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_LENGTH,
|
75
|
+
fiobj_num_new(length));
|
76
|
+
}
|
77
|
+
}
|
78
|
+
static inline void add_content_type(http_s *r) {
|
79
|
+
static uint64_t ct_hash = 0;
|
80
|
+
if (!ct_hash)
|
81
|
+
ct_hash = fiobj_hash_string("content-type", 12);
|
82
|
+
if (!fiobj_hash_get2(r->private_data.out_headers, ct_hash)) {
|
83
|
+
fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_TYPE,
|
84
|
+
http_mimetype_find2(r->path));
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
static FIOBJ current_date;
|
89
|
+
static time_t last_date_added;
|
90
|
+
static fio_lock_i date_lock;
|
91
|
+
static inline void add_date(http_s *r) {
|
92
|
+
static uint64_t date_hash = 0;
|
93
|
+
if (!date_hash)
|
94
|
+
date_hash = fiobj_hash_string("date", 4);
|
95
|
+
|
96
|
+
if (fio_last_tick().tv_sec > last_date_added) {
|
97
|
+
fio_lock(&date_lock);
|
98
|
+
if (fio_last_tick().tv_sec > last_date_added) { /* retest inside lock */
|
99
|
+
/* 32 chars are ok for a while, but http_time2str below has a buffer sized
|
100
|
+
* 48 chars and does a memcpy ... */
|
101
|
+
FIOBJ tmp = fiobj_str_buf(32);
|
102
|
+
FIOBJ old = current_date;
|
103
|
+
fiobj_str_resize(
|
104
|
+
tmp, http_time2str(fiobj_obj2cstr(tmp).data, fio_last_tick().tv_sec));
|
105
|
+
last_date_added = fio_last_tick().tv_sec;
|
106
|
+
current_date = tmp;
|
107
|
+
fiobj_free(old);
|
108
|
+
}
|
109
|
+
fio_unlock(&date_lock);
|
110
|
+
}
|
111
|
+
|
112
|
+
if (!fiobj_hash_get2(r->private_data.out_headers, date_hash)) {
|
113
|
+
fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_DATE,
|
114
|
+
fiobj_dup(current_date));
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
struct header_writer_s {
|
119
|
+
FIOBJ dest;
|
120
|
+
FIOBJ name;
|
121
|
+
FIOBJ value;
|
122
|
+
};
|
123
|
+
|
124
|
+
static int write_header(FIOBJ o, void *w_) {
|
125
|
+
struct header_writer_s *w = w_;
|
126
|
+
if (!o)
|
127
|
+
return 0;
|
128
|
+
if (fiobj_hash_key_in_loop()) {
|
129
|
+
w->name = fiobj_hash_key_in_loop();
|
130
|
+
}
|
131
|
+
if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) {
|
132
|
+
fiobj_each1(o, 0, write_header, w);
|
133
|
+
return 0;
|
134
|
+
}
|
135
|
+
fio_str_info_s name = fiobj_obj2cstr(w->name);
|
136
|
+
fio_str_info_s str = fiobj_obj2cstr(o);
|
137
|
+
if (!str.data)
|
138
|
+
return 0;
|
139
|
+
fiobj_str_write(w->dest, name.data, name.len);
|
140
|
+
fiobj_str_write(w->dest, ":", 1);
|
141
|
+
fiobj_str_write(w->dest, str.data, str.len);
|
142
|
+
fiobj_str_write(w->dest, "\r\n", 2);
|
143
|
+
return 0;
|
144
|
+
}
|
145
|
+
|
146
|
+
static char invalid_cookie_name_char[256];
|
147
|
+
|
148
|
+
static char invalid_cookie_value_char[256];
|
149
|
+
/* *****************************************************************************
|
150
|
+
The Request / Response type and functions
|
151
|
+
***************************************************************************** */
|
152
|
+
static const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
153
|
+
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Sets a response header, taking ownership of the value object, but NOT the
|
157
|
+
* name object (so name objects could be reused in future responses).
|
158
|
+
*
|
159
|
+
* Returns -1 on error and 0 on success.
|
160
|
+
*/
|
161
|
+
int http_set_header(http_s *r, FIOBJ name, FIOBJ value) {
|
162
|
+
if (HTTP_INVALID_HANDLE(r) || !name) {
|
163
|
+
fiobj_free(value);
|
164
|
+
return -1;
|
165
|
+
}
|
166
|
+
set_header_add(r->private_data.out_headers, name, value);
|
167
|
+
return 0;
|
168
|
+
}
|
169
|
+
/**
|
170
|
+
* Sets a response header.
|
171
|
+
*
|
172
|
+
* Returns -1 on error and 0 on success.
|
173
|
+
*/
|
174
|
+
int http_set_header2(http_s *r, fio_str_info_s n, fio_str_info_s v) {
|
175
|
+
if (HTTP_INVALID_HANDLE(r) || !n.data || !n.len || (v.data && !v.len))
|
176
|
+
return -1;
|
177
|
+
FIOBJ tmp = fiobj_str_new(n.data, n.len);
|
178
|
+
int ret = http_set_header(r, tmp, fiobj_str_new(v.data, v.len));
|
179
|
+
fiobj_free(tmp);
|
180
|
+
return ret;
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Sets a response cookie, taking ownership of the value object, but NOT the
|
185
|
+
* name object (so name objects could be reused in future responses).
|
186
|
+
*
|
187
|
+
* Returns -1 on error and 0 on success.
|
188
|
+
*/
|
189
|
+
#undef http_set_cookie
|
190
|
+
int http_set_cookie(http_s *h, http_cookie_args_s cookie) {
|
191
|
+
#if DEBUG
|
192
|
+
FIO_ASSERT(h, "Can't set cookie for NULL HTTP handler!");
|
193
|
+
#endif
|
194
|
+
if (HTTP_INVALID_HANDLE(h) || cookie.name_len >= 32768 ||
|
195
|
+
cookie.value_len >= 131072) {
|
196
|
+
return -1;
|
197
|
+
}
|
198
|
+
|
199
|
+
static int warn_illegal = 0;
|
200
|
+
|
201
|
+
/* write name and value while auto-correcting encoding issues */
|
202
|
+
size_t capa = cookie.name_len + cookie.value_len + 128;
|
203
|
+
size_t len = 0;
|
204
|
+
FIOBJ c = fiobj_str_buf(capa);
|
205
|
+
fio_str_info_s t = fiobj_obj2cstr(c);
|
206
|
+
|
207
|
+
#define copy_cookie_ch(ch_var) \
|
208
|
+
if (invalid_cookie_##ch_var##_char[(uint8_t)cookie.ch_var[tmp]]) { \
|
209
|
+
if (!warn_illegal) { \
|
210
|
+
++warn_illegal; \
|
211
|
+
FIO_LOG_WARNING("illegal char 0x%.2x in cookie " #ch_var " (in %s)\n" \
|
212
|
+
" automatic %% encoding applied", \
|
213
|
+
cookie.ch_var[tmp], cookie.ch_var); \
|
214
|
+
} \
|
215
|
+
t.data[len++] = '%'; \
|
216
|
+
t.data[len++] = hex_chars[((uint8_t)cookie.ch_var[tmp] >> 4) & 0x0F]; \
|
217
|
+
t.data[len++] = hex_chars[(uint8_t)cookie.ch_var[tmp] & 0x0F]; \
|
218
|
+
} else { \
|
219
|
+
t.data[len++] = cookie.ch_var[tmp]; \
|
220
|
+
} \
|
221
|
+
tmp += 1; \
|
222
|
+
if (capa <= len + 3) { \
|
223
|
+
capa += 32; \
|
224
|
+
fiobj_str_capa_assert(c, capa); \
|
225
|
+
t = fiobj_obj2cstr(c); \
|
226
|
+
}
|
227
|
+
|
228
|
+
if (cookie.name) {
|
229
|
+
size_t tmp = 0;
|
230
|
+
if (cookie.name_len) {
|
231
|
+
while (tmp < cookie.name_len) {
|
232
|
+
copy_cookie_ch(name);
|
233
|
+
}
|
234
|
+
} else {
|
235
|
+
while (cookie.name[tmp]) {
|
236
|
+
copy_cookie_ch(name);
|
237
|
+
}
|
238
|
+
}
|
239
|
+
}
|
240
|
+
t.data[len++] = '=';
|
241
|
+
if (cookie.value) {
|
242
|
+
size_t tmp = 0;
|
243
|
+
if (cookie.value_len) {
|
244
|
+
while (tmp < cookie.value_len) {
|
245
|
+
copy_cookie_ch(value);
|
246
|
+
}
|
247
|
+
} else {
|
248
|
+
while (cookie.value[tmp]) {
|
249
|
+
copy_cookie_ch(value);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
} else
|
253
|
+
cookie.max_age = -1;
|
254
|
+
|
255
|
+
if (http_settings(h) && http_settings(h)->is_client) {
|
256
|
+
if (!cookie.value) {
|
257
|
+
fiobj_free(c);
|
258
|
+
return -1;
|
259
|
+
}
|
260
|
+
set_header_add(h->private_data.out_headers, HTTP_HEADER_COOKIE, c);
|
261
|
+
return 0;
|
262
|
+
}
|
263
|
+
|
264
|
+
t.data[len++] = ';';
|
265
|
+
t.data[len++] = ' ';
|
266
|
+
|
267
|
+
if (h->status_str || !h->status) { /* on first request status == 0 */
|
268
|
+
static uint64_t cookie_hash;
|
269
|
+
if (!cookie_hash)
|
270
|
+
cookie_hash = fiobj_hash_string("cookie", 6);
|
271
|
+
FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, cookie_hash);
|
272
|
+
if (!tmp) {
|
273
|
+
set_header_add(h->private_data.out_headers, HTTP_HEADER_COOKIE, c);
|
274
|
+
} else {
|
275
|
+
fiobj_str_join(tmp, c);
|
276
|
+
fiobj_free(c);
|
277
|
+
}
|
278
|
+
return 0;
|
279
|
+
}
|
280
|
+
|
281
|
+
if (capa <= len + 40) {
|
282
|
+
capa = len + 40;
|
283
|
+
fiobj_str_capa_assert(c, capa);
|
284
|
+
t = fiobj_obj2cstr(c);
|
285
|
+
}
|
286
|
+
if (cookie.max_age) {
|
287
|
+
memcpy(t.data + len, "Max-Age=", 8);
|
288
|
+
len += 8;
|
289
|
+
len += fio_ltoa(t.data + len, cookie.max_age, 10);
|
290
|
+
t.data[len++] = ';';
|
291
|
+
t.data[len++] = ' ';
|
292
|
+
}
|
293
|
+
fiobj_str_resize(c, len);
|
294
|
+
|
295
|
+
if (cookie.domain && cookie.domain_len) {
|
296
|
+
fiobj_str_write(c, "domain=", 7);
|
297
|
+
fiobj_str_write(c, cookie.domain, cookie.domain_len);
|
298
|
+
fiobj_str_write(c, ";", 1);
|
299
|
+
t.data[len++] = ' ';
|
300
|
+
}
|
301
|
+
if (cookie.path && cookie.path_len) {
|
302
|
+
fiobj_str_write(c, "path=", 5);
|
303
|
+
fiobj_str_write(c, cookie.path, cookie.path_len);
|
304
|
+
fiobj_str_write(c, ";", 1);
|
305
|
+
t.data[len++] = ' ';
|
306
|
+
}
|
307
|
+
if (cookie.http_only) {
|
308
|
+
fiobj_str_write(c, "HttpOnly;", 9);
|
309
|
+
}
|
310
|
+
if (cookie.secure) {
|
311
|
+
fiobj_str_write(c, "secure;", 7);
|
312
|
+
}
|
313
|
+
set_header_add(h->private_data.out_headers, HTTP_HEADER_SET_COOKIE, c);
|
314
|
+
return 0;
|
315
|
+
}
|
316
|
+
#define http_set_cookie(http__req__, ...) \
|
317
|
+
http_set_cookie((http__req__), (http_cookie_args_s){__VA_ARGS__})
|
318
|
+
|
319
|
+
/**
|
320
|
+
* Sends the response headers and body.
|
321
|
+
*
|
322
|
+
* Returns -1 on error and 0 on success.
|
323
|
+
*
|
324
|
+
* AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID.
|
325
|
+
*/
|
326
|
+
int http_send_body(http_s *r, void *data, uintptr_t length) {
|
327
|
+
if (HTTP_INVALID_HANDLE(r))
|
328
|
+
return -1;
|
329
|
+
if (!length || !data) {
|
330
|
+
http_finish(r);
|
331
|
+
return 0;
|
332
|
+
}
|
333
|
+
add_content_length(r, length);
|
334
|
+
// add_content_type(r);
|
335
|
+
add_date(r);
|
336
|
+
return ((http_vtable_s *)r->private_data.vtbl)
|
337
|
+
->http_send_body(r, data, length);
|
338
|
+
}
|
339
|
+
/**
|
340
|
+
* Sends the response headers and the specified file (the response's body).
|
341
|
+
*
|
342
|
+
* Returns -1 on error and 0 on success.
|
343
|
+
*
|
344
|
+
* AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID.
|
345
|
+
*/
|
346
|
+
int http_sendfile(http_s *r, int fd, uintptr_t length, uintptr_t offset) {
|
347
|
+
if (HTTP_INVALID_HANDLE(r)) {
|
348
|
+
close(fd);
|
349
|
+
return -1;
|
350
|
+
};
|
351
|
+
add_content_length(r, length);
|
352
|
+
add_content_type(r);
|
353
|
+
add_date(r);
|
354
|
+
return ((http_vtable_s *)r->private_data.vtbl)
|
355
|
+
->http_sendfile(r, fd, length, offset);
|
356
|
+
}
|
357
|
+
|
358
|
+
static inline int http_test_encoded_path(const char *mem, size_t len) {
|
359
|
+
const char *pos = NULL;
|
360
|
+
const char *end = mem + len;
|
361
|
+
while (mem < end && (pos = memchr(mem, '/', (size_t)len))) {
|
362
|
+
len = end - pos;
|
363
|
+
mem = pos + 1;
|
364
|
+
if (pos[1] == '/')
|
365
|
+
return -1;
|
366
|
+
if (len > 3 && pos[1] == '.' && pos[2] == '.' && pos[3] == '/')
|
367
|
+
return -1;
|
368
|
+
}
|
369
|
+
return 0;
|
370
|
+
}
|
371
|
+
|
372
|
+
/**
|
373
|
+
* Sends the response headers and the specified file (the response's body).
|
374
|
+
*
|
375
|
+
* Returns -1 eton error and 0 on success.
|
376
|
+
*
|
377
|
+
* AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID.
|
378
|
+
*/
|
379
|
+
int http_sendfile2(http_s *h, const char *prefix, size_t prefix_len,
|
380
|
+
const char *encoded, size_t encoded_len) {
|
381
|
+
if (HTTP_INVALID_HANDLE(h))
|
382
|
+
return -1;
|
383
|
+
struct stat file_data = {.st_size = 0};
|
384
|
+
static uint64_t accept_enc_hash = 0;
|
385
|
+
if (!accept_enc_hash)
|
386
|
+
accept_enc_hash = fiobj_hash_string("accept-encoding", 15);
|
387
|
+
static uint64_t range_hash = 0;
|
388
|
+
if (!range_hash)
|
389
|
+
range_hash = fiobj_hash_string("range", 5);
|
390
|
+
|
391
|
+
/* create filename string */
|
392
|
+
FIOBJ filename = fiobj_str_tmp();
|
393
|
+
if (prefix && prefix_len) {
|
394
|
+
/* start with prefix path */
|
395
|
+
if (encoded && prefix[prefix_len - 1] == '/' && encoded[0] == '/')
|
396
|
+
--prefix_len;
|
397
|
+
fiobj_str_capa_assert(filename, prefix_len + encoded_len + 4);
|
398
|
+
fiobj_str_write(filename, prefix, prefix_len);
|
399
|
+
}
|
400
|
+
{
|
401
|
+
/* decode filename in cases where it's URL encoded */
|
402
|
+
fio_str_info_s tmp = fiobj_obj2cstr(filename);
|
403
|
+
if (encoded) {
|
404
|
+
char *pos = (char *)encoded;
|
405
|
+
const char *end = encoded + encoded_len;
|
406
|
+
while (pos < end) {
|
407
|
+
if (*pos == '%') {
|
408
|
+
// decode hex value (this is a percent encoded value).
|
409
|
+
if (hex2byte((uint8_t *)tmp.data + tmp.len, (uint8_t *)pos + 1))
|
410
|
+
return -1;
|
411
|
+
tmp.len++;
|
412
|
+
pos += 3;
|
413
|
+
} else
|
414
|
+
tmp.data[tmp.len++] = *(pos++);
|
415
|
+
}
|
416
|
+
tmp.data[tmp.len] = 0;
|
417
|
+
fiobj_str_resize(filename, tmp.len);
|
418
|
+
/* test for path manipulations after decoding */
|
419
|
+
if (http_test_encoded_path(tmp.data + prefix_len, tmp.len - prefix_len))
|
420
|
+
return -1;
|
421
|
+
}
|
422
|
+
if (tmp.data[tmp.len - 1] == '/')
|
423
|
+
fiobj_str_write(filename, "index.html", 10);
|
424
|
+
}
|
425
|
+
/* test for file existance */
|
426
|
+
|
427
|
+
int file = -1;
|
428
|
+
uint8_t is_gz = 0;
|
429
|
+
|
430
|
+
fio_str_info_s s = fiobj_obj2cstr(filename);
|
431
|
+
{
|
432
|
+
FIOBJ tmp = fiobj_hash_get2(h->headers, accept_enc_hash);
|
433
|
+
if (!tmp)
|
434
|
+
goto no_gzip_support;
|
435
|
+
fio_str_info_s ac_str = fiobj_obj2cstr(tmp);
|
436
|
+
if (!ac_str.data || !strstr(ac_str.data, "gzip"))
|
437
|
+
goto no_gzip_support;
|
438
|
+
if (s.data[s.len - 3] != '.' || s.data[s.len - 2] != 'g' ||
|
439
|
+
s.data[s.len - 1] != 'z') {
|
440
|
+
fiobj_str_write(filename, ".gz", 3);
|
441
|
+
s = fiobj_obj2cstr(filename);
|
442
|
+
if (!stat(s.data, &file_data) &&
|
443
|
+
(S_ISREG(file_data.st_mode) || S_ISLNK(file_data.st_mode))) {
|
444
|
+
is_gz = 1;
|
445
|
+
goto found_file;
|
446
|
+
}
|
447
|
+
fiobj_str_resize(filename, s.len - 3);
|
448
|
+
}
|
449
|
+
}
|
450
|
+
no_gzip_support:
|
451
|
+
if (stat(s.data, &file_data) ||
|
452
|
+
!(S_ISREG(file_data.st_mode) || S_ISLNK(file_data.st_mode)))
|
453
|
+
return -1;
|
454
|
+
found_file:
|
455
|
+
/* set last-modified */
|
456
|
+
{
|
457
|
+
FIOBJ tmp = fiobj_str_buf(32);
|
458
|
+
fiobj_str_resize(
|
459
|
+
tmp, http_time2str(fiobj_obj2cstr(tmp).data, file_data.st_mtime));
|
460
|
+
http_set_header(h, HTTP_HEADER_LAST_MODIFIED, tmp);
|
461
|
+
}
|
462
|
+
/* set cache-control */
|
463
|
+
http_set_header(h, HTTP_HEADER_CACHE_CONTROL, fiobj_dup(HTTP_HVALUE_MAX_AGE));
|
464
|
+
/* set & test etag */
|
465
|
+
uint64_t etag = (uint64_t)file_data.st_size;
|
466
|
+
etag ^= (uint64_t)file_data.st_mtime;
|
467
|
+
etag = fiobj_hash_string(&etag, sizeof(uint64_t));
|
468
|
+
FIOBJ etag_str = fiobj_str_buf(32);
|
469
|
+
fiobj_str_resize(etag_str,
|
470
|
+
fio_base64_encode(fiobj_obj2cstr(etag_str).data,
|
471
|
+
(void *)&etag, sizeof(uint64_t)));
|
472
|
+
/* set */
|
473
|
+
http_set_header(h, HTTP_HEADER_ETAG, etag_str);
|
474
|
+
/* test */
|
475
|
+
{
|
476
|
+
static uint64_t none_match_hash = 0;
|
477
|
+
if (!none_match_hash)
|
478
|
+
none_match_hash = fiobj_hash_string("if-none-match", 13);
|
479
|
+
FIOBJ tmp2 = fiobj_hash_get2(h->headers, none_match_hash);
|
480
|
+
if (tmp2 && fiobj_iseq(tmp2, etag_str)) {
|
481
|
+
h->status = 304;
|
482
|
+
http_finish(h);
|
483
|
+
return 0;
|
484
|
+
}
|
485
|
+
}
|
486
|
+
/* handle range requests */
|
487
|
+
int64_t offset = 0;
|
488
|
+
int64_t length = file_data.st_size;
|
489
|
+
{
|
490
|
+
static uint64_t ifrange_hash = 0;
|
491
|
+
if (!ifrange_hash)
|
492
|
+
ifrange_hash = fiobj_hash_string("if-range", 8);
|
493
|
+
FIOBJ tmp = fiobj_hash_get2(h->headers, ifrange_hash);
|
494
|
+
if (tmp && fiobj_iseq(tmp, etag_str)) {
|
495
|
+
fiobj_hash_delete2(h->headers, range_hash);
|
496
|
+
} else {
|
497
|
+
tmp = fiobj_hash_get2(h->headers, range_hash);
|
498
|
+
if (tmp) {
|
499
|
+
/* range ahead... */
|
500
|
+
if (FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY))
|
501
|
+
tmp = fiobj_ary_index(tmp, 0);
|
502
|
+
fio_str_info_s range = fiobj_obj2cstr(tmp);
|
503
|
+
if (!range.data || memcmp("bytes=", range.data, 6))
|
504
|
+
goto open_file;
|
505
|
+
char *pos = range.data + 6;
|
506
|
+
int64_t start_at = 0, end_at = 0;
|
507
|
+
start_at = fio_atol(&pos);
|
508
|
+
if (start_at >= file_data.st_size)
|
509
|
+
goto open_file;
|
510
|
+
if (start_at >= 0) {
|
511
|
+
pos++;
|
512
|
+
end_at = fio_atol(&pos);
|
513
|
+
if (end_at <= 0)
|
514
|
+
goto open_file;
|
515
|
+
}
|
516
|
+
/* we ignore multimple ranges, only responding with the first range. */
|
517
|
+
if (start_at < 0) {
|
518
|
+
if (0 - start_at < file_data.st_size) {
|
519
|
+
offset = file_data.st_size - start_at;
|
520
|
+
length = 0 - start_at;
|
521
|
+
}
|
522
|
+
} else if (end_at) {
|
523
|
+
offset = start_at;
|
524
|
+
length = end_at - start_at + 1;
|
525
|
+
if (length + start_at > file_data.st_size || length <= 0)
|
526
|
+
length = length - start_at;
|
527
|
+
} else {
|
528
|
+
offset = start_at;
|
529
|
+
length = length - start_at;
|
530
|
+
}
|
531
|
+
h->status = 206;
|
532
|
+
|
533
|
+
{
|
534
|
+
FIOBJ cranges = fiobj_str_buf(1);
|
535
|
+
fiobj_str_printf(cranges, "bytes %lu-%lu/%lu",
|
536
|
+
(unsigned long)start_at,
|
537
|
+
(unsigned long)(start_at + length - 1),
|
538
|
+
(unsigned long)file_data.st_size);
|
539
|
+
http_set_header(h, HTTP_HEADER_CONTENT_RANGE, cranges);
|
540
|
+
}
|
541
|
+
http_set_header(h, HTTP_HEADER_ACCEPT_RANGES,
|
542
|
+
fiobj_dup(HTTP_HVALUE_BYTES));
|
543
|
+
}
|
544
|
+
}
|
545
|
+
}
|
546
|
+
/* test for an OPTIONS request or invalid methods */
|
547
|
+
s = fiobj_obj2cstr(h->method);
|
548
|
+
switch (s.len) {
|
549
|
+
case 7:
|
550
|
+
if (!strncasecmp("options", s.data, 7)) {
|
551
|
+
http_set_header2(h, (fio_str_info_s){.data = (char *)"allow", .len = 5},
|
552
|
+
(fio_str_info_s){.data = (char *)"GET, HEAD", .len = 9});
|
553
|
+
h->status = 200;
|
554
|
+
http_finish(h);
|
555
|
+
return 0;
|
556
|
+
}
|
557
|
+
break;
|
558
|
+
case 3:
|
559
|
+
if (!strncasecmp("get", s.data, 3))
|
560
|
+
goto open_file;
|
561
|
+
break;
|
562
|
+
case 4:
|
563
|
+
if (!strncasecmp("head", s.data, 4)) {
|
564
|
+
http_set_header(h, HTTP_HEADER_CONTENT_LENGTH, fiobj_num_new(length));
|
565
|
+
http_finish(h);
|
566
|
+
return 0;
|
567
|
+
}
|
568
|
+
goto open_file;
|
569
|
+
break;
|
570
|
+
}
|
571
|
+
http_send_error(h, 403);
|
572
|
+
return 0;
|
573
|
+
open_file:
|
574
|
+
s = fiobj_obj2cstr(filename);
|
575
|
+
file = open(s.data, O_RDONLY);
|
576
|
+
if (file == -1) {
|
577
|
+
FIO_LOG_ERROR("(HTTP) couldn't open file %s!\n", s.data);
|
578
|
+
perror(" ");
|
579
|
+
http_send_error(h, 500);
|
580
|
+
return 0;
|
581
|
+
}
|
582
|
+
{
|
583
|
+
FIOBJ tmp = 0;
|
584
|
+
uintptr_t pos = 0;
|
585
|
+
if (is_gz) {
|
586
|
+
http_set_header(h, HTTP_HEADER_CONTENT_ENCODING,
|
587
|
+
fiobj_dup(HTTP_HVALUE_GZIP));
|
588
|
+
|
589
|
+
pos = s.len - 4;
|
590
|
+
while (pos && s.data[pos] != '.')
|
591
|
+
pos--;
|
592
|
+
pos++; /* assuming, but that's fine. */
|
593
|
+
tmp = http_mimetype_find(s.data + pos, s.len - pos - 3);
|
594
|
+
|
595
|
+
} else {
|
596
|
+
pos = s.len - 1;
|
597
|
+
while (pos && s.data[pos] != '.')
|
598
|
+
pos--;
|
599
|
+
pos++; /* assuming, but that's fine. */
|
600
|
+
tmp = http_mimetype_find(s.data + pos, s.len - pos);
|
601
|
+
}
|
602
|
+
if (tmp)
|
603
|
+
http_set_header(h, HTTP_HEADER_CONTENT_TYPE, tmp);
|
604
|
+
}
|
605
|
+
http_sendfile(h, file, length, offset);
|
606
|
+
return 0;
|
607
|
+
}
|
608
|
+
|
609
|
+
/**
|
610
|
+
* Sends an HTTP error response.
|
611
|
+
*
|
612
|
+
* Returns -1 on error and 0 on success.
|
613
|
+
*
|
614
|
+
* AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID.
|
615
|
+
*
|
616
|
+
* The `uuid` argument is optional and will be used only if the `http_s`
|
617
|
+
* argument is set to NULL.
|
618
|
+
*/
|
619
|
+
int http_send_error(http_s *r, size_t error) {
|
620
|
+
if (!r || !r->private_data.out_headers) {
|
621
|
+
return -1;
|
622
|
+
}
|
623
|
+
if (error < 100 || error >= 1000)
|
624
|
+
error = 500;
|
625
|
+
r->status = error;
|
626
|
+
char buffer[16];
|
627
|
+
buffer[0] = '/';
|
628
|
+
size_t pos = 1 + fio_ltoa(buffer + 1, error, 10);
|
629
|
+
buffer[pos++] = '.';
|
630
|
+
buffer[pos++] = 'h';
|
631
|
+
buffer[pos++] = 't';
|
632
|
+
buffer[pos++] = 'm';
|
633
|
+
buffer[pos++] = 'l';
|
634
|
+
buffer[pos] = 0;
|
635
|
+
if (http_sendfile2(r, http2protocol(r)->settings->public_folder,
|
636
|
+
http2protocol(r)->settings->public_folder_length, buffer,
|
637
|
+
pos)) {
|
638
|
+
http_set_header(r, HTTP_HEADER_CONTENT_TYPE,
|
639
|
+
http_mimetype_find((char *)"txt", 3));
|
640
|
+
fio_str_info_s t = http_status2str(error);
|
641
|
+
http_send_body(r, t.data, t.len);
|
642
|
+
}
|
643
|
+
return 0;
|
644
|
+
}
|
645
|
+
|
646
|
+
/**
|
647
|
+
* Sends the response headers for a header only response.
|
648
|
+
*
|
649
|
+
* AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID.
|
650
|
+
*/
|
651
|
+
void http_finish(http_s *r) {
|
652
|
+
if (!r || !r->private_data.vtbl) {
|
653
|
+
return;
|
654
|
+
}
|
655
|
+
add_content_length(r, 0);
|
656
|
+
add_date(r);
|
657
|
+
((http_vtable_s *)r->private_data.vtbl)->http_finish(r);
|
658
|
+
}
|
659
|
+
/**
|
660
|
+
* Pushes a data response when supported (HTTP/2 only).
|
661
|
+
*
|
662
|
+
* Returns -1 on error and 0 on success.
|
663
|
+
*/
|
664
|
+
int http_push_data(http_s *r, void *data, uintptr_t length, FIOBJ mime_type) {
|
665
|
+
if (!r || !(http_fio_protocol_s *)r->private_data.flag)
|
666
|
+
return -1;
|
667
|
+
return ((http_vtable_s *)r->private_data.vtbl)
|
668
|
+
->http_push_data(r, data, length, mime_type);
|
669
|
+
}
|
670
|
+
/**
|
671
|
+
* Pushes a file response when supported (HTTP/2 only).
|
672
|
+
*
|
673
|
+
* If `mime_type` is NULL, an attempt at automatic detection using
|
674
|
+
* `filename` will be made.
|
675
|
+
*
|
676
|
+
* Returns -1 on error and 0 on success.
|
677
|
+
*/
|
678
|
+
int http_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) {
|
679
|
+
if (HTTP_INVALID_HANDLE(h))
|
680
|
+
return -1;
|
681
|
+
return ((http_vtable_s *)h->private_data.vtbl)
|
682
|
+
->http_push_file(h, filename, mime_type);
|
683
|
+
}
|
684
|
+
|
685
|
+
/**
|
686
|
+
* Upgrades an HTTP/1.1 connection to a Websocket connection.
|
687
|
+
*/
|
688
|
+
#undef http_upgrade2ws
|
689
|
+
int http_upgrade2ws(http_s *h, websocket_settings_s args) {
|
690
|
+
if (!h) {
|
691
|
+
FIO_LOG_ERROR("`http_upgrade2ws` requires a valid `http_s` handle.");
|
692
|
+
goto error;
|
693
|
+
}
|
694
|
+
if (HTTP_INVALID_HANDLE(h))
|
695
|
+
goto error;
|
696
|
+
return ((http_vtable_s *)h->private_data.vtbl)->http2websocket(h, &args);
|
697
|
+
error:
|
698
|
+
if (args.on_close)
|
699
|
+
args.on_close(-1, args.udata);
|
700
|
+
return -1;
|
701
|
+
}
|
702
|
+
|
703
|
+
/* *****************************************************************************
|
704
|
+
Pause / Resume
|
705
|
+
***************************************************************************** */
|
706
|
+
|
707
|
+
/** Returns the `udata` associated with the paused opaque handle */
|
708
|
+
void *http_paused_udata_get(http_pause_handle_s *http) { return http->udata; }
|
709
|
+
|
710
|
+
/**
|
711
|
+
* Sets the `udata` associated with the paused opaque handle, returning the
|
712
|
+
* old value.
|
713
|
+
*/
|
714
|
+
void *http_paused_udata_set(http_pause_handle_s *http, void *udata) {
|
715
|
+
void *old = http->udata;
|
716
|
+
http->udata = udata;
|
717
|
+
return old;
|
718
|
+
}
|
719
|
+
|
720
|
+
/* perform the pause task outside of the connection's lock */
|
721
|
+
static void http_pause_wrapper(void *h_, void *task_) {
|
722
|
+
void (*task)(void *h) = (void (*)(void *h))((uintptr_t)task_);
|
723
|
+
task(h_);
|
724
|
+
}
|
725
|
+
|
726
|
+
/* perform the resume task within of the connection's lock */
|
727
|
+
static void http_resume_wrapper(intptr_t uuid, fio_protocol_s *p_, void *arg) {
|
728
|
+
http_fio_protocol_s *p = (http_fio_protocol_s *)p_;
|
729
|
+
http_pause_handle_s *http = arg;
|
730
|
+
http_s *h = http->h;
|
731
|
+
h->udata = http->udata;
|
732
|
+
http_vtable_s *vtbl = (http_vtable_s *)h->private_data.vtbl;
|
733
|
+
if (http->task)
|
734
|
+
http->task(h);
|
735
|
+
vtbl->http_on_resume(h, p);
|
736
|
+
fio_free(http);
|
737
|
+
(void)uuid;
|
738
|
+
}
|
739
|
+
|
740
|
+
/* perform the resume task fallback */
|
741
|
+
static void http_resume_fallback_wrapper(intptr_t uuid, void *arg) {
|
742
|
+
http_pause_handle_s *http = arg;
|
743
|
+
if (http->fallback)
|
744
|
+
http->fallback(http->udata);
|
745
|
+
fio_free(http);
|
746
|
+
(void)uuid;
|
747
|
+
}
|
748
|
+
|
749
|
+
/**
|
750
|
+
* Defers the request / response handling for later.
|
751
|
+
*/
|
752
|
+
void http_pause(http_s *h, void (*task)(http_pause_handle_s *http)) {
|
753
|
+
if (HTTP_INVALID_HANDLE(h)) {
|
754
|
+
return;
|
755
|
+
}
|
756
|
+
http_fio_protocol_s *p = (http_fio_protocol_s *)h->private_data.flag;
|
757
|
+
http_vtable_s *vtbl = (http_vtable_s *)h->private_data.vtbl;
|
758
|
+
http_pause_handle_s *http = fio_malloc(sizeof(*http));
|
759
|
+
FIO_ASSERT_ALLOC(http);
|
760
|
+
*http = (http_pause_handle_s){
|
761
|
+
.uuid = p->uuid,
|
762
|
+
.h = h,
|
763
|
+
.udata = h->udata,
|
764
|
+
};
|
765
|
+
vtbl->http_on_pause(h, p);
|
766
|
+
fio_defer(http_pause_wrapper, http, (void *)((uintptr_t)task));
|
767
|
+
}
|
768
|
+
|
769
|
+
/**
|
770
|
+
* Defers the request / response handling for later.
|
771
|
+
*/
|
772
|
+
void http_resume(http_pause_handle_s *http, void (*task)(http_s *h),
|
773
|
+
void (*fallback)(void *udata)) {
|
774
|
+
if (!http)
|
775
|
+
return;
|
776
|
+
http->task = task;
|
777
|
+
http->fallback = fallback;
|
778
|
+
fio_defer_io_task(http->uuid, .udata = http, .type = FIO_PR_LOCK_TASK,
|
779
|
+
.task = http_resume_wrapper,
|
780
|
+
.fallback = http_resume_fallback_wrapper);
|
781
|
+
}
|
782
|
+
|
783
|
+
/**
|
784
|
+
* Hijacks the socket away from the HTTP protocol and away from facil.io.
|
785
|
+
*/
|
786
|
+
intptr_t http_hijack(http_s *h, fio_str_info_s *leftover) {
|
787
|
+
if (!h)
|
788
|
+
return -1;
|
789
|
+
return ((http_vtable_s *)h->private_data.vtbl)->http_hijack(h, leftover);
|
790
|
+
}
|
791
|
+
|
792
|
+
/* *****************************************************************************
|
793
|
+
Setting the default settings and allocating a persistent copy
|
794
|
+
***************************************************************************** */
|
795
|
+
|
796
|
+
static void http_on_request_fallback(http_s *h) { http_send_error(h, 404); }
|
797
|
+
static void http_on_upgrade_fallback(http_s *h, char *p, size_t i) {
|
798
|
+
http_send_error(h, 400);
|
799
|
+
(void)p;
|
800
|
+
(void)i;
|
801
|
+
}
|
802
|
+
static void http_on_response_fallback(http_s *h) { http_send_error(h, 400); }
|
803
|
+
|
804
|
+
static http_settings_s *http_settings_new(http_settings_s arg_settings) {
|
805
|
+
/* TODO: improve locality by unifying malloc to a single call */
|
806
|
+
if (!arg_settings.on_request)
|
807
|
+
arg_settings.on_request = http_on_request_fallback;
|
808
|
+
if (!arg_settings.on_response)
|
809
|
+
arg_settings.on_response = http_on_response_fallback;
|
810
|
+
if (!arg_settings.on_upgrade)
|
811
|
+
arg_settings.on_upgrade = http_on_upgrade_fallback;
|
812
|
+
|
813
|
+
if (!arg_settings.max_body_size)
|
814
|
+
arg_settings.max_body_size = HTTP_DEFAULT_BODY_LIMIT;
|
815
|
+
if (!arg_settings.timeout)
|
816
|
+
arg_settings.timeout = 40;
|
817
|
+
if (!arg_settings.ws_max_msg_size)
|
818
|
+
arg_settings.ws_max_msg_size = 262144; /** defaults to ~250KB */
|
819
|
+
if (!arg_settings.ws_timeout)
|
820
|
+
arg_settings.ws_timeout = 40; /* defaults to 40 seconds */
|
821
|
+
if (!arg_settings.max_header_size)
|
822
|
+
arg_settings.max_header_size = 32 * 1024; /* defaults to 32Kib seconds */
|
823
|
+
if (arg_settings.max_clients <= 0 ||
|
824
|
+
(size_t)(arg_settings.max_clients + HTTP_BUSY_UNLESS_HAS_FDS) >
|
825
|
+
fio_capa()) {
|
826
|
+
arg_settings.max_clients = fio_capa();
|
827
|
+
if ((ssize_t)arg_settings.max_clients - HTTP_BUSY_UNLESS_HAS_FDS > 0)
|
828
|
+
arg_settings.max_clients -= HTTP_BUSY_UNLESS_HAS_FDS;
|
829
|
+
}
|
830
|
+
|
831
|
+
http_settings_s *settings = malloc(sizeof(*settings) + sizeof(void *));
|
832
|
+
*settings = arg_settings;
|
833
|
+
|
834
|
+
if (settings->public_folder) {
|
835
|
+
settings->public_folder_length = strlen(settings->public_folder);
|
836
|
+
if (settings->public_folder[0] == '~' &&
|
837
|
+
settings->public_folder[1] == '/' && getenv("HOME")) {
|
838
|
+
char *home = getenv("HOME");
|
839
|
+
size_t home_len = strlen(home);
|
840
|
+
char *tmp = malloc(settings->public_folder_length + home_len + 1);
|
841
|
+
FIO_ASSERT_ALLOC(tmp);
|
842
|
+
memcpy(tmp, home, home_len);
|
843
|
+
if (home[home_len - 1] == '/')
|
844
|
+
--home_len;
|
845
|
+
memcpy(tmp + home_len, settings->public_folder + 1,
|
846
|
+
settings->public_folder_length); // copy also the NULL
|
847
|
+
settings->public_folder = tmp;
|
848
|
+
settings->public_folder_length = strlen(settings->public_folder);
|
849
|
+
} else {
|
850
|
+
settings->public_folder = malloc(settings->public_folder_length + 1);
|
851
|
+
FIO_ASSERT_ALLOC(settings->public_folder);
|
852
|
+
memcpy((void *)settings->public_folder, arg_settings.public_folder,
|
853
|
+
settings->public_folder_length);
|
854
|
+
((uint8_t *)settings->public_folder)[settings->public_folder_length] = 0;
|
855
|
+
}
|
856
|
+
}
|
857
|
+
return settings;
|
858
|
+
}
|
859
|
+
|
860
|
+
static void http_settings_free(http_settings_s *s) {
|
861
|
+
free((void *)s->public_folder);
|
862
|
+
free(s);
|
863
|
+
}
|
864
|
+
/* *****************************************************************************
|
865
|
+
Listening to HTTP connections
|
866
|
+
***************************************************************************** */
|
867
|
+
|
868
|
+
static uint8_t fio_http_at_capa = 0;
|
869
|
+
|
870
|
+
static void http_on_server_protocol_http1(intptr_t uuid, void *set,
|
871
|
+
void *ignr_) {
|
872
|
+
if ((unsigned int)fio_uuid2fd(uuid) >=
|
873
|
+
((http_settings_s *)set)->max_clients) {
|
874
|
+
if (fio_uuid2fd(uuid) != -1) {
|
875
|
+
if (!fio_http_at_capa)
|
876
|
+
FIO_LOG_WARNING("HTTP server at capacity");
|
877
|
+
fio_http_at_capa = 1;
|
878
|
+
http_send_error2(503, uuid, set);
|
879
|
+
fio_close(uuid);
|
880
|
+
}
|
881
|
+
return;
|
882
|
+
}
|
883
|
+
fio_http_at_capa = 0;
|
884
|
+
fio_protocol_s *pr = http1_new(uuid, set, NULL, 0);
|
885
|
+
if (!pr)
|
886
|
+
fio_close(uuid);
|
887
|
+
else
|
888
|
+
fio_timeout_set(uuid, ((http_settings_s *)set)->timeout);
|
889
|
+
(void)ignr_;
|
890
|
+
}
|
891
|
+
|
892
|
+
static void http_on_open(intptr_t uuid, void *set) {
|
893
|
+
http_on_server_protocol_http1(uuid, set, NULL);
|
894
|
+
}
|
895
|
+
|
896
|
+
static void http_on_finish(intptr_t uuid, void *set) {
|
897
|
+
http_settings_s *settings = set;
|
898
|
+
|
899
|
+
if (settings->on_finish)
|
900
|
+
settings->on_finish(settings);
|
901
|
+
|
902
|
+
http_settings_free(settings);
|
903
|
+
(void)uuid;
|
904
|
+
}
|
905
|
+
|
906
|
+
/**
|
907
|
+
* Listens to HTTP connections at the specified `port`.
|
908
|
+
*
|
909
|
+
* Leave as NULL to ignore IP binding.
|
910
|
+
*
|
911
|
+
* Returns -1 on error and 0 on success.
|
912
|
+
*/
|
913
|
+
#undef http_listen
|
914
|
+
intptr_t http_listen(const char *port, const char *binding,
|
915
|
+
struct http_settings_s arg_settings) {
|
916
|
+
if (arg_settings.on_request == NULL) {
|
917
|
+
FIO_LOG_ERROR("http_listen requires the .on_request parameter "
|
918
|
+
"to be set\n");
|
919
|
+
kill(0, SIGINT);
|
920
|
+
exit(11);
|
921
|
+
}
|
922
|
+
|
923
|
+
http_settings_s *settings = http_settings_new(arg_settings);
|
924
|
+
settings->is_client = 0;
|
925
|
+
#ifndef __MINGW32__
|
926
|
+
if (settings->tls) {
|
927
|
+
fio_tls_alpn_add(settings->tls, "http/1.1", http_on_server_protocol_http1,
|
928
|
+
NULL, NULL);
|
929
|
+
}
|
930
|
+
#endif
|
931
|
+
|
932
|
+
return fio_listen(.port = port, .address = binding, .tls = arg_settings.tls,
|
933
|
+
.on_finish = http_on_finish, .on_open = http_on_open,
|
934
|
+
.udata = settings);
|
935
|
+
}
|
936
|
+
/** Listens to HTTP connections at the specified `port` and `binding`. */
|
937
|
+
#define http_listen(port, binding, ...) \
|
938
|
+
http_listen((port), (binding), (struct http_settings_s)(__VA_ARGS__))
|
939
|
+
|
940
|
+
/**
|
941
|
+
* Returns the settings used to setup the connection.
|
942
|
+
*
|
943
|
+
* Returns NULL on error (i.e., connection was lost).
|
944
|
+
*/
|
945
|
+
struct http_settings_s *http_settings(http_s *r) {
|
946
|
+
return ((http_fio_protocol_s *)r->private_data.flag)->settings;
|
947
|
+
}
|
948
|
+
|
949
|
+
/**
|
950
|
+
* Returns the direct address of the connected peer (likely an intermediary).
|
951
|
+
*/
|
952
|
+
fio_str_info_s http_peer_addr(http_s *h) {
|
953
|
+
return fio_peer_addr(((http_fio_protocol_s *)h->private_data.flag)->uuid);
|
954
|
+
}
|
955
|
+
|
956
|
+
/* *****************************************************************************
|
957
|
+
HTTP client connections
|
958
|
+
***************************************************************************** */
|
959
|
+
|
960
|
+
static void http_on_close_client(intptr_t uuid, fio_protocol_s *protocol) {
|
961
|
+
http_fio_protocol_s *p = (http_fio_protocol_s *)protocol;
|
962
|
+
http_settings_s *set = p->settings;
|
963
|
+
void (**original)(intptr_t, fio_protocol_s *) =
|
964
|
+
(void (**)(intptr_t, fio_protocol_s *))(set + 1);
|
965
|
+
if (set->on_finish)
|
966
|
+
set->on_finish(set);
|
967
|
+
|
968
|
+
original[0](uuid, protocol);
|
969
|
+
http_settings_free(set);
|
970
|
+
}
|
971
|
+
|
972
|
+
static void http_on_open_client_perform(http_settings_s *set) {
|
973
|
+
http_s *h = set->udata;
|
974
|
+
set->on_response(h);
|
975
|
+
}
|
976
|
+
static void http_on_open_client_http1(intptr_t uuid, void *set_,
|
977
|
+
void *ignore_) {
|
978
|
+
http_settings_s *set = set_;
|
979
|
+
http_s *h = set->udata;
|
980
|
+
fio_timeout_set(uuid, set->timeout);
|
981
|
+
fio_protocol_s *pr = http1_new(uuid, set, NULL, 0);
|
982
|
+
if (!pr) {
|
983
|
+
fio_close(uuid);
|
984
|
+
return;
|
985
|
+
}
|
986
|
+
{ /* store the original on_close at the end of the struct, we wrap it. */
|
987
|
+
void (**original)(intptr_t, fio_protocol_s *) =
|
988
|
+
(void (**)(intptr_t, fio_protocol_s *))(set + 1);
|
989
|
+
*original = pr->on_close;
|
990
|
+
pr->on_close = http_on_close_client;
|
991
|
+
}
|
992
|
+
h->private_data.flag = (uintptr_t)pr;
|
993
|
+
h->private_data.vtbl = http1_vtable();
|
994
|
+
http_on_open_client_perform(set);
|
995
|
+
(void)ignore_;
|
996
|
+
}
|
997
|
+
|
998
|
+
static void http_on_open_client(intptr_t uuid, void *set_) {
|
999
|
+
http_on_open_client_http1(uuid, set_, NULL);
|
1000
|
+
}
|
1001
|
+
|
1002
|
+
static void http_on_client_failed(intptr_t uuid, void *set_) {
|
1003
|
+
http_settings_s *set = set_;
|
1004
|
+
http_s *h = set->udata;
|
1005
|
+
set->udata = h->udata;
|
1006
|
+
http_s_destroy(h, 0);
|
1007
|
+
fio_free(h);
|
1008
|
+
if (set->on_finish)
|
1009
|
+
set->on_finish(set);
|
1010
|
+
http_settings_free(set);
|
1011
|
+
(void)uuid;
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
intptr_t http_connect__(void); /* sublime text marker */
|
1015
|
+
/**
|
1016
|
+
* Connects to an HTTP server as a client.
|
1017
|
+
*
|
1018
|
+
* Upon a successful connection, the `on_response` callback is called with an
|
1019
|
+
* empty `http_s*` handler (status == 0). Use the same API to set it's content
|
1020
|
+
* and send the request to the server. The next`on_response` will contain the
|
1021
|
+
* response.
|
1022
|
+
*
|
1023
|
+
* `address` should contain a full URL style address for the server. i.e.:
|
1024
|
+
* "http:/www.example.com:8080/"
|
1025
|
+
*
|
1026
|
+
* Returns -1 on error and 0 on success. the `on_finish` callback is always
|
1027
|
+
* called.
|
1028
|
+
*/
|
1029
|
+
intptr_t http_connect FIO_IGNORE_MACRO(const char *url,
|
1030
|
+
const char *unix_address,
|
1031
|
+
struct http_settings_s arg_settings) {
|
1032
|
+
if (!arg_settings.on_response && !arg_settings.on_upgrade) {
|
1033
|
+
FIO_LOG_ERROR("http_connect requires either an on_response "
|
1034
|
+
" or an on_upgrade callback.\n");
|
1035
|
+
errno = EINVAL;
|
1036
|
+
goto on_error;
|
1037
|
+
}
|
1038
|
+
size_t len = 0, h_len = 0;
|
1039
|
+
char *a = NULL, *p = NULL, *host = NULL;
|
1040
|
+
uint8_t is_websocket = 0;
|
1041
|
+
uint8_t is_secure = 0;
|
1042
|
+
FIOBJ path = FIOBJ_INVALID;
|
1043
|
+
if (!url && !unix_address) {
|
1044
|
+
FIO_LOG_ERROR("http_connect requires a valid address.");
|
1045
|
+
errno = EINVAL;
|
1046
|
+
goto on_error;
|
1047
|
+
}
|
1048
|
+
if (url) {
|
1049
|
+
fio_url_s u = fio_url_parse(url, strlen(url));
|
1050
|
+
if (u.scheme.data &&
|
1051
|
+
(u.scheme.len == 2 || (u.scheme.len == 3 && u.scheme.data[2] == 's')) &&
|
1052
|
+
u.scheme.data[0] == 'w' && u.scheme.data[1] == 's') {
|
1053
|
+
is_websocket = 1;
|
1054
|
+
is_secure = (u.scheme.len == 3);
|
1055
|
+
} else if (u.scheme.data &&
|
1056
|
+
(u.scheme.len == 4 ||
|
1057
|
+
(u.scheme.len == 5 && u.scheme.data[4] == 's')) &&
|
1058
|
+
u.scheme.data[0] == 'h' && u.scheme.data[1] == 't' &&
|
1059
|
+
u.scheme.data[2] == 't' && u.scheme.data[3] == 'p') {
|
1060
|
+
is_secure = (u.scheme.len == 5);
|
1061
|
+
}
|
1062
|
+
if (is_secure && !arg_settings.tls) {
|
1063
|
+
FIO_LOG_ERROR("Secure connections (%.*s) require a TLS object.",
|
1064
|
+
(int)u.scheme.len, u.scheme.data);
|
1065
|
+
errno = EINVAL;
|
1066
|
+
goto on_error;
|
1067
|
+
}
|
1068
|
+
if (u.path.data) {
|
1069
|
+
path = fiobj_str_new(
|
1070
|
+
u.path.data, strlen(u.path.data)); /* copy query and target as well */
|
1071
|
+
}
|
1072
|
+
if (unix_address) {
|
1073
|
+
a = (char *)unix_address;
|
1074
|
+
h_len = len = strlen(a);
|
1075
|
+
host = a;
|
1076
|
+
} else {
|
1077
|
+
if (!u.host.data) {
|
1078
|
+
FIO_LOG_ERROR("http_connect requires a valid address.");
|
1079
|
+
errno = EINVAL;
|
1080
|
+
goto on_error;
|
1081
|
+
}
|
1082
|
+
/***** no more error handling, since memory is allocated *****/
|
1083
|
+
/* copy address */
|
1084
|
+
a = fio_malloc(u.host.len + 1);
|
1085
|
+
memcpy(a, u.host.data, u.host.len);
|
1086
|
+
a[u.host.len] = 0;
|
1087
|
+
len = u.host.len;
|
1088
|
+
/* copy port */
|
1089
|
+
if (u.port.data) {
|
1090
|
+
p = fio_malloc(u.port.len + 1);
|
1091
|
+
memcpy(p, u.port.data, u.port.len);
|
1092
|
+
p[u.port.len] = 0;
|
1093
|
+
} else if (is_secure) {
|
1094
|
+
p = fio_malloc(3 + 1);
|
1095
|
+
memcpy(p, "443", 3);
|
1096
|
+
p[3] = 0;
|
1097
|
+
} else {
|
1098
|
+
p = fio_malloc(2 + 1);
|
1099
|
+
memcpy(p, "80", 2);
|
1100
|
+
p[2] = 0;
|
1101
|
+
}
|
1102
|
+
}
|
1103
|
+
if (u.host.data) {
|
1104
|
+
host = u.host.data;
|
1105
|
+
h_len = u.host.len;
|
1106
|
+
}
|
1107
|
+
}
|
1108
|
+
|
1109
|
+
/* set settings */
|
1110
|
+
if (!arg_settings.timeout)
|
1111
|
+
arg_settings.timeout = 30;
|
1112
|
+
http_settings_s *settings = http_settings_new(arg_settings);
|
1113
|
+
settings->is_client = 1;
|
1114
|
+
// if (settings->tls) {
|
1115
|
+
// fio_tls_alpn_add(settings->tls, "http/1.1", http_on_open_client_http1,
|
1116
|
+
// NULL, NULL);
|
1117
|
+
// }
|
1118
|
+
|
1119
|
+
if (!arg_settings.ws_timeout)
|
1120
|
+
settings->ws_timeout = 0; /* allow server to dictate timeout */
|
1121
|
+
if (!arg_settings.timeout)
|
1122
|
+
settings->timeout = 0; /* allow server to dictate timeout */
|
1123
|
+
http_s *h = fio_malloc(sizeof(*h));
|
1124
|
+
FIO_ASSERT(h, "HTTP Client handler allocation failed");
|
1125
|
+
http_s_new(h, 0, http1_vtable());
|
1126
|
+
h->udata = arg_settings.udata;
|
1127
|
+
h->status = 0;
|
1128
|
+
h->path = path;
|
1129
|
+
settings->udata = h;
|
1130
|
+
settings->tls = arg_settings.tls;
|
1131
|
+
if (host)
|
1132
|
+
http_set_header2(h, (fio_str_info_s){.data = (char *)"host", .len = 4},
|
1133
|
+
(fio_str_info_s){.data = host, .len = h_len});
|
1134
|
+
intptr_t ret;
|
1135
|
+
if (is_websocket) {
|
1136
|
+
/* force HTTP/1.1 */
|
1137
|
+
ret = fio_connect(.address = a, .port = p, .on_fail = http_on_client_failed,
|
1138
|
+
.on_connect = http_on_open_client, .udata = settings,
|
1139
|
+
.tls = arg_settings.tls);
|
1140
|
+
(void)0;
|
1141
|
+
} else {
|
1142
|
+
/* Allow for any HTTP version */
|
1143
|
+
ret = fio_connect(.address = a, .port = p, .on_fail = http_on_client_failed,
|
1144
|
+
.on_connect = http_on_open_client, .udata = settings,
|
1145
|
+
.tls = arg_settings.tls);
|
1146
|
+
(void)0;
|
1147
|
+
}
|
1148
|
+
if (a != unix_address)
|
1149
|
+
fio_free(a);
|
1150
|
+
fio_free(p);
|
1151
|
+
return ret;
|
1152
|
+
on_error:
|
1153
|
+
if (arg_settings.on_finish)
|
1154
|
+
arg_settings.on_finish(&arg_settings);
|
1155
|
+
return -1;
|
1156
|
+
}
|
1157
|
+
|
1158
|
+
/* *****************************************************************************
|
1159
|
+
HTTP Websocket Connect
|
1160
|
+
***************************************************************************** */
|
1161
|
+
|
1162
|
+
#undef http_upgrade2ws
|
1163
|
+
static void on_websocket_http_connected(http_s *h) {
|
1164
|
+
websocket_settings_s *s = h->udata;
|
1165
|
+
h->udata = http_settings(h)->udata = NULL;
|
1166
|
+
if (!h->path) {
|
1167
|
+
FIO_LOG_WARNING("(websocket client) path not specified in "
|
1168
|
+
"address, assuming root!");
|
1169
|
+
h->path = fiobj_str_new("/", 1);
|
1170
|
+
}
|
1171
|
+
http_upgrade2ws(h, *s);
|
1172
|
+
fio_free(s);
|
1173
|
+
}
|
1174
|
+
|
1175
|
+
static void on_websocket_http_connection_finished(http_settings_s *settings) {
|
1176
|
+
websocket_settings_s *s = settings->udata;
|
1177
|
+
if (s) {
|
1178
|
+
if (s->on_close)
|
1179
|
+
s->on_close(0, s->udata);
|
1180
|
+
fio_free(s);
|
1181
|
+
}
|
1182
|
+
}
|
1183
|
+
|
1184
|
+
#undef websocket_connect
|
1185
|
+
int websocket_connect(const char *address, websocket_settings_s settings) {
|
1186
|
+
websocket_settings_s *s = fio_malloc(sizeof(*s));
|
1187
|
+
FIO_ASSERT_ALLOC(s);
|
1188
|
+
*s = settings;
|
1189
|
+
return http_connect(address, NULL, .on_request = on_websocket_http_connected,
|
1190
|
+
.on_response = on_websocket_http_connected,
|
1191
|
+
.on_finish = on_websocket_http_connection_finished,
|
1192
|
+
.udata = s);
|
1193
|
+
}
|
1194
|
+
#define websocket_connect(address, ...) \
|
1195
|
+
websocket_connect((address), (websocket_settings_s){__VA_ARGS__})
|
1196
|
+
|
1197
|
+
/* *****************************************************************************
|
1198
|
+
EventSource Support (SSE)
|
1199
|
+
|
1200
|
+
Note:
|
1201
|
+
|
1202
|
+
* `http_sse_subscribe` and `http_sse_unsubscribe` are implemented in the
|
1203
|
+
http_internal logical unit.
|
1204
|
+
|
1205
|
+
***************************************************************************** */
|
1206
|
+
|
1207
|
+
static inline void http_sse_copy2str(FIOBJ dest, char *prefix, size_t pre_len,
|
1208
|
+
fio_str_info_s data) {
|
1209
|
+
if (!data.len)
|
1210
|
+
return;
|
1211
|
+
const char *stop = data.data + data.len;
|
1212
|
+
while (data.len) {
|
1213
|
+
fiobj_str_write(dest, prefix, pre_len);
|
1214
|
+
char *pos = data.data;
|
1215
|
+
while (pos < stop && *pos != '\n' && *pos != '\r')
|
1216
|
+
++pos;
|
1217
|
+
fiobj_str_write(dest, data.data, (uintptr_t)(pos - data.data));
|
1218
|
+
fiobj_str_write(dest, "\r\n", 2);
|
1219
|
+
if (*pos == '\r')
|
1220
|
+
++pos;
|
1221
|
+
if (*pos == '\n')
|
1222
|
+
++pos;
|
1223
|
+
data.len -= (uintptr_t)(pos - data.data);
|
1224
|
+
data.data = pos;
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
|
1228
|
+
/** The on message callback. the `*msg` pointer is to a temporary object. */
|
1229
|
+
static void http_sse_on_message(fio_msg_s *msg) {
|
1230
|
+
http_sse_internal_s *sse = msg->udata1;
|
1231
|
+
struct http_sse_subscribe_args *args = msg->udata2;
|
1232
|
+
/* perform a callback */
|
1233
|
+
fio_protocol_s *pr = fio_protocol_try_lock(sse->uuid, FIO_PR_LOCK_TASK);
|
1234
|
+
if (!pr)
|
1235
|
+
goto postpone;
|
1236
|
+
args->on_message(&sse->sse, msg->channel, msg->msg, args->udata);
|
1237
|
+
fio_protocol_unlock(pr, FIO_PR_LOCK_TASK);
|
1238
|
+
return;
|
1239
|
+
postpone:
|
1240
|
+
if (errno == EBADF)
|
1241
|
+
return;
|
1242
|
+
fio_message_defer(msg);
|
1243
|
+
return;
|
1244
|
+
}
|
1245
|
+
|
1246
|
+
static void http_sse_on_message__direct(http_sse_s *sse, fio_str_info_s channel,
|
1247
|
+
fio_str_info_s msg, void *udata) {
|
1248
|
+
http_sse_write(sse, .data = msg);
|
1249
|
+
(void)udata;
|
1250
|
+
(void)channel;
|
1251
|
+
}
|
1252
|
+
/** An optional callback for when a subscription is fully canceled. */
|
1253
|
+
static void http_sse_on_unsubscribe(void *sse_, void *args_) {
|
1254
|
+
http_sse_internal_s *sse = sse_;
|
1255
|
+
struct http_sse_subscribe_args *args = args_;
|
1256
|
+
if (args->on_unsubscribe)
|
1257
|
+
args->on_unsubscribe(args->udata);
|
1258
|
+
fio_free(args);
|
1259
|
+
http_sse_try_free(sse);
|
1260
|
+
}
|
1261
|
+
|
1262
|
+
/** This macro allows easy access to the `http_sse_subscribe` function. */
|
1263
|
+
#undef http_sse_subscribe
|
1264
|
+
/**
|
1265
|
+
* Subscribes to a channel. See {struct http_sse_subscribe_args} for possible
|
1266
|
+
* arguments.
|
1267
|
+
*
|
1268
|
+
* Returns a subscription ID on success and 0 on failure.
|
1269
|
+
*
|
1270
|
+
* All subscriptions are automatically revoked once the connection is closed.
|
1271
|
+
*
|
1272
|
+
* If the connections subscribes to the same channel more than once, messages
|
1273
|
+
* will be merged. However, another subscription ID will be assigned, and two
|
1274
|
+
* calls to {http_sse_unsubscribe} will be required in order to unregister from
|
1275
|
+
* the channel.
|
1276
|
+
*/
|
1277
|
+
uintptr_t http_sse_subscribe(http_sse_s *sse_,
|
1278
|
+
struct http_sse_subscribe_args args) {
|
1279
|
+
http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_);
|
1280
|
+
if (sse->uuid == -1)
|
1281
|
+
return 0;
|
1282
|
+
if (!args.on_message)
|
1283
|
+
args.on_message = http_sse_on_message__direct;
|
1284
|
+
struct http_sse_subscribe_args *udata = fio_malloc(sizeof(*udata));
|
1285
|
+
FIO_ASSERT_ALLOC(udata);
|
1286
|
+
*udata = args;
|
1287
|
+
|
1288
|
+
fio_atomic_add(&sse->ref, 1);
|
1289
|
+
subscription_s *sub =
|
1290
|
+
fio_subscribe(.channel = args.channel, .on_message = http_sse_on_message,
|
1291
|
+
.on_unsubscribe = http_sse_on_unsubscribe, .udata1 = sse,
|
1292
|
+
.udata2 = udata, .match = args.match);
|
1293
|
+
if (!sub)
|
1294
|
+
return 0;
|
1295
|
+
|
1296
|
+
fio_lock(&sse->lock);
|
1297
|
+
fio_ls_s *pos = fio_ls_push(&sse->subscriptions, sub);
|
1298
|
+
fio_unlock(&sse->lock);
|
1299
|
+
return (uintptr_t)pos;
|
1300
|
+
}
|
1301
|
+
|
1302
|
+
/**
|
1303
|
+
* Cancels a subscription and invalidates the subscription object.
|
1304
|
+
*/
|
1305
|
+
void http_sse_unsubscribe(http_sse_s *sse_, uintptr_t subscription) {
|
1306
|
+
if (!sse_ || !subscription)
|
1307
|
+
return;
|
1308
|
+
http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_);
|
1309
|
+
subscription_s *sub = (subscription_s *)((fio_ls_s *)subscription)->obj;
|
1310
|
+
fio_lock(&sse->lock);
|
1311
|
+
fio_ls_remove((fio_ls_s *)subscription);
|
1312
|
+
fio_unlock(&sse->lock);
|
1313
|
+
fio_unsubscribe(sub);
|
1314
|
+
}
|
1315
|
+
|
1316
|
+
#undef http_upgrade2sse
|
1317
|
+
/**
|
1318
|
+
* Upgrades an HTTP connection to an EventSource (SSE) connection.
|
1319
|
+
*
|
1320
|
+
* Thie `http_s` handle will be invalid after this call.
|
1321
|
+
*
|
1322
|
+
* On HTTP/1.1 connections, this will preclude future requests using the same
|
1323
|
+
* connection.
|
1324
|
+
*/
|
1325
|
+
int http_upgrade2sse(http_s *h, http_sse_s sse) {
|
1326
|
+
if (HTTP_INVALID_HANDLE(h)) {
|
1327
|
+
if (sse.on_close)
|
1328
|
+
sse.on_close(&sse);
|
1329
|
+
return -1;
|
1330
|
+
}
|
1331
|
+
return ((http_vtable_s *)h->private_data.vtbl)->http_upgrade2sse(h, &sse);
|
1332
|
+
}
|
1333
|
+
|
1334
|
+
/**
|
1335
|
+
* Sets the ping interval for SSE connections.
|
1336
|
+
*/
|
1337
|
+
void http_sse_set_timout(http_sse_s *sse_, uint8_t timeout) {
|
1338
|
+
if (!sse_)
|
1339
|
+
return;
|
1340
|
+
http_sse_internal_s *sse = FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse_);
|
1341
|
+
fio_timeout_set(sse->uuid, timeout);
|
1342
|
+
}
|
1343
|
+
|
1344
|
+
#undef http_sse_write
|
1345
|
+
/**
|
1346
|
+
* Writes data to an EventSource (SSE) connection.
|
1347
|
+
*/
|
1348
|
+
int http_sse_write(http_sse_s *sse, struct http_sse_write_args args) {
|
1349
|
+
if (!sse || !(args.id.len + args.data.len + args.event.len) ||
|
1350
|
+
fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid))
|
1351
|
+
return -1;
|
1352
|
+
FIOBJ buf;
|
1353
|
+
{
|
1354
|
+
/* best guess at data length, ignoring missing fields and multiline data */
|
1355
|
+
const size_t total = 4 + args.id.len + 2 + 7 + args.event.len + 2 + 6 +
|
1356
|
+
args.data.len + 2 + 7 + 10 + 4;
|
1357
|
+
buf = fiobj_str_buf(total);
|
1358
|
+
}
|
1359
|
+
http_sse_copy2str(buf, (char *)"id: ", 4, args.id);
|
1360
|
+
http_sse_copy2str(buf, (char *)"event: ", 7, args.event);
|
1361
|
+
if (args.retry) {
|
1362
|
+
FIOBJ i = fiobj_num_new(args.retry);
|
1363
|
+
fiobj_str_write(buf, (char *)"retry: ", 7);
|
1364
|
+
fiobj_str_join(buf, i);
|
1365
|
+
fiobj_free(i);
|
1366
|
+
}
|
1367
|
+
http_sse_copy2str(buf, (char *)"data: ", 6, args.data);
|
1368
|
+
fiobj_str_write(buf, "\r\n", 2);
|
1369
|
+
return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)
|
1370
|
+
->vtable->http_sse_write(sse, buf);
|
1371
|
+
}
|
1372
|
+
|
1373
|
+
/**
|
1374
|
+
* Get the connection's UUID (for fio_defer and similar use cases).
|
1375
|
+
*/
|
1376
|
+
intptr_t http_sse2uuid(http_sse_s *sse) {
|
1377
|
+
if (!sse ||
|
1378
|
+
fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid))
|
1379
|
+
return -1;
|
1380
|
+
return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid;
|
1381
|
+
}
|
1382
|
+
|
1383
|
+
/**
|
1384
|
+
* Closes an EventSource (SSE) connection.
|
1385
|
+
*/
|
1386
|
+
int http_sse_close(http_sse_s *sse) {
|
1387
|
+
if (!sse ||
|
1388
|
+
fio_is_closed(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->uuid))
|
1389
|
+
return -1;
|
1390
|
+
return FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)
|
1391
|
+
->vtable->http_sse_close(sse);
|
1392
|
+
}
|
1393
|
+
|
1394
|
+
/**
|
1395
|
+
* Duplicates an SSE handle by reference, remember to http_sse_free.
|
1396
|
+
*
|
1397
|
+
* Returns the same object (increases a reference count, no allocation is made).
|
1398
|
+
*/
|
1399
|
+
http_sse_s *http_sse_dup(http_sse_s *sse) {
|
1400
|
+
fio_atomic_add(&FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse)->ref, 1);
|
1401
|
+
return sse;
|
1402
|
+
}
|
1403
|
+
|
1404
|
+
/**
|
1405
|
+
* Frees an SSE handle by reference (decreases the reference count).
|
1406
|
+
*/
|
1407
|
+
void http_sse_free(http_sse_s *sse) {
|
1408
|
+
http_sse_try_free(FIO_LS_EMBD_OBJ(http_sse_internal_s, sse, sse));
|
1409
|
+
}
|
1410
|
+
|
1411
|
+
/* *****************************************************************************
|
1412
|
+
HTTP GET and POST parsing helpers
|
1413
|
+
***************************************************************************** */
|
1414
|
+
|
1415
|
+
/** URL decodes a string, returning a `FIOBJ`. */
|
1416
|
+
static inline FIOBJ http_urlstr2fiobj(char *s, size_t len) {
|
1417
|
+
FIOBJ o = fiobj_str_buf(len);
|
1418
|
+
ssize_t l = http_decode_url(fiobj_obj2cstr(o).data, s, len);
|
1419
|
+
if (l < 0) {
|
1420
|
+
fiobj_free(o);
|
1421
|
+
return fiobj_str_new(NULL, 0); /* empty string */
|
1422
|
+
}
|
1423
|
+
fiobj_str_resize(o, (size_t)l);
|
1424
|
+
return o;
|
1425
|
+
}
|
1426
|
+
|
1427
|
+
/** converts a string into a `FIOBJ`. */
|
1428
|
+
static inline FIOBJ http_str2fiobj(char *s, size_t len, uint8_t encoded) {
|
1429
|
+
switch (len) {
|
1430
|
+
case 0:
|
1431
|
+
return fiobj_str_new(NULL, 0); /* empty string */
|
1432
|
+
case 4:
|
1433
|
+
if (!strncasecmp(s, "true", 4))
|
1434
|
+
return fiobj_true();
|
1435
|
+
if (!strncasecmp(s, "null", 4))
|
1436
|
+
return fiobj_null();
|
1437
|
+
break;
|
1438
|
+
case 5:
|
1439
|
+
if (!strncasecmp(s, "false", 5))
|
1440
|
+
return fiobj_false();
|
1441
|
+
}
|
1442
|
+
{
|
1443
|
+
char *end = s;
|
1444
|
+
const uint64_t tmp = fio_atol(&end);
|
1445
|
+
if (end == s + len)
|
1446
|
+
return fiobj_num_new(tmp);
|
1447
|
+
}
|
1448
|
+
{
|
1449
|
+
char *end = s;
|
1450
|
+
const double tmp = fio_atof(&end);
|
1451
|
+
if (end == s + len)
|
1452
|
+
return fiobj_float_new(tmp);
|
1453
|
+
}
|
1454
|
+
if (encoded)
|
1455
|
+
return http_urlstr2fiobj(s, len);
|
1456
|
+
return fiobj_str_new(s, len);
|
1457
|
+
}
|
1458
|
+
|
1459
|
+
/** Parses the query part of an HTTP request/response. Uses `http_add2hash`. */
|
1460
|
+
void http_parse_query(http_s *h) {
|
1461
|
+
if (!h->query)
|
1462
|
+
return;
|
1463
|
+
if (!h->params)
|
1464
|
+
h->params = fiobj_hash_new();
|
1465
|
+
fio_str_info_s q = fiobj_obj2cstr(h->query);
|
1466
|
+
do {
|
1467
|
+
char *cut = memchr(q.data, '&', q.len);
|
1468
|
+
if (!cut)
|
1469
|
+
cut = q.data + q.len;
|
1470
|
+
char *cut2 = memchr(q.data, '=', (cut - q.data));
|
1471
|
+
if (cut2) {
|
1472
|
+
/* we only add named elements... */
|
1473
|
+
http_add2hash(h->params, q.data, (size_t)(cut2 - q.data), (cut2 + 1),
|
1474
|
+
(size_t)(cut - (cut2 + 1)), 1);
|
1475
|
+
}
|
1476
|
+
if (cut[0] == '&') {
|
1477
|
+
/* protecting against some ...less informed... clients */
|
1478
|
+
if (cut[1] == 'a' && cut[2] == 'm' && cut[3] == 'p' && cut[4] == ';')
|
1479
|
+
cut += 5;
|
1480
|
+
else
|
1481
|
+
cut += 1;
|
1482
|
+
}
|
1483
|
+
q.len -= (uintptr_t)(cut - q.data);
|
1484
|
+
q.data = cut;
|
1485
|
+
} while (q.len);
|
1486
|
+
}
|
1487
|
+
|
1488
|
+
static inline void http_parse_cookies_cookie_str(FIOBJ dest, FIOBJ str,
|
1489
|
+
uint8_t is_url_encoded) {
|
1490
|
+
if (!FIOBJ_TYPE_IS(str, FIOBJ_T_STRING))
|
1491
|
+
return;
|
1492
|
+
fio_str_info_s s = fiobj_obj2cstr(str);
|
1493
|
+
while (s.len) {
|
1494
|
+
if (s.data[0] == ' ') {
|
1495
|
+
++s.data;
|
1496
|
+
--s.len;
|
1497
|
+
continue;
|
1498
|
+
}
|
1499
|
+
char *cut = memchr(s.data, '=', s.len);
|
1500
|
+
if (!cut)
|
1501
|
+
cut = s.data;
|
1502
|
+
char *cut2 = memchr(cut, ';', s.len - (cut - s.data));
|
1503
|
+
if (!cut2)
|
1504
|
+
cut2 = s.data + s.len;
|
1505
|
+
http_add2hash(dest, s.data, cut - s.data, cut + 1, (cut2 - (cut + 1)),
|
1506
|
+
is_url_encoded);
|
1507
|
+
if ((size_t)((cut2 + 1) - s.data) > s.len)
|
1508
|
+
s.len = 0;
|
1509
|
+
else
|
1510
|
+
s.len -= ((cut2 + 1) - s.data);
|
1511
|
+
s.data = cut2 + 1;
|
1512
|
+
}
|
1513
|
+
}
|
1514
|
+
static inline void http_parse_cookies_setcookie_str(FIOBJ dest, FIOBJ str,
|
1515
|
+
uint8_t is_url_encoded) {
|
1516
|
+
if (!FIOBJ_TYPE_IS(str, FIOBJ_T_STRING))
|
1517
|
+
return;
|
1518
|
+
fio_str_info_s s = fiobj_obj2cstr(str);
|
1519
|
+
char *cut = memchr(s.data, '=', s.len);
|
1520
|
+
if (!cut)
|
1521
|
+
cut = s.data;
|
1522
|
+
char *cut2 = memchr(cut, ';', s.len - (cut - s.data));
|
1523
|
+
if (!cut2)
|
1524
|
+
cut2 = s.data + s.len;
|
1525
|
+
if (cut2 > cut)
|
1526
|
+
http_add2hash(dest, s.data, cut - s.data, cut + 1, (cut2 - (cut + 1)),
|
1527
|
+
is_url_encoded);
|
1528
|
+
}
|
1529
|
+
|
1530
|
+
/** Parses any Cookie / Set-Cookie headers, using the `http_add2hash` scheme. */
|
1531
|
+
void http_parse_cookies(http_s *h, uint8_t is_url_encoded) {
|
1532
|
+
if (!h->headers)
|
1533
|
+
return;
|
1534
|
+
if (h->cookies && fiobj_hash_count(h->cookies)) {
|
1535
|
+
FIO_LOG_WARNING("(http) attempting to parse cookies more than once.");
|
1536
|
+
return;
|
1537
|
+
}
|
1538
|
+
static uint64_t setcookie_header_hash;
|
1539
|
+
if (!setcookie_header_hash)
|
1540
|
+
setcookie_header_hash = fiobj_obj2hash(HTTP_HEADER_SET_COOKIE);
|
1541
|
+
FIOBJ c = fiobj_hash_get2(h->headers, fiobj_obj2hash(HTTP_HEADER_COOKIE));
|
1542
|
+
if (c) {
|
1543
|
+
if (!h->cookies)
|
1544
|
+
h->cookies = fiobj_hash_new();
|
1545
|
+
if (FIOBJ_TYPE_IS(c, FIOBJ_T_ARRAY)) {
|
1546
|
+
/* Array of Strings */
|
1547
|
+
size_t count = fiobj_ary_count(c);
|
1548
|
+
for (size_t i = 0; i < count; ++i) {
|
1549
|
+
http_parse_cookies_cookie_str(
|
1550
|
+
h->cookies, fiobj_ary_index(c, (int64_t)i), is_url_encoded);
|
1551
|
+
}
|
1552
|
+
} else {
|
1553
|
+
/* single string */
|
1554
|
+
http_parse_cookies_cookie_str(h->cookies, c, is_url_encoded);
|
1555
|
+
}
|
1556
|
+
}
|
1557
|
+
c = fiobj_hash_get2(h->headers, fiobj_obj2hash(HTTP_HEADER_SET_COOKIE));
|
1558
|
+
if (c) {
|
1559
|
+
if (!h->cookies)
|
1560
|
+
h->cookies = fiobj_hash_new();
|
1561
|
+
if (FIOBJ_TYPE_IS(c, FIOBJ_T_ARRAY)) {
|
1562
|
+
/* Array of Strings */
|
1563
|
+
size_t count = fiobj_ary_count(c);
|
1564
|
+
for (size_t i = 0; i < count; ++i) {
|
1565
|
+
http_parse_cookies_setcookie_str(
|
1566
|
+
h->cookies, fiobj_ary_index(c, (int64_t)i), is_url_encoded);
|
1567
|
+
}
|
1568
|
+
} else {
|
1569
|
+
/* single string */
|
1570
|
+
http_parse_cookies_setcookie_str(h->cookies, c, is_url_encoded);
|
1571
|
+
}
|
1572
|
+
}
|
1573
|
+
}
|
1574
|
+
|
1575
|
+
/**
|
1576
|
+
* Adds a named parameter to the hash, resolving nesting references.
|
1577
|
+
*
|
1578
|
+
* i.e.:
|
1579
|
+
*
|
1580
|
+
* * "name[]" references a nested Array (nested in the Hash).
|
1581
|
+
* * "name[key]" references a nested Hash.
|
1582
|
+
* * "name[][key]" references a nested Hash within an array. Hash keys will be
|
1583
|
+
* unique (repeating a key advances the hash).
|
1584
|
+
* * These rules can be nested (i.e. "name[][key1][][key2]...")
|
1585
|
+
* * "name[][]" is an error (there's no way for the parser to analyze
|
1586
|
+
* dimensions)
|
1587
|
+
*
|
1588
|
+
* Note: names can't begin with "[" or end with "]" as these are reserved
|
1589
|
+
* characters.
|
1590
|
+
*/
|
1591
|
+
int http_add2hash2(FIOBJ dest, char *name, size_t name_len, FIOBJ val,
|
1592
|
+
uint8_t encoded) {
|
1593
|
+
if (!name)
|
1594
|
+
goto error;
|
1595
|
+
FIOBJ nested_ary = FIOBJ_INVALID;
|
1596
|
+
char *cut1;
|
1597
|
+
/* we can't start with an empty object name */
|
1598
|
+
while (name_len && name[0] == '[') {
|
1599
|
+
--name_len;
|
1600
|
+
++name;
|
1601
|
+
}
|
1602
|
+
if (!name_len) {
|
1603
|
+
/* an empty name is an error */
|
1604
|
+
goto error;
|
1605
|
+
}
|
1606
|
+
uint32_t nesting = ((uint32_t)~0);
|
1607
|
+
rebase:
|
1608
|
+
/* test for nesting level limit (limit at 32) */
|
1609
|
+
if (!nesting)
|
1610
|
+
goto error;
|
1611
|
+
/* start clearing away bits. */
|
1612
|
+
nesting >>= 1;
|
1613
|
+
/* since we might be rebasing, notice that "name" might be "name]" */
|
1614
|
+
cut1 = memchr(name, '[', name_len);
|
1615
|
+
if (!cut1)
|
1616
|
+
goto place_in_hash;
|
1617
|
+
/* simple case "name=" (the "=" was already removed) */
|
1618
|
+
if (cut1 == name) {
|
1619
|
+
/* an empty name is an error */
|
1620
|
+
goto error;
|
1621
|
+
}
|
1622
|
+
if (cut1 + 1 == name + name_len) {
|
1623
|
+
/* we have name[= ... autocorrect */
|
1624
|
+
name_len -= 1;
|
1625
|
+
goto place_in_array;
|
1626
|
+
}
|
1627
|
+
|
1628
|
+
if (cut1[1] == ']') {
|
1629
|
+
/* Nested Array "name[]..." */
|
1630
|
+
|
1631
|
+
/* Test for name[]= */
|
1632
|
+
if ((cut1 + 2) == name + name_len) {
|
1633
|
+
name_len -= 2;
|
1634
|
+
goto place_in_array;
|
1635
|
+
}
|
1636
|
+
|
1637
|
+
/* Test for a nested Array format error */
|
1638
|
+
if (cut1[2] != '[' || cut1[3] == ']') { /* error, we can't parse this */
|
1639
|
+
goto error;
|
1640
|
+
}
|
1641
|
+
|
1642
|
+
/* we have name[][key...= */
|
1643
|
+
|
1644
|
+
/* ensure array exists and it's an array + set nested_ary */
|
1645
|
+
const size_t len = ((cut1[-1] == ']') ? (size_t)((cut1 - 1) - name)
|
1646
|
+
: (size_t)(cut1 - name));
|
1647
|
+
const uint64_t hash =
|
1648
|
+
fiobj_hash_string(name, len); /* hash the current name */
|
1649
|
+
nested_ary = fiobj_hash_get2(dest, hash);
|
1650
|
+
if (!nested_ary) {
|
1651
|
+
/* create a new nested array */
|
1652
|
+
FIOBJ key =
|
1653
|
+
encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len);
|
1654
|
+
nested_ary = fiobj_ary_new2(4);
|
1655
|
+
fiobj_hash_set(dest, key, nested_ary);
|
1656
|
+
fiobj_free(key);
|
1657
|
+
} else if (!FIOBJ_TYPE_IS(nested_ary, FIOBJ_T_ARRAY)) {
|
1658
|
+
/* convert existing object to an array - auto error correction */
|
1659
|
+
FIOBJ key =
|
1660
|
+
encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len);
|
1661
|
+
FIOBJ tmp = fiobj_ary_new2(4);
|
1662
|
+
fiobj_ary_push(tmp, nested_ary);
|
1663
|
+
nested_ary = tmp;
|
1664
|
+
fiobj_hash_set(dest, key, nested_ary);
|
1665
|
+
fiobj_free(key);
|
1666
|
+
}
|
1667
|
+
|
1668
|
+
/* test if last object in the array is a hash - create hash if not */
|
1669
|
+
dest = fiobj_ary_index(nested_ary, -1);
|
1670
|
+
if (!dest || !FIOBJ_TYPE_IS(dest, FIOBJ_T_HASH)) {
|
1671
|
+
dest = fiobj_hash_new();
|
1672
|
+
fiobj_ary_push(nested_ary, dest);
|
1673
|
+
}
|
1674
|
+
|
1675
|
+
/* rebase `name` to `key` and restart. */
|
1676
|
+
cut1 += 3; /* consume "[][" */
|
1677
|
+
name_len -= (size_t)(cut1 - name);
|
1678
|
+
name = cut1;
|
1679
|
+
goto rebase;
|
1680
|
+
|
1681
|
+
} else {
|
1682
|
+
/* we have name[key]... */
|
1683
|
+
const size_t len = ((cut1[-1] == ']') ? (size_t)((cut1 - 1) - name)
|
1684
|
+
: (size_t)(cut1 - name));
|
1685
|
+
const uint64_t hash =
|
1686
|
+
fiobj_hash_string(name, len); /* hash the current name */
|
1687
|
+
FIOBJ tmp = fiobj_hash_get2(dest, hash);
|
1688
|
+
if (!tmp) {
|
1689
|
+
/* hash doesn't exist, create it */
|
1690
|
+
FIOBJ key =
|
1691
|
+
encoded ? http_urlstr2fiobj(name, len) : fiobj_str_new(name, len);
|
1692
|
+
tmp = fiobj_hash_new();
|
1693
|
+
fiobj_hash_set(dest, key, tmp);
|
1694
|
+
fiobj_free(key);
|
1695
|
+
} else if (!FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH)) {
|
1696
|
+
/* type error, referencing an existing object that isn't a Hash */
|
1697
|
+
goto error;
|
1698
|
+
}
|
1699
|
+
dest = tmp;
|
1700
|
+
/* no rollback is possible once we enter the new nesting level... */
|
1701
|
+
nested_ary = FIOBJ_INVALID;
|
1702
|
+
/* rebase `name` to `key` and restart. */
|
1703
|
+
cut1 += 1; /* consume "[" */
|
1704
|
+
name_len -= (size_t)(cut1 - name);
|
1705
|
+
name = cut1;
|
1706
|
+
goto rebase;
|
1707
|
+
}
|
1708
|
+
|
1709
|
+
place_in_hash:
|
1710
|
+
if (name[name_len - 1] == ']')
|
1711
|
+
--name_len;
|
1712
|
+
{
|
1713
|
+
FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len)
|
1714
|
+
: fiobj_str_new(name, name_len);
|
1715
|
+
FIOBJ old = fiobj_hash_replace(dest, key, val);
|
1716
|
+
if (old) {
|
1717
|
+
if (nested_ary) {
|
1718
|
+
fiobj_hash_replace(dest, key, old);
|
1719
|
+
old = fiobj_hash_new();
|
1720
|
+
fiobj_hash_set(old, key, val);
|
1721
|
+
fiobj_ary_push(nested_ary, old);
|
1722
|
+
} else {
|
1723
|
+
if (!FIOBJ_TYPE_IS(old, FIOBJ_T_ARRAY)) {
|
1724
|
+
FIOBJ tmp = fiobj_ary_new2(4);
|
1725
|
+
fiobj_ary_push(tmp, old);
|
1726
|
+
old = tmp;
|
1727
|
+
}
|
1728
|
+
fiobj_ary_push(old, val);
|
1729
|
+
fiobj_hash_replace(dest, key, old);
|
1730
|
+
}
|
1731
|
+
}
|
1732
|
+
fiobj_free(key);
|
1733
|
+
}
|
1734
|
+
return 0;
|
1735
|
+
|
1736
|
+
place_in_array:
|
1737
|
+
if (name[name_len - 1] == ']')
|
1738
|
+
--name_len;
|
1739
|
+
{
|
1740
|
+
uint64_t hash = fiobj_hash_string(name, name_len);
|
1741
|
+
FIOBJ ary = fiobj_hash_get2(dest, hash);
|
1742
|
+
if (!ary) {
|
1743
|
+
FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len)
|
1744
|
+
: fiobj_str_new(name, name_len);
|
1745
|
+
ary = fiobj_ary_new2(4);
|
1746
|
+
fiobj_hash_set(dest, key, ary);
|
1747
|
+
fiobj_free(key);
|
1748
|
+
} else if (!FIOBJ_TYPE_IS(ary, FIOBJ_T_ARRAY)) {
|
1749
|
+
FIOBJ tmp = fiobj_ary_new2(4);
|
1750
|
+
fiobj_ary_push(tmp, ary);
|
1751
|
+
ary = tmp;
|
1752
|
+
FIOBJ key = encoded ? http_urlstr2fiobj(name, name_len)
|
1753
|
+
: fiobj_str_new(name, name_len);
|
1754
|
+
fiobj_hash_replace(dest, key, ary);
|
1755
|
+
fiobj_free(key);
|
1756
|
+
}
|
1757
|
+
fiobj_ary_push(ary, val);
|
1758
|
+
}
|
1759
|
+
return 0;
|
1760
|
+
error:
|
1761
|
+
fiobj_free(val);
|
1762
|
+
errno = EOPNOTSUPP;
|
1763
|
+
return -1;
|
1764
|
+
}
|
1765
|
+
|
1766
|
+
/**
|
1767
|
+
* Adds a named parameter to the hash, resolving nesting references.
|
1768
|
+
*
|
1769
|
+
* i.e.:
|
1770
|
+
*
|
1771
|
+
* * "name[]" references a nested Array (nested in the Hash).
|
1772
|
+
* * "name[key]" references a nested Hash.
|
1773
|
+
* * "name[][key]" references a nested Hash within an array. Hash keys will be
|
1774
|
+
* unique (repeating a key advances the hash).
|
1775
|
+
* * These rules can be nested (i.e. "name[][key1][][key2]...")
|
1776
|
+
* * "name[][]" is an error (there's no way for the parser to analyze
|
1777
|
+
* dimensions)
|
1778
|
+
*
|
1779
|
+
* Note: names can't begin with "[" or end with "]" as these are reserved
|
1780
|
+
* characters.
|
1781
|
+
*/
|
1782
|
+
int http_add2hash(FIOBJ dest, char *name, size_t name_len, char *value,
|
1783
|
+
size_t value_len, uint8_t encoded) {
|
1784
|
+
return http_add2hash2(dest, name, name_len,
|
1785
|
+
http_str2fiobj(value, value_len, encoded), encoded);
|
1786
|
+
}
|
1787
|
+
|
1788
|
+
/* *****************************************************************************
|
1789
|
+
HTTP Body Parsing
|
1790
|
+
***************************************************************************** */
|
1791
|
+
#include <http_mime_parser.h>
|
1792
|
+
|
1793
|
+
typedef struct {
|
1794
|
+
http_mime_parser_s p;
|
1795
|
+
http_s *h;
|
1796
|
+
fio_str_info_s buffer;
|
1797
|
+
size_t pos;
|
1798
|
+
size_t partial_offset;
|
1799
|
+
size_t partial_length;
|
1800
|
+
FIOBJ partial_name;
|
1801
|
+
} http_fio_mime_s;
|
1802
|
+
|
1803
|
+
#define http_mime_parser2fio(parser) ((http_fio_mime_s *)(parser))
|
1804
|
+
|
1805
|
+
/** Called when all the data is available at once. */
|
1806
|
+
static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name,
|
1807
|
+
size_t name_len, void *filename,
|
1808
|
+
size_t filename_len, void *mimetype,
|
1809
|
+
size_t mimetype_len, void *value,
|
1810
|
+
size_t value_len) {
|
1811
|
+
if (!filename_len) {
|
1812
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, name, name_len,
|
1813
|
+
value, value_len, 0);
|
1814
|
+
return;
|
1815
|
+
}
|
1816
|
+
FIOBJ n = fiobj_str_new(name, name_len);
|
1817
|
+
fiobj_str_write(n, "[data]", 6);
|
1818
|
+
fio_str_info_s tmp = fiobj_obj2cstr(n);
|
1819
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
|
1820
|
+
value, value_len, 0);
|
1821
|
+
fiobj_str_resize(n, name_len);
|
1822
|
+
fiobj_str_write(n, "[name]", 6);
|
1823
|
+
tmp = fiobj_obj2cstr(n);
|
1824
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
|
1825
|
+
filename, filename_len, 0);
|
1826
|
+
if (mimetype_len) {
|
1827
|
+
fiobj_str_resize(n, name_len);
|
1828
|
+
fiobj_str_write(n, "[type]", 6);
|
1829
|
+
tmp = fiobj_obj2cstr(n);
|
1830
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
|
1831
|
+
mimetype, mimetype_len, 0);
|
1832
|
+
}
|
1833
|
+
fiobj_free(n);
|
1834
|
+
}
|
1835
|
+
|
1836
|
+
/** Called when the data didn't fit in the buffer. Data will be streamed. */
|
1837
|
+
static void http_mime_parser_on_partial_start(
|
1838
|
+
http_mime_parser_s *parser, void *name, size_t name_len, void *filename,
|
1839
|
+
size_t filename_len, void *mimetype, size_t mimetype_len) {
|
1840
|
+
http_mime_parser2fio(parser)->partial_length = 0;
|
1841
|
+
http_mime_parser2fio(parser)->partial_offset = 0;
|
1842
|
+
http_mime_parser2fio(parser)->partial_name = fiobj_str_new(name, name_len);
|
1843
|
+
|
1844
|
+
if (!filename)
|
1845
|
+
return;
|
1846
|
+
|
1847
|
+
fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[type]", 6);
|
1848
|
+
fio_str_info_s tmp =
|
1849
|
+
fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
|
1850
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
|
1851
|
+
mimetype, mimetype_len, 0);
|
1852
|
+
|
1853
|
+
fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len);
|
1854
|
+
fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[name]", 6);
|
1855
|
+
tmp = fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
|
1856
|
+
http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
|
1857
|
+
filename, filename_len, 0);
|
1858
|
+
|
1859
|
+
fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len);
|
1860
|
+
fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[data]", 6);
|
1861
|
+
}
|
1862
|
+
|
1863
|
+
/** Called when partial data is available. */
|
1864
|
+
static void http_mime_parser_on_partial_data(http_mime_parser_s *parser,
|
1865
|
+
void *value, size_t value_len) {
|
1866
|
+
if (!http_mime_parser2fio(parser)->partial_offset)
|
1867
|
+
http_mime_parser2fio(parser)->partial_offset =
|
1868
|
+
http_mime_parser2fio(parser)->pos +
|
1869
|
+
((uintptr_t)value -
|
1870
|
+
(uintptr_t)http_mime_parser2fio(parser)->buffer.data);
|
1871
|
+
http_mime_parser2fio(parser)->partial_length += value_len;
|
1872
|
+
(void)value;
|
1873
|
+
}
|
1874
|
+
|
1875
|
+
/** Called when the partial data is complete. */
|
1876
|
+
static void http_mime_parser_on_partial_end(http_mime_parser_s *parser) {
|
1877
|
+
|
1878
|
+
fio_str_info_s tmp =
|
1879
|
+
fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
|
1880
|
+
FIOBJ o = FIOBJ_INVALID;
|
1881
|
+
if (!http_mime_parser2fio(parser)->partial_length)
|
1882
|
+
return;
|
1883
|
+
if (http_mime_parser2fio(parser)->partial_length < 42) {
|
1884
|
+
/* short data gets a new object */
|
1885
|
+
o = fiobj_str_new(http_mime_parser2fio(parser)->buffer.data +
|
1886
|
+
http_mime_parser2fio(parser)->partial_offset,
|
1887
|
+
http_mime_parser2fio(parser)->partial_length);
|
1888
|
+
} else {
|
1889
|
+
/* longer data gets a reference object (memory collision concerns) */
|
1890
|
+
o = fiobj_data_slice(http_mime_parser2fio(parser)->h->body,
|
1891
|
+
http_mime_parser2fio(parser)->partial_offset,
|
1892
|
+
http_mime_parser2fio(parser)->partial_length);
|
1893
|
+
}
|
1894
|
+
http_add2hash2(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, o,
|
1895
|
+
0);
|
1896
|
+
fiobj_free(http_mime_parser2fio(parser)->partial_name);
|
1897
|
+
http_mime_parser2fio(parser)->partial_name = FIOBJ_INVALID;
|
1898
|
+
http_mime_parser2fio(parser)->partial_offset = 0;
|
1899
|
+
}
|
1900
|
+
|
1901
|
+
/**
|
1902
|
+
* Called when URL decoding is required.
|
1903
|
+
*
|
1904
|
+
* Should support inplace decoding (`dest == encoded`).
|
1905
|
+
*
|
1906
|
+
* Should return the length of the decoded string.
|
1907
|
+
*/
|
1908
|
+
static inline size_t http_mime_decode_url(char *dest, const char *encoded,
|
1909
|
+
size_t length) {
|
1910
|
+
return http_decode_url(dest, encoded, length);
|
1911
|
+
}
|
1912
|
+
|
1913
|
+
/**
|
1914
|
+
* Attempts to decode the request's body.
|
1915
|
+
*
|
1916
|
+
* Supported Types include:
|
1917
|
+
* * application/x-www-form-urlencoded
|
1918
|
+
* * application/json
|
1919
|
+
* * multipart/form-data
|
1920
|
+
*/
|
1921
|
+
int http_parse_body(http_s *h) {
|
1922
|
+
static uint64_t content_type_hash;
|
1923
|
+
if (!h->body)
|
1924
|
+
return -1;
|
1925
|
+
if (!content_type_hash)
|
1926
|
+
content_type_hash = fiobj_hash_string("content-type", 12);
|
1927
|
+
FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
|
1928
|
+
fio_str_info_s content_type = fiobj_obj2cstr(ct);
|
1929
|
+
if (content_type.len < 16)
|
1930
|
+
return -1;
|
1931
|
+
if (content_type.len >= 33 &&
|
1932
|
+
!strncasecmp("application/x-www-form-urlencoded", content_type.data,
|
1933
|
+
33)) {
|
1934
|
+
if (!h->params)
|
1935
|
+
h->params = fiobj_hash_new();
|
1936
|
+
FIOBJ tmp = h->query;
|
1937
|
+
h->query = h->body;
|
1938
|
+
http_parse_query(h);
|
1939
|
+
h->query = tmp;
|
1940
|
+
return 0;
|
1941
|
+
}
|
1942
|
+
if (content_type.len >= 16 &&
|
1943
|
+
!strncasecmp("application/json", content_type.data, 16)) {
|
1944
|
+
content_type = fiobj_obj2cstr(h->body);
|
1945
|
+
if (h->params)
|
1946
|
+
return -1;
|
1947
|
+
if (fiobj_json2obj(&h->params, content_type.data, content_type.len) == 0)
|
1948
|
+
return -1;
|
1949
|
+
if (FIOBJ_TYPE_IS(h->params, FIOBJ_T_HASH))
|
1950
|
+
return 0;
|
1951
|
+
FIOBJ tmp = h->params;
|
1952
|
+
FIOBJ key = fiobj_str_new("JSON", 4);
|
1953
|
+
h->params = fiobj_hash_new2(4);
|
1954
|
+
fiobj_hash_set(h->params, key, tmp);
|
1955
|
+
fiobj_free(key);
|
1956
|
+
return 0;
|
1957
|
+
}
|
1958
|
+
|
1959
|
+
http_fio_mime_s p = {.h = h};
|
1960
|
+
if (http_mime_parser_init(&p.p, content_type.data, content_type.len))
|
1961
|
+
return -1;
|
1962
|
+
if (!h->params)
|
1963
|
+
h->params = fiobj_hash_new();
|
1964
|
+
|
1965
|
+
do {
|
1966
|
+
size_t cons = http_mime_parse(&p.p, p.buffer.data, p.buffer.len);
|
1967
|
+
p.pos += cons;
|
1968
|
+
p.buffer = fiobj_data_pread(h->body, p.pos, 4096);
|
1969
|
+
} while (p.buffer.data && !p.p.done && !p.p.error);
|
1970
|
+
fiobj_free(p.partial_name);
|
1971
|
+
p.partial_name = FIOBJ_INVALID;
|
1972
|
+
return 0;
|
1973
|
+
}
|
1974
|
+
|
1975
|
+
/* *****************************************************************************
|
1976
|
+
HTTP Helper functions that could be used globally
|
1977
|
+
***************************************************************************** */
|
1978
|
+
|
1979
|
+
/**
|
1980
|
+
* Returns a String object representing the unparsed HTTP request (HTTP
|
1981
|
+
* version is capped at HTTP/1.1). Mostly usable for proxy usage and
|
1982
|
+
* debugging.
|
1983
|
+
*/
|
1984
|
+
FIOBJ http_req2str(http_s *h) {
|
1985
|
+
if (HTTP_INVALID_HANDLE(h) || !fiobj_hash_count(h->headers))
|
1986
|
+
return FIOBJ_INVALID;
|
1987
|
+
|
1988
|
+
struct header_writer_s w;
|
1989
|
+
w.dest = fiobj_str_buf(0);
|
1990
|
+
if (h->status_str) {
|
1991
|
+
fiobj_str_join(w.dest, h->version);
|
1992
|
+
fiobj_str_write(w.dest, " ", 1);
|
1993
|
+
fiobj_str_join(w.dest, fiobj_num_tmp(h->status));
|
1994
|
+
fiobj_str_write(w.dest, " ", 1);
|
1995
|
+
fiobj_str_join(w.dest, h->status_str);
|
1996
|
+
fiobj_str_write(w.dest, "\r\n", 2);
|
1997
|
+
} else {
|
1998
|
+
fiobj_str_join(w.dest, h->method);
|
1999
|
+
fiobj_str_write(w.dest, " ", 1);
|
2000
|
+
fiobj_str_join(w.dest, h->path);
|
2001
|
+
if (h->query) {
|
2002
|
+
fiobj_str_write(w.dest, "?", 1);
|
2003
|
+
fiobj_str_join(w.dest, h->query);
|
2004
|
+
}
|
2005
|
+
{
|
2006
|
+
fio_str_info_s t = fiobj_obj2cstr(h->version);
|
2007
|
+
if (t.len < 6 || t.data[5] != '1')
|
2008
|
+
fiobj_str_write(w.dest, " HTTP/1.1\r\n", 10);
|
2009
|
+
else {
|
2010
|
+
fiobj_str_write(w.dest, " ", 1);
|
2011
|
+
fiobj_str_join(w.dest, h->version);
|
2012
|
+
fiobj_str_write(w.dest, "\r\n", 2);
|
2013
|
+
}
|
2014
|
+
}
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
fiobj_each1(h->headers, 0, write_header, &w);
|
2018
|
+
fiobj_str_write(w.dest, "\r\n", 2);
|
2019
|
+
if (h->body) {
|
2020
|
+
// fiobj_data_seek(h->body, 0);
|
2021
|
+
// fio_str_info_s t = fiobj_data_read(h->body, 0);
|
2022
|
+
// fiobj_str_write(w.dest, t.data, t.len);
|
2023
|
+
fiobj_str_join(w.dest, h->body);
|
2024
|
+
}
|
2025
|
+
return w.dest;
|
2026
|
+
}
|
2027
|
+
|
2028
|
+
void http_write_log(http_s *h) {
|
2029
|
+
FIOBJ l = fiobj_str_buf(128);
|
2030
|
+
|
2031
|
+
intptr_t bytes_sent = fiobj_obj2num(fiobj_hash_get2(
|
2032
|
+
h->private_data.out_headers, fiobj_obj2hash(HTTP_HEADER_CONTENT_LENGTH)));
|
2033
|
+
|
2034
|
+
struct timespec start, end;
|
2035
|
+
clock_gettime(CLOCK_REALTIME, &end);
|
2036
|
+
start = h->received_at;
|
2037
|
+
|
2038
|
+
{
|
2039
|
+
// TODO Guess IP address from headers (forwarded) where possible
|
2040
|
+
fio_str_info_s peer = fio_peer_addr(http2protocol(h)->uuid);
|
2041
|
+
fiobj_str_write(l, peer.data, peer.len);
|
2042
|
+
}
|
2043
|
+
fio_str_info_s buff = fiobj_obj2cstr(l);
|
2044
|
+
|
2045
|
+
if (buff.len == 0) {
|
2046
|
+
memcpy(buff.data, "[unknown]", 9);
|
2047
|
+
buff.len = 9;
|
2048
|
+
}
|
2049
|
+
memcpy(buff.data + buff.len, " - - [", 6);
|
2050
|
+
buff.len += 6;
|
2051
|
+
fiobj_str_resize(l, buff.len);
|
2052
|
+
{
|
2053
|
+
FIOBJ date;
|
2054
|
+
fio_lock(&date_lock);
|
2055
|
+
date = fiobj_dup(current_date);
|
2056
|
+
fio_unlock(&date_lock);
|
2057
|
+
fiobj_str_join(l, current_date);
|
2058
|
+
fiobj_free(date);
|
2059
|
+
}
|
2060
|
+
fiobj_str_write(l, "] \"", 3);
|
2061
|
+
fiobj_str_join(l, h->method);
|
2062
|
+
fiobj_str_write(l, " ", 1);
|
2063
|
+
fiobj_str_join(l, h->path);
|
2064
|
+
fiobj_str_write(l, " ", 1);
|
2065
|
+
fiobj_str_join(l, h->version);
|
2066
|
+
fiobj_str_write(l, "\" ", 2);
|
2067
|
+
if (bytes_sent > 0) {
|
2068
|
+
fiobj_str_write_i(l, h->status);
|
2069
|
+
fiobj_str_write(l, " ", 1);
|
2070
|
+
fiobj_str_write_i(l, bytes_sent);
|
2071
|
+
fiobj_str_write(l, "b ", 2);
|
2072
|
+
} else {
|
2073
|
+
fiobj_str_join(l, fiobj_num_tmp(h->status));
|
2074
|
+
fiobj_str_write(l, " -- ", 4);
|
2075
|
+
}
|
2076
|
+
|
2077
|
+
bytes_sent = ((end.tv_sec - start.tv_sec) * 1000) +
|
2078
|
+
((end.tv_nsec - start.tv_nsec) / 1000000);
|
2079
|
+
fiobj_str_write_i(l, bytes_sent);
|
2080
|
+
fiobj_str_write(l, "ms\r\n", 4);
|
2081
|
+
|
2082
|
+
buff = fiobj_obj2cstr(l);
|
2083
|
+
fwrite(buff.data, 1, buff.len, stderr);
|
2084
|
+
fiobj_free(l);
|
2085
|
+
}
|
2086
|
+
|
2087
|
+
/**
|
2088
|
+
A faster (yet less localized) alternative to `gmtime_r`.
|
2089
|
+
|
2090
|
+
See the libc `gmtime_r` documentation for details.
|
2091
|
+
|
2092
|
+
Falls back to `gmtime_r` for dates before epoch.
|
2093
|
+
*/
|
2094
|
+
struct tm *http_gmtime(time_t timer, struct tm *tm) {
|
2095
|
+
ssize_t a, b;
|
2096
|
+
#if HAVE_TM_TM_ZONE || defined(BSD)
|
2097
|
+
*tm = (struct tm){
|
2098
|
+
.tm_isdst = 0,
|
2099
|
+
.tm_zone = (char *)"UTC",
|
2100
|
+
};
|
2101
|
+
#else
|
2102
|
+
*tm = (struct tm){
|
2103
|
+
.tm_isdst = 0,
|
2104
|
+
};
|
2105
|
+
#endif
|
2106
|
+
|
2107
|
+
// convert seconds from epoch to days from epoch + extract data
|
2108
|
+
if (timer >= 0) {
|
2109
|
+
// for seconds up to weekdays, we reduce the reminder every step.
|
2110
|
+
a = (ssize_t)timer;
|
2111
|
+
b = a / 60; // b == time in minutes
|
2112
|
+
tm->tm_sec = a - (b * 60);
|
2113
|
+
a = b / 60; // b == time in hours
|
2114
|
+
tm->tm_min = b - (a * 60);
|
2115
|
+
b = a / 24; // b == time in days since epoch
|
2116
|
+
tm->tm_hour = a - (b * 24);
|
2117
|
+
// b == number of days since epoch
|
2118
|
+
// day of epoch was a thursday. Add + 4 so sunday == 0...
|
2119
|
+
tm->tm_wday = (b + 4) % 7;
|
2120
|
+
} else {
|
2121
|
+
// for seconds up to weekdays, we reduce the reminder every step.
|
2122
|
+
a = (ssize_t)timer;
|
2123
|
+
b = a / 60; // b == time in minutes
|
2124
|
+
if (b * 60 != a) {
|
2125
|
+
/* seconds passed */
|
2126
|
+
tm->tm_sec = (a - (b * 60)) + 60;
|
2127
|
+
--b;
|
2128
|
+
} else {
|
2129
|
+
/* no seconds */
|
2130
|
+
tm->tm_sec = 0;
|
2131
|
+
}
|
2132
|
+
a = b / 60; // b == time in hours
|
2133
|
+
if (a * 60 != b) {
|
2134
|
+
/* minutes passed */
|
2135
|
+
tm->tm_min = (b - (a * 60)) + 60;
|
2136
|
+
--a;
|
2137
|
+
} else {
|
2138
|
+
/* no minutes */
|
2139
|
+
tm->tm_min = 0;
|
2140
|
+
}
|
2141
|
+
b = a / 24; // b == time in days since epoch?
|
2142
|
+
if (b * 24 != a) {
|
2143
|
+
/* hours passed */
|
2144
|
+
tm->tm_hour = (a - (b * 24)) + 24;
|
2145
|
+
--b;
|
2146
|
+
} else {
|
2147
|
+
/* no hours */
|
2148
|
+
tm->tm_hour = 0;
|
2149
|
+
}
|
2150
|
+
// day of epoch was a thursday. Add + 4 so sunday == 0...
|
2151
|
+
tm->tm_wday = ((b - 3) % 7);
|
2152
|
+
if (tm->tm_wday)
|
2153
|
+
tm->tm_wday += 7;
|
2154
|
+
/* b == days from epoch */
|
2155
|
+
}
|
2156
|
+
|
2157
|
+
// at this point we can apply the algorithm described here:
|
2158
|
+
// http://howardhinnant.github.io/date_algorithms.html#civil_from_days
|
2159
|
+
// Credit to Howard Hinnant.
|
2160
|
+
{
|
2161
|
+
b += 719468L; // adjust to March 1st, 2000 (post leap of 400 year era)
|
2162
|
+
// 146,097 = days in era (400 years)
|
2163
|
+
const size_t era = (b >= 0 ? b : b - 146096) / 146097;
|
2164
|
+
const uint32_t doe = (b - (era * 146097)); // day of era
|
2165
|
+
const uint16_t yoe =
|
2166
|
+
(doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // year of era
|
2167
|
+
a = yoe;
|
2168
|
+
a += era * 400; // a == year number, assuming year starts on March 1st...
|
2169
|
+
const uint16_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
2170
|
+
const uint16_t mp = (5U * doy + 2) / 153;
|
2171
|
+
const uint16_t d = doy - (153U * mp + 2) / 5 + 1;
|
2172
|
+
const uint8_t m = mp + (mp < 10 ? 2 : -10);
|
2173
|
+
a += (m <= 1);
|
2174
|
+
tm->tm_year = a - 1900; // tm_year == years since 1900
|
2175
|
+
tm->tm_mon = m;
|
2176
|
+
tm->tm_mday = d;
|
2177
|
+
const uint8_t is_leap = (a % 4 == 0 && (a % 100 != 0 || a % 400 == 0));
|
2178
|
+
tm->tm_yday = (doy + (is_leap) + 28 + 31) % (365 + is_leap);
|
2179
|
+
}
|
2180
|
+
|
2181
|
+
return tm;
|
2182
|
+
}
|
2183
|
+
|
2184
|
+
static const char *DAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed",
|
2185
|
+
"Thu", "Fri", "Sat"};
|
2186
|
+
static const char *MONTH_NAMES[] = {"Jan ", "Feb ", "Mar ", "Apr ",
|
2187
|
+
"May ", "Jun ", "Jul ", "Aug ",
|
2188
|
+
"Sep ", "Oct ", "Nov ", "Dec "};
|
2189
|
+
static const char *GMT_STR = "GMT";
|
2190
|
+
|
2191
|
+
size_t http_date2rfc7231(char *target, struct tm *tmbuf) {
|
2192
|
+
/* note: day of month is always 2 digits */
|
2193
|
+
char *pos = target;
|
2194
|
+
uint16_t tmp;
|
2195
|
+
pos[0] = DAY_NAMES[tmbuf->tm_wday][0];
|
2196
|
+
pos[1] = DAY_NAMES[tmbuf->tm_wday][1];
|
2197
|
+
pos[2] = DAY_NAMES[tmbuf->tm_wday][2];
|
2198
|
+
pos[3] = ',';
|
2199
|
+
pos[4] = ' ';
|
2200
|
+
pos += 5;
|
2201
|
+
tmp = tmbuf->tm_mday / 10;
|
2202
|
+
pos[0] = '0' + tmp;
|
2203
|
+
pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10));
|
2204
|
+
pos += 2;
|
2205
|
+
*(pos++) = ' ';
|
2206
|
+
pos[0] = MONTH_NAMES[tmbuf->tm_mon][0];
|
2207
|
+
pos[1] = MONTH_NAMES[tmbuf->tm_mon][1];
|
2208
|
+
pos[2] = MONTH_NAMES[tmbuf->tm_mon][2];
|
2209
|
+
pos[3] = ' ';
|
2210
|
+
pos += 4;
|
2211
|
+
// write year.
|
2212
|
+
pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10);
|
2213
|
+
*(pos++) = ' ';
|
2214
|
+
tmp = tmbuf->tm_hour / 10;
|
2215
|
+
pos[0] = '0' + tmp;
|
2216
|
+
pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10));
|
2217
|
+
pos[2] = ':';
|
2218
|
+
tmp = tmbuf->tm_min / 10;
|
2219
|
+
pos[3] = '0' + tmp;
|
2220
|
+
pos[4] = '0' + (tmbuf->tm_min - (tmp * 10));
|
2221
|
+
pos[5] = ':';
|
2222
|
+
tmp = tmbuf->tm_sec / 10;
|
2223
|
+
pos[6] = '0' + tmp;
|
2224
|
+
pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10));
|
2225
|
+
pos += 8;
|
2226
|
+
pos[0] = ' ';
|
2227
|
+
pos[1] = GMT_STR[0];
|
2228
|
+
pos[2] = GMT_STR[1];
|
2229
|
+
pos[3] = GMT_STR[2];
|
2230
|
+
pos[4] = 0;
|
2231
|
+
pos += 4;
|
2232
|
+
return pos - target;
|
2233
|
+
}
|
2234
|
+
|
2235
|
+
size_t http_date2str(char *target, struct tm *tmbuf);
|
2236
|
+
|
2237
|
+
size_t http_date2rfc2822(char *target, struct tm *tmbuf) {
|
2238
|
+
/* note: day of month is either 1 or 2 digits */
|
2239
|
+
char *pos = target;
|
2240
|
+
uint16_t tmp;
|
2241
|
+
pos[0] = DAY_NAMES[tmbuf->tm_wday][0];
|
2242
|
+
pos[1] = DAY_NAMES[tmbuf->tm_wday][1];
|
2243
|
+
pos[2] = DAY_NAMES[tmbuf->tm_wday][2];
|
2244
|
+
pos[3] = ',';
|
2245
|
+
pos[4] = ' ';
|
2246
|
+
pos += 5;
|
2247
|
+
if (tmbuf->tm_mday < 10) {
|
2248
|
+
*pos = '0' + tmbuf->tm_mday;
|
2249
|
+
++pos;
|
2250
|
+
} else {
|
2251
|
+
tmp = tmbuf->tm_mday / 10;
|
2252
|
+
pos[0] = '0' + tmp;
|
2253
|
+
pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10));
|
2254
|
+
pos += 2;
|
2255
|
+
}
|
2256
|
+
*(pos++) = '-';
|
2257
|
+
pos[0] = MONTH_NAMES[tmbuf->tm_mon][0];
|
2258
|
+
pos[1] = MONTH_NAMES[tmbuf->tm_mon][1];
|
2259
|
+
pos[2] = MONTH_NAMES[tmbuf->tm_mon][2];
|
2260
|
+
pos += 3;
|
2261
|
+
*(pos++) = '-';
|
2262
|
+
// write year.
|
2263
|
+
pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10);
|
2264
|
+
*(pos++) = ' ';
|
2265
|
+
tmp = tmbuf->tm_hour / 10;
|
2266
|
+
pos[0] = '0' + tmp;
|
2267
|
+
pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10));
|
2268
|
+
pos[2] = ':';
|
2269
|
+
tmp = tmbuf->tm_min / 10;
|
2270
|
+
pos[3] = '0' + tmp;
|
2271
|
+
pos[4] = '0' + (tmbuf->tm_min - (tmp * 10));
|
2272
|
+
pos[5] = ':';
|
2273
|
+
tmp = tmbuf->tm_sec / 10;
|
2274
|
+
pos[6] = '0' + tmp;
|
2275
|
+
pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10));
|
2276
|
+
pos += 8;
|
2277
|
+
pos[0] = ' ';
|
2278
|
+
pos[1] = GMT_STR[0];
|
2279
|
+
pos[2] = GMT_STR[1];
|
2280
|
+
pos[3] = GMT_STR[2];
|
2281
|
+
pos[4] = 0;
|
2282
|
+
pos += 4;
|
2283
|
+
return pos - target;
|
2284
|
+
}
|
2285
|
+
|
2286
|
+
/* HTTP header format for Cookie ages */
|
2287
|
+
size_t http_date2rfc2109(char *target, struct tm *tmbuf) {
|
2288
|
+
/* note: day of month is always 2 digits */
|
2289
|
+
char *pos = target;
|
2290
|
+
uint16_t tmp;
|
2291
|
+
pos[0] = DAY_NAMES[tmbuf->tm_wday][0];
|
2292
|
+
pos[1] = DAY_NAMES[tmbuf->tm_wday][1];
|
2293
|
+
pos[2] = DAY_NAMES[tmbuf->tm_wday][2];
|
2294
|
+
pos[3] = ',';
|
2295
|
+
pos[4] = ' ';
|
2296
|
+
pos += 5;
|
2297
|
+
tmp = tmbuf->tm_mday / 10;
|
2298
|
+
pos[0] = '0' + tmp;
|
2299
|
+
pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10));
|
2300
|
+
pos += 2;
|
2301
|
+
*(pos++) = ' ';
|
2302
|
+
pos[0] = MONTH_NAMES[tmbuf->tm_mon][0];
|
2303
|
+
pos[1] = MONTH_NAMES[tmbuf->tm_mon][1];
|
2304
|
+
pos[2] = MONTH_NAMES[tmbuf->tm_mon][2];
|
2305
|
+
pos[3] = ' ';
|
2306
|
+
pos += 4;
|
2307
|
+
// write year.
|
2308
|
+
pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10);
|
2309
|
+
*(pos++) = ' ';
|
2310
|
+
tmp = tmbuf->tm_hour / 10;
|
2311
|
+
pos[0] = '0' + tmp;
|
2312
|
+
pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10));
|
2313
|
+
pos[2] = ':';
|
2314
|
+
tmp = tmbuf->tm_min / 10;
|
2315
|
+
pos[3] = '0' + tmp;
|
2316
|
+
pos[4] = '0' + (tmbuf->tm_min - (tmp * 10));
|
2317
|
+
pos[5] = ':';
|
2318
|
+
tmp = tmbuf->tm_sec / 10;
|
2319
|
+
pos[6] = '0' + tmp;
|
2320
|
+
pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10));
|
2321
|
+
pos += 8;
|
2322
|
+
*pos++ = ' ';
|
2323
|
+
*pos++ = '-';
|
2324
|
+
*pos++ = '0';
|
2325
|
+
*pos++ = '0';
|
2326
|
+
*pos++ = '0';
|
2327
|
+
*pos++ = '0';
|
2328
|
+
*pos = 0;
|
2329
|
+
return pos - target;
|
2330
|
+
}
|
2331
|
+
|
2332
|
+
static pthread_key_t cached_tick_key;
|
2333
|
+
static pthread_key_t cached_httpdate_key;
|
2334
|
+
static pthread_key_t cached_len_key;
|
2335
|
+
static pthread_once_t cached_once = PTHREAD_ONCE_INIT;
|
2336
|
+
static void init_cached_key(void) {
|
2337
|
+
pthread_key_create(&cached_tick_key, free);
|
2338
|
+
pthread_key_create(&cached_httpdate_key, free);
|
2339
|
+
pthread_key_create(&cached_len_key, free);
|
2340
|
+
}
|
2341
|
+
static void init_cached_key_ptr(void) {
|
2342
|
+
time_t *cached_tick = malloc(sizeof(time_t));
|
2343
|
+
FIO_ASSERT_ALLOC(cached_tick);
|
2344
|
+
memset(cached_tick, 0, sizeof(time_t));
|
2345
|
+
char *cached_httpdate = malloc(sizeof(char) * 48);
|
2346
|
+
FIO_ASSERT_ALLOC(cached_tick);
|
2347
|
+
memset(cached_httpdate, 0, 48);
|
2348
|
+
size_t *cached_len = malloc(sizeof(size_t));
|
2349
|
+
*cached_len = 0;
|
2350
|
+
FIO_ASSERT_ALLOC(cached_len);
|
2351
|
+
pthread_setspecific(cached_tick_key, cached_tick);
|
2352
|
+
pthread_setspecific(cached_httpdate_key, cached_httpdate);
|
2353
|
+
pthread_setspecific(cached_len_key, cached_len);
|
2354
|
+
}
|
2355
|
+
|
2356
|
+
/**
|
2357
|
+
* Prints Unix time to a HTTP time formatted string.
|
2358
|
+
*
|
2359
|
+
* This variation implements cached results for faster processing, at the
|
2360
|
+
* price of a less accurate string.
|
2361
|
+
*/
|
2362
|
+
size_t http_time2str(char *target, const time_t t) {
|
2363
|
+
/* pre-print time every 1 or 2 seconds or so. */
|
2364
|
+
pthread_once(&cached_once, init_cached_key);
|
2365
|
+
char *cached_httpdate = pthread_getspecific(cached_httpdate_key);
|
2366
|
+
if (!cached_httpdate) {
|
2367
|
+
init_cached_key_ptr();
|
2368
|
+
cached_httpdate = pthread_getspecific(cached_httpdate_key);
|
2369
|
+
}
|
2370
|
+
time_t *cached_tick = pthread_getspecific(cached_tick_key);
|
2371
|
+
size_t *cached_len = pthread_getspecific(cached_len_key);
|
2372
|
+
time_t last_tick = fio_last_tick().tv_sec;
|
2373
|
+
if ((t | 7) < last_tick) {
|
2374
|
+
/* this is a custom time, not "now", pass through */
|
2375
|
+
struct tm tm;
|
2376
|
+
http_gmtime(t, &tm);
|
2377
|
+
return http_date2str(target, &tm);
|
2378
|
+
}
|
2379
|
+
if (last_tick > *cached_tick) {
|
2380
|
+
struct tm tm;
|
2381
|
+
*cached_tick = last_tick; /* refresh every second */
|
2382
|
+
http_gmtime(last_tick, &tm);
|
2383
|
+
*cached_len = http_date2str(cached_httpdate, &tm);
|
2384
|
+
}
|
2385
|
+
memcpy(target, cached_httpdate, *cached_len);
|
2386
|
+
return *cached_len;
|
2387
|
+
}
|
2388
|
+
|
2389
|
+
/* Credit to Jonathan Leffler for the idea of a unified conditional */
|
2390
|
+
#define hex_val(c) \
|
2391
|
+
(((c) >= '0' && (c) <= '9') ? ((c)-48) \
|
2392
|
+
: (((c) | 32) >= 'a' && ((c) | 32) <= 'f') ? (((c) | 32) - 87) \
|
2393
|
+
: ({ \
|
2394
|
+
return -1; \
|
2395
|
+
0; \
|
2396
|
+
}))
|
2397
|
+
static inline int hex2byte(uint8_t *dest, const uint8_t *source) {
|
2398
|
+
if (source[0] >= '0' && source[0] <= '9')
|
2399
|
+
*dest = (source[0] - '0');
|
2400
|
+
else if ((source[0] | 32) >= 'a' && (source[0] | 32) <= 'f')
|
2401
|
+
*dest = (source[0] | 32) - ('a' - 10);
|
2402
|
+
else
|
2403
|
+
return -1;
|
2404
|
+
*dest <<= 4;
|
2405
|
+
if (source[1] >= '0' && source[1] <= '9')
|
2406
|
+
*dest |= (source[1] - '0');
|
2407
|
+
else if ((source[1] | 32) >= 'a' && (source[1] | 32) <= 'f')
|
2408
|
+
*dest |= (source[1] | 32) - ('a' - 10);
|
2409
|
+
else
|
2410
|
+
return -1;
|
2411
|
+
return 0;
|
2412
|
+
}
|
2413
|
+
|
2414
|
+
ssize_t http_decode_url(char *dest, const char *url_data, size_t length) {
|
2415
|
+
char *pos = dest;
|
2416
|
+
const char *end = url_data + length;
|
2417
|
+
while (url_data < end) {
|
2418
|
+
if (*url_data == '+') {
|
2419
|
+
// decode space
|
2420
|
+
*(pos++) = ' ';
|
2421
|
+
++url_data;
|
2422
|
+
} else if (*url_data == '%') {
|
2423
|
+
// decode hex value
|
2424
|
+
// this is a percent encoded value.
|
2425
|
+
if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
|
2426
|
+
return -1;
|
2427
|
+
pos++;
|
2428
|
+
url_data += 3;
|
2429
|
+
} else
|
2430
|
+
*(pos++) = *(url_data++);
|
2431
|
+
}
|
2432
|
+
*pos = 0;
|
2433
|
+
return pos - dest;
|
2434
|
+
}
|
2435
|
+
|
2436
|
+
ssize_t http_decode_url_unsafe(char *dest, const char *url_data) {
|
2437
|
+
char *pos = dest;
|
2438
|
+
while (*url_data) {
|
2439
|
+
if (*url_data == '+') {
|
2440
|
+
// decode space
|
2441
|
+
*(pos++) = ' ';
|
2442
|
+
++url_data;
|
2443
|
+
} else if (*url_data == '%') {
|
2444
|
+
// decode hex value
|
2445
|
+
// this is a percent encoded value.
|
2446
|
+
if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
|
2447
|
+
return -1;
|
2448
|
+
pos++;
|
2449
|
+
url_data += 3;
|
2450
|
+
} else
|
2451
|
+
*(pos++) = *(url_data++);
|
2452
|
+
}
|
2453
|
+
*pos = 0;
|
2454
|
+
return pos - dest;
|
2455
|
+
}
|
2456
|
+
|
2457
|
+
ssize_t http_decode_path(char *dest, const char *url_data, size_t length) {
|
2458
|
+
char *pos = dest;
|
2459
|
+
const char *end = url_data + length;
|
2460
|
+
while (url_data < end) {
|
2461
|
+
if (*url_data == '%') {
|
2462
|
+
// decode hex value
|
2463
|
+
// this is a percent encoded value.
|
2464
|
+
if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
|
2465
|
+
return -1;
|
2466
|
+
pos++;
|
2467
|
+
url_data += 3;
|
2468
|
+
} else
|
2469
|
+
*(pos++) = *(url_data++);
|
2470
|
+
}
|
2471
|
+
*pos = 0;
|
2472
|
+
return pos - dest;
|
2473
|
+
}
|
2474
|
+
|
2475
|
+
ssize_t http_decode_path_unsafe(char *dest, const char *url_data) {
|
2476
|
+
char *pos = dest;
|
2477
|
+
while (*url_data) {
|
2478
|
+
if (*url_data == '%') {
|
2479
|
+
// decode hex value
|
2480
|
+
// this is a percent encoded value.
|
2481
|
+
if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
|
2482
|
+
return -1;
|
2483
|
+
pos++;
|
2484
|
+
url_data += 3;
|
2485
|
+
} else
|
2486
|
+
*(pos++) = *(url_data++);
|
2487
|
+
}
|
2488
|
+
*pos = 0;
|
2489
|
+
return pos - dest;
|
2490
|
+
}
|
2491
|
+
|
2492
|
+
/* *****************************************************************************
|
2493
|
+
Lookup Tables / functions
|
2494
|
+
***************************************************************************** */
|
2495
|
+
|
2496
|
+
#define FIO_FORCE_MALLOC_TMP 1 /* use malloc for the mime registry */
|
2497
|
+
#define FIO_SET_NAME fio_mime_set
|
2498
|
+
#define FIO_SET_OBJ_TYPE FIOBJ
|
2499
|
+
#define FIO_SET_OBJ_COMPARE(o1, o2) (1)
|
2500
|
+
#define FIO_SET_OBJ_COPY(dest, o) (dest) = fiobj_dup((o))
|
2501
|
+
#define FIO_SET_OBJ_DESTROY(o) fiobj_free((o))
|
2502
|
+
|
2503
|
+
#include <fio.h>
|
2504
|
+
|
2505
|
+
static fio_mime_set_s fio_http_mime_types = FIO_SET_INIT;
|
2506
|
+
|
2507
|
+
#define LONGEST_FILE_EXTENSION_LENGTH 15
|
2508
|
+
|
2509
|
+
/** Registers a Mime-Type to be associated with the file extension. */
|
2510
|
+
void http_mimetype_register(char *file_ext, size_t file_ext_len,
|
2511
|
+
FIOBJ mime_type_str) {
|
2512
|
+
uintptr_t hash = FIO_HASH_FN(file_ext, file_ext_len, 0, 0);
|
2513
|
+
if (mime_type_str == FIOBJ_INVALID) {
|
2514
|
+
fio_mime_set_remove(&fio_http_mime_types, hash, FIOBJ_INVALID, NULL);
|
2515
|
+
} else {
|
2516
|
+
FIOBJ old = FIOBJ_INVALID;
|
2517
|
+
fio_mime_set_overwrite(&fio_http_mime_types, hash, mime_type_str, &old);
|
2518
|
+
if (old != FIOBJ_INVALID) {
|
2519
|
+
FIO_LOG_WARNING("mime-type collision: %.*s was %s, now %s",
|
2520
|
+
(int)file_ext_len, file_ext, fiobj_obj2cstr(old).data,
|
2521
|
+
fiobj_obj2cstr(mime_type_str).data);
|
2522
|
+
fiobj_free(old);
|
2523
|
+
}
|
2524
|
+
fiobj_free(mime_type_str); /* move ownership to the registry */
|
2525
|
+
}
|
2526
|
+
}
|
2527
|
+
|
2528
|
+
/** Registers a Mime-Type to be associated with the file extension. */
|
2529
|
+
void http_mimetype_stats(void) {
|
2530
|
+
FIO_LOG_DEBUG("HTTP MIME hash storage count/capa: %zu / %zu",
|
2531
|
+
fio_mime_set_count(&fio_http_mime_types),
|
2532
|
+
fio_mime_set_capa(&fio_http_mime_types));
|
2533
|
+
}
|
2534
|
+
|
2535
|
+
/**
|
2536
|
+
* Finds the mime-type associated with the file extension.
|
2537
|
+
* Remember to call `fiobj_free`.
|
2538
|
+
*/
|
2539
|
+
FIOBJ http_mimetype_find(char *file_ext, size_t file_ext_len) {
|
2540
|
+
uintptr_t hash = FIO_HASH_FN(file_ext, file_ext_len, 0, 0);
|
2541
|
+
return fiobj_dup(
|
2542
|
+
fio_mime_set_find(&fio_http_mime_types, hash, FIOBJ_INVALID));
|
2543
|
+
}
|
2544
|
+
|
2545
|
+
static pthread_key_t buffer_key;
|
2546
|
+
static pthread_once_t buffer_once = PTHREAD_ONCE_INIT;
|
2547
|
+
static void init_buffer_key(void) { pthread_key_create(&buffer_key, free); }
|
2548
|
+
static void init_buffer_ptr(void) {
|
2549
|
+
char *buffer = malloc(sizeof(char) * (LONGEST_FILE_EXTENSION_LENGTH + 1));
|
2550
|
+
FIO_ASSERT_ALLOC(buffer);
|
2551
|
+
memset(buffer, 0, sizeof(char) * (LONGEST_FILE_EXTENSION_LENGTH + 1));
|
2552
|
+
pthread_setspecific(buffer_key, buffer);
|
2553
|
+
}
|
2554
|
+
/**
|
2555
|
+
* Finds the mime-type associated with the URL.
|
2556
|
+
* Remember to call `fiobj_free`.
|
2557
|
+
*/
|
2558
|
+
FIOBJ http_mimetype_find2(FIOBJ url) {
|
2559
|
+
pthread_once(&buffer_once, init_buffer_key);
|
2560
|
+
char *buffer = pthread_getspecific(buffer_key);
|
2561
|
+
if (!buffer) {
|
2562
|
+
init_buffer_ptr();
|
2563
|
+
buffer = pthread_getspecific(buffer_key);
|
2564
|
+
}
|
2565
|
+
fio_str_info_s ext = {.data = NULL};
|
2566
|
+
FIOBJ mimetype;
|
2567
|
+
if (!url)
|
2568
|
+
goto finish;
|
2569
|
+
fio_str_info_s tmp = fiobj_obj2cstr(url);
|
2570
|
+
uint8_t steps = 1;
|
2571
|
+
while (tmp.len > steps || steps >= LONGEST_FILE_EXTENSION_LENGTH) {
|
2572
|
+
switch (tmp.data[tmp.len - steps]) {
|
2573
|
+
case '.':
|
2574
|
+
--steps;
|
2575
|
+
if (steps) {
|
2576
|
+
ext.len = steps;
|
2577
|
+
ext.data = buffer;
|
2578
|
+
buffer[steps] = 0;
|
2579
|
+
for (size_t i = 1; i <= steps; ++i) {
|
2580
|
+
buffer[steps - i] = tolower(tmp.data[tmp.len - i]);
|
2581
|
+
}
|
2582
|
+
}
|
2583
|
+
/* fallthrough */
|
2584
|
+
case '/':
|
2585
|
+
goto finish;
|
2586
|
+
break;
|
2587
|
+
}
|
2588
|
+
++steps;
|
2589
|
+
}
|
2590
|
+
finish:
|
2591
|
+
mimetype = http_mimetype_find(ext.data, ext.len);
|
2592
|
+
if (!mimetype)
|
2593
|
+
mimetype = fiobj_dup(HTTP_HVALUE_CONTENT_TYPE_DEFAULT);
|
2594
|
+
return mimetype;
|
2595
|
+
}
|
2596
|
+
|
2597
|
+
/** Clears the Mime-Type registry (it will be empty afterthis call). */
|
2598
|
+
void http_mimetype_clear(void) {
|
2599
|
+
fio_mime_set_free(&fio_http_mime_types);
|
2600
|
+
fiobj_free(current_date);
|
2601
|
+
current_date = FIOBJ_INVALID;
|
2602
|
+
last_date_added = 0;
|
2603
|
+
}
|
2604
|
+
|
2605
|
+
/**
|
2606
|
+
* Create with Ruby using:
|
2607
|
+
|
2608
|
+
a = []
|
2609
|
+
256.times {|i| a[i] = 1;}
|
2610
|
+
('a'.ord..'z'.ord).each {|i| a[i] = 0;}
|
2611
|
+
('A'.ord..'Z'.ord).each {|i| a[i] = 0;}
|
2612
|
+
('0'.ord..'9'.ord).each {|i| a[i] = 0;}
|
2613
|
+
"!#$%&'*+-.^_`|~".bytes.each {|i| a[i] = 0;}
|
2614
|
+
p a; nil
|
2615
|
+
"!#$%&'()*+-./:<=>?@[]^_`{|}~".bytes.each {|i| a[i] = 0;} # for values
|
2616
|
+
p a; nil
|
2617
|
+
*/
|
2618
|
+
static char invalid_cookie_name_char[256] = {
|
2619
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2620
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1,
|
2621
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
2622
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
2623
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
2624
|
+
0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2625
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2626
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2627
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2628
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2629
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
2630
|
+
|
2631
|
+
static char invalid_cookie_value_char[256] = {
|
2632
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2633
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
2634
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
2635
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
2636
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
2637
|
+
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2638
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2639
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2640
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2641
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
2642
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
2643
|
+
|
2644
|
+
// clang-format off
|
2645
|
+
#define HTTP_SET_STATUS_STR(status, str) [status-100] = { .data = (char *)(#status " " str), .len = (sizeof( #status " " str) - 1) }
|
2646
|
+
// clang-format on
|
2647
|
+
|
2648
|
+
/** Returns the status as a C string struct */
|
2649
|
+
fio_str_info_s http_status2str(uintptr_t status) {
|
2650
|
+
static const fio_str_info_s status2str[] = {
|
2651
|
+
HTTP_SET_STATUS_STR(100, "Continue"),
|
2652
|
+
HTTP_SET_STATUS_STR(101, "Switching Protocols"),
|
2653
|
+
HTTP_SET_STATUS_STR(102, "Processing"),
|
2654
|
+
HTTP_SET_STATUS_STR(103, "Early Hints"),
|
2655
|
+
HTTP_SET_STATUS_STR(200, "OK"),
|
2656
|
+
HTTP_SET_STATUS_STR(201, "Created"),
|
2657
|
+
HTTP_SET_STATUS_STR(202, "Accepted"),
|
2658
|
+
HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"),
|
2659
|
+
HTTP_SET_STATUS_STR(204, "No Content"),
|
2660
|
+
HTTP_SET_STATUS_STR(205, "Reset Content"),
|
2661
|
+
HTTP_SET_STATUS_STR(206, "Partial Content"),
|
2662
|
+
HTTP_SET_STATUS_STR(207, "Multi-Status"),
|
2663
|
+
HTTP_SET_STATUS_STR(208, "Already Reported"),
|
2664
|
+
HTTP_SET_STATUS_STR(226, "IM Used"),
|
2665
|
+
HTTP_SET_STATUS_STR(300, "Multiple Choices"),
|
2666
|
+
HTTP_SET_STATUS_STR(301, "Moved Permanently"),
|
2667
|
+
HTTP_SET_STATUS_STR(302, "Found"),
|
2668
|
+
HTTP_SET_STATUS_STR(303, "See Other"),
|
2669
|
+
HTTP_SET_STATUS_STR(304, "Not Modified"),
|
2670
|
+
HTTP_SET_STATUS_STR(305, "Use Proxy"),
|
2671
|
+
HTTP_SET_STATUS_STR(306, "(Unused), "),
|
2672
|
+
HTTP_SET_STATUS_STR(307, "Temporary Redirect"),
|
2673
|
+
HTTP_SET_STATUS_STR(308, "Permanent Redirect"),
|
2674
|
+
HTTP_SET_STATUS_STR(400, "Bad Request"),
|
2675
|
+
HTTP_SET_STATUS_STR(403, "Forbidden"),
|
2676
|
+
HTTP_SET_STATUS_STR(404, "Not Found"),
|
2677
|
+
HTTP_SET_STATUS_STR(401, "Unauthorized"),
|
2678
|
+
HTTP_SET_STATUS_STR(402, "Payment Required"),
|
2679
|
+
HTTP_SET_STATUS_STR(405, "Method Not Allowed"),
|
2680
|
+
HTTP_SET_STATUS_STR(406, "Not Acceptable"),
|
2681
|
+
HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"),
|
2682
|
+
HTTP_SET_STATUS_STR(408, "Request Timeout"),
|
2683
|
+
HTTP_SET_STATUS_STR(409, "Conflict"),
|
2684
|
+
HTTP_SET_STATUS_STR(410, "Gone"),
|
2685
|
+
HTTP_SET_STATUS_STR(411, "Length Required"),
|
2686
|
+
HTTP_SET_STATUS_STR(412, "Precondition Failed"),
|
2687
|
+
HTTP_SET_STATUS_STR(413, "Payload Too Large"),
|
2688
|
+
HTTP_SET_STATUS_STR(414, "URI Too Long"),
|
2689
|
+
HTTP_SET_STATUS_STR(415, "Unsupported Media Type"),
|
2690
|
+
HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"),
|
2691
|
+
HTTP_SET_STATUS_STR(417, "Expectation Failed"),
|
2692
|
+
HTTP_SET_STATUS_STR(418, "I'm a teapot"),
|
2693
|
+
HTTP_SET_STATUS_STR(421, "Misdirected Request"),
|
2694
|
+
HTTP_SET_STATUS_STR(422, "Unprocessable Entity"),
|
2695
|
+
HTTP_SET_STATUS_STR(423, "Locked"),
|
2696
|
+
HTTP_SET_STATUS_STR(424, "Failed Dependency"),
|
2697
|
+
HTTP_SET_STATUS_STR(425, "Unassigned"),
|
2698
|
+
HTTP_SET_STATUS_STR(426, "Upgrade Required"),
|
2699
|
+
HTTP_SET_STATUS_STR(427, "Unassigned"),
|
2700
|
+
HTTP_SET_STATUS_STR(428, "Precondition Required"),
|
2701
|
+
HTTP_SET_STATUS_STR(429, "Too Many Requests"),
|
2702
|
+
HTTP_SET_STATUS_STR(430, "Unassigned"),
|
2703
|
+
HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"),
|
2704
|
+
HTTP_SET_STATUS_STR(500, "Internal Server Error"),
|
2705
|
+
HTTP_SET_STATUS_STR(501, "Not Implemented"),
|
2706
|
+
HTTP_SET_STATUS_STR(502, "Bad Gateway"),
|
2707
|
+
HTTP_SET_STATUS_STR(503, "Service Unavailable"),
|
2708
|
+
HTTP_SET_STATUS_STR(504, "Gateway Timeout"),
|
2709
|
+
HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"),
|
2710
|
+
HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"),
|
2711
|
+
HTTP_SET_STATUS_STR(507, "Insufficient Storage"),
|
2712
|
+
HTTP_SET_STATUS_STR(508, "Loop Detected"),
|
2713
|
+
HTTP_SET_STATUS_STR(509, "Unassigned"),
|
2714
|
+
HTTP_SET_STATUS_STR(510, "Not Extended"),
|
2715
|
+
HTTP_SET_STATUS_STR(511, "Network Authentication Required"),
|
2716
|
+
};
|
2717
|
+
fio_str_info_s ret = (fio_str_info_s){.len = 0, .data = NULL};
|
2718
|
+
if (status >= 100 &&
|
2719
|
+
(status - 100) < sizeof(status2str) / sizeof(status2str[0]))
|
2720
|
+
ret = status2str[status - 100];
|
2721
|
+
if (!ret.data) {
|
2722
|
+
ret = status2str[400];
|
2723
|
+
}
|
2724
|
+
return ret;
|
2725
|
+
}
|
2726
|
+
#undef HTTP_SET_STATUS_STR
|
2727
|
+
|
2728
|
+
#if DEBUG
|
2729
|
+
void http_tests(void) {
|
2730
|
+
fprintf(stderr, "=== Testing HTTP helpers\n");
|
2731
|
+
FIOBJ html_mime = http_mimetype_find("html", 4);
|
2732
|
+
FIO_ASSERT(html_mime,
|
2733
|
+
"HTML mime-type not found! Mime-Type registry invalid!\n");
|
2734
|
+
fiobj_free(html_mime);
|
2735
|
+
}
|
2736
|
+
#endif
|