rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -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\": \"" \\u0022 %22 0x22 034 "\",\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
|