isomorfeus-iodine 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1038 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +44 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/config.ru +56 -0
  25. data/examples/echo.ru +59 -0
  26. data/examples/hello.ru +29 -0
  27. data/examples/pubsub_engine.ru +81 -0
  28. data/examples/redis.ru +70 -0
  29. data/examples/shootout.ru +73 -0
  30. data/examples/sub-protocols.ru +90 -0
  31. data/examples/tcp_client.rb +66 -0
  32. data/examples/x-sendfile.ru +14 -0
  33. data/exe/iodine +277 -0
  34. data/ext/iodine/extconf.rb +109 -0
  35. data/ext/iodine/fio.c +11985 -0
  36. data/ext/iodine/fio.h +6373 -0
  37. data/ext/iodine/fio_cli.c +431 -0
  38. data/ext/iodine/fio_cli.h +189 -0
  39. data/ext/iodine/fio_json_parser.h +687 -0
  40. data/ext/iodine/fio_siphash.c +157 -0
  41. data/ext/iodine/fio_siphash.h +37 -0
  42. data/ext/iodine/fio_tls.h +129 -0
  43. data/ext/iodine/fio_tls_missing.c +649 -0
  44. data/ext/iodine/fio_tls_openssl.c +1056 -0
  45. data/ext/iodine/fio_tmpfile.h +50 -0
  46. data/ext/iodine/fiobj.h +44 -0
  47. data/ext/iodine/fiobj4fio.h +21 -0
  48. data/ext/iodine/fiobj_ary.c +333 -0
  49. data/ext/iodine/fiobj_ary.h +139 -0
  50. data/ext/iodine/fiobj_data.c +1185 -0
  51. data/ext/iodine/fiobj_data.h +167 -0
  52. data/ext/iodine/fiobj_hash.c +409 -0
  53. data/ext/iodine/fiobj_hash.h +176 -0
  54. data/ext/iodine/fiobj_json.c +622 -0
  55. data/ext/iodine/fiobj_json.h +68 -0
  56. data/ext/iodine/fiobj_mem.h +71 -0
  57. data/ext/iodine/fiobj_mustache.c +317 -0
  58. data/ext/iodine/fiobj_mustache.h +62 -0
  59. data/ext/iodine/fiobj_numbers.c +344 -0
  60. data/ext/iodine/fiobj_numbers.h +127 -0
  61. data/ext/iodine/fiobj_str.c +433 -0
  62. data/ext/iodine/fiobj_str.h +172 -0
  63. data/ext/iodine/fiobject.c +620 -0
  64. data/ext/iodine/fiobject.h +654 -0
  65. data/ext/iodine/hpack.h +1923 -0
  66. data/ext/iodine/http.c +2754 -0
  67. data/ext/iodine/http.h +1002 -0
  68. data/ext/iodine/http1.c +912 -0
  69. data/ext/iodine/http1.h +29 -0
  70. data/ext/iodine/http1_parser.h +873 -0
  71. data/ext/iodine/http_internal.c +1278 -0
  72. data/ext/iodine/http_internal.h +237 -0
  73. data/ext/iodine/http_mime_parser.h +350 -0
  74. data/ext/iodine/iodine.c +1430 -0
  75. data/ext/iodine/iodine.h +63 -0
  76. data/ext/iodine/iodine_caller.c +218 -0
  77. data/ext/iodine/iodine_caller.h +27 -0
  78. data/ext/iodine/iodine_connection.c +933 -0
  79. data/ext/iodine/iodine_connection.h +55 -0
  80. data/ext/iodine/iodine_defer.c +420 -0
  81. data/ext/iodine/iodine_defer.h +6 -0
  82. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  83. data/ext/iodine/iodine_helpers.c +282 -0
  84. data/ext/iodine/iodine_helpers.h +12 -0
  85. data/ext/iodine/iodine_http.c +1171 -0
  86. data/ext/iodine/iodine_http.h +23 -0
  87. data/ext/iodine/iodine_json.c +302 -0
  88. data/ext/iodine/iodine_json.h +6 -0
  89. data/ext/iodine/iodine_mustache.c +567 -0
  90. data/ext/iodine/iodine_mustache.h +6 -0
  91. data/ext/iodine/iodine_pubsub.c +580 -0
  92. data/ext/iodine/iodine_pubsub.h +26 -0
  93. data/ext/iodine/iodine_rack_io.c +281 -0
  94. data/ext/iodine/iodine_rack_io.h +20 -0
  95. data/ext/iodine/iodine_store.c +142 -0
  96. data/ext/iodine/iodine_store.h +20 -0
  97. data/ext/iodine/iodine_tcp.c +346 -0
  98. data/ext/iodine/iodine_tcp.h +13 -0
  99. data/ext/iodine/iodine_tls.c +261 -0
  100. data/ext/iodine/iodine_tls.h +13 -0
  101. data/ext/iodine/mustache_parser.h +1546 -0
  102. data/ext/iodine/redis_engine.c +957 -0
  103. data/ext/iodine/redis_engine.h +79 -0
  104. data/ext/iodine/resp_parser.h +317 -0
  105. data/ext/iodine/websocket_parser.h +505 -0
  106. data/ext/iodine/websockets.c +735 -0
  107. data/ext/iodine/websockets.h +185 -0
  108. data/isomorfeus-iodine.gemspec +42 -0
  109. data/lib/iodine/connection.rb +61 -0
  110. data/lib/iodine/json.rb +42 -0
  111. data/lib/iodine/mustache.rb +113 -0
  112. data/lib/iodine/pubsub.rb +55 -0
  113. data/lib/iodine/rack_utils.rb +43 -0
  114. data/lib/iodine/tls.rb +16 -0
  115. data/lib/iodine/version.rb +3 -0
  116. data/lib/iodine.rb +274 -0
  117. data/lib/rack/handler/iodine.rb +33 -0
  118. data/logo.png +0 -0
  119. metadata +271 -0
