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