From fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 20:02:34 +0200 Subject: Adding upstream version 1.0.18. Signed-off-by: Daniel Baumann --- src/client/json_writer.c | 374 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 src/client/json_writer.c (limited to 'src/client/json_writer.c') diff --git a/src/client/json_writer.c b/src/client/json_writer.c new file mode 100644 index 0000000..dc6665a --- /dev/null +++ b/src/client/json_writer.c @@ -0,0 +1,374 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2017 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "writer.h" +#include "../compat/compat.h" +#include "../log.h" + +enum tag { STRING, BOOL, ARRAY, OBJECT }; + +struct element { + struct element *parent; /* Parent (if any) */ + TAILQ_ENTRY(element) next; /* Sibling (if any) */ + char *key; /* Key if parent is an object */ + enum tag tag; /* Kind of element */ + union { + char *string; /* STRING */ + int boolean; /* BOOL */ + TAILQ_HEAD(, element) children; /* ARRAY or OBJECT */ + }; +}; + +struct json_writer_private { + FILE *fh; + int variant; + struct element *root; + struct element *current; /* should always be an object */ +}; + +/* Create a new element. If a parent is provided, it will also be attached to + * the parent. */ +static struct element * +json_element_new(struct element *parent, const char *key, enum tag tag) +{ + struct element *child = malloc(sizeof(*child)); + if (child == NULL) fatal(NULL, NULL); + child->parent = parent; + child->key = key ? strdup(key) : NULL; + child->tag = tag; + TAILQ_INIT(&child->children); + if (parent) TAILQ_INSERT_TAIL(&parent->children, child, next); + return child; +} + +/* Free the element content (but not the element itself) */ +static void +json_element_free(struct element *current) +{ + struct element *el, *el_next; + switch (current->tag) { + case STRING: + free(current->string); + break; + case BOOL: + break; + case ARRAY: + case OBJECT: + for (el = TAILQ_FIRST(¤t->children); el != NULL; el = el_next) { + el_next = TAILQ_NEXT(el, next); + json_element_free(el); + TAILQ_REMOVE(¤t->children, el, next); + if (current->tag == OBJECT) free(el->key); + free(el); + } + break; + } +} + +static void +json_free(struct json_writer_private *p) +{ + json_element_free(p->root); + free(p->root); +} + +static void +json_string_dump(FILE *fh, const char *s) +{ + fprintf(fh, "\""); + while (*s != '\0') { + unsigned int c = *s; + size_t len; + switch (c) { + case '"': + fprintf(fh, "\\\""); + s++; + break; + case '\\': + fprintf(fh, "\\\\"); + s++; + break; + case '\b': + fprintf(fh, "\\b"); + s++; + break; + case '\f': + fprintf(fh, "\\f"); + s++; + break; + case '\n': + fprintf(fh, "\\n"); + s++; + break; + case '\r': + fprintf(fh, "\\r"); + s++; + break; + case '\t': + fprintf(fh, "\\t"); + s++; + break; + default: + len = utf8_validate_cz(s); + if (len == 0) { + /* Not a valid UTF-8 char, use a + * replacement character */ + fprintf(fh, "\\uFFFD"); + s++; + } else if (c < 0x1f) { + /* 7-bit ASCII character */ + fprintf(fh, "\\u%04X", c); + s++; + } else { + /* UTF-8, write as is */ + while (len--) + fprintf(fh, "%c", *s++); + } + break; + } + } + fprintf(fh, "\""); +} + +/* Dump an element to the specified file handle. */ +static void +json_element_dump(FILE *fh, struct element *current, int indent) +{ + static const char pairs[2][2] = { "{}", "[]" }; + struct element *el; + switch (current->tag) { + case STRING: + json_string_dump(fh, current->string); + break; + case BOOL: + fprintf(fh, current->boolean ? "true" : "false"); + break; + case ARRAY: + case OBJECT: + fprintf(fh, "%c\n%*s", pairs[(current->tag == ARRAY)][0], indent + 2, + ""); + TAILQ_FOREACH (el, ¤t->children, next) { + if (current->tag == OBJECT) fprintf(fh, "\"%s\": ", el->key); + json_element_dump(fh, el, indent + 2); + if (TAILQ_NEXT(el, next)) fprintf(fh, ",\n%*s", indent + 2, ""); + } + fprintf(fh, "\n%*c", indent + 1, pairs[(current->tag == ARRAY)][1]); + break; + } +} + +static void +json_dump(struct json_writer_private *p) +{ + json_element_dump(p->fh, p->root, 0); + fprintf(p->fh, "\n"); +} + +static void +json_start(struct writer *w, const char *tag, const char *descr) +{ + struct json_writer_private *p = w->priv; + struct element *child; + struct element *new; + + /* Look for the tag in the current object. */ + TAILQ_FOREACH (child, &p->current->children, next) { + if (!strcmp(child->key, tag)) break; + } + if (!child) child = json_element_new(p->current, tag, ARRAY); + + /* Queue the new element. */ + new = json_element_new(child, NULL, OBJECT); + p->current = new; +} + +static void +json_attr(struct writer *w, const char *tag, const char *descr, const char *value) +{ + struct json_writer_private *p = w->priv; + struct element *new = json_element_new(p->current, tag, STRING); + if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) { + new->tag = BOOL; + new->boolean = 1; + } else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) { + new->tag = BOOL; + new->boolean = 0; + } else { + new->string = strdup(value ? value : ""); + } +} + +static void +json_data(struct writer *w, const char *data) +{ + struct json_writer_private *p = w->priv; + struct element *new = json_element_new(p->current, "value", STRING); + new->string = strdup(data ? data : ""); +} + +/* When an array has only one member, just remove the array. When an object has + * `value` as the only key, remove the object. Moreover, for an object, move the + * `name` key outside (inside a new object). This is a recursive function. We + * think the depth will be limited. Also, the provided element can be + * destroyed. Don't use it after this function! + * + * During the cleaning process, we will generate array of 1-size objects that + * could be turned into an object. We don't do that since people may rely on + * this format. Another problem is the format is changing depending on the + * number of interfaces or the number of neighbors. + */ +static void +json_element_cleanup(struct element *el) +{ +#ifndef ENABLE_JSON0 + struct element *child, *child_next; + + /* If array with one element, steal the content. Object with only one + * value whose key is "value", steal the content. */ + if ((el->tag == ARRAY || el->tag == OBJECT) && + (child = TAILQ_FIRST(&el->children)) && !TAILQ_NEXT(child, next) && + (el->tag == ARRAY || !strcmp(child->key, "value"))) { + free(child->key); + child->key = el->key; + child->parent = el->parent; + TAILQ_INSERT_BEFORE(el, child, next); + TAILQ_REMOVE(&el->parent->children, el, next); + free(el); + json_element_cleanup(child); + return; + } + + /* Other kind of arrays, recursively clean */ + if (el->tag == ARRAY) { + for (child = TAILQ_FIRST(&el->children); child; child = child_next) { + child_next = TAILQ_NEXT(child, next); + json_element_cleanup(child); + } + return; + } + + /* Other kind of objects, recursively clean, but if one key is "name", + * use it's value as a key for a new object stealing the existing + * one. */ + if (el->tag == OBJECT) { + struct element *name_child = NULL; + for (child = TAILQ_FIRST(&el->children); child; child = child_next) { + child_next = TAILQ_NEXT(child, next); + json_element_cleanup(child); + } + /* Redo a check to find if we have a "name" key now */ + for (child = TAILQ_FIRST(&el->children); child; child = child_next) { + child_next = TAILQ_NEXT(child, next); + if (!strcmp(child->key, "name") && child->tag == STRING) { + name_child = child; + } + } + if (name_child) { + struct element *new_el = json_element_new(NULL, NULL, OBJECT); + /* Replace el by new_el in parent object/array */ + new_el->parent = el->parent; + TAILQ_INSERT_BEFORE(el, new_el, next); + TAILQ_REMOVE(&el->parent->children, el, next); + new_el->key = el->key; + + /* new_el is parent of el */ + el->parent = new_el; + el->key = name_child->string; /* stolen */ + TAILQ_INSERT_TAIL(&new_el->children, el, next); + + /* Remove "name" child */ + TAILQ_REMOVE(&el->children, name_child, next); + free(name_child->key); + free(name_child); + } + return; + } +#endif +} + +static void +json_cleanup(struct json_writer_private *p) +{ + if (p->variant != 0) json_element_cleanup(p->root); +} + +static void +json_end(struct writer *w) +{ + struct json_writer_private *p = w->priv; + while ((p->current = p->current->parent) != NULL && p->current->tag != OBJECT) + ; + if (p->current == NULL) { + fatalx("lldpctl", "unbalanced tags"); + return; + } + + /* Display current object if last one */ + if (p->current == p->root) { + json_cleanup(p); + json_dump(p); + json_free(p); + fprintf(p->fh, "\n"); + fflush(p->fh); + p->root = p->current = json_element_new(NULL, NULL, OBJECT); + } +} + +static void +json_finish(struct writer *w) +{ + struct json_writer_private *p = w->priv; + if (p->current != p->root) log_warnx("lldpctl", "unbalanced tags"); + json_free(p); + free(p); + free(w); +} + +struct writer * +json_init(FILE *fh, int variant) +{ + struct writer *result; + struct json_writer_private *priv; + + priv = malloc(sizeof(*priv)); + if (priv == NULL) fatal(NULL, NULL); + + priv->fh = fh; + priv->root = priv->current = json_element_new(NULL, NULL, OBJECT); + priv->variant = variant; + + result = malloc(sizeof(*result)); + if (result == NULL) fatal(NULL, NULL); + + result->priv = priv; + result->start = json_start; + result->attr = json_attr; + result->data = json_data; + result->end = json_end; + result->finish = json_finish; + + return result; +} -- cgit v1.2.3