isomorfeus-iodine 0.7.44

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