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.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
data/ext/iodine/http.c CHANGED
@@ -1,80 +1,838 @@
1
1
  /*
2
- copyright: Boaz segev, 2016-2017
3
- license: MIT
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 "http.h"
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 <strings.h>
15
- #include <time.h>
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 global HTTP protocol
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
- Return the callback used for creating the correct `settings` HTTP protocol.
22
- */
23
- http_on_open_func http_get_on_open_func(http_settings_s *settings) {
24
- static void *route[2] = {(void *)http1_on_open};
25
- return (http_on_open_func)route[settings->version];
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
- Return the callback used for freeing the HTTP protocol in the `settings`.
29
- */
30
- http_on_finish_func http_get_on_finish_func(http_settings_s *settings) {
31
- static void *route[2] = {NULL};
32
- return (http_on_finish_func)route[settings->version];
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
- void http_on_finish(intptr_t uuid, void *set) {
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
- if (settings->on_finish)
41
- settings->on_finish(uuid, settings->udata);
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
- if (settings->private_metaflags & 2) {
44
- free((void *)settings->public_folder);
45
- free(settings);
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
- Listens for incoming HTTP connections on the specified posrt and address,
52
- implementing the requested settings.
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
- Since facil.io doesn't support native TLS/SLL
55
- */
56
- #undef http_listen
57
- int http_listen(const char *port, const char *address,
58
- http_settings_s arg_settings) {
59
- if (arg_settings.on_request == NULL) {
60
- fprintf(
61
- stderr,
62
- "ERROR: http_listen requires the .on_request parameter to be set\n");
63
- kill(0, SIGINT), exit(11);
64
- }
65
- http_on_open_func on_open_callback = http_get_on_open_func(&arg_settings);
66
- if (!on_open_callback) {
67
- fprintf(stderr, "ERROR: The requested HTTP protocol version isn't "
68
- "supported at the moment.\n");
69
- kill(0, SIGINT), exit(11);
70
- }
71
- http_settings_s *settings = malloc(sizeof(*settings));
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
- settings->private_metaflags = 2;
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
- return facil_listen(.port = port, .address = address,
101
- .set_rw_hooks = arg_settings.set_rw_hooks,
102
- .rw_udata = arg_settings.rw_udata,
103
- .on_finish_rw = arg_settings.on_finish_rw,
104
- .on_finish = http_on_finish, .on_open = on_open_callback,
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 helpers.
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(const time_t *timer, struct tm *tmbuf) {
120
- // static char* DAYS[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
121
- // static char * Months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
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 (*timer < 0)
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 larger
137
- // values.
138
- a = ((ssize_t)*timer);
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
- *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]);
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
- *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]);
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 += http_ul2a(pos, tmbuf->tm_year + 1900);
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
- *((uint32_t *)(pos + 1)) = *((uint32_t *)GMT_STR);
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
- *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]);
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
- *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]);
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 += http_ul2a(pos, tmbuf->tm_year + 1900);
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
- *((uint32_t *)(pos + 1)) = *((uint32_t *)GMT_STR);
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
- *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]);
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
- *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]);
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 += http_ul2a(pos, tmbuf->tm_year + 1900);
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(&t, &tm);
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 | 1;
371
- http_gmtime(&last_tick, &tm);
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
- #undef hex_val
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