1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #include <libwebsockets.h>
26 #include <private-lib-core.h>
27
28 #include <assert.h>
29
30 signed char
lws_struct_schema_only_lejp_cb(struct lejp_ctx * ctx,char reason)31 lws_struct_schema_only_lejp_cb(struct lejp_ctx *ctx, char reason)
32 {
33 lws_struct_args_t *a = (lws_struct_args_t *)ctx->user;
34 const lws_struct_map_t *map = a->map_st[ctx->pst_sp];
35 size_t n = a->map_entries_st[ctx->pst_sp];
36 lejp_callback cb = map->lejp_cb;
37
38 if (reason != LEJPCB_VAL_STR_END || ctx->path_match != 1)
39 return 0;
40
41 while (n--) {
42 if (strcmp(ctx->buf, map->colname)) {
43 map++;
44 continue;
45 }
46
47 a->dest = lwsac_use_zero(&a->ac, map->aux, a->ac_block_size);
48 if (!a->dest) {
49 lwsl_err("%s: OOT\n", __func__);
50
51 return 1;
52 }
53 a->dest_len = map->aux;
54 if (!ctx->pst_sp)
55 a->top_schema_index = (int)(map - a->map_st[ctx->pst_sp]);
56
57 if (!cb)
58 cb = lws_struct_default_lejp_cb;
59
60 lejp_parser_push(ctx, a->dest, &map->child_map[0].colname,
61 (uint8_t)map->child_map_size, cb);
62 a->map_st[ctx->pst_sp] = map->child_map;
63 a->map_entries_st[ctx->pst_sp] = map->child_map_size;
64
65 return 0;
66 }
67
68 lwsl_notice("%s: unknown schema %s\n", __func__, ctx->buf);
69
70 return 1;
71 }
72
73 static int
lws_struct_lejp_push(struct lejp_ctx * ctx,lws_struct_args_t * args,const lws_struct_map_t * map,uint8_t * ch)74 lws_struct_lejp_push(struct lejp_ctx *ctx, lws_struct_args_t *args,
75 const lws_struct_map_t *map, uint8_t *ch)
76 {
77 lejp_callback cb = map->lejp_cb;
78
79 if (!cb)
80 cb = lws_struct_default_lejp_cb;
81
82 lejp_parser_push(ctx, ch, (const char * const*)map->child_map,
83 (uint8_t)map->child_map_size, cb);
84
85 args->map_st[ctx->pst_sp] = map->child_map;
86 args->map_entries_st[ctx->pst_sp] = map->child_map_size;
87
88 return 0;
89 }
90
91 signed char
lws_struct_default_lejp_cb(struct lejp_ctx * ctx,char reason)92 lws_struct_default_lejp_cb(struct lejp_ctx *ctx, char reason)
93 {
94 lws_struct_args_t *args = (lws_struct_args_t *)ctx->user;
95 const lws_struct_map_t *map, *pmap = NULL;
96 uint8_t *ch;
97 size_t n;
98 char *u;
99
100 if (reason == LEJPCB_ARRAY_END) {
101 lejp_parser_pop(ctx);
102
103 return 0;
104 }
105
106 if (reason == LEJPCB_ARRAY_START) {
107 map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
108
109 if (map->type == LSMT_LIST)
110 lws_struct_lejp_push(ctx, args, map, NULL);
111
112 return 0;
113 }
114
115 if (ctx->pst_sp)
116 pmap = &args->map_st[ctx->pst_sp - 1]
117 [ctx->pst[ctx->pst_sp - 1].path_match - 1];
118 map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
119 n = args->map_entries_st[ctx->pst_sp];
120
121 if (reason == LEJPCB_OBJECT_START) {
122
123 if (map->type != LSMT_CHILD_PTR) {
124 ctx->pst[ctx->pst_sp].user = NULL;
125
126 return 0;
127 }
128 pmap = map;
129
130 lws_struct_lejp_push(ctx, args, map, NULL);
131 map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
132 n = args->map_entries_st[ctx->pst_sp];
133 }
134
135 if (reason == LEJPCB_OBJECT_END && pmap && pmap->type == LSMT_CHILD_PTR)
136 lejp_parser_pop(ctx);
137
138 if (map->type == LSMT_SCHEMA) {
139
140 while (n--) {
141 if (strcmp(map->colname, ctx->buf)) {
142 map++;
143 continue;
144 }
145
146 /* instantiate the correct toplevel object */
147
148 ch = lwsac_use_zero(&args->ac, map->aux,
149 args->ac_block_size);
150 if (!ch) {
151 lwsl_err("OOM\n");
152
153 return 1;
154 }
155
156 lws_struct_lejp_push(ctx, args, map, ch);
157
158 return 0;
159 }
160 lwsl_notice("%s: unknown schema\n", __func__);
161
162 goto cleanup;
163 }
164
165 if (!ctx->pst[ctx->pst_sp].user) {
166 struct lws_dll2_owner *owner;
167 struct lws_dll2 *list;
168
169 /* create list item object if none already */
170
171 if (!ctx->path_match || !pmap)
172 return 0;
173
174 map = &args->map_st[ctx->pst_sp - 1][ctx->path_match - 1];
175 n = args->map_entries_st[ctx->pst_sp - 1];
176
177 if (pmap->type != LSMT_LIST && pmap->type != LSMT_CHILD_PTR)
178 return 1;
179
180 /* we need to create a child or array item object */
181
182 owner = (struct lws_dll2_owner *)
183 (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs);
184
185 assert(pmap->aux);
186
187 /* instantiate one of the child objects */
188
189 ctx->pst[ctx->pst_sp].user = lwsac_use_zero(&args->ac,
190 pmap->aux, args->ac_block_size);
191 if (!ctx->pst[ctx->pst_sp].user) {
192 lwsl_err("OOM\n");
193
194 return 1;
195 }
196 lwsl_notice("%s: created child object size %d\n", __func__,
197 (int)pmap->aux);
198
199 if (pmap->type == LSMT_LIST) {
200 list = (struct lws_dll2 *)((char *)ctx->pst[ctx->pst_sp].user +
201 map->ofs_clist);
202
203 lws_dll2_add_tail(list, owner);
204 }
205 }
206
207 if (!ctx->path_match)
208 return 0;
209
210 if (reason == LEJPCB_VAL_STR_CHUNK) {
211 lejp_collation_t *coll;
212
213 /* don't cache stuff we are going to ignore */
214
215 if (map->type == LSMT_STRING_CHAR_ARRAY &&
216 args->chunks_length >= map->aux)
217 return 0;
218
219 coll = lwsac_use_zero(&args->ac_chunks, sizeof(*coll),
220 sizeof(*coll));
221 if (!coll) {
222 lwsl_err("%s: OOT\n", __func__);
223
224 return 1;
225 }
226 coll->chunks.prev = NULL;
227 coll->chunks.next = NULL;
228 coll->chunks.owner = NULL;
229
230 coll->len = ctx->npos;
231 lws_dll2_add_tail(&coll->chunks, &args->chunks_owner);
232
233 memcpy(coll->buf, ctx->buf, ctx->npos);
234
235 args->chunks_length += ctx->npos;
236
237 return 0;
238 }
239
240 if (reason != LEJPCB_VAL_STR_END && reason != LEJPCB_VAL_NUM_INT &&
241 reason != LEJPCB_VAL_TRUE && reason != LEJPCB_VAL_FALSE)
242 return 0;
243
244 /* this is the end of the string */
245
246 if (ctx->pst[ctx->pst_sp].user && pmap && pmap->type == LSMT_CHILD_PTR) {
247 void **pp = (void **)
248 (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs);
249
250 *pp = ctx->pst[ctx->pst_sp].user;
251 }
252
253 u = (char *)ctx->pst[ctx->pst_sp].user;
254 if (!u)
255 u = (char *)ctx->pst[ctx->pst_sp - 1].user;
256
257 {
258 char **pp, *s;
259 size_t lim, b;
260 long long li;
261
262 switch (map->type) {
263 case LSMT_SIGNED:
264 if (map->aux == sizeof(signed char)) {
265 signed char *pc;
266 pc = (signed char *)(u + map->ofs);
267 *pc = atoi(ctx->buf);
268 break;
269 }
270 if (map->aux == sizeof(int)) {
271 int *pi;
272 pi = (int *)(u + map->ofs);
273 *pi = atoi(ctx->buf);
274 break;
275 }
276 if (map->aux == sizeof(long)) {
277 long *pl;
278 pl = (long *)(u + map->ofs);
279 *pl = atol(ctx->buf);
280 } else {
281 long long *pll;
282 pll = (long long *)(u + map->ofs);
283 *pll = atoll(ctx->buf);
284 }
285 break;
286
287 case LSMT_UNSIGNED:
288 if (map->aux == sizeof(unsigned char)) {
289 unsigned char *pc;
290 pc = (unsigned char *)(u + map->ofs);
291 *pc = atoi(ctx->buf);
292 break;
293 }
294 if (map->aux == sizeof(unsigned int)) {
295 unsigned int *pi;
296 pi = (unsigned int *)(u + map->ofs);
297 *pi = atoi(ctx->buf);
298 break;
299 }
300 if (map->aux == sizeof(unsigned long)) {
301 unsigned long *pl;
302 pl = (unsigned long *)(u + map->ofs);
303 *pl = atol(ctx->buf);
304 } else {
305 unsigned long long *pll;
306 pll = (unsigned long long *)(u + map->ofs);
307 *pll = atoll(ctx->buf);
308 }
309 break;
310
311 case LSMT_BOOLEAN:
312 li = reason == LEJPCB_VAL_TRUE;
313 if (map->aux == sizeof(char)) {
314 char *pc;
315 pc = (char *)(u + map->ofs);
316 *pc = (char)li;
317 break;
318 }
319 if (map->aux == sizeof(int)) {
320 int *pi;
321 pi = (int *)(u + map->ofs);
322 *pi = (int)li;
323 } else {
324 uint64_t *p64;
325 p64 = (uint64_t *)(u + map->ofs);
326 *p64 = li;
327 }
328 break;
329
330 case LSMT_STRING_CHAR_ARRAY:
331 s = (char *)(u + map->ofs);
332 lim = map->aux - 1;
333 goto chunk_copy;
334
335 case LSMT_STRING_PTR:
336 pp = (char **)(u + map->ofs);
337 lim = args->chunks_length + ctx->npos;
338 s = lwsac_use(&args->ac, lim + 1, args->ac_block_size);
339 if (!s)
340 goto cleanup;
341 *pp = s;
342
343 chunk_copy:
344 s[lim] = '\0';
345 /* copy up to lim from the string chunk ac first */
346 lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
347 args->chunks_owner.head) {
348 lejp_collation_t *coll = (lejp_collation_t *)p;
349
350 if (lim) {
351 b = coll->len;
352 if (b > lim)
353 b = lim;
354 memcpy(s, coll->buf, b);
355 s += b;
356 lim -= b;
357 }
358 } lws_end_foreach_dll_safe(p, p1);
359
360 lwsac_free(&args->ac_chunks);
361 args->chunks_owner.count = 0;
362 args->chunks_owner.head = NULL;
363 args->chunks_owner.tail = NULL;
364
365 if (lim) {
366 b = ctx->npos;
367 if (b > lim)
368 b = lim;
369 memcpy(s, ctx->buf, b);
370 s[b] = '\0';
371 }
372 break;
373 default:
374 break;
375 }
376 }
377
378 if (args->cb)
379 args->cb(args->dest, args->cb_arg);
380
381 return 0;
382
383 cleanup:
384 lwsl_notice("%s: cleanup\n", __func__);
385 lwsac_free(&args->ac_chunks);
386 args->chunks_owner.count = 0;
387 args->chunks_owner.head = NULL;
388 args->chunks_owner.tail = NULL;
389
390 return 1;
391 }
392
393 static const char * schema[] = { "schema" };
394
395 int
lws_struct_json_init_parse(struct lejp_ctx * ctx,lejp_callback cb,void * user)396 lws_struct_json_init_parse(struct lejp_ctx *ctx, lejp_callback cb, void *user)
397 {
398 if (!cb)
399 cb = lws_struct_schema_only_lejp_cb;
400 lejp_construct(ctx, cb, user, schema, 1);
401
402 ctx->path_stride = sizeof(lws_struct_map_t);
403
404 return 0;
405 }
406
407 lws_struct_serialize_t *
lws_struct_json_serialize_create(const lws_struct_map_t * map,size_t map_entries,int flags,const void * ptoplevel)408 lws_struct_json_serialize_create(const lws_struct_map_t *map,
409 size_t map_entries, int flags,
410 const void *ptoplevel)
411 {
412 lws_struct_serialize_t *js = lws_zalloc(sizeof(*js), __func__);
413 lws_struct_serialize_st_t *j;
414
415 if (!js)
416 return NULL;
417
418 js->flags = flags;
419
420 j = &js->st[0];
421 j->map = map;
422 j->map_entries = map_entries;
423 j->obj = ptoplevel;
424 j->idt = 0;
425
426 return js;
427 }
428
429 void
lws_struct_json_serialize_destroy(lws_struct_serialize_t ** pjs)430 lws_struct_json_serialize_destroy(lws_struct_serialize_t **pjs)
431 {
432 if (!*pjs)
433 return;
434
435 lws_free(*pjs);
436
437 *pjs = NULL;
438 }
439
440 static void
lws_struct_pretty(lws_struct_serialize_t * js,uint8_t ** pbuf,size_t * plen)441 lws_struct_pretty(lws_struct_serialize_t *js, uint8_t **pbuf, size_t *plen)
442 {
443 if (js->flags & LSSERJ_FLAG_PRETTY) {
444 int n;
445
446 *(*pbuf)++ = '\n';
447 (*plen)--;
448 for (n = 0; n < js->st[js->sp].idt; n++) {
449 *(*pbuf)++ = ' ';
450 (*plen)--;
451 }
452 }
453 }
454
455 lws_struct_json_serialize_result_t
lws_struct_json_serialize(lws_struct_serialize_t * js,uint8_t * buf,size_t len,size_t * written)456 lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf,
457 size_t len, size_t *written)
458 {
459 lws_struct_serialize_st_t *j;
460 const lws_struct_map_t *map;
461 size_t budget = 0, olen = len, m;
462 struct lws_dll2_owner *o;
463 unsigned long long uli;
464 const char *q;
465 const void *p;
466 char dbuf[72];
467 long long li;
468 int n, used;
469
470 *written = 0;
471 *buf = '\0';
472
473 while (len > sizeof(dbuf) + 20) {
474 j = &js->st[js->sp];
475 map = &j->map[j->map_entry];
476 q = j->obj + map->ofs;
477
478 /* early check if the entry should be elided */
479
480 switch (map->type) {
481 case LSMT_STRING_PTR:
482 case LSMT_CHILD_PTR:
483 q = (char *)*(char **)q;
484 if (!q)
485 goto up;
486 break;
487
488 case LSMT_LIST:
489 o = (struct lws_dll2_owner *)q;
490 p = j->dllpos = lws_dll2_get_head(o);
491 if (!p)
492 goto up;
493 break;
494
495 default:
496 break;
497 }
498
499 if (j->subsequent) {
500 *buf++ = ',';
501 len--;
502 lws_struct_pretty(js, &buf, &len);
503 }
504 j->subsequent = 1;
505
506 if (map->type != LSMT_SCHEMA && !js->offset) {
507 n = lws_snprintf((char *)buf, len, "\"%s\":",
508 map->colname);
509 buf += n;
510 len -= n;
511 if (js->flags & LSSERJ_FLAG_PRETTY) {
512 *buf++ = ' ';
513 len--;
514 }
515 }
516
517 switch (map->type) {
518 case LSMT_BOOLEAN:
519 case LSMT_UNSIGNED:
520 if (map->aux == sizeof(char)) {
521 uli = *(unsigned char *)q;
522 } else {
523 if (map->aux == sizeof(int)) {
524 uli = *(unsigned int *)q;
525 } else {
526 if (map->aux == sizeof(long))
527 uli = *(unsigned long *)q;
528 else
529 uli = *(unsigned long long *)q;
530 }
531 }
532 q = dbuf;
533
534 if (map->type == LSMT_BOOLEAN) {
535 budget = lws_snprintf(dbuf, sizeof(dbuf),
536 "%s", uli ? "true" : "false");
537 } else
538 budget = lws_snprintf(dbuf, sizeof(dbuf),
539 "%llu", uli);
540 break;
541
542 case LSMT_SIGNED:
543 if (map->aux == sizeof(signed char)) {
544 li = (long long)*(signed char *)q;
545 } else {
546 if (map->aux == sizeof(int)) {
547 li = (long long)*(int *)q;
548 } else {
549 if (map->aux == sizeof(long))
550 li = (long long)*(long *)q;
551 else
552 li = *(long long *)q;
553 }
554 }
555 q = dbuf;
556 budget = lws_snprintf(dbuf, sizeof(dbuf), "%lld", li);
557 break;
558
559 case LSMT_STRING_CHAR_ARRAY:
560 case LSMT_STRING_PTR:
561 if (!js->offset) {
562 *buf++ = '\"';
563 len--;
564 }
565 break;
566
567 case LSMT_LIST:
568 *buf++ = '[';
569 len--;
570 if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
571 return LSJS_RESULT_ERROR;
572
573 /* add a stack level to handle parsing array members */
574
575 o = (struct lws_dll2_owner *)q;
576 p = j->dllpos = lws_dll2_get_head(o);
577
578 if (!j->dllpos) {
579 *buf++ = ']';
580 len--;
581 goto up;
582 }
583
584 n = j->idt;
585 j = &js->st[++js->sp];
586 j->idt = n + 2;
587 j->map = map->child_map;
588 j->map_entries = map->child_map_size;
589 j->size = map->aux;
590 j->subsequent = 0;
591 j->map_entry = 0;
592 lws_struct_pretty(js, &buf, &len);
593 *buf++ = '{';
594 len--;
595 lws_struct_pretty(js, &buf, &len);
596 if (p)
597 j->obj = ((char *)p) - j->map->ofs_clist;
598 else
599 j->obj = NULL;
600 continue;
601
602 case LSMT_CHILD_PTR:
603
604 if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
605 return LSJS_RESULT_ERROR;
606
607 /* add a stack level to handle parsing child members */
608
609 n = j->idt;
610 j = &js->st[++js->sp];
611 j->idt = n + 2;
612 j->map = map->child_map;
613 j->map_entries = map->child_map_size;
614 j->size = map->aux;
615 j->subsequent = 0;
616 j->map_entry = 0;
617 *buf++ = '{';
618 len--;
619 lws_struct_pretty(js, &buf, &len);
620 j->obj = q;
621
622 continue;
623
624 case LSMT_SCHEMA:
625 q = dbuf;
626 *buf++ = '{';
627 len--;
628 j = &js->st[++js->sp];
629 lws_struct_pretty(js, &buf, &len);
630 budget = lws_snprintf(dbuf, 15, "\"schema\":");
631 if (js->flags & LSSERJ_FLAG_PRETTY)
632 dbuf[budget++] = ' ';
633
634 budget += lws_snprintf(dbuf + budget,
635 sizeof(dbuf) - budget,
636 "\"%s\"", map->colname);
637
638
639 if (js->sp != 1)
640 return LSJS_RESULT_ERROR;
641 j->map = map->child_map;
642 j->map_entries = map->child_map_size;
643 j->size = map->aux;
644 j->subsequent = 0;
645 j->map_entry = 0;
646 j->obj = js->st[js->sp - 1].obj;
647 j->dllpos = NULL;
648 /* we're actually at the same level */
649 j->subsequent = 1;
650 j->idt = 1;
651 break;
652 }
653
654 switch (map->type) {
655 case LSMT_STRING_CHAR_ARRAY:
656 case LSMT_STRING_PTR:
657 /*
658 * This is a bit tricky... we have to escape the string
659 * which may 6x its length depending on what the
660 * contents are.
661 *
662 * We offset the unescaped string starting point first
663 */
664
665 q += js->offset;
666 budget = strlen(q); /* how much unescaped is left */
667
668 /*
669 * This is going to escape as much as it can fit, and
670 * let us know the amount of input that was consumed
671 * in "used".
672 */
673
674 lws_json_purify((char *)buf, q, len, &used);
675 m = strlen((const char *)buf);
676 buf += m;
677 len -= m;
678 js->remaining = budget - used;
679 js->offset = used;
680 if (!js->remaining)
681 js->offset = 0;
682
683 break;
684 default:
685 q += js->offset;
686 budget -= js->remaining;
687
688 if (budget > len) {
689 js->remaining = budget - len;
690 js->offset = len;
691 budget = len;
692 } else {
693 js->remaining = 0;
694 js->offset = 0;
695 }
696
697 memcpy(buf, q, budget);
698 buf += budget;
699 *buf = '\0';
700 len -= budget;
701 break;
702 }
703
704
705
706 switch (map->type) {
707 case LSMT_STRING_CHAR_ARRAY:
708 case LSMT_STRING_PTR:
709 *buf++ = '\"';
710 len--;
711 break;
712 case LSMT_SCHEMA:
713 continue;
714 default:
715 break;
716 }
717
718 if (js->remaining)
719 continue;
720 up:
721 if (++j->map_entry < j->map_entries)
722 continue;
723
724 if (!js->sp)
725 continue;
726 js->sp--;
727 if (!js->sp) {
728 lws_struct_pretty(js, &buf, &len);
729 *buf++ = '}';
730 len--;
731 lws_struct_pretty(js, &buf, &len);
732 break;
733 }
734 js->offset = 0;
735 j = &js->st[js->sp];
736 map = &j->map[j->map_entry];
737
738 if (map->type == LSMT_CHILD_PTR) {
739 lws_struct_pretty(js, &buf, &len);
740 *buf++ = '}';
741 len--;
742
743 /* we have done the singular child pointer */
744
745 js->offset = 0;
746 goto up;
747 }
748
749 if (map->type != LSMT_LIST)
750 continue;
751 /*
752 * we are coming back up to an array map, it means we should
753 * advance to the next array member if there is one
754 */
755
756 lws_struct_pretty(js, &buf, &len);
757 *buf++ = '}';
758 len--;
759
760 p = j->dllpos = j->dllpos->next;
761 if (j->dllpos) {
762 /*
763 * there was another item in the array to do... let's
764 * move on to that and do it
765 */
766 *buf++ = ',';
767 len--;
768 lws_struct_pretty(js, &buf, &len);
769 js->offset = 0;
770 j = &js->st[++js->sp];
771 j->map_entry = 0;
772 map = &j->map[j->map_entry];
773
774 *buf++ = '{';
775 len--;
776 lws_struct_pretty(js, &buf, &len);
777
778 j->subsequent = 0;
779 j->obj = ((char *)p) - j->map->ofs_clist;
780 continue;
781 }
782
783 /* there are no further items in the array */
784
785 js->offset = 0;
786 lws_struct_pretty(js, &buf, &len);
787 *buf++ = ']';
788 len--;
789 goto up;
790 }
791
792 *written = olen - len;
793 *buf = '\0'; /* convenience, a NUL after the official end */
794
795 return LSJS_RESULT_FINISH;
796 }
797