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,622 @@
1
+ /*
2
+ Copyright: Boaz Segev, 2017-2019
3
+ License: MIT
4
+ */
5
+ #include <fiobj_json.h>
6
+ #define FIO_ARY_NAME fio_json_stack
7
+ #define FIO_ARY_TYPE FIOBJ
8
+ #include <fio.h>
9
+
10
+ #include <fio_json_parser.h>
11
+
12
+ #include <assert.h>
13
+ #include <ctype.h>
14
+ #include <math.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ /* *****************************************************************************
19
+ JSON API
20
+ ***************************************************************************** */
21
+
22
+ /**
23
+ * Parses JSON, setting `pobj` to point to the new Object.
24
+ *
25
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
26
+ * consumed.
27
+ */
28
+ size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len);
29
+ /* Formats an object into a JSON string. Remember to `fiobj_free`. */
30
+ FIOBJ fiobj_obj2json(FIOBJ, uint8_t);
31
+
32
+ /* *****************************************************************************
33
+ FIOBJ Parser
34
+ ***************************************************************************** */
35
+
36
+ typedef struct {
37
+ json_parser_s p;
38
+ FIOBJ key;
39
+ FIOBJ top;
40
+ FIOBJ target;
41
+ fio_json_stack_s stack;
42
+ uint8_t is_hash;
43
+ } fiobj_json_parser_s;
44
+
45
+ /* *****************************************************************************
46
+ FIOBJ Callacks
47
+ ***************************************************************************** */
48
+
49
+ static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) {
50
+ if (p->top) {
51
+ if (p->is_hash) {
52
+ if (p->key) {
53
+ fiobj_hash_set(p->top, p->key, o);
54
+ fiobj_free(p->key);
55
+ p->key = FIOBJ_INVALID;
56
+ } else {
57
+ p->key = o;
58
+ }
59
+ } else {
60
+ fiobj_ary_push(p->top, o);
61
+ }
62
+ } else {
63
+ p->top = o;
64
+ }
65
+ }
66
+
67
+ /** a NULL object was detected */
68
+ static void fio_json_on_null(json_parser_s *p) {
69
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_null());
70
+ }
71
+ /** a TRUE object was detected */
72
+ static void fio_json_on_true(json_parser_s *p) {
73
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true());
74
+ }
75
+ /** a FALSE object was detected */
76
+ static void fio_json_on_false(json_parser_s *p) {
77
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false());
78
+ }
79
+ /** a Numberl was detected (long long). */
80
+ static void fio_json_on_number(json_parser_s *p, long long i) {
81
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_num_new(i));
82
+ }
83
+ /** a Float was detected (double). */
84
+ static void fio_json_on_float(json_parser_s *p, double f) {
85
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_float_new(f));
86
+ }
87
+ /** a String was detected (int / float). update `pos` to point at ending */
88
+ static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
89
+ FIOBJ str = fiobj_str_buf(length);
90
+ fiobj_str_resize(
91
+ str, fio_json_unescape_str(fiobj_obj2cstr(str).data, start, length));
92
+ fiobj_json_add2parser((fiobj_json_parser_s *)p, str);
93
+ }
94
+ /** a dictionary object was detected */
95
+ static int fio_json_on_start_object(json_parser_s *p) {
96
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
97
+ if (pr->target) {
98
+ /* push NULL, don't free the objects */
99
+ fio_json_stack_push(&pr->stack, pr->top);
100
+ pr->top = pr->target;
101
+ pr->target = FIOBJ_INVALID;
102
+ } else {
103
+ FIOBJ hash = fiobj_hash_new();
104
+ fiobj_json_add2parser(pr, hash);
105
+ fio_json_stack_push(&pr->stack, pr->top);
106
+ pr->top = hash;
107
+ }
108
+ pr->is_hash = 1;
109
+ return 0;
110
+ }
111
+ /** a dictionary object closure detected */
112
+ static void fio_json_on_end_object(json_parser_s *p) {
113
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
114
+ if (pr->key) {
115
+ FIO_LOG_WARNING("(JSON parsing) malformed JSON, "
116
+ "ignoring dangling Hash key.");
117
+ fiobj_free(pr->key);
118
+ pr->key = FIOBJ_INVALID;
119
+ }
120
+ pr->top = FIOBJ_INVALID;
121
+ fio_json_stack_pop(&pr->stack, &pr->top);
122
+ pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
123
+ }
124
+ /** an array object was detected */
125
+ static int fio_json_on_start_array(json_parser_s *p) {
126
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
127
+ if (pr->target)
128
+ return -1;
129
+ FIOBJ ary = fiobj_ary_new();
130
+ fiobj_json_add2parser(pr, ary);
131
+ fio_json_stack_push(&pr->stack, pr->top);
132
+ pr->top = ary;
133
+ pr->is_hash = 0;
134
+ return 0;
135
+ }
136
+ /** an array closure was detected */
137
+ static void fio_json_on_end_array(json_parser_s *p) {
138
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
139
+ pr->top = FIOBJ_INVALID;
140
+ fio_json_stack_pop(&pr->stack, &pr->top);
141
+ pr->is_hash = FIOBJ_TYPE_IS(pr->top, FIOBJ_T_HASH);
142
+ }
143
+ /** the JSON parsing is complete */
144
+ static void fio_json_on_json(json_parser_s *p) {
145
+ // fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
146
+ // FIO_ARY_FOR(&pr->stack, pos) { fiobj_free((FIOBJ)pos.obj); }
147
+ // fio_json_stack_free(&pr->stack);
148
+ (void)p; /* nothing special... right? */
149
+ }
150
+ /** the JSON parsing is complete */
151
+ static void fio_json_on_error(json_parser_s *p) {
152
+ fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p;
153
+ #if DEBUG
154
+ FIO_LOG_DEBUG("JSON on error called.");
155
+ #endif
156
+ fiobj_free((FIOBJ)fio_json_stack_get(&pr->stack, 0));
157
+ fiobj_free(pr->key);
158
+ fio_json_stack_free(&pr->stack);
159
+ *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID};
160
+ }
161
+
162
+ /* *****************************************************************************
163
+ JSON formatting
164
+ ***************************************************************************** */
165
+
166
+ /** Writes a JSON friendly version of the src String */
167
+ static void write_safe_str(FIOBJ dest, const FIOBJ str) {
168
+ fio_str_info_s s = fiobj_obj2cstr(str);
169
+ fio_str_info_s t = fiobj_obj2cstr(dest);
170
+ t.data[t.len] = '"';
171
+ t.len++;
172
+ fiobj_str_resize(dest, t.len);
173
+ t = fiobj_obj2cstr(dest);
174
+ const uint8_t *restrict src = (const uint8_t *)s.data;
175
+ size_t len = s.len;
176
+ uint64_t end = t.len;
177
+ /* make sure we have some room */
178
+ size_t added = 0;
179
+ size_t capa = fiobj_str_capa(dest);
180
+ if (capa <= end + s.len + 64) {
181
+ if (0) {
182
+ capa = (((capa >> 12) + 1) << 12) - 1;
183
+ capa = fiobj_str_capa_assert(dest, capa);
184
+ } else {
185
+ capa = fiobj_str_capa_assert(dest, (end + s.len + 64));
186
+ }
187
+ fio_str_info_s tmp = fiobj_obj2cstr(dest);
188
+ t = tmp;
189
+ }
190
+ while (len) {
191
+ char *restrict writer = (char *)t.data;
192
+ while (len && src[0] > 32 && src[0] != '"' && src[0] != '\\') {
193
+ len--;
194
+ writer[end++] = *(src++);
195
+ }
196
+ if (!len)
197
+ break;
198
+ switch (src[0]) {
199
+ case '\b':
200
+ writer[end++] = '\\';
201
+ writer[end++] = 'b';
202
+ added++;
203
+ break; /* from switch */
204
+ case '\f':
205
+ writer[end++] = '\\';
206
+ writer[end++] = 'f';
207
+ added++;
208
+ break; /* from switch */
209
+ case '\n':
210
+ writer[end++] = '\\';
211
+ writer[end++] = 'n';
212
+ added++;
213
+ break; /* from switch */
214
+ case '\r':
215
+ writer[end++] = '\\';
216
+ writer[end++] = 'r';
217
+ added++;
218
+ break; /* from switch */
219
+ case '\t':
220
+ writer[end++] = '\\';
221
+ writer[end++] = 't';
222
+ added++;
223
+ break; /* from switch */
224
+ case '"':
225
+ case '\\':
226
+ case '/':
227
+ writer[end++] = '\\';
228
+ writer[end++] = src[0];
229
+ added++;
230
+ break; /* from switch */
231
+ default:
232
+ if (src[0] <= 31) {
233
+ /* MUST escape all control values less than 32 */
234
+ writer[end++] = '\\';
235
+ writer[end++] = 'u';
236
+ writer[end++] = '0';
237
+ writer[end++] = '0';
238
+ writer[end++] = hex_chars[src[0] >> 4];
239
+ writer[end++] = hex_chars[src[0] & 15];
240
+ added += 4;
241
+ } else
242
+ writer[end++] = src[0];
243
+ break; /* from switch */
244
+ }
245
+ src++;
246
+ len--;
247
+ if (added >= 48 && capa <= end + len + 64) {
248
+ writer[end] = 0;
249
+ fiobj_str_resize(dest, end);
250
+ fiobj_str_capa_assert(dest, (end + len + 64));
251
+ t = fiobj_obj2cstr(dest);
252
+ writer = (char *)t.data;
253
+ capa = t.capa;
254
+ added = 0;
255
+ }
256
+ }
257
+ t.data[end++] = '"';
258
+ fiobj_str_resize(dest, end);
259
+ }
260
+
261
+ typedef struct {
262
+ FIOBJ dest;
263
+ FIOBJ parent;
264
+ fio_json_stack_s *stack;
265
+ uintptr_t count;
266
+ uint8_t pretty;
267
+ } obj2json_data_s;
268
+
269
+ static int fiobj_obj2json_task(FIOBJ o, void *data_) {
270
+ obj2json_data_s *data = data_;
271
+ uint8_t add_seperator = 1;
272
+ if (fiobj_hash_key_in_loop()) {
273
+ write_safe_str(data->dest, fiobj_hash_key_in_loop());
274
+ fiobj_str_write(data->dest, ":", 1);
275
+ }
276
+ switch (FIOBJ_TYPE(o)) {
277
+ case FIOBJ_T_NUMBER:
278
+ case FIOBJ_T_NULL:
279
+ case FIOBJ_T_TRUE:
280
+ case FIOBJ_T_FALSE:
281
+ case FIOBJ_T_FLOAT:
282
+ fiobj_str_join(data->dest, o);
283
+ --data->count;
284
+ break;
285
+
286
+ case FIOBJ_T_DATA:
287
+ case FIOBJ_T_UNKNOWN:
288
+ case FIOBJ_T_STRING:
289
+ write_safe_str(data->dest, o);
290
+ --data->count;
291
+ break;
292
+
293
+ case FIOBJ_T_ARRAY:
294
+ --data->count;
295
+ fio_json_stack_push(data->stack, data->parent);
296
+ fio_json_stack_push(data->stack, (FIOBJ)data->count);
297
+ data->parent = o;
298
+ data->count = fiobj_ary_count(o);
299
+ fiobj_str_write(data->dest, "[", 1);
300
+ add_seperator = 0;
301
+ break;
302
+
303
+ case FIOBJ_T_HASH:
304
+ --data->count;
305
+ fio_json_stack_push(data->stack, data->parent);
306
+ fio_json_stack_push(data->stack, (FIOBJ)data->count);
307
+ data->parent = o;
308
+ data->count = fiobj_hash_count(o);
309
+ fiobj_str_write(data->dest, "{", 1);
310
+ add_seperator = 0;
311
+ break;
312
+ }
313
+ if (data->pretty) {
314
+ fiobj_str_capa_assert(data->dest,
315
+ fiobj_obj2cstr(data->dest).len +
316
+ (fio_json_stack_count(data->stack) * 5));
317
+ while (!data->count && data->parent) {
318
+ if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
319
+ fiobj_str_write(data->dest, "}", 1);
320
+ } else {
321
+ fiobj_str_write(data->dest, "]", 1);
322
+ }
323
+ add_seperator = 1;
324
+ data->count = 0;
325
+ fio_json_stack_pop(data->stack, &data->count);
326
+ data->parent = FIOBJ_INVALID;
327
+ fio_json_stack_pop(data->stack, &data->parent);
328
+ }
329
+
330
+ if (add_seperator && data->parent) {
331
+ fiobj_str_write(data->dest, ",\n", 2);
332
+ uintptr_t indent = fio_json_stack_count(data->stack) - 1;
333
+ fiobj_str_capa_assert(data->dest,
334
+ fiobj_obj2cstr(data->dest).len + (indent * 2));
335
+ fio_str_info_s buf = fiobj_obj2cstr(data->dest);
336
+ while (indent--) {
337
+ buf.data[buf.len++] = ' ';
338
+ buf.data[buf.len++] = ' ';
339
+ }
340
+ fiobj_str_resize(data->dest, buf.len);
341
+ }
342
+ } else {
343
+ fiobj_str_capa_assert(data->dest,
344
+ fiobj_obj2cstr(data->dest).len +
345
+ (fio_json_stack_count(data->stack) << 1));
346
+ while (!data->count && data->parent) {
347
+ if (FIOBJ_TYPE_IS(data->parent, FIOBJ_T_HASH)) {
348
+ fiobj_str_write(data->dest, "}", 1);
349
+ } else {
350
+ fiobj_str_write(data->dest, "]", 1);
351
+ }
352
+ add_seperator = 1;
353
+ data->count = 0;
354
+ data->parent = FIOBJ_INVALID;
355
+ fio_json_stack_pop(data->stack, &data->count);
356
+ fio_json_stack_pop(data->stack, &data->parent);
357
+ }
358
+
359
+ if (add_seperator && data->parent) {
360
+ fiobj_str_write(data->dest, ",", 1);
361
+ }
362
+ }
363
+
364
+ return 0;
365
+ }
366
+
367
+ /* *****************************************************************************
368
+ FIOBJ API
369
+ ***************************************************************************** */
370
+
371
+ /**
372
+ * Parses JSON, setting `pobj` to point to the new Object.
373
+ *
374
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
375
+ * consumed.
376
+ */
377
+ size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len) {
378
+ fiobj_json_parser_s p = {.top = FIOBJ_INVALID};
379
+ size_t consumed = fio_json_parse(&p.p, data, len);
380
+ if (!consumed || p.p.depth) {
381
+ fiobj_free(fio_json_stack_get(&p.stack, 0));
382
+ p.top = FIOBJ_INVALID;
383
+ }
384
+ fio_json_stack_free(&p.stack);
385
+ fiobj_free(p.key);
386
+ *pobj = p.top;
387
+ return consumed;
388
+ }
389
+
390
+ /**
391
+ * Updates a Hash using JSON data.
392
+ *
393
+ * Parsing errors and non-dictionar object JSON data are silently ignored,
394
+ * attempting to update the Hash as much as possible before any errors
395
+ * encountered.
396
+ *
397
+ * Conflicting Hash data is overwritten (prefering the new over the old).
398
+ *
399
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
400
+ * consumed.
401
+ */
402
+ size_t fiobj_hash_update_json(FIOBJ hash, const void *data, size_t len) {
403
+ if (!hash)
404
+ return 0;
405
+ fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash};
406
+ size_t consumed = fio_json_parse(&p.p, data, len);
407
+ fio_json_stack_free(&p.stack);
408
+ fiobj_free(p.key);
409
+ if (p.top != hash)
410
+ fiobj_free(p.top);
411
+ return consumed;
412
+ }
413
+
414
+ /**
415
+ * Formats an object into a JSON string, appending the JSON string to an
416
+ * existing String. Remember to `fiobj_free`.
417
+ */
418
+ FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ o, uint8_t pretty) {
419
+ assert(dest && FIOBJ_TYPE_IS(dest, FIOBJ_T_STRING));
420
+ if (!o) {
421
+ fiobj_str_write(dest, "null", 4);
422
+ return 0;
423
+ }
424
+ fio_json_stack_s stack = FIO_ARY_INIT;
425
+ obj2json_data_s data = {
426
+ .dest = dest,
427
+ .stack = &stack,
428
+ .pretty = pretty,
429
+ .count = 1,
430
+ };
431
+ if (!o || !FIOBJ_IS_ALLOCATED(o) || !FIOBJECT2VTBL(o)->each) {
432
+ fiobj_obj2json_task(o, &data);
433
+ return dest;
434
+ }
435
+ fiobj_each2(o, fiobj_obj2json_task, &data);
436
+ fio_json_stack_free(&stack);
437
+ return dest;
438
+ }
439
+
440
+ /* Formats an object into a JSON string. Remember to `fiobj_free`. */
441
+ FIOBJ fiobj_obj2json(FIOBJ obj, uint8_t pretty) {
442
+ return fiobj_obj2json2(fiobj_str_buf(128), obj, pretty);
443
+ }
444
+
445
+ /* *****************************************************************************
446
+ Test
447
+ ***************************************************************************** */
448
+
449
+ #if DEBUG
450
+ void fiobj_test_json(void) {
451
+ fprintf(stderr, "=== Testing JSON parser (simple test)\n");
452
+ #define TEST_ASSERT(cond, ...) \
453
+ if (!(cond)) { \
454
+ fprintf(stderr, "* " __VA_ARGS__); \
455
+ fprintf(stderr, "\n !!! Testing failed !!!\n"); \
456
+ exit(-1); \
457
+ }
458
+ char json_str[] = "{\"array\":[1,2,3,\"boom\"],\"my\":{\"secret\":42},"
459
+ "\"true\":true,\"false\":false,\"null\":null,\"float\":-2."
460
+ "2,\"string\":\"I \\\"wrote\\\" this.\"}";
461
+ char json_str_update[] = "{\"array\":[1,2,3]}";
462
+ char json_str2[] =
463
+ "[\n \"JSON Test Pattern pass1\",\n {\"object with 1 "
464
+ "member\":[\"array with 1 element\"]},\n {},\n [],\n -42,\n "
465
+ "true,\n false,\n null,\n {\n \"integer\": 1234567890,\n "
466
+ " \"real\": -9876.543210,\n \"e\": 0.123456789e-12,\n "
467
+ " \"E\": 1.234567890E+34,\n \"\": 23456789012E66,\n "
468
+ "\"zero\": 0,\n \"one\": 1,\n \"space\": \" \",\n "
469
+ "\"quote\": \"\\\"\",\n \"backslash\": \"\\\\\",\n "
470
+ "\"controls\": \"\\b\\f\\n\\r\\t\",\n \"slash\": \"/ & \\/\",\n "
471
+ " \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n \"ALPHA\": "
472
+ "\"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n \"digit\": \"0123456789\",\n "
473
+ " \"0123456789\": \"digit\",\n \"special\": "
474
+ "\"`1~!@#$%^&*()_+-={':[,]}|;.</>?\",\n \"hex\": "
475
+ "\"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n \"true\": "
476
+ "true,\n \"false\": false,\n \"null\": null,\n "
477
+ "\"array\":[ ],\n \"object\":{ },\n \"address\": \"50 "
478
+ "St. James Street\",\n \"url\": \"http://www.JSON.org/\",\n "
479
+ " \"comment\": \"// /* <!-- --\",\n \"# -- --> */\": \" \",\n "
480
+ " \" s p a c e d \" :[1,2 , 3\n\n,\n\n4 , 5 , 6 "
481
+ " ,7 ],\"compact\":[1,2,3,4,5,6,7],\n \"jsontext\": "
482
+ "\"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n "
483
+ " \"quotes\": \"&#34; \\u0022 %22 0x22 034 &#x22;\",\n "
484
+ "\"\\/"
485
+ "\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#$"
486
+ "%^&*()_+-=[]{}|;:',./<>?\"\n: \"A key can be any string\"\n },\n "
487
+ "0.5 "
488
+ ",98.6\n,\n99.44\n,\n\n1066,\n1e1,\n0.1e1,\n1e-1,\n1e00,2e+00,2e-00\n,"
489
+ "\"rosebud\"]";
490
+
491
+ FIOBJ o = 0;
492
+ TEST_ASSERT(fiobj_json2obj(&o, "1", 2) == 1,
493
+ "JSON number parsing failed to run!\n");
494
+ TEST_ASSERT(o, "JSON (single) object missing!\n");
495
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_NUMBER),
496
+ "JSON (single) not a number!\n");
497
+ TEST_ASSERT(fiobj_obj2num(o) == 1, "JSON (single) not == 1!\n");
498
+ fiobj_free(o);
499
+
500
+ TEST_ASSERT(fiobj_json2obj(&o, "2.0", 5) == 3,
501
+ "JSON float parsing failed to run!\n");
502
+ TEST_ASSERT(o, "JSON (float) object missing!\n");
503
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_FLOAT), "JSON (float) not a float!\n");
504
+ TEST_ASSERT(fiobj_obj2float(o) == 2, "JSON (float) not == 2!\n");
505
+ fiobj_free(o);
506
+
507
+ TEST_ASSERT(fiobj_json2obj(&o, json_str, sizeof(json_str)) ==
508
+ (sizeof(json_str) - 1),
509
+ "JSON parsing failed to run!\n");
510
+ TEST_ASSERT(o, "JSON object missing!\n");
511
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH),
512
+ "JSON root not a dictionary (not a hash)!\n");
513
+ FIOBJ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
514
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
515
+ "JSON 'array' not an Array!\n");
516
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 0)) == 1,
517
+ "JSON 'array' index 0 error!\n");
518
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 1)) == 2,
519
+ "JSON 'array' index 1 error!\n");
520
+ TEST_ASSERT(fiobj_obj2num(fiobj_ary_index(tmp, 2)) == 3,
521
+ "JSON 'array' index 2 error!\n");
522
+ TEST_ASSERT(FIOBJ_TYPE_IS(fiobj_ary_index(tmp, 3), FIOBJ_T_STRING),
523
+ "JSON 'array' index 3 type error!\n");
524
+ TEST_ASSERT(!memcmp("boom", fiobj_obj2cstr(fiobj_ary_index(tmp, 3)).data, 4),
525
+ "JSON 'array' index 3 error!\n");
526
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("my", 2));
527
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_HASH),
528
+ "JSON 'my:secret' not a Hash!\n");
529
+ TEST_ASSERT(
530
+ FIOBJ_TYPE_IS(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6)),
531
+ FIOBJ_T_NUMBER),
532
+ "JSON 'my:secret' doesn't hold a number!\n");
533
+ TEST_ASSERT(
534
+ fiobj_obj2num(fiobj_hash_get2(tmp, fiobj_hash_string("secret", 6))) == 42,
535
+ "JSON 'my:secret' not 42!\n");
536
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("true", 4)) == fiobj_true(),
537
+ "JSON 'true' not true!\n");
538
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("false", 5)) ==
539
+ fiobj_false(),
540
+ "JSON 'false' not false!\n");
541
+ TEST_ASSERT(fiobj_hash_get2(o, fiobj_hash_string("null", 4)) == fiobj_null(),
542
+ "JSON 'null' not null!\n");
543
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
544
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT), "JSON 'float' not a float!\n");
545
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("string", 6));
546
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
547
+ "JSON 'string' not a string!\n");
548
+ TEST_ASSERT(!strcmp(fiobj_obj2cstr(tmp).data, "I \"wrote\" this."),
549
+ "JSON 'string' incorrect!\n");
550
+ fprintf(stderr, "* passed.\n");
551
+ fprintf(stderr, "=== Testing JSON formatting (simple test)\n");
552
+ tmp = fiobj_obj2json(o, 0);
553
+ fprintf(stderr, "* data (%p):\n%.*s\n", (void *)fiobj_obj2cstr(tmp).data,
554
+ (int)fiobj_obj2cstr(tmp).len, fiobj_obj2cstr(tmp).data);
555
+ if (!strcmp(fiobj_obj2cstr(tmp).data, json_str))
556
+ fprintf(stderr, "* Stringify == Original.\n");
557
+ TEST_ASSERT(
558
+ fiobj_hash_update_json(o, json_str_update, strlen(json_str_update)),
559
+ "JSON update failed to parse data.");
560
+ fiobj_free(tmp);
561
+
562
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("array", 5));
563
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY),
564
+ "JSON updated 'array' not an Array!\n");
565
+ TEST_ASSERT(fiobj_ary_count(tmp) == 3, "JSON updated 'array' not updated?");
566
+ tmp = fiobj_hash_get2(o, fiobj_hash_string("float", 5));
567
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_FLOAT),
568
+ "JSON updated (old) 'float' missing!\n");
569
+ fiobj_free(o);
570
+ fprintf(stderr, "* passed.\n");
571
+
572
+ fprintf(stderr, "=== Testing JSON parsing (UTF-8 and special cases)\n");
573
+ fiobj_json2obj(&o, "[\"\\uD834\\uDD1E\"]", 16);
574
+ TEST_ASSERT(o, "JSON G clef String failed to parse!\n");
575
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
576
+ "JSON G clef container has an incorrect type! (%s)\n",
577
+ fiobj_type_name(o));
578
+ tmp = o;
579
+ o = fiobj_ary_pop(o);
580
+ fiobj_free(tmp);
581
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
582
+ "JSON G clef String incorrect type! %p => %s\n", (void *)o,
583
+ fiobj_type_name(o));
584
+ TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
585
+ "JSON G clef String incorrect %s !\n", fiobj_obj2cstr(o).data);
586
+ fiobj_free(o);
587
+
588
+ fiobj_json2obj(&o, "\"\\uD834\\uDD1E\"", 14);
589
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
590
+ "JSON direct G clef String incorrect type! %p => %s\n", (void *)o,
591
+ fiobj_type_name(o));
592
+ TEST_ASSERT((!strcmp(fiobj_obj2cstr(o).data, "\xF0\x9D\x84\x9E")),
593
+ "JSON direct G clef String incorrect %s !\n",
594
+ fiobj_obj2cstr(o).data);
595
+ fiobj_free(o);
596
+ fiobj_json2obj(&o, "\"Hello\\u0000World\"", 19);
597
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_STRING),
598
+ "JSON NUL containing String incorrect type! %p => %s\n",
599
+ (void *)o, fiobj_type_name(o));
600
+ TEST_ASSERT(
601
+ (!memcmp(fiobj_obj2cstr(o).data, "Hello\0World", fiobj_obj2cstr(o).len)),
602
+ "JSON NUL containing String incorrect! (%u): %s . %s\n",
603
+ (int)fiobj_obj2cstr(o).len, fiobj_obj2cstr(o).data,
604
+ fiobj_obj2cstr(o).data + 3);
605
+ fiobj_free(o);
606
+ size_t consumed = fiobj_json2obj(&o, json_str2, sizeof(json_str2));
607
+ TEST_ASSERT(
608
+ consumed == (sizeof(json_str2) - 1),
609
+ "JSON messy string failed to parse (consumed %lu instead of %lu\n",
610
+ (unsigned long)consumed, (unsigned long)(sizeof(json_str2) - 1));
611
+ TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY),
612
+ "JSON messy string object error\n");
613
+ tmp = fiobj_obj2json(o, 1);
614
+ TEST_ASSERT(FIOBJ_TYPE_IS(tmp, FIOBJ_T_STRING),
615
+ "JSON messy string isn't a string\n");
616
+ fprintf(stderr, "Messy JSON:\n%s\n", fiobj_obj2cstr(tmp).data);
617
+ fiobj_free(o);
618
+ fiobj_free(tmp);
619
+ fprintf(stderr, "* passed.\n");
620
+ }
621
+
622
+ #endif
@@ -0,0 +1,68 @@
1
+ #ifndef H_FIOBJ_JSON_H
2
+ #define H_FIOBJ_JSON_H
3
+
4
+ /*
5
+ Copyright: Boaz Segev, 2017-2019
6
+ License: MIT
7
+ */
8
+
9
+ #include <fiobj_ary.h>
10
+ #include <fiobj_hash.h>
11
+ #include <fiobj_numbers.h>
12
+ #include <fiobj_str.h>
13
+ #include <fiobject.h>
14
+
15
+ #ifdef __cplusplus
16
+ extern "C" {
17
+ #endif
18
+
19
+ /* *****************************************************************************
20
+ JSON API
21
+ ***************************************************************************** */
22
+
23
+ /** Limit JSON nesting, 32 is the limit to accomodate a 32 bit type. */
24
+ #if !defined(JSON_MAX_DEPTH) || JSON_MAX_DEPTH > 32
25
+ #undef JSON_MAX_DEPTH
26
+ #define JSON_MAX_DEPTH 32
27
+ #endif
28
+
29
+ /**
30
+ * Parses JSON, setting `pobj` to point to the new Object.
31
+ *
32
+ * Returns the number of bytes consumed. On Error, 0 is returned and no data is
33
+ * consumed.
34
+ */
35
+ size_t fiobj_json2obj(FIOBJ *pobj, const void *data, size_t len);
36
+ /**
37
+ * Stringify an object into a JSON string. Remember to `fiobj_free`.
38
+ *
39
+ * Note that only the foloowing basic fiobj types are supported: Primitives
40
+ * (True / False / NULL), Numbers (Number / Float), Strings, Hashes and Arrays.
41
+ *
42
+ * Some objects (such as the POSIX specific IO type) are unsupported and may be
43
+ * formatted incorrectly.
44
+ */
45
+ FIOBJ fiobj_obj2json(FIOBJ, uint8_t pretty);
46
+
47
+ /**
48
+ * Formats an object into a JSON string, appending the JSON string to an
49
+ * existing String. Remember to `fiobj_free` as usual.
50
+ *
51
+ * Note that only the following basic fiobj types are supported: Primitives
52
+ * (True / False / NULL), Numbers (Number / Float), Strings, Hashes and
53
+ * Arrays.
54
+ *
55
+ * Some objects (such as the POSIX specific IO type) are unsupported and may be
56
+ * formatted incorrectly.
57
+ */
58
+ FIOBJ fiobj_obj2json2(FIOBJ dest, FIOBJ object, uint8_t pretty);
59
+
60
+ #if DEBUG
61
+ void fiobj_test_json(void);
62
+ #endif
63
+
64
+ #ifdef __cplusplus
65
+ } /* extern "C" */
66
+ #endif
67
+
68
+ #endif