]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/client/json_writer.c
client: add "json0" to the list of available formats
[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 int variant;
53 struct element *root;
54 struct element *current; /* should always be an object */
55 };
56
57 /* Create a new element. If a parent is provided, it will also be attached to
58 * the parent. */
59 static struct element*
60 json_element_new(struct element *parent, const char *key, enum tag tag)
61 {
62 struct element *child = malloc(sizeof(*child));
63 if (child == NULL) fatal(NULL, NULL);
64 child->parent = parent;
65 child->key = key?strdup(key):NULL;
66 child->tag = tag;
67 TAILQ_INIT(&child->children);
68 if (parent) TAILQ_INSERT_TAIL(&parent->children, child, next);
69 return child;
70 }
71
72 /* Free the element content (but not the element itself) */
73 static void
74 json_element_free(struct element *current)
75 {
76 struct element *el, *el_next;
77 switch (current->tag) {
78 case STRING:
79 free(current->string);
80 break;
81 case BOOL:
82 break;
83 case ARRAY:
84 case OBJECT:
85 for (el = TAILQ_FIRST(&current->children);
86 el != NULL;
87 el = el_next) {
88 el_next = TAILQ_NEXT(el, next);
89 json_element_free(el);
90 TAILQ_REMOVE(&current->children, el, next);
91 if (current->tag == OBJECT) free(el->key);
92 free(el);
93 }
94 break;
95 }
96 }
97
98 static void
99 json_free(struct json_writer_private *p)
100 {
101 json_element_free(p->root);
102 free(p->root);
103 }
104
105 static void
106 json_string_dump(FILE *fh, const char *s)
107 {
108 fprintf(fh, "\"");
109 while (*s != '\0') {
110 unsigned int c = *s;
111 size_t len;
112 switch (c) {
113 case '"': fprintf(fh, "\\\""); s++; break;
114 case '\\': fprintf(fh, "\\\\"); s++; break;
115 case '\b': fprintf(fh, "\\\b"); s++; break;
116 case '\f': fprintf(fh, "\\\f"); s++; break;
117 case '\n': fprintf(fh, "\\\n"); s++; break;
118 case '\r': fprintf(fh, "\\\r"); s++; break;
119 case '\t': fprintf(fh, "\\\t"); s++; break;
120 default:
121 len = utf8_validate_cz(s);
122 if (len == 0) {
123 /* Not a valid UTF-8 char, use a
124 * replacement character */
125 fprintf(fh, "\\uFFFD");
126 s++;
127 } else if (c < 0x1f) {
128 /* 7-bit ASCII character */
129 fprintf(fh, "\\u%04X", c);
130 s++;
131 } else {
132 /* UTF-8, write as is */
133 while (len--) fprintf(fh, "%c", *s++);
134 }
135 break;
136 }
137 }
138 fprintf(fh, "\"");
139 }
140
141 /* Dump an element to the specified file handle. */
142 static void
143 json_element_dump(FILE *fh, struct element *current, int indent)
144 {
145 static const char pairs[2][2] = { "{}", "[]" };
146 struct element *el;
147 switch (current->tag) {
148 case STRING:
149 json_string_dump(fh, current->string);
150 break;
151 case BOOL:
152 fprintf(fh, current->boolean?"true":"false");
153 break;
154 case ARRAY:
155 case OBJECT:
156 fprintf(fh, "%c\n%*s", pairs[(current->tag == ARRAY)][0],
157 indent + 2, "");
158 TAILQ_FOREACH(el, &current->children, next) {
159 if (current->tag == OBJECT)
160 fprintf(fh, "\"%s\": ", el->key);
161 json_element_dump(fh, el, indent + 2);
162 if (TAILQ_NEXT(el, next))
163 fprintf(fh, ",\n%*s", indent + 2, "");
164 }
165 fprintf(fh, "\n%*c", indent + 1,
166 pairs[(current->tag == ARRAY)][1]);
167 break;
168 }
169 }
170
171 static void
172 json_dump(struct json_writer_private *p)
173 {
174 json_element_dump(p->fh, p->root, 0);
175 fprintf(p->fh, "\n");
176 }
177
178 static void
179 json_start(struct writer *w, const char *tag,
180 const char *descr)
181 {
182 struct json_writer_private *p = w->priv;
183 struct element *child;
184 struct element *new;
185
186 /* Look for the tag in the current object. */
187 TAILQ_FOREACH(child, &p->current->children, next) {
188 if (!strcmp(child->key, tag)) break;
189 }
190 if (!child)
191 child = json_element_new(p->current, tag, ARRAY);
192
193 /* Queue the new element. */
194 new = json_element_new(child, NULL, OBJECT);
195 p->current = new;
196 }
197
198 static void
199 json_attr(struct writer *w, const char *tag,
200 const char *descr, const char *value)
201 {
202 struct json_writer_private *p = w->priv;
203 struct element *new = json_element_new(p->current, tag, STRING);
204 if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) {
205 new->tag = BOOL;
206 new->boolean = 1;
207 } else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) {
208 new->tag = BOOL;
209 new->boolean = 0;
210 } else {
211 new->string = strdup(value?value:"");
212 }
213 }
214
215 static void
216 json_data(struct writer *w, const char *data)
217 {
218 struct json_writer_private *p = w->priv;
219 struct element *new = json_element_new(p->current, "value", STRING);
220 new->string = strdup(data?data:"");
221 }
222
223 /* When an array has only one member, just remove the array. When an object has
224 * `value` as the only key, remove the object. Moreover, for an object, move the
225 * `name` key outside (inside a new object). This is a recursive function. We
226 * think the depth will be limited. Also, the provided element can be
227 * destroyed. Don't use it after this function!
228 *
229 * During the cleaning process, we will generate array of 1-size objects that
230 * could be turned into an object. We don't do that since people may rely on
231 * this format. Another problem is the format is changing depending on the
232 * number of interfaces or the number of neighbors.
233 */
234 static void
235 json_element_cleanup(struct element *el)
236 {
237 #ifndef ENABLE_JSON0
238 struct element *child, *child_next;
239
240 /* If array with one element, steal the content. Object with only one
241 * value whose key is "value", steal the content. */
242 if ((el->tag == ARRAY || el->tag == OBJECT) &&
243 (child = TAILQ_FIRST(&el->children)) &&
244 !TAILQ_NEXT(child, next) &&
245 (el->tag == ARRAY || !strcmp(child->key, "value"))) {
246 free(child->key);
247 child->key = el->key;
248 child->parent = el->parent;
249 TAILQ_INSERT_BEFORE(el, child, next);
250 TAILQ_REMOVE(&el->parent->children, el, next);
251 free(el);
252 json_element_cleanup(child);
253 return;
254 }
255
256 /* Other kind of arrays, recursively clean */
257 if (el->tag == ARRAY) {
258 for (child = TAILQ_FIRST(&el->children);
259 child;
260 child = child_next) {
261 child_next = TAILQ_NEXT(child, next);
262 json_element_cleanup(child);
263 }
264 return;
265 }
266
267 /* Other kind of objects, recursively clean, but if one key is "name",
268 * use it's value as a key for a new object stealing the existing
269 * one. */
270 if (el->tag == OBJECT) {
271 struct element *name_child = NULL;
272 for (child = TAILQ_FIRST(&el->children);
273 child;
274 child = child_next) {
275 child_next = TAILQ_NEXT(child, next);
276 json_element_cleanup(child);
277 }
278 /* Redo a check to find if we have a "name" key now */
279 for (child = TAILQ_FIRST(&el->children);
280 child;
281 child = child_next) {
282 child_next = TAILQ_NEXT(child, next);
283 if (!strcmp(child->key, "name") &&
284 child->tag == STRING) {
285 name_child = child;
286 }
287 }
288 if (name_child) {
289 struct element *new_el = json_element_new(NULL, NULL, OBJECT);
290 /* Replace el by new_el in parent object/array */
291 new_el->parent = el->parent;
292 TAILQ_INSERT_BEFORE(el, new_el, next);
293 TAILQ_REMOVE(&el->parent->children, el, next);
294 new_el->key = el->key;
295
296 /* new_el is parent of el */
297 el->parent = new_el;
298 el->key = name_child->string; /* stolen */
299 TAILQ_INSERT_TAIL(&new_el->children, el, next);
300
301 /* Remove "name" child */
302 TAILQ_REMOVE(&el->children, name_child, next);
303 free(name_child->key);
304 free(name_child);
305 }
306 return;
307 }
308 #endif
309 }
310
311 static void
312 json_cleanup(struct json_writer_private *p)
313 {
314 if (p->variant != 0)
315 json_element_cleanup(p->root);
316 }
317
318 static void
319 json_end(struct writer *w)
320 {
321 struct json_writer_private *p = w->priv;
322 while ((p->current = p->current->parent) != NULL && p->current->tag != OBJECT);
323 if (p->current == NULL) {
324 fatalx("lldpctl", "unbalanced tags");
325 return;
326 }
327
328 /* Display current object if last one */
329 if (p->current == p->root) {
330 json_cleanup(p);
331 json_dump(p);
332 json_free(p);
333 fprintf(p->fh,"\n");
334 fflush(p->fh);
335 p->root = p->current = json_element_new(NULL, NULL, OBJECT);
336 }
337 }
338
339 static void
340 json_finish(struct writer *w)
341 {
342 struct json_writer_private *p = w->priv;
343 if (p->current != p->root)
344 log_warnx("lldpctl", "unbalanced tags");
345 json_free(p);
346 free(p);
347 free(w);
348 }
349
350 struct writer*
351 json_init(FILE *fh, int variant)
352 {
353 struct writer *result;
354 struct json_writer_private *priv;
355
356 priv = malloc(sizeof(*priv));
357 if (priv == NULL) fatal(NULL, NULL);
358
359 priv->fh = fh;
360 priv->root = priv->current = json_element_new(NULL, NULL, OBJECT);
361 priv->variant = variant;
362
363 result = malloc(sizeof(*result));
364 if (result == NULL) fatal(NULL, NULL);
365
366 result->priv = priv;
367 result->start = json_start;
368 result->attr = json_attr;
369 result->data = json_data;
370 result->end = json_end;
371 result->finish = json_finish;
372
373 return result;
374 }