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