@@ -0,0 +1,912 @@
1
+ /*
2
+ Copyright: Boaz Segev, 2017-2019
3
+ License: MIT
4
+ */
5
+ #include <fio.h>
6
+
7
+ #include <http1.h>
8
+ #include <http1_parser.h>
9
+ #include <http_internal.h>
10
+ #include <websockets.h>
11
+
12
+ #include <fiobj.h>
13
+
14
+ #include <assert.h>
15
+ #include <stddef.h>
16
+
17
+ /* *****************************************************************************
18
+ The HTTP/1.1 Protocol Object
19
+ ***************************************************************************** */
20
+
21
+ typedef struct http1pr_s {
22
+ http_fio_protocol_s p;
23
+ http1_parser_s parser;
24
+ http_s request;
25
+ uintptr_t buf_len;
26
+ uintptr_t max_header_size;
27
+ uintptr_t header_size;
28
+ uint8_t close;
29
+ uint8_t is_client;
30
+ uint8_t stop;
31
+ uint8_t buf[];
32
+ } http1pr_s;
33
+
34
+ struct http_vtable_s HTTP1_VTABLE; /* initialized later on */
35
+
36
+ /* *****************************************************************************
37
+ Internal Helpers
38
+ ***************************************************************************** */
39
+
40
+ #define parser2http(x) \
41
+ ((http1pr_s *)((uintptr_t)(x) - (uintptr_t)(&((http1pr_s *)0)->parser)))
42
+
43
+ inline static void h1_reset(http1pr_s *p) { p->header_size = 0; }
44
+
45
+ #define http1_pr2handle(pr) (((http1pr_s *)(pr))->request)
46
+ #define handle2pr(h) ((http1pr_s *)h->private_data.flag)
47
+
48
+ static fio_str_info_s http1pr_status2str(uintptr_t status);
49
+
50
+ /* cleanup an HTTP/1.1 handler object */
51
+ static inline void http1_after_finish(http_s *h) {
52
+ http1pr_s *p = handle2pr(h);
53
+ p->stop = p->stop & (~1UL);
54
+ if (h != &p->request) {
55
+ http_s_destroy(h, 0);
56
+ fio_free(h);
57
+ } else {
58
+ http_s_clear(h, p->p.settings->log);
59
+ }
60
+ if (p->close)
61
+ fio_close(p->p.uuid);
62
+ }
63
+
64
+ /* *****************************************************************************
65
+ HTTP Request / Response (Virtual) Functions
66
+ ***************************************************************************** */
67
+ struct header_writer_s {
68
+ FIOBJ dest;
69
+ FIOBJ name;
70
+ FIOBJ value;
71
+ };
72
+
73
+ static int write_header(FIOBJ o, void *w_) {
74
+ struct header_writer_s *w = w_;
75
+ if (!o)
76
+ return 0;
77
+ if (fiobj_hash_key_in_loop()) {
78
+ w->name = fiobj_hash_key_in_loop();
79
+ }
80
+ if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) {
81
+ fiobj_each1(o, 0, write_header, w);
82
+ return 0;
83
+ }
84
+ fio_str_info_s name = fiobj_obj2cstr(w->name);
85
+ fio_str_info_s str = fiobj_obj2cstr(o);
86
+ if (!str.data)
87
+ return 0;
88
+ // fiobj_str_capa_assert(w->dest,
89
+ // fiobj_obj2cstr(w->dest).len + name.len + str.len +
90
+ // 5);
91
+ fiobj_str_write(w->dest, name.data, name.len);
92
+ fiobj_str_write(w->dest, ":", 1);
93
+ fiobj_str_write(w->dest, str.data, str.len);
94
+ fiobj_str_write(w->dest, "\r\n", 2);
95
+ return 0;
96
+ }
97
+
98
+ static FIOBJ headers2str(http_s *h, uintptr_t padding) {
99
+ if (!h->method && !!h->status_str)
100
+ return FIOBJ_INVALID;
101
+
102
+ static uintptr_t connection_hash;
103
+ if (!connection_hash)
104
+ connection_hash = fiobj_hash_string("connection", 10);
105
+
106
+ struct header_writer_s w;
107
+ {
108
+ const uintptr_t header_length_guess =
109
+ fiobj_hash_count(h->private_data.out_headers) * 64;
110
+ w.dest = fiobj_str_buf(header_length_guess + padding);
111
+ }
112
+ http1pr_s *p = handle2pr(h);
113
+
114
+ if (p->is_client == 0) {
115
+ fio_str_info_s t = http1pr_status2str(h->status);
116
+ fiobj_str_write(w.dest, t.data, t.len);
117
+ FIOBJ tmp = fiobj_hash_get2(h->private_data.out_headers, connection_hash);
118
+ if (tmp) {
119
+ t = fiobj_obj2cstr(tmp);
120
+ if (t.data[0] == 'c' || t.data[0] == 'C')
121
+ p->close = 1;
122
+ } else {
123
+ tmp = fiobj_hash_get2(h->headers, connection_hash);
124
+ if (tmp) {
125
+ t = fiobj_obj2cstr(tmp);
126
+ if (!t.data || !t.len || t.data[0] == 'k' || t.data[0] == 'K')
127
+ fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
128
+ else {
129
+ fiobj_str_write(w.dest, "connection:close\r\n", 18);
130
+ p->close = 1;
131
+ }
132
+ } else {
133
+ t = fiobj_obj2cstr(h->version);
134
+ if (!p->close && t.len > 7 && t.data && t.data[5] == '1' &&
135
+ t.data[6] == '.' && t.data[7] == '1')
136
+ fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
137
+ else {
138
+ fiobj_str_write(w.dest, "connection:close\r\n", 18);
139
+ p->close = 1;
140
+ }
141
+ }
142
+ }
143
+ } else {
144
+ if (h->method) {
145
+ fiobj_str_join(w.dest, h->method);
146
+ fiobj_str_write(w.dest, " ", 1);
147
+ } else {
148
+ fiobj_str_write(w.dest, "GET ", 4);
149
+ }
150
+ fiobj_str_join(w.dest, h->path);
151
+ if (h->query) {
152
+ fiobj_str_write(w.dest, "?", 1);
153
+ fiobj_str_join(w.dest, h->query);
154
+ }
155
+ fiobj_str_write(w.dest, " HTTP/1.1\r\n", 11);
156
+ /* make sure we have a host header? */
157
+ static uint64_t host_hash;
158
+ if (!host_hash)
159
+ host_hash = fiobj_hash_string("host", 4);
160
+ FIOBJ tmp;
161
+ if (!fiobj_hash_get2(h->private_data.out_headers, host_hash) &&
162
+ (tmp = fiobj_hash_get2(h->headers, host_hash))) {
163
+ fiobj_str_write(w.dest, "host:", 5);
164
+ fiobj_str_join(w.dest, tmp);
165
+ fiobj_str_write(w.dest, "\r\n", 2);
166
+ }
167
+ if (!fiobj_hash_get2(h->private_data.out_headers, connection_hash))
168
+ fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
169
+ }
170
+
171
+ fiobj_each1(h->private_data.out_headers, 0, write_header, &w);
172
+ fiobj_str_write(w.dest, "\r\n", 2);
173
+ return w.dest;
174
+ }
175
+
176
+ /** Should send existing headers and data */
177
+ static int http1_send_body(http_s *h, void *data, uintptr_t length) {
178
+
179
+ FIOBJ packet = headers2str(h, length);
180
+ if (!packet) {
181
+ http1_after_finish(h);
182
+ return -1;
183
+ }
184
+ fiobj_str_write(packet, data, length);
185
+ fiobj_send_free((handle2pr(h)->p.uuid), packet);
186
+ http1_after_finish(h);
187
+ return 0;
188
+ }
189
+ /** Should send existing headers and file */
190
+ static int http1_sendfile(http_s *h, int fd, uintptr_t length,
191
+ uintptr_t offset) {
192
+ FIOBJ packet = headers2str(h, 0);
193
+ if (!packet) {
194
+ close(fd);
195
+ http1_after_finish(h);
196
+ return -1;
197
+ }
198
+ if (length < HTTP_MAX_HEADER_LENGTH) {
199
+ /* optimize away small files */
200
+ fio_str_info_s s = fiobj_obj2cstr(packet);
201
+ fiobj_str_capa_assert(packet, s.len + length);
202
+ s = fiobj_obj2cstr(packet);
203
+ intptr_t i = pread(fd, s.data + s.len, length, offset);
204
+ if (i < 0) {
205
+ close(fd);
206
+ fiobj_send_free((handle2pr(h)->p.uuid), packet);
207
+ fio_close((handle2pr(h)->p.uuid));
208
+ return -1;
209
+ }
210
+ close(fd);
211
+ fiobj_str_resize(packet, s.len + i);
212
+ fiobj_send_free((handle2pr(h)->p.uuid), packet);
213
+ http1_after_finish(h);
214
+ return 0;
215
+ }
216
+ fiobj_send_free((handle2pr(h)->p.uuid), packet);
217
+ fio_sendfile((handle2pr(h)->p.uuid), fd, offset, length);
218
+ http1_after_finish(h);
219
+ return 0;
220
+ }
221
+
222
+ /** Should send existing headers or complete streaming */
223
+ static void htt1p_finish(http_s *h) {
224
+ FIOBJ packet = headers2str(h, 0);
225
+ if (packet)
226
+ fiobj_send_free((handle2pr(h)->p.uuid), packet);
227
+ else {
228
+ // fprintf(stderr, "WARNING: invalid call to `htt1p_finish`\n");
229
+ }
230
+ http1_after_finish(h);
231
+ }
232
+ /** Push for data - unsupported. */
233
+ static int http1_push_data(http_s *h, void *data, uintptr_t length,
234
+ FIOBJ mime_type) {
235
+ return -1;
236
+ (void)h;
237
+ (void)data;
238
+ (void)length;
239
+ (void)mime_type;
240
+ }
241
+ /** Push for files - unsupported. */
242
+ static int http1_push_file(http_s *h, FIOBJ filename, FIOBJ mime_type) {
243
+ return -1;
244
+ (void)h;
245
+ (void)filename;
246
+ (void)mime_type;
247
+ }
248
+
249
+ /**
250
+ * Called befor a pause task,
251
+ */
252
+ static void http1_on_pause(http_s *h, http_fio_protocol_s *pr) {
253
+ ((http1pr_s *)pr)->stop = 1;
254
+ fio_suspend(pr->uuid);
255
+ (void)h;
256
+ }
257
+
258
+ /**
259
+ * called after the resume task had completed.
260
+ */
261
+ static void http1_on_resume(http_s *h, http_fio_protocol_s *pr) {
262
+ if (!((http1pr_s *)pr)->stop) {
263
+ fio_force_event(pr->uuid, FIO_EVENT_ON_DATA);
264
+ }
265
+ (void)h;
266
+ }
267
+
268
+ static intptr_t http1_hijack(http_s *h, fio_str_info_s *leftover) {
269
+ if (leftover) {
270
+ intptr_t len =
271
+ handle2pr(h)->buf_len -
272
+ (intptr_t)(handle2pr(h)->parser.state.next - handle2pr(h)->buf);
273
+ if (len) {
274
+ *leftover = (fio_str_info_s){
275
+ .len = len, .data = (char *)handle2pr(h)->parser.state.next};
276
+ } else {
277
+ *leftover = (fio_str_info_s){.len = 0, .data = NULL};
278
+ }
279
+ }
280
+
281
+ handle2pr(h)->stop = 3;
282
+ intptr_t uuid = handle2pr(h)->p.uuid;
283
+ fio_attach(uuid, NULL);
284
+ return uuid;
285
+ }
286
+
287
+ /* *****************************************************************************
288
+ Websockets Upgrading
289
+ ***************************************************************************** */
290
+
291
+ static void http1_websocket_client_on_upgrade(http_s *h, char *proto,
292
+ size_t len) {
293
+ http1pr_s *p = handle2pr(h);
294
+ websocket_settings_s *args = h->udata;
295
+ const intptr_t uuid = handle2pr(h)->p.uuid;
296
+ http_settings_s *set = handle2pr(h)->p.settings;
297
+ set->udata = NULL;
298
+ http_finish(h);
299
+ p->stop = 1;
300
+ websocket_attach(uuid, set, args, p->parser.state.next,
301
+ p->buf_len - (intptr_t)(p->parser.state.next - p->buf));
302
+ fio_free(args);
303
+ (void)proto;
304
+ (void)len;
305
+ }
306
+ static void http1_websocket_client_on_failed(http_s *h) {
307
+ websocket_settings_s *s = h->udata;
308
+ if (s->on_close)
309
+ s->on_close(0, s->udata);
310
+ fio_free(h->udata);
311
+ h->udata = http_settings(h)->udata = NULL;
312
+ }
313
+ static void http1_websocket_client_on_hangup(http_settings_s *settings) {
314
+ websocket_settings_s *s = settings->udata;
315
+ if (s) {
316
+ if (s->on_close)
317
+ s->on_close(0, s->udata);
318
+ fio_free(settings->udata);
319
+ settings->udata = NULL;
320
+ }
321
+ }
322
+
323
+ static int http1_http2websocket_server(http_s *h, websocket_settings_s *args) {
324
+ // A static data used for all websocket connections.
325
+ static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
326
+ static uintptr_t sec_version = 0;
327
+ static uintptr_t sec_key = 0;
328
+ if (!sec_version)
329
+ sec_version = fiobj_hash_string("sec-websocket-version", 21);
330
+ if (!sec_key)
331
+ sec_key = fiobj_hash_string("sec-websocket-key", 17);
332
+
333
+ FIOBJ tmp = fiobj_hash_get2(h->headers, sec_version);
334
+ if (!tmp)
335
+ goto bad_request;
336
+ fio_str_info_s stmp = fiobj_obj2cstr(tmp);
337
+ if (stmp.len != 2 || stmp.data[0] != '1' || stmp.data[1] != '3')
338
+ goto bad_request;
339
+
340
+ tmp = fiobj_hash_get2(h->headers, sec_key);
341
+ if (!tmp)
342
+ goto bad_request;
343
+ stmp = fiobj_obj2cstr(tmp);
344
+
345
+ fio_sha1_s sha1 = fio_sha1_init();
346
+ fio_sha1_write(&sha1, stmp.data, stmp.len);
347
+ fio_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1);
348
+ tmp = fiobj_str_buf(32);
349
+ stmp = fiobj_obj2cstr(tmp);
350
+ fiobj_str_resize(tmp,
351
+ fio_base64_encode(stmp.data, fio_sha1_result(&sha1), 20));
352
+ http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE));
353
+ http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET));
354
+ http_set_header(h, HTTP_HEADER_WS_SEC_KEY, tmp);
355
+ h->status = 101;
356
+ http1pr_s *pr = handle2pr(h);
357
+ const intptr_t uuid = handle2pr(h)->p.uuid;
358
+ http_settings_s *set = handle2pr(h)->p.settings;
359
+ http_finish(h);
360
+ pr->stop = 1;
361
+ websocket_attach(uuid, set, args, pr->parser.state.next,
362
+ pr->buf_len - (intptr_t)(pr->parser.state.next - pr->buf));
363
+ return 0;
364
+ bad_request:
365
+ http_send_error(h, 400);
366
+ if (args->on_close)
367
+ args->on_close(0, args->udata);
368
+ return -1;
369
+ }
370
+
371
+ static int http1_http2websocket_client(http_s *h, websocket_settings_s *args) {
372
+ http1pr_s *p = handle2pr(h);
373
+ /* We're done with the HTTP stage, so we call the `on_finish` */
374
+ if (p->p.settings->on_finish)
375
+ p->p.settings->on_finish(p->p.settings);
376
+ /* Copy the Websocket setting arguments to the HTTP settings `udata` */
377
+ p->p.settings->udata = fio_malloc(sizeof(*args));
378
+ FIO_ASSERT_ALLOC(p->p.settings->udata);
379
+ ((websocket_settings_s *)(p->p.settings->udata))[0] = *args;
380
+ /* Set callbacks */
381
+ p->p.settings->on_finish = http1_websocket_client_on_hangup; /* unknown */
382
+ p->p.settings->on_upgrade = http1_websocket_client_on_upgrade; /* sucess */
383
+ p->p.settings->on_response = http1_websocket_client_on_failed; /* failed */
384
+ p->p.settings->on_request = http1_websocket_client_on_failed; /* failed */
385
+ /* Set headers */
386
+ http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE));
387
+ http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET));
388
+ http_set_header(h, HTTP_HVALUE_WS_SEC_VERSION,
389
+ fiobj_dup(HTTP_HVALUE_WS_VERSION));
390
+
391
+ /* we don't set the Origin header since we're not a browser... should we? */
392
+ // http_set_header(
393
+ // h, HTTP_HEADER_ORIGIN,
394
+ // fiobj_dup(fiobj_hash_get2(h->private_data.out_headers,
395
+ // fiobj_obj2hash(HTTP_HEADER_HOST))));
396
+
397
+ /* create nonce */
398
+ uint64_t key[2]; /* 16 bytes */
399
+ key[0] = (uintptr_t)h ^ (uint64_t)fio_last_tick().tv_sec;
400
+ key[1] = (uintptr_t)args->udata ^ (uint64_t)fio_last_tick().tv_nsec;
401
+ FIOBJ encoded = fiobj_str_buf(26); /* we need 24 really. */
402
+ fio_str_info_s tmp = fiobj_obj2cstr(encoded);
403
+ tmp.len = fio_base64_encode(tmp.data, (char *)key, 16);
404
+ fiobj_str_resize(encoded, tmp.len);
405
+ http_set_header(h, HTTP_HEADER_WS_SEC_CLIENT_KEY, encoded);
406
+ http_finish(h);
407
+ return 0;
408
+ }
409
+
410
+ static int http1_http2websocket(http_s *h, websocket_settings_s *args) {
411
+ assert(h);
412
+ http1pr_s *p = handle2pr(h);
413
+
414
+ if (p->is_client == 0) {
415
+ return http1_http2websocket_server(h, args);
416
+ }
417
+ return http1_http2websocket_client(h, args);
418
+ }
419
+
420
+ /* *****************************************************************************
421
+ EventSource Support (SSE)
422
+ ***************************************************************************** */
423
+
424
+ #undef http_upgrade2sse
425
+
426
+ typedef struct {
427
+ fio_protocol_s p;
428
+ http_sse_internal_s *sse;
429
+ } http1_sse_fio_protocol_s;
430
+
431
+ static void http1_sse_on_ready(intptr_t uuid, fio_protocol_s *p_) {
432
+ http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_;
433
+ if (p->sse->sse.on_ready)
434
+ p->sse->sse.on_ready(&p->sse->sse);
435
+ (void)uuid;
436
+ }
437
+ static uint8_t http1_sse_on_shutdown(intptr_t uuid, fio_protocol_s *p_) {
438
+ http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_;
439
+ if (p->sse->sse.on_shutdown)
440
+ p->sse->sse.on_shutdown(&p->sse->sse);
441
+ return 0;
442
+ (void)uuid;
443
+ }
444
+ static void http1_sse_on_close(intptr_t uuid, fio_protocol_s *p_) {
445
+ http1_sse_fio_protocol_s *p = (http1_sse_fio_protocol_s *)p_;
446
+ http_sse_destroy(p->sse);
447
+ fio_free(p);
448
+ (void)uuid;
449
+ }
450
+ static void http1_sse_ping(intptr_t uuid, fio_protocol_s *p_) {
451
+ fio_write2(uuid, .data.buffer = ": ping\n\n", .length = 8,
452
+ .after.dealloc = FIO_DEALLOC_NOOP);
453
+ (void)p_;
454
+ }
455
+
456
+ /**
457
+ * Upgrades an HTTP connection to an EventSource (SSE) connection.
458
+ *
459
+ * Thie `http_s` handle will be invalid after this call.
460
+ *
461
+ * On HTTP/1.1 connections, this will preclude future requests using the same
462
+ * connection.
463
+ */
464
+ static int http1_upgrade2sse(http_s *h, http_sse_s *sse) {
465
+ const intptr_t uuid = handle2pr(h)->p.uuid;
466
+ /* send response */
467
+ h->status = 200;
468
+ http_set_header(h, HTTP_HEADER_CONTENT_TYPE, fiobj_dup(HTTP_HVALUE_SSE_MIME));
469
+ http_set_header(h, HTTP_HEADER_CACHE_CONTROL,
470
+ fiobj_dup(HTTP_HVALUE_NO_CACHE));
471
+ http_set_header(h, HTTP_HEADER_CONTENT_ENCODING,
472
+ fiobj_str_new("identity", 8));
473
+ handle2pr(h)->stop = 1;
474
+ htt1p_finish(h); /* avoid the enforced content length in http_finish */
475
+
476
+ /* switch protocol to SSE */
477
+ http1_sse_fio_protocol_s *sse_pr = fio_malloc(sizeof(*sse_pr));
478
+ if (!sse_pr)
479
+ goto failed;
480
+ *sse_pr = (http1_sse_fio_protocol_s){
481
+ .p =
482
+ {
483
+ .on_ready = http1_sse_on_ready,
484
+ .on_shutdown = http1_sse_on_shutdown,
485
+ .on_close = http1_sse_on_close,
486
+ .ping = http1_sse_ping,
487
+ },
488
+ .sse = fio_malloc(sizeof(*(sse_pr->sse))),
489
+ };
490
+
491
+ if (!sse_pr->sse)
492
+ goto failed;
493
+
494
+ http_sse_init(sse_pr->sse, uuid, &HTTP1_VTABLE, sse);
495
+ fio_timeout_set(uuid, handle2pr(h)->p.settings->ws_timeout);
496
+ if (sse->on_open)
497
+ sse->on_open(&sse_pr->sse->sse);
498
+ fio_attach(uuid, &sse_pr->p);
499
+ return 0;
500
+
501
+ failed:
502
+ fio_close(handle2pr(h)->p.uuid);
503
+ if (sse->on_close)
504
+ sse->on_close(sse);
505
+ return -1;
506
+ (void)sse;
507
+ }
508
+
509
+ #undef http_sse_write
510
+ /**
511
+ * Writes data to an EventSource (SSE) connection.
512
+ *
513
+ * See the {struct http_sse_write_args} for possible named arguments.
514
+ */
515
+ static int http1_sse_write(http_sse_s *sse, FIOBJ str) {
516
+ return fiobj_send_free(((http_sse_internal_s *)sse)->uuid, str);
517
+ }
518
+
519
+ /**
520
+ * Closes an EventSource (SSE) connection.
521
+ */
522
+ static int http1_sse_close(http_sse_s *sse) {
523
+ fio_close(((http_sse_internal_s *)sse)->uuid);
524
+ return 0;
525
+ }
526
+ /* *****************************************************************************
527
+ Virtual Table Decleration
528
+ ***************************************************************************** */
529
+
530
+ struct http_vtable_s HTTP1_VTABLE = {
531
+ .http_send_body = http1_send_body,
532
+ .http_sendfile = http1_sendfile,
533
+ .http_finish = htt1p_finish,
534
+ .http_push_data = http1_push_data,
535
+ .http_push_file = http1_push_file,
536
+ .http_on_pause = http1_on_pause,
537
+ .http_on_resume = http1_on_resume,
538
+ .http_hijack = http1_hijack,
539
+ .http2websocket = http1_http2websocket,
540
+ .http_upgrade2sse = http1_upgrade2sse,
541
+ .http_sse_write = http1_sse_write,
542
+ .http_sse_close = http1_sse_close,
543
+ };
544
+
545
+ void *http1_vtable(void) { return (void *)&HTTP1_VTABLE; }
546
+
547
+ /* *****************************************************************************
548
+ Parser Callbacks
549
+ ***************************************************************************** */
550
+
551
+ /** called when a request was received. */
552
+ static int http1_on_request(http1_parser_s *parser) {
553
+ http1pr_s *p = parser2http(parser);
554
+ http_on_request_handler______internal(&http1_pr2handle(p), p->p.settings);
555
+ if (p->request.method && !p->stop)
556
+ http_finish(&p->request);
557
+ h1_reset(p);
558
+ return fio_is_closed(p->p.uuid);
559
+ }
560
+ /** called when a response was received. */
561
+ static int http1_on_response(http1_parser_s *parser) {
562
+ http1pr_s *p = parser2http(parser);
563
+ http_on_response_handler______internal(&http1_pr2handle(p), p->p.settings);
564
+ if (p->request.status_str && !p->stop)
565
+ http_finish(&p->request);
566
+ h1_reset(p);
567
+ return fio_is_closed(p->p.uuid);
568
+ }
569
+ /** called when a request method is parsed. */
570
+ static int http1_on_method(http1_parser_s *parser, char *method,
571
+ size_t method_len) {
572
+ http1_pr2handle(parser2http(parser)).method =
573
+ fiobj_str_new(method, method_len);
574
+ parser2http(parser)->header_size += method_len;
575
+ return 0;
576
+ }
577
+
578
+ /** called when a response status is parsed. the status_str is the string
579
+ * without the prefixed numerical status indicator.*/
580
+ static int http1_on_status(http1_parser_s *parser, size_t status,
581
+ char *status_str, size_t len) {
582
+ http1_pr2handle(parser2http(parser)).status_str =
583
+ fiobj_str_new(status_str, len);
584
+ http1_pr2handle(parser2http(parser)).status = status;
585
+ parser2http(parser)->header_size += len;
586
+ return 0;
587
+ }
588
+
589
+ /** called when a request path (excluding query) is parsed. */
590
+ static int http1_on_path(http1_parser_s *parser, char *path, size_t len) {
591
+ http1_pr2handle(parser2http(parser)).path = fiobj_str_new(path, len);
592
+ parser2http(parser)->header_size += len;
593
+ return 0;
594
+ }
595
+
596
+ /** called when a request path (excluding query) is parsed. */
597
+ static int http1_on_query(http1_parser_s *parser, char *query, size_t len) {
598
+ http1_pr2handle(parser2http(parser)).query = fiobj_str_new(query, len);
599
+ parser2http(parser)->header_size += len;
600
+ return 0;
601
+ }
602
+ /** called when a the HTTP/1.x version is parsed. */
603
+ static int http1_on_version(http1_parser_s *parser, char *version, size_t len) {
604
+ http1_pr2handle(parser2http(parser)).version = fiobj_str_new(version, len);
605
+ parser2http(parser)->header_size += len;
606
+ /* start counting - occurs on the first line of both requests and responses */
607
+ #if FIO_HTTP_EXACT_LOGGING
608
+ clock_gettime(CLOCK_REALTIME,
609
+ &http1_pr2handle(parser2http(parser)).received_at);
610
+ #else
611
+ http1_pr2handle(parser2http(parser)).received_at = fio_last_tick();
612
+ #endif
613
+ return 0;
614
+ }
615
+ /** called when a header is parsed. */
616
+ static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len,
617
+ char *data, size_t data_len) {
618
+ FIOBJ sym;
619
+ FIOBJ obj;
620
+ if (!http1_pr2handle(parser2http(parser)).headers) {
621
+ FIO_LOG_ERROR("(http1 parse ordering error) missing HashMap for header "
622
+ "%s: %s",
623
+ name, data);
624
+ http_send_error2(500, parser2http(parser)->p.uuid,
625
+ parser2http(parser)->p.settings);
626
+ return -1;
627
+ }
628
+ parser2http(parser)->header_size += name_len + data_len;
629
+ if (parser2http(parser)->header_size >=
630
+ parser2http(parser)->max_header_size ||
631
+ fiobj_hash_count(http1_pr2handle(parser2http(parser)).headers) >
632
+ HTTP_MAX_HEADER_COUNT) {
633
+ if (parser2http(parser)->p.settings->log) {
634
+ FIO_LOG_WARNING("(HTTP) security alert - header flood detected.");
635
+ }
636
+ http_send_error(&http1_pr2handle(parser2http(parser)), 413);
637
+ return -1;
638
+ }
639
+ sym = fiobj_str_new(name, name_len);
640
+ obj = fiobj_str_new(data, data_len);
641
+ set_header_add(http1_pr2handle(parser2http(parser)).headers, sym, obj);
642
+ fiobj_free(sym);
643
+ return 0;
644
+ }
645
+ /** called when a body chunk is parsed. */
646
+ static int http1_on_body_chunk(http1_parser_s *parser, char *data,
647
+ size_t data_len) {
648
+ if (parser->state.content_length >
649
+ (ssize_t)parser2http(parser)->p.settings->max_body_size ||
650
+ parser->state.read >
651
+ (ssize_t)parser2http(parser)->p.settings->max_body_size) {
652
+ http_send_error(&http1_pr2handle(parser2http(parser)), 413);
653
+ return -1; /* test every time, in case of chunked data */
654
+ }
655
+ if (!parser->state.read) {
656
+ if (parser->state.content_length > 0 &&
657
+ parser->state.content_length <= HTTP_MAX_HEADER_LENGTH) {
658
+ http1_pr2handle(parser2http(parser)).body = fiobj_data_newstr();
659
+ } else {
660
+ http1_pr2handle(parser2http(parser)).body = fiobj_data_newtmpfile();
661
+ }
662
+ }
663
+ fiobj_data_write(http1_pr2handle(parser2http(parser)).body, data, data_len);
664
+ return 0;
665
+ }
666
+
667
+ /** called when a protocol error occurred. */
668
+ static int http1_on_error(http1_parser_s *parser) {
669
+ if (parser2http(parser)->close)
670
+ return -1;
671
+ FIO_LOG_DEBUG("HTTP parser error.");
672
+ fio_close(parser2http(parser)->p.uuid);
673
+ return -1;
674
+ }
675
+
676
+ /* *****************************************************************************
677
+ Connection Callbacks
678
+ ***************************************************************************** */
679
+
680
+ static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) {
681
+ if (fio_pending(uuid) > 4) {
682
+ goto throttle;
683
+ }
684
+ ssize_t i = 0;
685
+ size_t org_len = p->buf_len;
686
+ int pipeline_limit = 8;
687
+ if (!p->buf_len)
688
+ return;
689
+ do {
690
+ i = http1_parse(&p->parser, p->buf + (org_len - p->buf_len), p->buf_len);
691
+ p->buf_len -= i;
692
+ --pipeline_limit;
693
+ } while (i && p->buf_len && pipeline_limit && !p->stop);
694
+
695
+ if (p->buf_len && org_len != p->buf_len) {
696
+ memmove(p->buf, p->buf + (org_len - p->buf_len), p->buf_len);
697
+ }
698
+
699
+ if (p->buf_len == HTTP_MAX_HEADER_LENGTH) {
700
+ /* no room to read... parser not consuming data */
701
+ if (p->request.method)
702
+ http_send_error(&p->request, 413);
703
+ else {
704
+ p->request.method = fiobj_str_tmp();
705
+ http_send_error(&p->request, 413);
706
+ }
707
+ }
708
+
709
+ if (!pipeline_limit) {
710
+ fio_force_event(uuid, FIO_EVENT_ON_DATA);
711
+ }
712
+ return;
713
+
714
+ throttle:
715
+ /* throttle busy clients (slowloris) */
716
+ p->stop |= 4;
717
+ fio_suspend(uuid);
718
+ FIO_LOG_DEBUG("(HTTP/1,1) throttling client at %.*s",
719
+ (int)fio_peer_addr(uuid).len, fio_peer_addr(uuid).data);
720
+ }
721
+
722
+ /** called when a data is available, but will not run concurrently */
723
+ static void http1_on_data(intptr_t uuid, fio_protocol_s *protocol) {
724
+ http1pr_s *p = (http1pr_s *)protocol;
725
+ if (p->stop) {
726
+ fio_suspend(uuid);
727
+ return;
728
+ }
729
+ ssize_t i = 0;
730
+ if (HTTP_MAX_HEADER_LENGTH - p->buf_len)
731
+ i = fio_read(uuid, p->buf + p->buf_len,
732
+ HTTP_MAX_HEADER_LENGTH - p->buf_len);
733
+ if (i > 0) {
734
+ p->buf_len += i;
735
+ }
736
+ http1_consume_data(uuid, p);
737
+ }
738
+
739
+ /** called when the connection was closed, but will not run concurrently */
740
+ static void http1_on_close(intptr_t uuid, fio_protocol_s *protocol) {
741
+ http1_destroy(protocol);
742
+ (void)uuid;
743
+ }
744
+
745
+ /** called when the connection was closed, but will not run concurrently */
746
+ static void http1_on_ready(intptr_t uuid, fio_protocol_s *protocol) {
747
+ /* resume slow clients from suspension */
748
+ http1pr_s *p = (http1pr_s *)protocol;
749
+ if (p->stop & 4) {
750
+ p->stop ^= 4; /* flip back the bit, so it's zero */
751
+ fio_force_event(uuid, FIO_EVENT_ON_DATA);
752
+ }
753
+ (void)protocol;
754
+ }
755
+
756
+ /** called when a data is available for the first time */
757
+ static void http1_on_data_first_time(intptr_t uuid, fio_protocol_s *protocol) {
758
+ http1pr_s *p = (http1pr_s *)protocol;
759
+ ssize_t i;
760
+
761
+ i = fio_read(uuid, p->buf + p->buf_len, HTTP_MAX_HEADER_LENGTH - p->buf_len);
762
+
763
+ if (i <= 0)
764
+ return;
765
+ p->buf_len += i;
766
+
767
+ /* ensure future reads skip this first time HTTP/2.0 test */
768
+ p->p.protocol.on_data = http1_on_data;
769
+ if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) {
770
+ FIO_LOG_WARNING("client claimed unsupported HTTP/2 prior knowledge.");
771
+ fio_close(uuid);
772
+ return;
773
+ }
774
+
775
+ /* Finish handling the same way as the normal `on_data` */
776
+ http1_consume_data(uuid, p);
777
+ }
778
+
779
+ /* *****************************************************************************
780
+ Public API
781
+ ***************************************************************************** */
782
+
783
+ /** Creates an HTTP1 protocol object and handles any unread data in the buffer
784
+ * (if any). */
785
+ fio_protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings,
786
+ void *unread_data, size_t unread_length) {
787
+ if (unread_data && unread_length > HTTP_MAX_HEADER_LENGTH)
788
+ return NULL;
789
+ http1pr_s *p = fio_malloc(sizeof(*p) + HTTP_MAX_HEADER_LENGTH);
790
+ // FIO_LOG_DEBUG("Allocated HTTP/1.1 protocol %p(%d)=>%p", (void *)uuid,
791
+ // (int)fio_uuid2fd(uuid), (void *)p);
792
+ FIO_ASSERT_ALLOC(p);
793
+ *p = (http1pr_s){
794
+ .p.protocol =
795
+ {
796
+ .on_data = http1_on_data_first_time,
797
+ .on_close = http1_on_close,
798
+ .on_ready = http1_on_ready,
799
+ },
800
+ .p.uuid = uuid,
801
+ .p.settings = settings,
802
+ .max_header_size = settings->max_header_size,
803
+ .is_client = settings->is_client,
804
+ };
805
+ http_s_new(&p->request, &p->p, &HTTP1_VTABLE);
806
+ if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) {
807
+ memcpy(p->buf, unread_data, unread_length);
808
+ p->buf_len = unread_length;
809
+ }
810
+ fio_attach(uuid, &p->p.protocol);
811
+ if (unread_data && unread_length <= HTTP_MAX_HEADER_LENGTH) {
812
+ fio_force_event(uuid, FIO_EVENT_ON_DATA);
813
+ }
814
+ return &p->p.protocol;
815
+ }
816
+
817
+ /** Manually destroys the HTTP1 protocol object. */
818
+ void http1_destroy(fio_protocol_s *pr) {
819
+ http1pr_s *p = (http1pr_s *)pr;
820
+ http1_pr2handle(p).status = 0;
821
+ http_s_destroy(&http1_pr2handle(p), 0);
822
+ // FIO_LOG_DEBUG("Deallocating HTTP/1.1 protocol %p(%d)=>%p", (void
823
+ // *)p->p.uuid, (int)fio_uuid2fd(p->p.uuid), (void *)p);
824
+ fio_free(p); // occasional Windows crash bug
825
+ }
826
+
827
+ /* *****************************************************************************
828
+ Protocol Data
829
+ ***************************************************************************** */
830
+
831
+ // clang-format off
832
+ #define HTTP_SET_STATUS_STR(status, str) [((status)-100)] = { .data = (char*)("HTTP/1.1 " #status " " str "\r\n"), .len = (sizeof("HTTP/1.1 " #status " " str "\r\n") - 1) }
833
+ // #undef HTTP_SET_STATUS_STR
834
+ // clang-format on
835
+
836
+ static fio_str_info_s http1pr_status2str(uintptr_t status) {
837
+ static fio_str_info_s status2str[] = {
838
+ HTTP_SET_STATUS_STR(100, "Continue"),
839
+ HTTP_SET_STATUS_STR(101, "Switching Protocols"),
840
+ HTTP_SET_STATUS_STR(102, "Processing"),
841
+ HTTP_SET_STATUS_STR(103, "Early Hints"),
842
+ HTTP_SET_STATUS_STR(200, "OK"),
843
+ HTTP_SET_STATUS_STR(201, "Created"),
844
+ HTTP_SET_STATUS_STR(202, "Accepted"),
845
+ HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"),
846
+ HTTP_SET_STATUS_STR(204, "No Content"),
847
+ HTTP_SET_STATUS_STR(205, "Reset Content"),
848
+ HTTP_SET_STATUS_STR(206, "Partial Content"),
849
+ HTTP_SET_STATUS_STR(207, "Multi-Status"),
850
+ HTTP_SET_STATUS_STR(208, "Already Reported"),
851
+ HTTP_SET_STATUS_STR(226, "IM Used"),
852
+ HTTP_SET_STATUS_STR(300, "Multiple Choices"),
853
+ HTTP_SET_STATUS_STR(301, "Moved Permanently"),
854
+ HTTP_SET_STATUS_STR(302, "Found"),
855
+ HTTP_SET_STATUS_STR(303, "See Other"),
856
+ HTTP_SET_STATUS_STR(304, "Not Modified"),
857
+ HTTP_SET_STATUS_STR(305, "Use Proxy"),
858
+ HTTP_SET_STATUS_STR(306, "(Unused), "),
859
+ HTTP_SET_STATUS_STR(307, "Temporary Redirect"),
860
+ HTTP_SET_STATUS_STR(308, "Permanent Redirect"),
861
+ HTTP_SET_STATUS_STR(400, "Bad Request"),
862
+ HTTP_SET_STATUS_STR(403, "Forbidden"),
863
+ HTTP_SET_STATUS_STR(404, "Not Found"),
864
+ HTTP_SET_STATUS_STR(401, "Unauthorized"),
865
+ HTTP_SET_STATUS_STR(402, "Payment Required"),
866
+ HTTP_SET_STATUS_STR(405, "Method Not Allowed"),
867
+ HTTP_SET_STATUS_STR(406, "Not Acceptable"),
868
+ HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"),
869
+ HTTP_SET_STATUS_STR(408, "Request Timeout"),
870
+ HTTP_SET_STATUS_STR(409, "Conflict"),
871
+ HTTP_SET_STATUS_STR(410, "Gone"),
872
+ HTTP_SET_STATUS_STR(411, "Length Required"),
873
+ HTTP_SET_STATUS_STR(412, "Precondition Failed"),
874
+ HTTP_SET_STATUS_STR(413, "Payload Too Large"),
875
+ HTTP_SET_STATUS_STR(414, "URI Too Long"),
876
+ HTTP_SET_STATUS_STR(415, "Unsupported Media Type"),
877
+ HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"),
878
+ HTTP_SET_STATUS_STR(417, "Expectation Failed"),
879
+ HTTP_SET_STATUS_STR(421, "Misdirected Request"),
880
+ HTTP_SET_STATUS_STR(422, "Unprocessable Entity"),
881
+ HTTP_SET_STATUS_STR(423, "Locked"),
882
+ HTTP_SET_STATUS_STR(424, "Failed Dependency"),
883
+ HTTP_SET_STATUS_STR(425, "Unassigned"),
884
+ HTTP_SET_STATUS_STR(426, "Upgrade Required"),
885
+ HTTP_SET_STATUS_STR(427, "Unassigned"),
886
+ HTTP_SET_STATUS_STR(428, "Precondition Required"),
887
+ HTTP_SET_STATUS_STR(429, "Too Many Requests"),
888
+ HTTP_SET_STATUS_STR(430, "Unassigned"),
889
+ HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"),
890
+ HTTP_SET_STATUS_STR(500, "Internal Server Error"),
891
+ HTTP_SET_STATUS_STR(501, "Not Implemented"),
892
+ HTTP_SET_STATUS_STR(502, "Bad Gateway"),
893
+ HTTP_SET_STATUS_STR(503, "Service Unavailable"),
894
+ HTTP_SET_STATUS_STR(504, "Gateway Timeout"),
895
+ HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"),
896
+ HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"),
897
+ HTTP_SET_STATUS_STR(507, "Insufficient Storage"),
898
+ HTTP_SET_STATUS_STR(508, "Loop Detected"),
899
+ HTTP_SET_STATUS_STR(509, "Unassigned"),
900
+ HTTP_SET_STATUS_STR(510, "Not Extended"),
901
+ HTTP_SET_STATUS_STR(511, "Network Authentication Required"),
902
+ };
903
+ fio_str_info_s ret = (fio_str_info_s){.len = 0, .data = NULL};
904
+ if (status >= 100 &&
905
+ (status - 100) < sizeof(status2str) / sizeof(status2str[0]))
906
+ ret = status2str[status - 100];
907
+ if (!ret.data) {
908
+ ret = status2str[400];
909
+ }
910
+ return ret;
911
+ }
912
+ #undef HTTP_SET_STATUS_STR