]>
git.ipfire.org Git - thirdparty/lldpd.git/blob - src/client/json_writer.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
3 * Copyright (c) 2017 Vincent Bernat <bernat@luffy.cx>
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.
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.
25 #include <sys/queue.h>
28 #include "../compat/compat.h"
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 */
44 char *string
; /* STRING */
45 int boolean
; /* BOOL */
46 TAILQ_HEAD(, element
) children
; /* ARRAY or OBJECT */
50 struct json_writer_private
{
54 struct element
*current
; /* should always be an object */
57 /* Create a new element. If a parent is provided, it will also be attached to
59 static struct element
*
60 json_element_new(struct element
*parent
, const char *key
, enum tag tag
)
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
;
67 TAILQ_INIT(&child
->children
);
68 if (parent
) TAILQ_INSERT_TAIL(&parent
->children
, child
, next
);
72 /* Free the element content (but not the element itself) */
74 json_element_free(struct element
*current
)
76 struct element
*el
, *el_next
;
77 switch (current
->tag
) {
79 free(current
->string
);
85 for (el
= TAILQ_FIRST(¤t
->children
);
88 el_next
= TAILQ_NEXT(el
, next
);
89 json_element_free(el
);
90 TAILQ_REMOVE(¤t
->children
, el
, next
);
91 if (current
->tag
== OBJECT
) free(el
->key
);
99 json_free(struct json_writer_private
*p
)
101 json_element_free(p
->root
);
106 json_string_dump(FILE *fh
, const char *s
)
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;
121 len
= utf8_validate_cz(s
);
123 /* Not a valid UTF-8 char, use a
124 * replacement character */
125 fprintf(fh
, "\\uFFFD");
127 } else if (c
< 0x1f) {
128 /* 7-bit ASCII character */
129 fprintf(fh
, "\\u%04X", c
);
132 /* UTF-8, write as is */
133 while (len
--) fprintf(fh
, "%c", *s
++);
141 /* Dump an element to the specified file handle. */
143 json_element_dump(FILE *fh
, struct element
*current
, int indent
)
145 static const char pairs
[2][2] = { "{}", "[]" };
147 switch (current
->tag
) {
149 json_string_dump(fh
, current
->string
);
152 fprintf(fh
, current
->boolean
?"true":"false");
156 fprintf(fh
, "%c\n%*s", pairs
[(current
->tag
== ARRAY
)][0],
158 TAILQ_FOREACH(el
, ¤t
->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, "");
165 fprintf(fh
, "\n%*c", indent
+ 1,
166 pairs
[(current
->tag
== ARRAY
)][1]);
172 json_dump(struct json_writer_private
*p
)
174 json_element_dump(p
->fh
, p
->root
, 0);
175 fprintf(p
->fh
, "\n");
179 json_start(struct writer
*w
, const char *tag
,
182 struct json_writer_private
*p
= w
->priv
;
183 struct element
*child
;
186 /* Look for the tag in the current object. */
187 TAILQ_FOREACH(child
, &p
->current
->children
, next
) {
188 if (!strcmp(child
->key
, tag
)) break;
191 child
= json_element_new(p
->current
, tag
, ARRAY
);
193 /* Queue the new element. */
194 new = json_element_new(child
, NULL
, OBJECT
);
199 json_attr(struct writer
*w
, const char *tag
,
200 const char *descr
, const char *value
)
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"))) {
207 } else if (value
&& (!strcmp(value
, "no") || !strcmp(value
, "off"))) {
211 new->string
= strdup(value
?value
:"");
216 json_data(struct writer
*w
, const char *data
)
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
:"");
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!
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.
235 json_element_cleanup(struct element
*el
)
238 struct element
*child
, *child_next
;
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"))) {
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
);
252 json_element_cleanup(child
);
256 /* Other kind of arrays, recursively clean */
257 if (el
->tag
== ARRAY
) {
258 for (child
= TAILQ_FIRST(&el
->children
);
260 child
= child_next
) {
261 child_next
= TAILQ_NEXT(child
, next
);
262 json_element_cleanup(child
);
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
270 if (el
->tag
== OBJECT
) {
271 struct element
*name_child
= NULL
;
272 for (child
= TAILQ_FIRST(&el
->children
);
274 child
= child_next
) {
275 child_next
= TAILQ_NEXT(child
, next
);
276 json_element_cleanup(child
);
278 /* Redo a check to find if we have a "name" key now */
279 for (child
= TAILQ_FIRST(&el
->children
);
281 child
= child_next
) {
282 child_next
= TAILQ_NEXT(child
, next
);
283 if (!strcmp(child
->key
, "name") &&
284 child
->tag
== STRING
) {
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
;
296 /* new_el is parent of el */
298 el
->key
= name_child
->string
; /* stolen */
299 TAILQ_INSERT_TAIL(&new_el
->children
, el
, next
);
301 /* Remove "name" child */
302 TAILQ_REMOVE(&el
->children
, name_child
, next
);
303 free(name_child
->key
);
312 json_cleanup(struct json_writer_private
*p
)
315 json_element_cleanup(p
->root
);
319 json_end(struct writer
*w
)
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");
328 /* Display current object if last one */
329 if (p
->current
== p
->root
) {
335 p
->root
= p
->current
= json_element_new(NULL
, NULL
, OBJECT
);
340 json_finish(struct writer
*w
)
342 struct json_writer_private
*p
= w
->priv
;
343 if (p
->current
!= p
->root
)
344 log_warnx("lldpctl", "unbalanced tags");
351 json_init(FILE *fh
, int variant
)
353 struct writer
*result
;
354 struct json_writer_private
*priv
;
356 priv
= malloc(sizeof(*priv
));
357 if (priv
== NULL
) fatal(NULL
, NULL
);
360 priv
->root
= priv
->current
= json_element_new(NULL
, NULL
, OBJECT
);
361 priv
->variant
= variant
;
363 result
= malloc(sizeof(*result
));
364 if (result
== NULL
) fatal(NULL
, NULL
);
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
;