1/*
2 * Copyright (c) 2009-2014 Petri Lehtinen <petri@digip.org>
3 *
4 * Jansson is free software; you can redistribute it and/or modify
5 * it under the terms of the MIT license. See LICENSE for details.
6 */
7
8#ifndef _GNU_SOURCE
9#define _GNU_SOURCE
10#endif
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <assert.h>
16
17#include "jansson.h"
18#include "jansson_private.h"
19#include "strbuffer.h"
20#include "utf.h"
21
22#define MAX_INTEGER_STR_LENGTH  100
23#define MAX_REAL_STR_LENGTH     100
24
25#define FLAGS_TO_INDENT(f)      ((f) & 0x1F)
26#define FLAGS_TO_PRECISION(f)   (((f) >> 11) & 0x1F)
27
28struct object_key {
29    size_t serial;
30    const char *key;
31};
32
33static int dump_to_strbuffer(const char *buffer, size_t size, void *data)
34{
35    return strbuffer_append_bytes((strbuffer_t *)data, buffer, size);
36}
37
38static int dump_to_file(const char *buffer, size_t size, void *data)
39{
40    FILE *dest = (FILE *)data;
41    if(fwrite(buffer, size, 1, dest) != 1)
42        return -1;
43    return 0;
44}
45
46/* 32 spaces (the maximum indentation size) */
47static const char whitespace[] = "                                ";
48
49static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data)
50{
51    if(FLAGS_TO_INDENT(flags) > 0)
52    {
53        int i, ws_count = FLAGS_TO_INDENT(flags);
54
55        if(dump("\n", 1, data))
56            return -1;
57
58        for(i = 0; i < depth; i++)
59        {
60            if(dump(whitespace, ws_count, data))
61                return -1;
62        }
63    }
64    else if(space && !(flags & JSON_COMPACT))
65    {
66        return dump(" ", 1, data);
67    }
68    return 0;
69}
70
71static int dump_string(const char *str, size_t len, json_dump_callback_t dump, void *data, size_t flags)
72{
73    const char *pos, *end, *lim;
74    int32_t codepoint;
75
76    if(dump("\"", 1, data))
77        return -1;
78
79    end = pos = str;
80    lim = str + len;
81    while(1)
82    {
83        const char *text;
84        char seq[13];
85        int length;
86
87        while(end < lim)
88        {
89            end = utf8_iterate(pos, lim - pos, &codepoint);
90            if(!end)
91                return -1;
92
93            /* mandatory escape or control char */
94            if(codepoint == '\\' || codepoint == '"' || codepoint < 0x20)
95                break;
96
97            /* slash */
98            if((flags & JSON_ESCAPE_SLASH) && codepoint == '/')
99                break;
100
101            /* non-ASCII */
102            if((flags & JSON_ENSURE_ASCII) && codepoint > 0x7F)
103                break;
104
105            pos = end;
106        }
107
108        if(pos != str) {
109            if(dump(str, pos - str, data))
110                return -1;
111        }
112
113        if(end == pos)
114            break;
115
116        /* handle \, /, ", and control codes */
117        length = 2;
118        switch(codepoint)
119        {
120            case '\\': text = "\\\\"; break;
121            case '\"': text = "\\\""; break;
122            case '\b': text = "\\b"; break;
123            case '\f': text = "\\f"; break;
124            case '\n': text = "\\n"; break;
125            case '\r': text = "\\r"; break;
126            case '\t': text = "\\t"; break;
127            case '/':  text = "\\/"; break;
128            default:
129            {
130                /* codepoint is in BMP */
131                if(codepoint < 0x10000)
132                {
133                    sprintf(seq, "\\u%04X", codepoint);
134                    length = 6;
135                }
136
137                /* not in BMP -> construct a UTF-16 surrogate pair */
138                else
139                {
140                    int32_t first, last;
141
142                    codepoint -= 0x10000;
143                    first = 0xD800 | ((codepoint & 0xffc00) >> 10);
144                    last = 0xDC00 | (codepoint & 0x003ff);
145
146                    sprintf(seq, "\\u%04X\\u%04X", first, last);
147                    length = 12;
148                }
149
150                text = seq;
151                break;
152            }
153        }
154
155        if(dump(text, length, data))
156            return -1;
157
158        str = pos = end;
159    }
160
161    return dump("\"", 1, data);
162}
163
164static int object_key_compare_keys(const void *key1, const void *key2)
165{
166    return strcmp(((const struct object_key *)key1)->key,
167                  ((const struct object_key *)key2)->key);
168}
169
170static int object_key_compare_serials(const void *key1, const void *key2)
171{
172    size_t a = ((const struct object_key *)key1)->serial;
173    size_t b = ((const struct object_key *)key2)->serial;
174
175    return a < b ? -1 : a == b ? 0 : 1;
176}
177
178static int dump_integer(const json_t *json, json_dump_callback_t dump, const char *fmt,
179                        void *data) {
180    char buffer[MAX_INTEGER_STR_LENGTH];
181    int size;
182
183    size = snprintf(buffer, MAX_INTEGER_STR_LENGTH,
184                    fmt, json_integer_value(json));
185    if(size < 0 || size >= MAX_INTEGER_STR_LENGTH)
186        return -1;
187
188    return dump(buffer, size, data);
189}
190
191static int do_dump(const json_t *json, size_t flags, int depth,
192                   json_dump_callback_t dump, void *data)
193{
194    if(!json)
195        return -1;
196
197    switch(json_typeof(json)) {
198        case JSON_NULL:
199            return dump("null", 4, data);
200
201        case JSON_TRUE:
202            return dump("true", 4, data);
203
204        case JSON_FALSE:
205            return dump("false", 5, data);
206
207        case JSON_INTEGER:
208            return dump_integer(json, dump, "%" JSON_INTEGER_FORMAT, data);
209
210        case JSON_NATURAL:
211            return dump_integer(json, dump, "%" JSON_NATURAL_FORMAT, data);
212
213        case JSON_REAL:
214        {
215            char buffer[MAX_REAL_STR_LENGTH];
216            int size;
217            double value = json_real_value(json);
218
219            size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value,
220                                FLAGS_TO_PRECISION(flags));
221            if(size < 0)
222                return -1;
223
224            return dump(buffer, size, data);
225        }
226
227        case JSON_STRING:
228            return dump_string(json_string_value(json), json_string_length(json), dump, data, flags);
229
230        case JSON_ARRAY:
231        {
232            int i;
233            int n;
234            json_array_t *array;
235
236            /* detect circular references */
237            array = json_to_array(json);
238            if(array->visited)
239                goto array_error;
240            array->visited = 1;
241
242            n = json_array_size(json);
243
244            if(dump("[", 1, data))
245                goto array_error;
246            if(n == 0) {
247                array->visited = 0;
248                return dump("]", 1, data);
249            }
250            if(dump_indent(flags, depth + 1, 0, dump, data))
251                goto array_error;
252
253            for(i = 0; i < n; ++i) {
254                if(do_dump(json_array_get(json, i), flags, depth + 1,
255                           dump, data))
256                    goto array_error;
257
258                if(i < n - 1)
259                {
260                    if(dump(",", 1, data) ||
261                       dump_indent(flags, depth + 1, 1, dump, data))
262                        goto array_error;
263                }
264                else
265                {
266                    if(dump_indent(flags, depth, 0, dump, data))
267                        goto array_error;
268                }
269            }
270
271            array->visited = 0;
272            return dump("]", 1, data);
273
274        array_error:
275            array->visited = 0;
276            return -1;
277        }
278
279        case JSON_OBJECT:
280        {
281            json_object_t *object;
282            void *iter;
283            const char *separator;
284            int separator_length;
285
286            if(flags & JSON_COMPACT) {
287                separator = ":";
288                separator_length = 1;
289            }
290            else {
291                separator = ": ";
292                separator_length = 2;
293            }
294
295            /* detect circular references */
296            object = json_to_object(json);
297            if(object->visited)
298                goto object_error;
299            object->visited = 1;
300
301            iter = json_object_iter((json_t *)json);
302
303            if(dump("{", 1, data))
304                goto object_error;
305            if(!iter) {
306                object->visited = 0;
307                return dump("}", 1, data);
308            }
309            if(dump_indent(flags, depth + 1, 0, dump, data))
310                goto object_error;
311
312            if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER)
313            {
314                struct object_key *keys;
315                size_t size, i;
316                int (*cmp_func)(const void *, const void *);
317
318                size = json_object_size(json);
319                keys = jsonp_malloc(size * sizeof(struct object_key));
320                if(!keys)
321                    goto object_error;
322
323                i = 0;
324                while(iter)
325                {
326                    keys[i].serial = hashtable_iter_serial(iter);
327                    keys[i].key = json_object_iter_key(iter);
328                    iter = json_object_iter_next((json_t *)json, iter);
329                    i++;
330                }
331                assert(i == size);
332
333                if(flags & JSON_SORT_KEYS)
334                    cmp_func = object_key_compare_keys;
335                else
336                    cmp_func = object_key_compare_serials;
337
338                qsort(keys, size, sizeof(struct object_key), cmp_func);
339
340                for(i = 0; i < size; i++)
341                {
342                    const char *key;
343                    json_t *value;
344
345                    key = keys[i].key;
346                    value = json_object_get(json, key);
347                    assert(value);
348
349                    dump_string(key, strlen(key), dump, data, flags);
350                    if(dump(separator, separator_length, data) ||
351                       do_dump(value, flags, depth + 1, dump, data))
352                    {
353                        jsonp_free(keys);
354                        goto object_error;
355                    }
356
357                    if(i < size - 1)
358                    {
359                        if(dump(",", 1, data) ||
360                           dump_indent(flags, depth + 1, 1, dump, data))
361                        {
362                            jsonp_free(keys);
363                            goto object_error;
364                        }
365                    }
366                    else
367                    {
368                        if(dump_indent(flags, depth, 0, dump, data))
369                        {
370                            jsonp_free(keys);
371                            goto object_error;
372                        }
373                    }
374                }
375
376                jsonp_free(keys);
377            }
378            else
379            {
380                /* Don't sort keys */
381
382                while(iter)
383                {
384                    void *next = json_object_iter_next((json_t *)json, iter);
385                    const char *key = json_object_iter_key(iter);
386
387                    dump_string(key, strlen(key), dump, data, flags);
388                    if(dump(separator, separator_length, data) ||
389                       do_dump(json_object_iter_value(iter), flags, depth + 1,
390                               dump, data))
391                        goto object_error;
392
393                    if(next)
394                    {
395                        if(dump(",", 1, data) ||
396                           dump_indent(flags, depth + 1, 1, dump, data))
397                            goto object_error;
398                    }
399                    else
400                    {
401                        if(dump_indent(flags, depth, 0, dump, data))
402                            goto object_error;
403                    }
404
405                    iter = next;
406                }
407            }
408
409            object->visited = 0;
410            return dump("}", 1, data);
411
412        object_error:
413            object->visited = 0;
414            return -1;
415        }
416
417        default:
418            /* not reached */
419            return -1;
420    }
421}
422
423char *json_dumps(const json_t *json, size_t flags)
424{
425    strbuffer_t strbuff;
426    char *result;
427
428    if(strbuffer_init(&strbuff))
429        return NULL;
430
431    if(json_dump_callback(json, dump_to_strbuffer, (void *)&strbuff, flags))
432        result = NULL;
433    else
434        result = jsonp_strdup(strbuffer_value(&strbuff));
435
436    strbuffer_close(&strbuff);
437    return result;
438}
439
440int json_dumpf(const json_t *json, FILE *output, size_t flags)
441{
442    return json_dump_callback(json, dump_to_file, (void *)output, flags);
443}
444
445int json_dump_file(const json_t *json, const char *path, size_t flags)
446{
447    int result;
448
449    FILE *output = fopen(path, "w");
450    if(!output)
451        return -1;
452
453    result = json_dumpf(json, output, flags);
454
455    fclose(output);
456    return result;
457}
458
459int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags)
460{
461    if(!(flags & JSON_ENCODE_ANY)) {
462        if(!json_is_array(json) && !json_is_object(json))
463           return -1;
464    }
465
466    return do_dump(json, flags, 0, callback, data);
467}
468