xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/base/json.c (revision d3273b5b76f5afaafe308cead5511dbb8df8c5e9)
1 /*	$NetBSD: json.c,v 1.2 2017/01/28 21:31:45 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 2010 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  *
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * 3. Neither the name of the Institute nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include "baselocl.h"
39 #include <ctype.h>
40 #include <krb5/base64.h>
41 
42 static heim_base_once_t heim_json_once = HEIM_BASE_ONCE_INIT;
43 static heim_string_t heim_tid_data_uuid_key = NULL;
44 static const char base64_chars[] =
45     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
46 
47 static void
json_init_once(void * arg)48 json_init_once(void *arg)
49 {
50     heim_tid_data_uuid_key = __heim_string_constant("heimdal-type-data-76d7fca2-d0da-4b20-a126-1a10f8a0eae6");
51 }
52 
53 struct twojson {
54     void *ctx;
55     void (*out)(void *, const char *);
56     size_t indent;
57     heim_json_flags_t flags;
58     int ret;
59     int first;
60 };
61 
62 struct heim_strbuf {
63     char *str;
64     size_t len;
65     size_t alloced;
66     int	enomem;
67     heim_json_flags_t flags;
68 };
69 
70 static int
71 base2json(heim_object_t, struct twojson *);
72 
73 static void
indent(struct twojson * j)74 indent(struct twojson *j)
75 {
76     size_t i = j->indent;
77     if (j->flags & HEIM_JSON_F_ONE_LINE)
78 	return;
79     while (i--)
80 	j->out(j->ctx, "\t");
81 }
82 
83 static void
array2json(heim_object_t value,void * ctx,int * stop)84 array2json(heim_object_t value, void *ctx, int *stop)
85 {
86     struct twojson *j = ctx;
87     if (j->ret)
88 	return;
89     if (j->first) {
90 	j->first = 0;
91     } else {
92 	j->out(j->ctx, NULL); /* eat previous '\n' if possible */
93 	j->out(j->ctx, ",\n");
94     }
95     j->ret = base2json(value, j);
96 }
97 
98 static void
dict2json(heim_object_t key,heim_object_t value,void * ctx)99 dict2json(heim_object_t key, heim_object_t value, void *ctx)
100 {
101     struct twojson *j = ctx;
102     if (j->ret)
103 	return;
104     if (j->first) {
105 	j->first = 0;
106     } else {
107 	j->out(j->ctx, NULL); /* eat previous '\n' if possible */
108 	j->out(j->ctx, ",\n");
109     }
110     j->ret = base2json(key, j);
111     if (j->ret)
112 	return;
113     j->out(j->ctx, " : \n");
114     j->indent++;
115     j->ret = base2json(value, j);
116     if (j->ret)
117 	return;
118     j->indent--;
119 }
120 
121 static int
base2json(heim_object_t obj,struct twojson * j)122 base2json(heim_object_t obj, struct twojson *j)
123 {
124     heim_tid_t type;
125     int first = 0;
126 
127     if (obj == NULL) {
128 	if (j->flags & HEIM_JSON_F_CNULL2JSNULL) {
129 	    obj = heim_null_create();
130 	} else if (j->flags & HEIM_JSON_F_NO_C_NULL) {
131 	    return EINVAL;
132 	} else {
133 	    indent(j);
134 	    j->out(j->ctx, "<NULL>\n"); /* This is NOT valid JSON! */
135 	    return 0;
136 	}
137     }
138 
139     type = heim_get_tid(obj);
140     switch (type) {
141     case HEIM_TID_ARRAY:
142 	indent(j);
143 	j->out(j->ctx, "[\n");
144 	j->indent++;
145 	first = j->first;
146 	j->first = 1;
147 	heim_array_iterate_f(obj, j, array2json);
148 	j->indent--;
149 	if (!j->first)
150 	    j->out(j->ctx, "\n");
151 	indent(j);
152 	j->out(j->ctx, "]\n");
153 	j->first = first;
154 	break;
155 
156     case HEIM_TID_DICT:
157 	indent(j);
158 	j->out(j->ctx, "{\n");
159 	j->indent++;
160 	first = j->first;
161 	j->first = 1;
162 	heim_dict_iterate_f(obj, j, dict2json);
163 	j->indent--;
164 	if (!j->first)
165 	    j->out(j->ctx, "\n");
166 	indent(j);
167 	j->out(j->ctx, "}\n");
168 	j->first = first;
169 	break;
170 
171     case HEIM_TID_STRING:
172 	indent(j);
173 	j->out(j->ctx, "\"");
174 	j->out(j->ctx, heim_string_get_utf8(obj));
175 	j->out(j->ctx, "\"");
176 	break;
177 
178     case HEIM_TID_DATA: {
179 	heim_dict_t d;
180 	heim_string_t v;
181 	const heim_octet_string *data;
182 	char *b64 = NULL;
183 	int ret;
184 
185 	if (j->flags & HEIM_JSON_F_NO_DATA)
186 	    return EINVAL; /* JSON doesn't do binary */
187 
188 	data = heim_data_get_data(obj);
189 	ret = rk_base64_encode(data->data, data->length, &b64);
190 	if (ret < 0 || b64 == NULL)
191 	    return ENOMEM;
192 
193 	if (j->flags & HEIM_JSON_F_NO_DATA_DICT) {
194 	    indent(j);
195 	    j->out(j->ctx, "\"");
196 	    j->out(j->ctx, b64); /* base64-encode; hope there's no aliasing */
197 	    j->out(j->ctx, "\"");
198 	    free(b64);
199 	} else {
200 	    /*
201 	     * JSON has no way to represent binary data, therefore the
202 	     * following is a Heimdal-specific convention.
203 	     *
204 	     * We encode binary data as a dict with a single very magic
205 	     * key with a base64-encoded value.  The magic key includes
206 	     * a uuid, so we're not likely to alias accidentally.
207 	     */
208 	    d = heim_dict_create(2);
209 	    if (d == NULL) {
210 		free(b64);
211 		return ENOMEM;
212 	    }
213 	    v = heim_string_ref_create(b64, free);
214 	    if (v == NULL) {
215 		free(b64);
216 		heim_release(d);
217 		return ENOMEM;
218 	    }
219 	    ret = heim_dict_set_value(d, heim_tid_data_uuid_key, v);
220 	    heim_release(v);
221 	    if (ret) {
222 		heim_release(d);
223 		return ENOMEM;
224 	    }
225 	    ret = base2json(d, j);
226 	    heim_release(d);
227 	    if (ret)
228 		return ret;
229 	}
230 	break;
231     }
232 
233     case HEIM_TID_NUMBER: {
234 	char num[32];
235 	indent(j);
236 	snprintf(num, sizeof (num), "%d", heim_number_get_int(obj));
237 	j->out(j->ctx, num);
238 	break;
239     }
240     case HEIM_TID_NULL:
241 	indent(j);
242 	j->out(j->ctx, "null");
243 	break;
244     case HEIM_TID_BOOL:
245 	indent(j);
246 	j->out(j->ctx, heim_bool_val(obj) ? "true" : "false");
247 	break;
248     default:
249 	return 1;
250     }
251     return 0;
252 }
253 
254 static int
heim_base2json(heim_object_t obj,void * ctx,heim_json_flags_t flags,void (* out)(void *,const char *))255 heim_base2json(heim_object_t obj, void *ctx, heim_json_flags_t flags,
256 	       void (*out)(void *, const char *))
257 {
258     struct twojson j;
259 
260     if (flags & HEIM_JSON_F_STRICT_STRINGS)
261 	return ENOTSUP; /* Sorry, not yet! */
262 
263     heim_base_once_f(&heim_json_once, NULL, json_init_once);
264 
265     j.indent = 0;
266     j.ctx = ctx;
267     j.out = out;
268     j.flags = flags;
269     j.ret = 0;
270     j.first = 1;
271 
272     return base2json(obj, &j);
273 }
274 
275 
276 /*
277  *
278  */
279 
280 struct parse_ctx {
281     unsigned long lineno;
282     const uint8_t *p;
283     const uint8_t *pstart;
284     const uint8_t *pend;
285     heim_error_t error;
286     size_t depth;
287     heim_json_flags_t flags;
288 };
289 
290 
291 static heim_object_t
292 parse_value(struct parse_ctx *ctx);
293 
294 /*
295  * This function eats whitespace, but, critically, it also succeeds
296  * only if there's anything left to parse.
297  */
298 static int
white_spaces(struct parse_ctx * ctx)299 white_spaces(struct parse_ctx *ctx)
300 {
301     while (ctx->p < ctx->pend) {
302 	uint8_t c = *ctx->p;
303 	if (c == ' ' || c == '\t' || c == '\r') {
304 
305 	} else if (c == '\n') {
306 	    ctx->lineno++;
307 	} else
308 	    return 0;
309 	(ctx->p)++;
310     }
311     return -1;
312 }
313 
314 static int
is_number(uint8_t n)315 is_number(uint8_t n)
316 {
317     return ('0' <= n && n <= '9');
318 }
319 
320 static heim_number_t
parse_number(struct parse_ctx * ctx)321 parse_number(struct parse_ctx *ctx)
322 {
323     int number = 0, neg = 1;
324 
325     if (ctx->p >= ctx->pend)
326 	return NULL;
327 
328     if (*ctx->p == '-') {
329 	if (ctx->p + 1 >= ctx->pend)
330 	    return NULL;
331 	neg = -1;
332 	ctx->p += 1;
333     }
334 
335     while (ctx->p < ctx->pend) {
336 	if (is_number(*ctx->p)) {
337 	    number = (number * 10) + (*ctx->p - '0');
338 	} else {
339 	    break;
340 	}
341 	ctx->p += 1;
342     }
343 
344     return heim_number_create(number * neg);
345 }
346 
347 static heim_string_t
parse_string(struct parse_ctx * ctx)348 parse_string(struct parse_ctx *ctx)
349 {
350     const uint8_t *start;
351     int quote = 0;
352 
353     if (ctx->flags & HEIM_JSON_F_STRICT_STRINGS) {
354 	ctx->error = heim_error_create(EINVAL, "Strict JSON string encoding "
355 				       "not yet supported");
356 	return NULL;
357     }
358 
359     if (*ctx->p != '"') {
360 	ctx->error = heim_error_create(EINVAL, "Expected a JSON string but "
361 				       "found something else at line %lu",
362 				       ctx->lineno);
363 	return NULL;
364     }
365     start = ++ctx->p;
366 
367     while (ctx->p < ctx->pend) {
368 	if (*ctx->p == '\n') {
369 	    ctx->lineno++;
370 	} else if (*ctx->p == '\\') {
371 	    if (ctx->p + 1 == ctx->pend)
372 		goto out;
373 	    ctx->p++;
374 	    quote = 1;
375 	} else if (*ctx->p == '"') {
376 	    heim_object_t o;
377 
378 	    if (quote) {
379 		char *p0, *p;
380 		p = p0 = malloc(ctx->p - start);
381 		if (p == NULL)
382 		    goto out;
383 		while (start < ctx->p) {
384 		    if (*start == '\\') {
385 			start++;
386 			/* XXX validate quoted char */
387 		    }
388 		    *p++ = *start++;
389 		}
390 		o = heim_string_create_with_bytes(p0, p - p0);
391 		free(p0);
392 	    } else {
393 		o = heim_string_create_with_bytes(start, ctx->p - start);
394 		if (o == NULL) {
395 		    ctx->error = heim_error_create_enomem();
396 		    return NULL;
397 		}
398 
399 		/* If we can decode as base64, then let's */
400 		if (ctx->flags & HEIM_JSON_F_TRY_DECODE_DATA) {
401 		    void *buf;
402 		    size_t len;
403 		    const char *s;
404 
405 		    s = heim_string_get_utf8(o);
406 		    len = strlen(s);
407 
408 		    if (len >= 4 && strspn(s, base64_chars) >= len - 2) {
409 			buf = malloc(len);
410 			if (buf == NULL) {
411 			    heim_release(o);
412 			    ctx->error = heim_error_create_enomem();
413 			    return NULL;
414 			}
415 			len = rk_base64_decode(s, buf);
416 			if (len == -1) {
417 			    free(buf);
418 			    return o;
419 			}
420 			heim_release(o);
421 			o = heim_data_ref_create(buf, len, free);
422 		    }
423 		}
424 	    }
425 	    ctx->p += 1;
426 
427 	    return o;
428 	}
429 	ctx->p += 1;
430     }
431     out:
432     ctx->error = heim_error_create(EINVAL, "ran out of string");
433     return NULL;
434 }
435 
436 static int
parse_pair(heim_dict_t dict,struct parse_ctx * ctx)437 parse_pair(heim_dict_t dict, struct parse_ctx *ctx)
438 {
439     heim_string_t key;
440     heim_object_t value;
441 
442     if (white_spaces(ctx))
443 	return -1;
444 
445     if (*ctx->p == '}') {
446 	ctx->p++;
447 	return 0;
448     }
449 
450     if (ctx->flags & HEIM_JSON_F_STRICT_DICT)
451 	/* JSON allows only string keys */
452 	key = parse_string(ctx);
453     else
454 	/* heim_dict_t allows any heim_object_t as key */
455 	key = parse_value(ctx);
456     if (key == NULL)
457 	/* Even heim_dict_t does not allow C NULLs as keys though! */
458 	return -1;
459 
460     if (white_spaces(ctx)) {
461 	heim_release(key);
462 	return -1;
463     }
464 
465     if (*ctx->p != ':') {
466 	heim_release(key);
467 	return -1;
468     }
469 
470     ctx->p += 1; /* safe because we call white_spaces() next */
471 
472     if (white_spaces(ctx)) {
473 	heim_release(key);
474 	return -1;
475     }
476 
477     value = parse_value(ctx);
478     if (value == NULL &&
479 	(ctx->error != NULL || (ctx->flags & HEIM_JSON_F_NO_C_NULL))) {
480 	if (ctx->error == NULL)
481 	    ctx->error = heim_error_create(EINVAL, "Invalid JSON encoding");
482 	heim_release(key);
483 	return -1;
484     }
485     heim_dict_set_value(dict, key, value);
486     heim_release(key);
487     heim_release(value);
488 
489     if (white_spaces(ctx))
490 	return -1;
491 
492     if (*ctx->p == '}') {
493 	/*
494 	 * Return 1 but don't consume the '}' so we can count the one
495 	 * pair in a one-pair dict
496 	 */
497 	return 1;
498     } else if (*ctx->p == ',') {
499 	ctx->p++;
500 	return 1;
501     }
502     return -1;
503 }
504 
505 static heim_dict_t
parse_dict(struct parse_ctx * ctx)506 parse_dict(struct parse_ctx *ctx)
507 {
508     heim_dict_t dict;
509     size_t count = 0;
510     int ret;
511 
512     heim_assert(*ctx->p == '{', "string doesn't start with {");
513 
514     dict = heim_dict_create(11);
515     if (dict == NULL) {
516 	ctx->error = heim_error_create_enomem();
517 	return NULL;
518     }
519 
520     ctx->p += 1; /* safe because parse_pair() calls white_spaces() first */
521 
522     while ((ret = parse_pair(dict, ctx)) > 0)
523 	count++;
524     if (ret < 0) {
525 	heim_release(dict);
526 	return NULL;
527     }
528     if (count == 1 && !(ctx->flags & HEIM_JSON_F_NO_DATA_DICT)) {
529 	heim_object_t v = heim_dict_copy_value(dict, heim_tid_data_uuid_key);
530 
531 	/*
532 	 * Binary data encoded as a dict with a single magic key with
533 	 * base64-encoded value?  Decode as heim_data_t.
534 	 */
535 	if (v != NULL && heim_get_tid(v) == HEIM_TID_STRING) {
536 	    void *buf;
537 	    size_t len;
538 
539 	    buf = malloc(strlen(heim_string_get_utf8(v)));
540 	    if (buf == NULL) {
541 		heim_release(dict);
542 		heim_release(v);
543 		ctx->error = heim_error_create_enomem();
544 		return NULL;
545 	    }
546 	    len = rk_base64_decode(heim_string_get_utf8(v), buf);
547 	    heim_release(v);
548 	    if (len == -1) {
549 		free(buf);
550 		return dict; /* assume aliasing accident */
551 	    }
552 	    heim_release(dict);
553 	    return (heim_dict_t)heim_data_ref_create(buf, len, free);
554 	}
555     }
556     return dict;
557 }
558 
559 static int
parse_item(heim_array_t array,struct parse_ctx * ctx)560 parse_item(heim_array_t array, struct parse_ctx *ctx)
561 {
562     heim_object_t value;
563 
564     if (white_spaces(ctx))
565 	return -1;
566 
567     if (*ctx->p == ']') {
568 	ctx->p++; /* safe because parse_value() calls white_spaces() first */
569 	return 0;
570     }
571 
572     value = parse_value(ctx);
573     if (value == NULL &&
574 	(ctx->error || (ctx->flags & HEIM_JSON_F_NO_C_NULL)))
575 	return -1;
576 
577     heim_array_append_value(array, value);
578     heim_release(value);
579 
580     if (white_spaces(ctx))
581 	return -1;
582 
583     if (*ctx->p == ']') {
584 	ctx->p++;
585 	return 0;
586     } else if (*ctx->p == ',') {
587 	ctx->p++;
588 	return 1;
589     }
590     return -1;
591 }
592 
593 static heim_array_t
parse_array(struct parse_ctx * ctx)594 parse_array(struct parse_ctx *ctx)
595 {
596     heim_array_t array = heim_array_create();
597     int ret;
598 
599     heim_assert(*ctx->p == '[', "array doesn't start with [");
600     ctx->p += 1;
601 
602     while ((ret = parse_item(array, ctx)) > 0)
603 	;
604     if (ret < 0) {
605 	heim_release(array);
606 	return NULL;
607     }
608     return array;
609 }
610 
611 static heim_object_t
parse_value(struct parse_ctx * ctx)612 parse_value(struct parse_ctx *ctx)
613 {
614     size_t len;
615     heim_object_t o;
616 
617     if (white_spaces(ctx))
618 	return NULL;
619 
620     if (*ctx->p == '"') {
621 	return parse_string(ctx);
622     } else if (*ctx->p == '{') {
623 	if (ctx->depth-- == 1) {
624 	    ctx->error = heim_error_create(EINVAL, "JSON object too deep");
625 	    return NULL;
626 	}
627 	o = parse_dict(ctx);
628 	ctx->depth++;
629 	return o;
630     } else if (*ctx->p == '[') {
631 	if (ctx->depth-- == 1) {
632 	    ctx->error = heim_error_create(EINVAL, "JSON object too deep");
633 	    return NULL;
634 	}
635 	o = parse_array(ctx);
636 	ctx->depth++;
637 	return o;
638     } else if (is_number(*ctx->p) || *ctx->p == '-') {
639 	return parse_number(ctx);
640     }
641 
642     len = ctx->pend - ctx->p;
643 
644     if ((ctx->flags & HEIM_JSON_F_NO_C_NULL) == 0 &&
645 	len >= 6 && memcmp(ctx->p, "<NULL>", 6) == 0) {
646 	ctx->p += 6;
647 	return heim_null_create();
648     } else if (len >= 4 && memcmp(ctx->p, "null", 4) == 0) {
649 	ctx->p += 4;
650 	return heim_null_create();
651     } else if (len >= 4 && strncasecmp((char *)ctx->p, "true", 4) == 0) {
652 	ctx->p += 4;
653 	return heim_bool_create(1);
654     } else if (len >= 5 && strncasecmp((char *)ctx->p, "false", 5) == 0) {
655 	ctx->p += 5;
656 	return heim_bool_create(0);
657     }
658 
659     ctx->error = heim_error_create(EINVAL, "unknown char %c at %lu line %lu",
660 				   (char)*ctx->p,
661 				   (unsigned long)(ctx->p - ctx->pstart),
662 				   ctx->lineno);
663     return NULL;
664 }
665 
666 
667 heim_object_t
heim_json_create(const char * string,size_t max_depth,heim_json_flags_t flags,heim_error_t * error)668 heim_json_create(const char *string, size_t max_depth, heim_json_flags_t flags,
669 		 heim_error_t *error)
670 {
671     return heim_json_create_with_bytes(string, strlen(string), max_depth, flags,
672 				       error);
673 }
674 
675 heim_object_t
heim_json_create_with_bytes(const void * data,size_t length,size_t max_depth,heim_json_flags_t flags,heim_error_t * error)676 heim_json_create_with_bytes(const void *data, size_t length, size_t max_depth,
677 			    heim_json_flags_t flags, heim_error_t *error)
678 {
679     struct parse_ctx ctx;
680     heim_object_t o;
681 
682     heim_base_once_f(&heim_json_once, NULL, json_init_once);
683 
684     ctx.lineno = 1;
685     ctx.p = data;
686     ctx.pstart = data;
687     ctx.pend = ((uint8_t *)data) + length;
688     ctx.error = NULL;
689     ctx.flags = flags;
690     ctx.depth = max_depth;
691 
692     o = parse_value(&ctx);
693 
694     if (o == NULL && error) {
695 	*error = ctx.error;
696     } else if (ctx.error) {
697 	heim_release(ctx.error);
698     }
699 
700     return o;
701 }
702 
703 
704 static void
show_printf(void * ctx,const char * str)705 show_printf(void *ctx, const char *str)
706 {
707     if (str == NULL)
708 	return;
709     fprintf(ctx, "%s", str);
710 }
711 
712 /**
713  * Dump a heimbase object to stderr (useful from the debugger!)
714  *
715  * @param obj object to dump using JSON or JSON-like format
716  *
717  * @addtogroup heimbase
718  */
719 void
heim_show(heim_object_t obj)720 heim_show(heim_object_t obj)
721 {
722     heim_base2json(obj, stderr, HEIM_JSON_F_NO_DATA_DICT, show_printf);
723 }
724 
725 static void
strbuf_add(void * ctx,const char * str)726 strbuf_add(void *ctx, const char *str)
727 {
728     struct heim_strbuf *strbuf = ctx;
729     size_t len;
730 
731     if (strbuf->enomem)
732 	return;
733 
734     if (str == NULL) {
735 	/*
736 	 * Eat the last '\n'; this is used when formatting dict pairs
737 	 * and array items so that the ',' separating them is never
738 	 * preceded by a '\n'.
739 	 */
740 	if (strbuf->len > 0 && strbuf->str[strbuf->len - 1] == '\n')
741 	    strbuf->len--;
742 	return;
743     }
744 
745     len = strlen(str);
746     if ((len + 1) > (strbuf->alloced - strbuf->len)) {
747 	size_t new_len = strbuf->alloced + (strbuf->alloced >> 2) + len + 1;
748 	char *s;
749 
750 	s = realloc(strbuf->str, new_len);
751 	if (s == NULL) {
752 	    strbuf->enomem = 1;
753 	    return;
754 	}
755 	strbuf->str = s;
756 	strbuf->alloced = new_len;
757     }
758     /* +1 so we copy the NUL */
759     (void) memcpy(strbuf->str + strbuf->len, str, len + 1);
760     strbuf->len += len;
761     if (strbuf->str[strbuf->len - 1] == '\n' &&
762 	strbuf->flags & HEIM_JSON_F_ONE_LINE)
763 	strbuf->len--;
764 }
765 
766 #define STRBUF_INIT_SZ 64
767 
768 heim_string_t
heim_json_copy_serialize(heim_object_t obj,heim_json_flags_t flags,heim_error_t * error)769 heim_json_copy_serialize(heim_object_t obj, heim_json_flags_t flags, heim_error_t *error)
770 {
771     heim_string_t str;
772     struct heim_strbuf strbuf;
773     int ret;
774 
775     if (error)
776 	*error = NULL;
777 
778     memset(&strbuf, 0, sizeof (strbuf));
779     strbuf.str = malloc(STRBUF_INIT_SZ);
780     if (strbuf.str == NULL) {
781 	if (error)
782 	    *error = heim_error_create_enomem();
783 	return NULL;
784     }
785     strbuf.len = 0;
786     strbuf.alloced = STRBUF_INIT_SZ;
787     strbuf.str[0] = '\0';
788     strbuf.flags = flags;
789 
790     ret = heim_base2json(obj, &strbuf, flags, strbuf_add);
791     if (ret || strbuf.enomem) {
792 	if (error) {
793 	    if (strbuf.enomem || ret == ENOMEM)
794 		*error = heim_error_create_enomem();
795 	    else
796 		*error = heim_error_create(1, "Impossible to JSON-encode "
797 					   "object");
798 	}
799 	free(strbuf.str);
800 	return NULL;
801     }
802     if (flags & HEIM_JSON_F_ONE_LINE) {
803 	strbuf.flags &= ~HEIM_JSON_F_ONE_LINE;
804 	strbuf_add(&strbuf, "\n");
805     }
806     str = heim_string_ref_create(strbuf.str, free);
807     if (str == NULL) {
808 	if (error)
809 	    *error = heim_error_create_enomem();
810 	free(strbuf.str);
811     }
812     return str;
813 }
814