2 * Copyright (c) 2010 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 static heim_base_once_t heim_json_once
= HEIM_BASE_ONCE_INIT
;
41 static heim_string_t heim_tid_data_uuid_key
= NULL
;
42 static const char base64_chars
[] =
43 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
46 json_init_once(void *arg
)
48 heim_tid_data_uuid_key
= __heim_string_constant("heimdal-type-data-76d7fca2-d0da-4b20-a126-1a10f8a0eae6");
53 void (*out
)(void *, const char *);
55 heim_json_flags_t flags
;
65 heim_json_flags_t flags
;
69 base2json(heim_object_t
, struct twojson
*);
72 indent(struct twojson
*j
)
75 if (j
->flags
& HEIM_JSON_F_ONE_LINE
)
82 array2json(heim_object_t value
, void *ctx
, int *stop
)
84 struct twojson
*j
= ctx
;
90 j
->out(j
->ctx
, NULL
); /* eat previous '\n' if possible */
91 j
->out(j
->ctx
, ",\n");
93 j
->ret
= base2json(value
, j
);
97 dict2json(heim_object_t key
, heim_object_t value
, void *ctx
)
99 struct twojson
*j
= ctx
;
105 j
->out(j
->ctx
, NULL
); /* eat previous '\n' if possible */
106 j
->out(j
->ctx
, ",\n");
108 j
->ret
= base2json(key
, j
);
111 j
->out(j
->ctx
, " : \n");
113 j
->ret
= base2json(value
, j
);
120 base2json(heim_object_t obj
, struct twojson
*j
)
126 if (j
->flags
& HEIM_JSON_F_CNULL2JSNULL
) {
127 obj
= heim_null_create();
128 } else if (j
->flags
& HEIM_JSON_F_NO_C_NULL
) {
132 j
->out(j
->ctx
, "<NULL>\n"); /* This is NOT valid JSON! */
137 type
= heim_get_tid(obj
);
141 j
->out(j
->ctx
, "[\n");
145 heim_array_iterate_f(obj
, j
, array2json
);
148 j
->out(j
->ctx
, "\n");
150 j
->out(j
->ctx
, "]\n");
156 j
->out(j
->ctx
, "{\n");
160 heim_dict_iterate_f(obj
, j
, dict2json
);
163 j
->out(j
->ctx
, "\n");
165 j
->out(j
->ctx
, "}\n");
169 case HEIM_TID_STRING
:
171 j
->out(j
->ctx
, "\"");
172 j
->out(j
->ctx
, heim_string_get_utf8(obj
));
173 j
->out(j
->ctx
, "\"");
176 case HEIM_TID_DATA
: {
179 const heim_octet_string
*data
;
183 if (j
->flags
& HEIM_JSON_F_NO_DATA
)
184 return EINVAL
; /* JSON doesn't do binary */
186 data
= heim_data_get_data(obj
);
187 ret
= rk_base64_encode(data
->data
, data
->length
, &b64
);
188 if (ret
< 0 || b64
== NULL
)
191 if (j
->flags
& HEIM_JSON_F_NO_DATA_DICT
) {
193 j
->out(j
->ctx
, "\"");
194 j
->out(j
->ctx
, b64
); /* base64-encode; hope there's no aliasing */
195 j
->out(j
->ctx
, "\"");
199 * JSON has no way to represent binary data, therefore the
200 * following is a Heimdal-specific convention.
202 * We encode binary data as a dict with a single very magic
203 * key with a base64-encoded value. The magic key includes
204 * a uuid, so we're not likely to alias accidentally.
206 d
= heim_dict_create(2);
211 v
= heim_string_ref_create(b64
, free
);
217 ret
= heim_dict_set_value(d
, heim_tid_data_uuid_key
, v
);
223 ret
= base2json(d
, j
);
231 case HEIM_TID_NUMBER
: {
234 snprintf(num
, sizeof (num
), "%d", heim_number_get_int(obj
));
240 j
->out(j
->ctx
, "null");
244 j
->out(j
->ctx
, heim_bool_val(obj
) ? "true" : "false");
253 heim_base2json(heim_object_t obj
, void *ctx
, heim_json_flags_t flags
,
254 void (*out
)(void *, const char *))
258 if (flags
& HEIM_JSON_F_STRICT_STRINGS
)
259 return ENOTSUP
; /* Sorry, not yet! */
261 heim_base_once_f(&heim_json_once
, NULL
, json_init_once
);
270 return base2json(obj
, &j
);
279 unsigned long lineno
;
281 const uint8_t *pstart
;
285 heim_json_flags_t flags
;
290 parse_value(struct parse_ctx
*ctx
);
293 * This function eats whitespace, but, critically, it also succeeds
294 * only if there's anything left to parse.
297 white_spaces(struct parse_ctx
*ctx
)
299 while (ctx
->p
< ctx
->pend
) {
301 if (c
== ' ' || c
== '\t' || c
== '\r') {
303 } else if (c
== '\n') {
315 return ('0' <= n
&& n
<= '9');
319 parse_number(struct parse_ctx
*ctx
)
321 int number
= 0, neg
= 1;
323 if (ctx
->p
>= ctx
->pend
)
326 if (*ctx
->p
== '-') {
327 if (ctx
->p
+ 1 >= ctx
->pend
)
333 while (ctx
->p
< ctx
->pend
) {
334 if (is_number(*ctx
->p
)) {
335 number
= (number
* 10) + (*ctx
->p
- '0');
342 return heim_number_create(number
* neg
);
346 parse_string(struct parse_ctx
*ctx
)
348 const uint8_t *start
;
351 if (ctx
->flags
& HEIM_JSON_F_STRICT_STRINGS
) {
352 ctx
->error
= heim_error_create(EINVAL
, "Strict JSON string encoding "
353 "not yet supported");
357 if (*ctx
->p
!= '"') {
358 ctx
->error
= heim_error_create(EINVAL
, "Expected a JSON string but "
359 "found something else at line %lu",
365 while (ctx
->p
< ctx
->pend
) {
366 if (*ctx
->p
== '\n') {
368 } else if (*ctx
->p
== '\\') {
369 if (ctx
->p
+ 1 == ctx
->pend
)
373 } else if (*ctx
->p
== '"') {
378 p
= p0
= malloc(ctx
->p
- start
);
381 while (start
< ctx
->p
) {
382 if (*start
== '\\') {
384 /* XXX validate quoted char */
388 o
= heim_string_create_with_bytes(p0
, p
- p0
);
391 o
= heim_string_create_with_bytes(start
, ctx
->p
- start
);
393 ctx
->error
= heim_error_create_enomem();
397 /* If we can decode as base64, then let's */
398 if (ctx
->flags
& HEIM_JSON_F_TRY_DECODE_DATA
) {
403 s
= heim_string_get_utf8(o
);
406 if (len
>= 4 && strspn(s
, base64_chars
) >= len
- 2) {
410 ctx
->error
= heim_error_create_enomem();
413 len
= rk_base64_decode(s
, buf
);
419 o
= heim_data_ref_create(buf
, len
, free
);
430 ctx
->error
= heim_error_create(EINVAL
, "ran out of string");
435 parse_pair(heim_dict_t dict
, struct parse_ctx
*ctx
)
440 if (white_spaces(ctx
))
443 if (*ctx
->p
== '}') {
448 if (ctx
->flags
& HEIM_JSON_F_STRICT_DICT
)
449 /* JSON allows only string keys */
450 key
= parse_string(ctx
);
452 /* heim_dict_t allows any heim_object_t as key */
453 key
= parse_value(ctx
);
455 /* Even heim_dict_t does not allow C NULLs as keys though! */
458 if (white_spaces(ctx
)) {
463 if (*ctx
->p
!= ':') {
468 ctx
->p
+= 1; /* safe because we call white_spaces() next */
470 if (white_spaces(ctx
)) {
475 value
= parse_value(ctx
);
477 (ctx
->error
!= NULL
|| (ctx
->flags
& HEIM_JSON_F_NO_C_NULL
))) {
478 if (ctx
->error
== NULL
)
479 ctx
->error
= heim_error_create(EINVAL
, "Invalid JSON encoding");
483 heim_dict_set_value(dict
, key
, value
);
487 if (white_spaces(ctx
))
490 if (*ctx
->p
== '}') {
492 * Return 1 but don't consume the '}' so we can count the one
493 * pair in a one-pair dict
496 } else if (*ctx
->p
== ',') {
504 parse_dict(struct parse_ctx
*ctx
)
510 heim_assert(*ctx
->p
== '{', "string doesn't start with {");
512 dict
= heim_dict_create(11);
514 ctx
->error
= heim_error_create_enomem();
518 ctx
->p
+= 1; /* safe because parse_pair() calls white_spaces() first */
520 while ((ret
= parse_pair(dict
, ctx
)) > 0)
526 if (count
== 1 && !(ctx
->flags
& HEIM_JSON_F_NO_DATA_DICT
)) {
527 heim_object_t v
= heim_dict_copy_value(dict
, heim_tid_data_uuid_key
);
530 * Binary data encoded as a dict with a single magic key with
531 * base64-encoded value? Decode as heim_data_t.
533 if (v
!= NULL
&& heim_get_tid(v
) == HEIM_TID_STRING
) {
537 buf
= malloc(strlen(heim_string_get_utf8(v
)));
541 ctx
->error
= heim_error_create_enomem();
544 len
= rk_base64_decode(heim_string_get_utf8(v
), buf
);
548 return dict
; /* assume aliasing accident */
551 return (heim_dict_t
)heim_data_ref_create(buf
, len
, free
);
558 parse_item(heim_array_t array
, struct parse_ctx
*ctx
)
562 if (white_spaces(ctx
))
565 if (*ctx
->p
== ']') {
566 ctx
->p
++; /* safe because parse_value() calls white_spaces() first */
570 value
= parse_value(ctx
);
572 (ctx
->error
|| (ctx
->flags
& HEIM_JSON_F_NO_C_NULL
)))
575 heim_array_append_value(array
, value
);
578 if (white_spaces(ctx
))
581 if (*ctx
->p
== ']') {
584 } else if (*ctx
->p
== ',') {
592 parse_array(struct parse_ctx
*ctx
)
594 heim_array_t array
= heim_array_create();
597 heim_assert(*ctx
->p
== '[', "array doesn't start with [");
600 while ((ret
= parse_item(array
, ctx
)) > 0)
610 parse_value(struct parse_ctx
*ctx
)
615 if (white_spaces(ctx
))
618 if (*ctx
->p
== '"') {
619 return parse_string(ctx
);
620 } else if (*ctx
->p
== '{') {
621 if (ctx
->depth
-- == 1) {
622 ctx
->error
= heim_error_create(EINVAL
, "JSON object too deep");
628 } else if (*ctx
->p
== '[') {
629 if (ctx
->depth
-- == 1) {
630 ctx
->error
= heim_error_create(EINVAL
, "JSON object too deep");
633 o
= parse_array(ctx
);
636 } else if (is_number(*ctx
->p
) || *ctx
->p
== '-') {
637 return parse_number(ctx
);
640 len
= ctx
->pend
- ctx
->p
;
642 if ((ctx
->flags
& HEIM_JSON_F_NO_C_NULL
) == 0 &&
643 len
>= 6 && memcmp(ctx
->p
, "<NULL>", 6) == 0) {
645 return heim_null_create();
646 } else if (len
>= 4 && memcmp(ctx
->p
, "null", 4) == 0) {
648 return heim_null_create();
649 } else if (len
>= 4 && strncasecmp((char *)ctx
->p
, "true", 4) == 0) {
651 return heim_bool_create(1);
652 } else if (len
>= 5 && strncasecmp((char *)ctx
->p
, "false", 5) == 0) {
654 return heim_bool_create(0);
657 ctx
->error
= heim_error_create(EINVAL
, "unknown char %c at %lu line %lu",
659 (unsigned long)(ctx
->p
- ctx
->pstart
),
666 heim_json_create(const char *string
, size_t max_depth
, heim_json_flags_t flags
,
669 return heim_json_create_with_bytes(string
, strlen(string
), max_depth
, flags
,
674 heim_json_create_with_bytes(const void *data
, size_t length
, size_t max_depth
,
675 heim_json_flags_t flags
, heim_error_t
*error
)
677 struct parse_ctx ctx
;
680 heim_base_once_f(&heim_json_once
, NULL
, json_init_once
);
685 ctx
.pend
= ((uint8_t *)data
) + length
;
688 ctx
.depth
= max_depth
;
690 o
= parse_value(&ctx
);
692 if (o
== NULL
&& error
) {
694 } else if (ctx
.error
) {
695 heim_release(ctx
.error
);
703 show_printf(void *ctx
, const char *str
)
707 fprintf(ctx
, "%s", str
);
711 * Dump a heimbase object to stderr (useful from the debugger!)
713 * @param obj object to dump using JSON or JSON-like format
715 * @addtogroup heimbase
718 heim_show(heim_object_t obj
)
720 heim_base2json(obj
, stderr
, HEIM_JSON_F_NO_DATA_DICT
, show_printf
);
724 strbuf_add(void *ctx
, const char *str
)
726 struct heim_strbuf
*strbuf
= ctx
;
734 * Eat the last '\n'; this is used when formatting dict pairs
735 * and array items so that the ',' separating them is never
736 * preceded by a '\n'.
738 if (strbuf
->len
> 0 && strbuf
->str
[strbuf
->len
- 1] == '\n')
744 if ((len
+ 1) > (strbuf
->alloced
- strbuf
->len
)) {
745 size_t new_len
= strbuf
->alloced
+ (strbuf
->alloced
>> 2) + len
+ 1;
748 s
= realloc(strbuf
->str
, new_len
);
754 strbuf
->alloced
= new_len
;
756 /* +1 so we copy the NUL */
757 (void) memcpy(strbuf
->str
+ strbuf
->len
, str
, len
+ 1);
759 if (strbuf
->str
[strbuf
->len
- 1] == '\n' &&
760 strbuf
->flags
& HEIM_JSON_F_ONE_LINE
)
764 #define STRBUF_INIT_SZ 64
767 heim_json_copy_serialize(heim_object_t obj
, heim_json_flags_t flags
, heim_error_t
*error
)
770 struct heim_strbuf strbuf
;
776 memset(&strbuf
, 0, sizeof (strbuf
));
777 strbuf
.str
= malloc(STRBUF_INIT_SZ
);
778 if (strbuf
.str
== NULL
) {
780 *error
= heim_error_create_enomem();
784 strbuf
.alloced
= STRBUF_INIT_SZ
;
785 strbuf
.str
[0] = '\0';
786 strbuf
.flags
= flags
;
788 ret
= heim_base2json(obj
, &strbuf
, flags
, strbuf_add
);
789 if (ret
|| strbuf
.enomem
) {
791 if (strbuf
.enomem
|| ret
== ENOMEM
)
792 *error
= heim_error_create_enomem();
794 *error
= heim_error_create(1, "Impossible to JSON-encode "
800 if (flags
& HEIM_JSON_F_ONE_LINE
) {
801 strbuf
.flags
&= ~HEIM_JSON_F_ONE_LINE
;
802 strbuf_add(&strbuf
, "\n");
804 str
= heim_string_ref_create(strbuf
.str
, free
);
807 *error
= heim_error_create_enomem();