]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/client/json_writer.c
client: built-in JSON support
[thirdparty/lldpd.git] / src / client / json_writer.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
2 /*
3 * Copyright (c) 2017 Vincent Bernat <bernat@luffy.cx>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/queue.h>
26
27 #include "writer.h"
28 #include "../compat/compat.h"
29 #include "../log.h"
30
31 enum tag {
32 STRING,
33 BOOL,
34 ARRAY,
35 OBJECT
36 };
37
38 struct element {
39 struct element *parent; /* Parent (if any) */
40 TAILQ_ENTRY(element) next; /* Sibling (if any) */
41 char *key; /* Key if parent is an object */
42 enum tag tag; /* Kind of element */
43 union {
44 char *string; /* STRING */
45 int boolean; /* BOOL */
46 TAILQ_HEAD(, element) children; /* ARRAY or OBJECT */
47 };
48 };
49
50 struct json_writer_private {
51 FILE *fh;
52 struct element *root;
53 struct element *current; /* should always be an object */
54 };
55
56 /* Create a new element. If a parent is provided, it will also be attached to
57 * the parent. */
58 static struct element*
59 json_element_new(struct element *parent, const char *key, enum tag tag)
60 {
61 struct element *child = malloc(sizeof(*child));
62 if (child == NULL) fatal(NULL, NULL);
63 child->parent = parent;
64 child->key = key?strdup(key):NULL;
65 child->tag = tag;
66 TAILQ_INIT(&child->children);
67 if (parent) TAILQ_INSERT_TAIL(&parent->children, child, next);
68 return child;
69 }
70
71 /* Free the element content (but not the element itself) */
72 static void
73 json_element_free(struct element *current)
74 {
75 struct element *el, *el_next;
76 switch (current->tag) {
77 case STRING:
78 free(current->string);
79 break;
80 case BOOL:
81 break;
82 case ARRAY:
83 case OBJECT:
84 for (el = TAILQ_FIRST(&current->children);
85 el != NULL;
86 el = el_next) {
87 el_next = TAILQ_NEXT(el, next);
88 json_element_free(el);
89 TAILQ_REMOVE(&current->children, el, next);
90 if (current->tag == OBJECT) free(el->key);
91 free(el);
92 }
93 break;
94 }
95 }
96
97 static void
98 json_free(struct json_writer_private *p)
99 {
100 json_element_free(p->root);
101 free(p->root);
102 }
103
104 static void
105 json_string_dump(FILE *fh, const char *s)
106 {
107 fprintf(fh, "\"");
108 while (*s != '\0') {
109 unsigned int c = *s;
110 size_t len;
111 switch (c) {
112 case '"': fprintf(fh, "\\\""); s++; break;
113 case '\\': fprintf(fh, "\\\\"); s++; break;
114 case '\b': fprintf(fh, "\\\b"); s++; break;
115 case '\f': fprintf(fh, "\\\f"); s++; break;
116 case '\n': fprintf(fh, "\\\n"); s++; break;
117 case '\r': fprintf(fh, "\\\r"); s++; break;
118 case '\t': fprintf(fh, "\\\t"); s++; break;
119 default:
120 len = utf8_validate_cz(s);
121 if (len == 0) {
122 /* Not a valid UTF-8 char, use a
123 * replacement character */
124 fprintf(fh, "\\uFFFD");
125 s++;
126 } else if (c < 0x1f) {
127 /* 7-bit ASCII character */
128 fprintf(fh, "\\u%04X", c);
129 s++;
130 } else {
131 /* UTF-8, write as is */
132 while (len--) fprintf(fh, "%c", *s++);
133 }
134 break;
135 }
136 }
137 fprintf(fh, "\"");
138 }
139
140 /* Dump an element to the specified file handle. */
141 static void
142 json_element_dump(FILE *fh, struct element *current, int indent)
143 {
144 static const char pairs[2][2] = { "{}", "[]" };
145 struct element *el;
146 switch (current->tag) {
147 case STRING:
148 json_string_dump(fh, current->string);
149 break;
150 case BOOL:
151 fprintf(fh, current->boolean?"true":"false");
152 break;
153 case ARRAY:
154 case OBJECT:
155 fprintf(fh, "%c\n%*s", pairs[(current->tag == ARRAY)][0],
156 indent + 2, "");
157 TAILQ_FOREACH(el, &current->children, next) {
158 if (current->tag == OBJECT)
159 fprintf(fh, "\"%s\": ", el->key);
160 json_element_dump(fh, el, indent + 2);
161 if (TAILQ_NEXT(el, next))
162 fprintf(fh, ",\n%*s", indent + 2, "");
163 }
164 fprintf(fh, "\n%*c", indent + 1,
165 pairs[(current->tag == ARRAY)][1]);
166 break;
167 }
168 }
169
170 static void
171 json_dump(struct json_writer_private *p)
172 {
173 json_element_dump(p->fh, p->root, 0);
174 fprintf(p->fh, "\n");
175 }
176
177 static void
178 json_start(struct writer *w, const char *tag,
179 const char *descr)
180 {
181 struct json_writer_private *p = w->priv;
182 struct element *child;
183 struct element *new;
184
185 /* Look for the tag in the current object. */
186 TAILQ_FOREACH(child, &p->current->children, next) {
187 if (!strcmp(child->key, tag)) break;
188 }
189 if (!child)
190 child = json_element_new(p->current, tag, ARRAY);
191
192 /* Queue the new element. */
193 new = json_element_new(child, NULL, OBJECT);
194 p->current = new;
195 }
196
197 static void
198 json_attr(struct writer *w, const char *tag,
199 const char *descr, const char *value)
200 {
201 struct json_writer_private *p = w->priv;
202 struct element *new = json_element_new(p->current, tag, STRING);
203 if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) {
204 new->tag = BOOL;
205 new->boolean = 1;
206 } else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) {
207 new->tag = BOOL;
208 new->boolean = 0;
209 } else {
210 new->string = strdup(value?value:"");
211 }
212 }
213
214 static void
215 json_data(struct writer *w, const char *data)
216 {
217 struct json_writer_private *p = w->priv;
218 struct element *new = json_element_new(p->current, "value", STRING);
219 new->string = strdup(data?data:"");
220 }
221
222 /* When an array has only one member, just remove the array. When an object has
223 * `value` as the only key, remove the object. Moreover, for an object, move the
224 * `name` key outside (inside a new object). This is a recursive function. We
225 * think the depth will be limited. Also, the provided element can be
226 * destroyed. Don't use it after this function!
227 *
228 * During the cleaning process, we will generate array of 1-size objects that
229 * could be turned into an object. We don't do that since people may rely on
230 * this format. Another problem is the format is changing depending on the
231 * number of interfaces or the number of neighbors.
232 */
233 static void
234 json_element_cleanup(struct element *el)
235 {
236 #ifndef ENABLE_JSON0
237 struct element *child, *child_next;
238
239 /* If array with one element, steal the content. Object with only one
240 * value whose key is "value", steal the content. */
241 if ((el->tag == ARRAY || el->tag == OBJECT) &&
242 (child = TAILQ_FIRST(&el->children)) &&
243 !TAILQ_NEXT(child, next) &&
244 (el->tag == ARRAY || !strcmp(child->key, "value"))) {
245 free(child->key);
246 child->key = el->key;
247 child->parent = el->parent;
248 TAILQ_INSERT_BEFORE(el, child, next);
249 TAILQ_REMOVE(&el->parent->children, el, next);
250 free(el);
251 json_element_cleanup(child);
252 return;
253 }
254
255 /* Other kind of arrays, recursively clean */
256 if (el->tag == ARRAY) {
257 for (child = TAILQ_FIRST(&el->children);
258 child;
259 child = child_next) {
260 child_next = TAILQ_NEXT(child, next);
261 json_element_cleanup(child);
262 }
263 return;
264 }
265
266 /* Other kind of objects, recursively clean, but if one key is "name",
267 * use it's value as a key for a new object stealing the existing
268 * one. */
269 if (el->tag == OBJECT) {
270 struct element *name_child = NULL;
271 for (child = TAILQ_FIRST(&el->children);
272 child;
273 child = child_next) {
274 child_next = TAILQ_NEXT(child, next);
275 json_element_cleanup(child);
276 }
277 /* Redo a check to find if we have a "name" key now */
278 for (child = TAILQ_FIRST(&el->children);
279 child;
280 child = child_next) {
281 child_next = TAILQ_NEXT(child, next);
282 if (!strcmp(child->key, "name") &&
283 child->tag == STRING) {
284 name_child = child;
285 }
286 }
287 if (name_child) {
288 struct element *new_el = json_element_new(NULL, NULL, OBJECT);
289 /* Replace el by new_el in parent object/array */
290 new_el->parent = el->parent;
291 TAILQ_INSERT_BEFORE(el, new_el, next);
292 TAILQ_REMOVE(&el->parent->children, el, next);
293 new_el->key = el->key;
294
295 /* new_el is parent of el */
296 el->parent = new_el;
297 el->key = name_child->string; /* stolen */
298 TAILQ_INSERT_TAIL(&new_el->children, el, next);
299
300 /* Remove "name" child */
301 TAILQ_REMOVE(&el->children, name_child, next);
302 free(name_child->key);
303 free(name_child);
304 }
305 return;
306 }
307 #endif
308 }
309
310 static void
311 json_cleanup(struct json_writer_private *p)
312 {
313 json_element_cleanup(p->root);
314 }
315
316 static void
317 json_end(struct writer *w)
318 {
319 struct json_writer_private *p = w->priv;
320 while ((p->current = p->current->parent) != NULL && p->current->tag != OBJECT);
321 if (p->current == NULL) {
322 fatalx("lldpctl", "unbalanced tags");
323 return;
324 }
325
326 /* Display current object if last one */
327 if (p->current == p->root) {
328 json_cleanup(p);
329 json_dump(p);
330 json_free(p);
331 fprintf(p->fh,"\n");
332 fflush(p->fh);
333 p->root = p->current = json_element_new(NULL, NULL, OBJECT);
334 }
335 }
336
337 static void
338 json_finish(struct writer *w)
339 {
340 struct json_writer_private *p = w->priv;
341 if (p->current != p->root)
342 log_warnx("lldpctl", "unbalanced tags");
343 json_free(p);
344 free(p);
345 free(w);
346 }
347
348 struct writer*
349 json_init(FILE *fh)
350 {
351 struct writer *result;
352 struct json_writer_private *priv;
353
354 priv = malloc(sizeof(*priv));
355 if (priv == NULL) fatal(NULL, NULL);
356
357 priv->fh = fh;
358 priv->root = priv->current = json_element_new(NULL, NULL, OBJECT);
359
360 result = malloc(sizeof(*result));
361 if (result == NULL) fatal(NULL, NULL);
362
363 result->priv = priv;
364 result->start = json_start;
365 result->attr = json_attr;
366 result->data = json_data;
367 result->end = json_end;
368 result->finish = json_finish;
369
370 return result;
371 }