1 #include <inttypes.h>
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <talloc.h>
5 #include "sprinter.h"
6 
7 struct sprinter_json {
8     struct sprinter vtable;
9     FILE *stream;
10     /* Top of the state stack, or NULL if the printer is not currently
11      * inside any aggregate types. */
12     struct json_state *state;
13 
14     /* A flag to signify that a separator should be inserted in the
15      * output as soon as possible.
16      */
17     bool insert_separator;
18 };
19 
20 struct json_state {
21     struct json_state *parent;
22     /* True if nothing has been printed in this aggregate yet.
23      * Suppresses the comma before a value. */
24     bool first;
25     /* The character that closes the current aggregate. */
26     char close;
27 };
28 
29 /* Helper function to set up the stream to print a value.  If this
30  * value follows another value, prints a comma. */
31 static struct sprinter_json *
json_begin_value(struct sprinter * sp)32 json_begin_value (struct sprinter *sp)
33 {
34     struct sprinter_json *spj = (struct sprinter_json *) sp;
35 
36     if (spj->state) {
37 	if (! spj->state->first) {
38 	    fputc (',', spj->stream);
39 	    if (spj->insert_separator) {
40 		fputc ('\n', spj->stream);
41 		spj->insert_separator = false;
42 	    } else {
43 		fputc (' ', spj->stream);
44 	    }
45 	} else {
46 	    spj->state->first = false;
47 	}
48     }
49     return spj;
50 }
51 
52 /* Helper function to begin an aggregate type.  Prints the open
53  * character and pushes a new state frame. */
54 static void
json_begin_aggregate(struct sprinter * sp,char open,char close)55 json_begin_aggregate (struct sprinter *sp, char open, char close)
56 {
57     struct sprinter_json *spj = json_begin_value (sp);
58     struct json_state *state = talloc (spj, struct json_state);
59 
60     fputc (open, spj->stream);
61     state->parent = spj->state;
62     state->first = true;
63     state->close = close;
64     spj->state = state;
65 }
66 
67 static void
json_begin_map(struct sprinter * sp)68 json_begin_map (struct sprinter *sp)
69 {
70     json_begin_aggregate (sp, '{', '}');
71 }
72 
73 static void
json_begin_list(struct sprinter * sp)74 json_begin_list (struct sprinter *sp)
75 {
76     json_begin_aggregate (sp, '[', ']');
77 }
78 
79 static void
json_end(struct sprinter * sp)80 json_end (struct sprinter *sp)
81 {
82     struct sprinter_json *spj = (struct sprinter_json *) sp;
83     struct json_state *state = spj->state;
84 
85     fputc (spj->state->close, spj->stream);
86     spj->state = state->parent;
87     talloc_free (state);
88     if (spj->state == NULL)
89 	fputc ('\n', spj->stream);
90 }
91 
92 /* This implementation supports embedded NULs as allowed by the JSON
93  * specification and Unicode.  Support for *parsing* embedded NULs
94  * varies, but is generally not a problem outside of C-based parsers
95  * (Python's json module and Emacs' json.el take embedded NULs in
96  * stride). */
97 static void
json_string_len(struct sprinter * sp,const char * val,size_t len)98 json_string_len (struct sprinter *sp, const char *val, size_t len)
99 {
100     static const char *const escapes[] = {
101 	['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
102 	['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
103     };
104     struct sprinter_json *spj = json_begin_value (sp);
105 
106     fputc ('"', spj->stream);
107     for (; len; ++val, --len) {
108 	unsigned char ch = *val;
109 	if (ch < ARRAY_SIZE (escapes) && escapes[ch])
110 	    fputs (escapes[ch], spj->stream);
111 	else if (ch >= 32)
112 	    fputc (ch, spj->stream);
113 	else
114 	    fprintf (spj->stream, "\\u%04x", ch);
115     }
116     fputc ('"', spj->stream);
117 }
118 
119 static void
json_string(struct sprinter * sp,const char * val)120 json_string (struct sprinter *sp, const char *val)
121 {
122     if (val == NULL)
123 	val = "";
124     json_string_len (sp, val, strlen (val));
125 }
126 
127 static void
json_integer(struct sprinter * sp,int64_t val)128 json_integer (struct sprinter *sp, int64_t val)
129 {
130     struct sprinter_json *spj = json_begin_value (sp);
131 
132     fprintf (spj->stream, "%" PRId64, val);
133 }
134 
135 static void
json_boolean(struct sprinter * sp,bool val)136 json_boolean (struct sprinter *sp, bool val)
137 {
138     struct sprinter_json *spj = json_begin_value (sp);
139 
140     fputs (val ? "true" : "false", spj->stream);
141 }
142 
143 static void
json_null(struct sprinter * sp)144 json_null (struct sprinter *sp)
145 {
146     struct sprinter_json *spj = json_begin_value (sp);
147 
148     fputs ("null", spj->stream);
149 }
150 
151 static void
json_map_key(struct sprinter * sp,const char * key)152 json_map_key (struct sprinter *sp, const char *key)
153 {
154     struct sprinter_json *spj = (struct sprinter_json *) sp;
155 
156     json_string (sp, key);
157     fputs (": ", spj->stream);
158     spj->state->first = true;
159 }
160 
161 static void
json_set_prefix(unused (struct sprinter * sp),unused (const char * name))162 json_set_prefix (unused (struct sprinter *sp), unused (const char *name))
163 {
164 }
165 
166 static void
json_separator(struct sprinter * sp)167 json_separator (struct sprinter *sp)
168 {
169     struct sprinter_json *spj = (struct sprinter_json *) sp;
170 
171     spj->insert_separator = true;
172 }
173 
174 struct sprinter *
sprinter_json_create(const void * ctx,FILE * stream)175 sprinter_json_create (const void *ctx, FILE *stream)
176 {
177     static const struct sprinter_json template = {
178 	.vtable = {
179 	    .begin_map = json_begin_map,
180 	    .begin_list = json_begin_list,
181 	    .end = json_end,
182 	    .string = json_string,
183 	    .string_len = json_string_len,
184 	    .integer = json_integer,
185 	    .boolean = json_boolean,
186 	    .null = json_null,
187 	    .map_key = json_map_key,
188 	    .separator = json_separator,
189 	    .set_prefix = json_set_prefix,
190 	    .is_text_printer = false,
191 	}
192     };
193     struct sprinter_json *res;
194 
195     res = talloc (ctx, struct sprinter_json);
196     if (! res)
197 	return NULL;
198 
199     *res = template;
200     res->stream = stream;
201     return &res->vtable;
202 }
203