rage-iodine 1.7.58

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