From 261ee12c04a922fe53addaa6aed0aa87390a7eb8 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 06:55:30 +0000 Subject: [PATCH 01/42] Feature: Added stub src/json.[ch] --- src/Makefile.am | 2 ++ src/json.c | 23 +++++++++++++++++++++++ src/json.h | 15 +++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/json.c create mode 100644 src/json.h diff --git a/src/Makefile.am b/src/Makefile.am index d1c29bee..50ac53b2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,6 +35,7 @@ noinst_HEADERS = \ module.h \ reportxml.h \ reportxml_helper.h \ + json.h \ listensocket.h \ fastevent.h \ event.h \ @@ -82,6 +83,7 @@ icecast_SOURCES = \ module.c \ reportxml.c \ reportxml_helper.c \ + json.c \ listensocket.c \ fastevent.c \ format.c \ diff --git a/src/json.c b/src/json.c new file mode 100644 index 00000000..11f72e1f --- /dev/null +++ b/src/json.c @@ -0,0 +1,23 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018-2020, Philipp "ph3-der-loewe" Schafft , + */ + +/** + * This file contains functions for rendering JSON. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "json.h" + +#include "logging.h" +#define CATMODULE "json" + diff --git a/src/json.h b/src/json.h new file mode 100644 index 00000000..8c2fa8f0 --- /dev/null +++ b/src/json.h @@ -0,0 +1,15 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2020, Philipp "ph3-der-loewe" Schafft , + */ + +/* This file contains functions for rendering JSON. */ + +#ifndef __JSON_H__ +#define __JSON_H__ + + +#endif From 5a72f70dcb30422b776e2386d83b961360f4ace5 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 07:06:13 +0000 Subject: [PATCH 02/42] Feature: Outlined basic API --- src/json.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/json.h b/src/json.h index 8c2fa8f0..cc6a38f4 100644 --- a/src/json.h +++ b/src/json.h @@ -11,5 +11,29 @@ #ifndef __JSON_H__ #define __JSON_H__ +#include + +#define JSON_RENDERER_FLAGS_NONE 0 + +typedef enum { + JSON_ELEMENT_TYPE_OBJECT, + JSON_ELEMENT_TYPE_ARRAY +} json_element_type_t; + +typedef struct json_renderer_tag json_renderer_t; + +json_renderer_t * json_renderer_create(unsigned int flags); +void json_renderer_destroy(json_renderer_t *renderer); + +char * json_renderer_finish(json_renderer_t *renderer); + +void json_renderer_begin(json_renderer_t *renderer, json_element_type_t type); +void json_renderer_end(json_renderer_t *renderer); + +void json_renderer_write_null(json_renderer_t *renderer); +void json_renderer_write_boolean(json_renderer_t *renderer, int val); +void json_renderer_write_string(json_renderer_t *renderer, const char *string, unsigned int flags); +void json_renderer_write_int(json_renderer_t *renderer, intmax_t val); +void json_renderer_write_uint(json_renderer_t *renderer, uintmax_t val); #endif From 2400df01ca9a142911f6244ef158f3163ca02a53 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 08:47:16 +0000 Subject: [PATCH 03/42] Feature: Implement JSON renderer --- src/json.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/json.h | 3 +- 2 files changed, 330 insertions(+), 1 deletion(-) diff --git a/src/json.c b/src/json.c index 11f72e1f..be946507 100644 --- a/src/json.c +++ b/src/json.c @@ -14,10 +14,338 @@ #include #endif +#include + #include +#include +#include #include "json.h" #include "logging.h" #define CATMODULE "json" +#define MAX_RECURSION 64 + +struct json_renderer_tag { + unsigned int flags; + + int valid; + + char *buffer; + size_t bufferlen; + size_t bufferfill; + + char levelinfo[MAX_RECURSION]; + size_t level; +}; + +static int allocate_buffer(json_renderer_t *renderer, size_t needed) +{ + size_t required = needed + renderer->level; + size_t have = renderer->bufferlen - renderer->bufferfill; + + if (!renderer->valid) + return 1; + + if (have < required) { + size_t want; + char *n; + + if (required < 128) + required = 128; + + want = renderer->bufferfill + required; + if (want < 512) + want = 512; + + n = realloc(renderer->buffer, want); + + if (!n) + return 1; + + renderer->buffer = n; + } + + return 0; +} + +static void json_renderer_destroy(json_renderer_t *renderer) +{ + if (!renderer) + return; + + renderer->valid = 0; + + free(renderer->buffer); + free(renderer); +} + +json_renderer_t * json_renderer_create(unsigned int flags) +{ + json_renderer_t *renderer = calloc(1, sizeof(json_renderer_t)); + + if (!renderer) + return NULL; + + renderer->flags = flags; + renderer->valid = 1; + + renderer->levelinfo[0] = '0'; + renderer->level = 1; + + if (allocate_buffer(renderer, 0) != 0) { + json_renderer_destroy(renderer); + return NULL; + } + + return renderer; +} + +char * json_renderer_finish(json_renderer_t *renderer) +{ + char *ret; + + if (!renderer) + return NULL; + + if (!renderer->valid) { + json_renderer_destroy(renderer); + return NULL; + } + + for (; renderer->level; renderer->level--) { + switch (renderer->levelinfo[renderer->level-1]) { + case '0': + renderer->buffer[renderer->bufferfill++] = 0; + break; + case 'o': + case 'O': + renderer->buffer[renderer->bufferfill++] = '}'; + break; + case 'a': + case 'A': + renderer->buffer[renderer->bufferfill++] = ']'; + break; + default: + json_renderer_destroy(renderer); + return NULL; + break; + } + } + + ret = renderer->buffer; + renderer->buffer = NULL; + json_renderer_destroy(renderer); + return ret; +} + +static int write_raw(json_renderer_t *renderer, const char *raw, int begin) +{ + size_t rawlen = strlen(raw); + size_t want = rawlen; + char level; + char seperator = 0; + + if (!renderer->valid) + return 1; + + level = renderer->levelinfo[renderer->level-1]; + + if (begin) { + if (level == 'O' || level == 'A') { + seperator = ','; + } else if (level == 'o') { + renderer->levelinfo[renderer->level-1] = 'O'; + } else if (level == 'a') { + renderer->levelinfo[renderer->level-1] = 'A'; + } else if (level == 'P') { + seperator = ':'; + renderer->level--; + } + } + + if (seperator) + want++; + + if (allocate_buffer(renderer, want) != 0) + return 1; + + if (seperator) + renderer->buffer[renderer->bufferfill++] = seperator; + + memcpy(&(renderer->buffer[renderer->bufferfill]), raw, rawlen); + renderer->bufferfill += rawlen; + + return 0; +} + +static int want_write_value(json_renderer_t *renderer) +{ + char level; + + + if (!renderer || !renderer->valid) + return 1; + + level = renderer->levelinfo[renderer->level-1]; + if (level == 'o' || level == 'O') { + renderer->valid = 0; + return 1; + } + + return 0; +} + +void json_renderer_begin(json_renderer_t *renderer, json_element_type_t type) +{ + const char *towrite; + char next_level; + + if (!renderer || !renderer->valid) + return; + + if (renderer->level == MAX_RECURSION) { + renderer->valid = 0; + return; + } + + switch (type) { + case JSON_ELEMENT_TYPE_OBJECT: + next_level = 'o'; + towrite = "{"; + break; + case JSON_ELEMENT_TYPE_ARRAY: + next_level = 'a'; + towrite = "["; + break; + default: + renderer->valid = 0; + return; + } + + write_raw(renderer, towrite, 1); + renderer->levelinfo[renderer->level++] = next_level; +} + +void json_renderer_end(json_renderer_t *renderer) +{ + if (!renderer || !renderer->valid) + return; + + switch (renderer->levelinfo[renderer->level-1]) { + case 'o': + case 'O': + write_raw(renderer, "}", 0); + renderer->level--; + break; + case 'a': + case 'A': + write_raw(renderer, "]", 0); + renderer->level--; + break; + default: + renderer->valid = 0; + break; + } +} + +void json_renderer_write_null(json_renderer_t *renderer) +{ + if (want_write_value(renderer) != 0) + return; + write_raw(renderer, "null", 1); +} + +void json_renderer_write_boolean(json_renderer_t *renderer, int val) +{ + if (want_write_value(renderer) != 0) + return; + write_raw(renderer, val ? "true" : "false", 1); +} + +static void write_string(json_renderer_t *renderer, const char *string, unsigned int flags) +{ + // Allocate for the quotes plus 110% of the string length to account for escapes. + if (allocate_buffer(renderer, 2 + ((strlen(string) * 110) / 100)) != 0) + return; + + write_raw(renderer, "\"", 1); + + for (; *string && renderer->valid; string++) { + if (renderer->bufferfill == renderer->bufferlen) { + // Reallocate buffer, same rules as above, expect that we only need one additional quote. + if (allocate_buffer(renderer, 1 + ((strlen(string) * 110) / 100)) != 0) + return; + } + + if (*string < 0x20) { + char buf[7]; + snprintf(buf, sizeof(buf), "\\u%.4x", (unsigned int)*string); + write_raw(renderer, buf, 0); + } else if (*string == '\\' || *string == '"') { + if (allocate_buffer(renderer, 2) != 0) + return; + renderer->buffer[renderer->bufferfill++] = '\\'; + renderer->buffer[renderer->bufferfill++] = *string; + } else { + renderer->buffer[renderer->bufferfill++] = *string; + } + } + + write_raw(renderer, "\"", 0); +} + +void json_renderer_write_key(json_renderer_t *renderer, const char *key, unsigned int flags) +{ + char level; + + if (!renderer || !renderer->valid) + return; + + level = renderer->levelinfo[renderer->level-1]; + if (level != 'o' && level != 'O') { + renderer->valid = 0; + return; + } + + if (renderer->level == MAX_RECURSION) { + renderer->valid = 0; + return; + } + + write_string(renderer, key, flags); + + renderer->levelinfo[renderer->level++] = 'P'; +} + +void json_renderer_write_string(json_renderer_t *renderer, const char *string, unsigned int flags) +{ + if (want_write_value(renderer) != 0) + return; + write_string(renderer, string, flags); +} + +void json_renderer_write_int(json_renderer_t *renderer, intmax_t val) +{ + char buf[80]; + + if (want_write_value(renderer) != 0) + return; + + snprintf(buf, sizeof(buf), "%" PRIdMAX, val); + + write_raw(renderer, buf, 1); +} + +void json_renderer_write_uint(json_renderer_t *renderer, uintmax_t val) +{ + char buf[80]; + + if (want_write_value(renderer) != 0) + return; + + snprintf(buf, sizeof(buf), "%" PRIuMAX, val); + + write_raw(renderer, buf, 1); +} diff --git a/src/json.h b/src/json.h index cc6a38f4..4468891f 100644 --- a/src/json.h +++ b/src/json.h @@ -23,13 +23,14 @@ typedef enum { typedef struct json_renderer_tag json_renderer_t; json_renderer_t * json_renderer_create(unsigned int flags); -void json_renderer_destroy(json_renderer_t *renderer); char * json_renderer_finish(json_renderer_t *renderer); void json_renderer_begin(json_renderer_t *renderer, json_element_type_t type); void json_renderer_end(json_renderer_t *renderer); +void json_renderer_write_key(json_renderer_t *renderer, const char *key, unsigned int flags); + void json_renderer_write_null(json_renderer_t *renderer); void json_renderer_write_boolean(json_renderer_t *renderer, int val); void json_renderer_write_string(json_renderer_t *renderer, const char *string, unsigned int flags); From 9afbfcf40e94b6c8eda9ce12c6942b397ad258ae Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 08:54:32 +0000 Subject: [PATCH 04/42] Update: Made json_renderer_finish() take a pointer to the renderer --- src/json.c | 9 ++++++++- src/json.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/json.c b/src/json.c index be946507..c25bd0db 100644 --- a/src/json.c +++ b/src/json.c @@ -102,10 +102,17 @@ json_renderer_t * json_renderer_create(unsigned int flags) return renderer; } -char * json_renderer_finish(json_renderer_t *renderer) +char * json_renderer_finish(json_renderer_t **rendererptr) { + json_renderer_t *renderer; char *ret; + if (!rendererptr) + return NULL; + + renderer = *rendererptr; + *rendererptr = NULL; + if (!renderer) return NULL; diff --git a/src/json.h b/src/json.h index 4468891f..ac60ac4a 100644 --- a/src/json.h +++ b/src/json.h @@ -24,7 +24,7 @@ typedef struct json_renderer_tag json_renderer_t; json_renderer_t * json_renderer_create(unsigned int flags); -char * json_renderer_finish(json_renderer_t *renderer); +char * json_renderer_finish(json_renderer_t **rendererptr); void json_renderer_begin(json_renderer_t *renderer, json_element_type_t type); void json_renderer_end(json_renderer_t *renderer); From dccf0e046a6b3281feb7278d984b21412c874dd8 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 09:09:24 +0000 Subject: [PATCH 05/42] Feature: Added comments/docs for JSON renderer in the header --- src/json.h | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/json.h b/src/json.h index ac60ac4a..233cf656 100644 --- a/src/json.h +++ b/src/json.h @@ -13,28 +13,99 @@ #include +/* + * Overview: + * --------- + * * First a renderer is created using json_renderer_create(). + * * Elements can then be written to the renderer in order they appear in the + * final JSON using json_renderer_write_*(). + * * Objects and arrays can be opened and closed using json_renderer_begin() + * and json_renderer_end() + * * Objects and arrays are automatically closed on json_renderer_finish() + * * json_renderer_finish() is used to fetch the final rendering result + * and destroy the renderer object. The renderer object is invalid after this. + * The returned value must be freed using free() by the caller. + */ + +/* Dummy value used when no flags are passed. */ #define JSON_RENDERER_FLAGS_NONE 0 +/* Enum of JSON element types, may not include all element types */ typedef enum { + /* Objects ('{key:value}') */ JSON_ELEMENT_TYPE_OBJECT, + /* Arrays ('[a,b,c]') */ JSON_ELEMENT_TYPE_ARRAY } json_element_type_t; +/* Type of the renderer */ typedef struct json_renderer_tag json_renderer_t; +/* + * json_renderer_create() creates a renderer object using + * the given flags. See the definition of the flags for details. + * If no flags are to be passed, JSON_RENDERER_FLAGS_NONE MUST be used. + */ json_renderer_t * json_renderer_create(unsigned int flags); +/* + * json_renderer_finish() finishes the rendering, destroys the renderer, + * and returns the rendered JSON. + * The renderer is invalid after this and MUST NOT be reused. + * The returned buffer MUST be freed by the caller using free(). + */ char * json_renderer_finish(json_renderer_t **rendererptr); +/* + * json_renderer_begin() begins a new sub-element that might be an object or array. + * json_renderer_begin() MUST be matched with a correspoinding json_renderer_end() + * unless at end of the document as all elements that are still open are closed + * automatically. + */ void json_renderer_begin(json_renderer_t *renderer, json_element_type_t type); +/* + * json_renderer_end() closes the current sub-element that was opened using json_renderer_begin(). + */ void json_renderer_end(json_renderer_t *renderer); +/* + * json_renderer_write_key() writes a key for a object key-value pair. + * Parameters work the same as for json_renderer_write_string(). + */ void json_renderer_write_key(json_renderer_t *renderer, const char *key, unsigned int flags); +/* + * json_renderer_write_null() writes a null-value. + */ void json_renderer_write_null(json_renderer_t *renderer); + +/* + * json_renderer_write_boolean() writes a boolean value. + * The parameter val is interpreted by standard C rules for truth. + */ + void json_renderer_write_boolean(json_renderer_t *renderer, int val); + +/* + * json_renderer_write_string() writes a string value. + * The parameter string must be in UTF-8 encoding and \0-terminated. + * The parameter flags can be used to hint the rendering engine. + * If no flags are given JSON_RENDERER_FLAGS_NONE MUST be given. + */ void json_renderer_write_string(json_renderer_t *renderer, const char *string, unsigned int flags); + +/* + * json_renderer_write_int() writes am signed integer value. + * Note: JSON does not define specific types of numbers (signed/unsigned, integer/fixed ponint/floating point) + * so this writes the integer as generic number. + */ void json_renderer_write_int(json_renderer_t *renderer, intmax_t val); + +/* + * json_renderer_write_uint() writes an unsigned integer value. + * Note: JSON does not define specific types of numbers (signed/unsigned, integer/fixed ponint/floating point) + * so this writes the integer as generic number. + */ void json_renderer_write_uint(json_renderer_t *renderer, uintmax_t val); #endif From 4cab251919c42f922da5062bc5a6f4e2293d31ff Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 09:44:43 +0000 Subject: [PATCH 06/42] Feature: Added stub src/xml2json.[ch] --- src/Makefile.am | 2 ++ src/xml2json.c | 21 +++++++++++++++++++++ src/xml2json.h | 14 ++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/xml2json.c create mode 100644 src/xml2json.h diff --git a/src/Makefile.am b/src/Makefile.am index 50ac53b2..8051f707 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,6 +36,7 @@ noinst_HEADERS = \ reportxml.h \ reportxml_helper.h \ json.h \ + xml2json.h \ listensocket.h \ fastevent.h \ event.h \ @@ -84,6 +85,7 @@ icecast_SOURCES = \ reportxml.c \ reportxml_helper.c \ json.c \ + xml2json.c \ listensocket.c \ fastevent.c \ format.c \ diff --git a/src/xml2json.c b/src/xml2json.c new file mode 100644 index 00000000..1d9cdbb4 --- /dev/null +++ b/src/xml2json.c @@ -0,0 +1,21 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018-2020, Philipp "ph3-der-loewe" Schafft , + */ + +/** + * This file contains functions for rendering XML as JSON. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "xml2json.h" +#include "json.h" + +#include "logging.h" +#define CATMODULE "xml2json" diff --git a/src/xml2json.h b/src/xml2json.h new file mode 100644 index 00000000..4a1fee04 --- /dev/null +++ b/src/xml2json.h @@ -0,0 +1,14 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2020, Philipp "ph3-der-loewe" Schafft , + */ + +/* This file contains functions for rendering XML as JSON. */ + +#ifndef __XML2JSON_H__ +#define __XML2JSON_H__ + +#endif From 7b5f52d77d38d03015de9c9df9bc155fd4b3a259 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 10:45:51 +0000 Subject: [PATCH 07/42] Fix: Corrected memory allocator --- src/json.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/json.c b/src/json.c index c25bd0db..7a001b9c 100644 --- a/src/json.c +++ b/src/json.c @@ -48,6 +48,9 @@ static int allocate_buffer(json_renderer_t *renderer, size_t needed) if (!renderer->valid) return 1; + if (have) + have--; + if (have < required) { size_t want; char *n; @@ -65,6 +68,7 @@ static int allocate_buffer(json_renderer_t *renderer, size_t needed) return 1; renderer->buffer = n; + renderer->bufferlen = want; } return 0; From 34fa9f0ce326ec30d04f9fe64ae89ac0be377a33 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 12:27:22 +0000 Subject: [PATCH 08/42] Fix: Export xmlns as namespace not property --- src/reportxml.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/reportxml.c b/src/reportxml.c index 67a4d161..56d49477 100644 --- a/src/reportxml.c +++ b/src/reportxml.c @@ -605,6 +605,7 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) ssize_t xml_child_count; size_t i; xmlChar *definition; + xmlChar *xmlns; if (!node) return NULL; @@ -628,6 +629,15 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) xmlFree(definition); } + xmlns = xmlGetProp(ret, XMLSTR("xmlns")); + if (xmlns) { + xmlNsPtr ns; + xmlUnsetProp(ret, XMLSTR("xmlns")); + ns = xmlNewNs(ret, xmlns, NULL); + xmlSetNs(ret, ns); + xmlFree(xmlns); + } + for (i = 0; i < (size_t)child_count; i++) { reportxml_node_t *child = reportxml_node_get_child(node, i); xmlNodePtr xmlchild; From 395b994707e278ea6bc650d0ac88c04a2245df6b Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 13:36:36 +0000 Subject: [PATCH 09/42] Fix: Set namespace correctly for child nodes --- src/reportxml.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/reportxml.c b/src/reportxml.c index 56d49477..51597d58 100644 --- a/src/reportxml.c +++ b/src/reportxml.c @@ -598,7 +598,7 @@ reportxml_node_t * reportxml_node_copy(reportxml_node_t *node) return __reportxml_node_copy_with_db(node, NULL, -1); } -xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) +static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns) { xmlNodePtr ret; ssize_t child_count; @@ -631,13 +631,14 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) xmlns = xmlGetProp(ret, XMLSTR("xmlns")); if (xmlns) { - xmlNsPtr ns; xmlUnsetProp(ret, XMLSTR("xmlns")); ns = xmlNewNs(ret, xmlns, NULL); - xmlSetNs(ret, ns); xmlFree(xmlns); } + if (ns) + xmlSetNs(ret, ns); + for (i = 0; i < (size_t)child_count; i++) { reportxml_node_t *child = reportxml_node_get_child(node, i); xmlNodePtr xmlchild; @@ -647,7 +648,7 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) return NULL; } - xmlchild = reportxml_node_render_xmlnode(child); + xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns); refobject_unref(child); if (!xmlchild) { xmlFreeNode(ret); @@ -683,6 +684,11 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) return ret; } +xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) +{ + return reportxml_node_render_xmlnode_with_ns(node, NULL); +} + reportxml_node_type_t reportxml_node_get_type(reportxml_node_t *node) { if (!node) From 731ac5c8e6be587589d26011dfac3ca3da253f60 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 15:30:50 +0000 Subject: [PATCH 10/42] Feature: Added a basic outline of how converting could happen with a first generic mapping format --- src/xml2json.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/xml2json.h | 4 +++ 2 files changed, 96 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index 1d9cdbb4..17c120ba 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -19,3 +19,95 @@ #include "logging.h" #define CATMODULE "xml2json" + +static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent) +{ + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + + json_renderer_write_key(renderer, "type", JSON_RENDERER_FLAGS_NONE); + switch (node->type) { + case XML_ELEMENT_NODE: + json_renderer_write_string(renderer, "element", JSON_RENDERER_FLAGS_NONE); + if (node->name) { + json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char *)node->name, JSON_RENDERER_FLAGS_NONE); + } + break; + case XML_TEXT_NODE: + json_renderer_write_string(renderer, "text", JSON_RENDERER_FLAGS_NONE); + break; + case XML_COMMENT_NODE: + json_renderer_write_string(renderer, "comment", JSON_RENDERER_FLAGS_NONE); + break; + default: + json_renderer_write_null(renderer); + break; + } + + if (node->content) { + json_renderer_write_key(renderer, "text", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char *)node->content, JSON_RENDERER_FLAGS_NONE); + } + + json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); + if (node->ns && node->ns->href) { + json_renderer_write_string(renderer, (const char *)node->ns->href, JSON_RENDERER_FLAGS_NONE); + if (node->ns->prefix) { + json_renderer_write_key(renderer, "nsprefix", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char *)node->ns->prefix, JSON_RENDERER_FLAGS_NONE); + } + } else { + json_renderer_write_null(renderer); + } + + if (node->properties) { + xmlAttrPtr cur = node->properties; + + json_renderer_write_key(renderer, "properties", JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + do { + xmlChar *value = xmlNodeListGetString(doc, cur->children, 1); + if (value) { + json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } + } while ((cur = cur->next)); + json_renderer_end(renderer); + } + + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + + json_renderer_write_key(renderer, "children", JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + do { + render_node(renderer, doc, cur, node); + cur = cur->next; + } while (cur); + json_renderer_end(renderer); + } + + json_renderer_end(renderer); +} + +char * xml2json_render_doc_simple(xmlDocPtr doc) +{ + json_renderer_t *renderer; + xmlNodePtr xmlroot; + + if (!doc) + return NULL; + + renderer = json_renderer_create(JSON_RENDERER_FLAGS_NONE); + if (!renderer) + return NULL; + + xmlroot = xmlDocGetRootElement(doc); + if (!xmlroot) + return NULL; + + render_node(renderer, doc, xmlroot, NULL); + + return json_renderer_finish(&renderer); +} diff --git a/src/xml2json.h b/src/xml2json.h index 4a1fee04..c49828b4 100644 --- a/src/xml2json.h +++ b/src/xml2json.h @@ -11,4 +11,8 @@ #ifndef __XML2JSON_H__ #define __XML2JSON_H__ +#include + +char * xml2json_render_doc_simple(xmlDocPtr doc); + #endif From deaad3246cf0d7410985c8d396bb8bccac77673f Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 15:32:25 +0000 Subject: [PATCH 11/42] Feature: Assign ADMIN_FORMAT_JSON --- src/icecasttypes.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/icecasttypes.h b/src/icecasttypes.h index 70e67d60..d7374c05 100644 --- a/src/icecasttypes.h +++ b/src/icecasttypes.h @@ -33,7 +33,8 @@ typedef enum { ADMIN_FORMAT_AUTO, ADMIN_FORMAT_RAW, ADMIN_FORMAT_HTML, - ADMIN_FORMAT_PLAINTEXT + ADMIN_FORMAT_PLAINTEXT, + ADMIN_FORMAT_JSON } admin_format_t; /* ---[ acl.[ch] ]--- */ From b44df0da76b0119e6dae54273b25231ce8cbfad5 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 15:33:13 +0000 Subject: [PATCH 12/42] Feature: Support JSON mapping for all reportxml based outputs --- src/client.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/client.c b/src/client.c index e99e56c0..9ce8f14c 100644 --- a/src/client.c +++ b/src/client.c @@ -43,6 +43,7 @@ #include "reportxml.h" #include "refobject.h" #include "xslt.h" +#include "xml2json.h" #include "source.h" #include "client.h" @@ -363,6 +364,7 @@ static inline void _client_send_report(client_t *client, const char *uuid, const switch (admin_format) { case ADMIN_FORMAT_RAW: + case ADMIN_FORMAT_JSON: xslt = NULL; break; case ADMIN_FORMAT_HTML: @@ -554,6 +556,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai if (!xsl) { switch (admin_format) { case ADMIN_FORMAT_RAW: + case ADMIN_FORMAT_JSON: /* noop, we don't need to set xsl */ break; case ADMIN_FORMAT_HTML: @@ -581,14 +584,24 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai return; } - if (admin_format == ADMIN_FORMAT_RAW) { + if (admin_format == ADMIN_FORMAT_RAW || admin_format == ADMIN_FORMAT_JSON) { xmlChar *buff = NULL; size_t location_length = 0; int len = 0; size_t buf_len; ssize_t ret; + const char *content_type; - xmlDocDumpMemory(doc, &buff, &len); + if (admin_format == ADMIN_FORMAT_RAW) { + xmlDocDumpMemory(doc, &buff, &len); + content_type = "text/xml"; + } else { + char *json = xml2json_render_doc_simple(doc); + buff = xmlStrdup(XMLSTR(json)); + len = strlen(json); + free(json); + content_type = "application/json"; + } if (location) { location_length = strlen(location); @@ -604,7 +617,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai ret = util_http_build_header(client->refbuf->data, buf_len, 0, 0, status, NULL, - "text/xml", "utf-8", + content_type, "utf-8", NULL, NULL, client); if (ret < 0) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); @@ -621,7 +634,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai client->refbuf->len = buf_len; ret = util_http_build_header(client->refbuf->data, buf_len, 0, 0, status, NULL, - "text/xml", "utf-8", + content_type, "utf-8", NULL, NULL, client); if (ret == -1) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); @@ -756,7 +769,7 @@ admin_format_t client_get_admin_format_by_content_negotiation(client_t *client) if (!client || !client->parser) return CLIENT_DEFAULT_ADMIN_FORMAT; - pref = util_http_select_best(httpp_getvar(client->parser, "accept"), "text/xml", "text/html", "text/plain", (const char*)NULL); + pref = util_http_select_best(httpp_getvar(client->parser, "accept"), "text/xml", "text/html", "text/plain", "application/json", (const char*)NULL); if (strcmp(pref, "text/xml") == 0) { return ADMIN_FORMAT_RAW; @@ -764,6 +777,8 @@ admin_format_t client_get_admin_format_by_content_negotiation(client_t *client) return ADMIN_FORMAT_HTML; } else if (strcmp(pref, "text/plain") == 0) { return ADMIN_FORMAT_PLAINTEXT; + } else if (strcmp(pref, "application/json") == 0) { + return ADMIN_FORMAT_JSON; } else { return CLIENT_DEFAULT_ADMIN_FORMAT; } From c7be3122db1a5a2dd3f1e58aca156f3215468491 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 15:51:29 +0000 Subject: [PATCH 13/42] Feature: Added super basic logic for namespace based render selecting --- src/xml2json.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index 17c120ba..81cdb6c8 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -14,13 +14,22 @@ #include #endif +#include + #include "xml2json.h" #include "json.h" #include "logging.h" #define CATMODULE "xml2json" -static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent) +struct xml2json_cache { + xmlNsPtr ns; + void (*render)(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); +}; + +static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); + +static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -82,7 +91,7 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod json_renderer_write_key(renderer, "children", JSON_RENDERER_FLAGS_NONE); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); do { - render_node(renderer, doc, cur, node); + render_node(renderer, doc, cur, node, cache); cur = cur->next; } while (cur); json_renderer_end(renderer); @@ -91,8 +100,33 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod json_renderer_end(renderer); } + +static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + void (*render)(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) = NULL; + + if (node->ns == cache->ns) + render = cache->render; + + if (render == NULL) { + if (node->ns) { + render = render_node_generic; + + cache->ns = node->ns; + cache->render = render; + } + } + + // If nothing found, use generic render. + if (render == NULL) + render = render_node_generic; + + render(renderer, doc, node, parent, cache); +} + char * xml2json_render_doc_simple(xmlDocPtr doc) { + struct xml2json_cache cache; json_renderer_t *renderer; xmlNodePtr xmlroot; @@ -107,7 +141,9 @@ char * xml2json_render_doc_simple(xmlDocPtr doc) if (!xmlroot) return NULL; - render_node(renderer, doc, xmlroot, NULL); + memset(&cache, 0, sizeof(cache)); + + render_node(renderer, doc, xmlroot, NULL, &cache); return json_renderer_finish(&renderer); } From 2130a80f7062c09fb803451234ecbecd0d81ff80 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 20:37:16 +0000 Subject: [PATCH 14/42] Update: Changed xml2json_render_doc_simple's signature --- src/client.c | 2 +- src/xml2json.c | 2 +- src/xml2json.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.c b/src/client.c index 9ce8f14c..2b9e397b 100644 --- a/src/client.c +++ b/src/client.c @@ -596,7 +596,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai xmlDocDumpMemory(doc, &buff, &len); content_type = "text/xml"; } else { - char *json = xml2json_render_doc_simple(doc); + char *json = xml2json_render_doc_simple(doc, NULL); buff = xmlStrdup(XMLSTR(json)); len = strlen(json); free(json); diff --git a/src/xml2json.c b/src/xml2json.c index 81cdb6c8..e10804a4 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -124,7 +124,7 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod render(renderer, doc, node, parent, cache); } -char * xml2json_render_doc_simple(xmlDocPtr doc) +char * xml2json_render_doc_simple(xmlDocPtr doc, const char *default_namespace) { struct xml2json_cache cache; json_renderer_t *renderer; diff --git a/src/xml2json.h b/src/xml2json.h index c49828b4..586c092f 100644 --- a/src/xml2json.h +++ b/src/xml2json.h @@ -13,6 +13,6 @@ #include -char * xml2json_render_doc_simple(xmlDocPtr doc); +char * xml2json_render_doc_simple(xmlDocPtr doc, const char *default_namespace); #endif From 5045271d3cb7a40e1186ffcd984cbeab8e89024e Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 20:37:46 +0000 Subject: [PATCH 15/42] Feature: Added infrastructure to send JSON as part of admin/ --- src/admin.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/admin.c b/src/admin.c index 7f8d8a51..53606804 100644 --- a/src/admin.c +++ b/src/admin.c @@ -37,6 +37,7 @@ #include "errors.h" #include "reportxml.h" #include "reportxml_helper.h" +#include "xml2json.h" #include "format.h" @@ -425,13 +426,24 @@ void admin_send_response(xmlDocPtr doc, admin_format_t response, const char *xslt_template) { - if (response == ADMIN_FORMAT_RAW) { + if (response == ADMIN_FORMAT_RAW || response == ADMIN_FORMAT_JSON) { xmlChar *buff = NULL; int len = 0; size_t buf_len; ssize_t ret; + const char *content_type; + + if (response == ADMIN_FORMAT_RAW) { + xmlDocDumpMemory(doc, &buff, &len); + content_type = "text/xml"; + } else { + char *json = xml2json_render_doc_simple(doc, "http://icecast.org/specs/legacyresponse-0.0.1"); + buff = xmlStrdup(XMLSTR(json)); + len = strlen(json); + free(json); + content_type = "application/json"; + } - xmlDocDumpMemory(doc, &buff, &len); buf_len = len + 1024; if (buf_len < 4096) @@ -442,7 +454,7 @@ void admin_send_response(xmlDocPtr doc, ret = util_http_build_header(client->refbuf->data, buf_len, 0, 0, 200, NULL, - "text/xml", "utf-8", + content_type, "utf-8", NULL, NULL, client); if (ret < 0) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); @@ -459,7 +471,7 @@ void admin_send_response(xmlDocPtr doc, client->refbuf->len = buf_len; ret = util_http_build_header(client->refbuf->data, buf_len, 0, 0, 200, NULL, - "text/xml", "utf-8", + content_type, "utf-8", NULL, NULL, client); if (ret == -1) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); From 1666b478a1558742a3256134228de06ef50ee60d Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 4 Oct 2020 20:46:19 +0000 Subject: [PATCH 16/42] Update: Only set namespace for reportxml if not used for XSLT --- src/client.c | 2 +- src/reportxml.c | 13 +++++++------ src/reportxml.h | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/client.c b/src/client.c index 2b9e397b..38b5caf9 100644 --- a/src/client.c +++ b/src/client.c @@ -577,7 +577,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai return; } - doc = reportxml_render_xmldoc(report); + doc = reportxml_render_xmldoc(report, admin_format == ADMIN_FORMAT_RAW || admin_format == ADMIN_FORMAT_JSON); if (!doc) { ICECAST_LOG_ERROR("Can not render XML Document from report. Sending 500 to client %p", client); client_send_500(client, "Can not render XML Document from report."); diff --git a/src/reportxml.c b/src/reportxml.c index 51597d58..8945715f 100644 --- a/src/reportxml.c +++ b/src/reportxml.c @@ -110,6 +110,7 @@ struct nodedef { /* Prototypes */ static int __attach_copy_of_node_or_definition(reportxml_node_t *parent, reportxml_node_t *node, reportxml_database_t *db, ssize_t depth); static reportxml_node_t * __reportxml_database_build_node_ext(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t *acst_type_ret); +static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace); /* definition of known attributes */ static const struct nodeattr __attr__eol[1] = {{NULL, NULL, NULL, 0, NULL, {NULL}}}; @@ -330,7 +331,7 @@ reportxml_t * reportxml_parse_xmldoc(xmlDocPtr doc) return ret; } -xmlDocPtr reportxml_render_xmldoc(reportxml_t *report) +xmlDocPtr reportxml_render_xmldoc(reportxml_t *report, int set_namespace) { xmlDocPtr ret; xmlNodePtr node; @@ -338,7 +339,7 @@ xmlDocPtr reportxml_render_xmldoc(reportxml_t *report) if (!report) return NULL; - node = reportxml_node_render_xmlnode(report->root); + node = reportxml_node_render_xmlnode_with_ns(report->root, NULL, set_namespace); if (!node) return NULL; @@ -598,7 +599,7 @@ reportxml_node_t * reportxml_node_copy(reportxml_node_t *node) return __reportxml_node_copy_with_db(node, NULL, -1); } -static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns) +static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace) { xmlNodePtr ret; ssize_t child_count; @@ -636,7 +637,7 @@ static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_n xmlFree(xmlns); } - if (ns) + if (ns && set_namespace) xmlSetNs(ret, ns); for (i = 0; i < (size_t)child_count; i++) { @@ -648,7 +649,7 @@ static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_n return NULL; } - xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns); + xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns, set_namespace); refobject_unref(child); if (!xmlchild) { xmlFreeNode(ret); @@ -686,7 +687,7 @@ static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_n xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node) { - return reportxml_node_render_xmlnode_with_ns(node, NULL); + return reportxml_node_render_xmlnode_with_ns(node, NULL, 1); } reportxml_node_type_t reportxml_node_get_type(reportxml_node_t *node) diff --git a/src/reportxml.h b/src/reportxml.h index fb4c494c..427bc57b 100644 --- a/src/reportxml.h +++ b/src/reportxml.h @@ -87,7 +87,7 @@ reportxml_node_t * reportxml_get_node_by_type(reportxml_t *report, reportxm /* This function parses an XML document and returns the parst report XML document */ reportxml_t * reportxml_parse_xmldoc(xmlDocPtr doc); /* This function renders an report XML document as XML structure */ -xmlDocPtr reportxml_render_xmldoc(reportxml_t *report); +xmlDocPtr reportxml_render_xmldoc(reportxml_t *report, int set_namespace); /* ---[ Node level ]--- */ From 6fc7f21a11164cd87e6ad3f4c83ec77652d9ab42 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Tue, 6 Oct 2020 21:00:53 +0000 Subject: [PATCH 17/42] Feature: Improved logic for selecting a specific renderer --- src/xml2json.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index e10804a4..5c651db0 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -23,6 +23,7 @@ #define CATMODULE "xml2json" struct xml2json_cache { + const char *default_namespace; xmlNsPtr ns; void (*render)(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); }; @@ -109,18 +110,22 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod render = cache->render; if (render == NULL) { - if (node->ns) { + const char *href = cache->default_namespace; + + if (node->ns && node->ns->href) + href = (const char *)node->ns->href; + + if (href) { + // TODO: Select alternative renderer here. + } + + if (render == NULL) render = render_node_generic; - cache->ns = node->ns; - cache->render = render; - } + cache->ns = node->ns; + cache->render = render; } - // If nothing found, use generic render. - if (render == NULL) - render = render_node_generic; - render(renderer, doc, node, parent, cache); } @@ -142,6 +147,7 @@ char * xml2json_render_doc_simple(xmlDocPtr doc, const char *default_namespace) return NULL; memset(&cache, 0, sizeof(cache)); + cache.default_namespace = default_namespace; render_node(renderer, doc, xmlroot, NULL, &cache); From 236c0dfa5657763aeaf682218562bc64ccb06a23 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 11:56:58 +0000 Subject: [PATCH 18/42] Feature: Added a renderer for -style XML --- src/xml2json.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/src/xml2json.c b/src/xml2json.c index 5c651db0..81aca821 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -19,6 +19,9 @@ #include "xml2json.h" #include "json.h" +/* For XMLSTR() */ +#include "cfgfile.h" + #include "logging.h" #define CATMODULE "xml2json" @@ -29,6 +32,98 @@ struct xml2json_cache { }; static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); +static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); + +static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + int handled = 0; + + if (node->type == XML_ELEMENT_NODE) { + const char *nodename = (const char *)node->name; + handled = 1; + if (strcmp(nodename, "iceresponse") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + + do { + int handled_child = 1; + + if (cur->type == XML_ELEMENT_NODE && cur->name) { + if (strcmp((const char *)cur->name, "message") == 0) { + xmlChar *value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, "message", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } + } else if (strcmp((const char *)cur->name, "return") == 0) { + xmlChar *value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, "success", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_boolean(renderer, strcmp((const char *)value, "1") == 0); + xmlFree(value); + } + } else if (strcmp((const char *)cur->name, "modules") == 0) { + json_renderer_write_key(renderer, "modules", JSON_RENDERER_FLAGS_NONE); + render_node(renderer, doc, cur, node, cache); + } else { + handled_child = 0; + } + } else { + handled_child = 0; + } + + if (!handled_child) { + json_renderer_write_key(renderer, "unhandled-child", JSON_RENDERER_FLAGS_NONE); + render_node(renderer, doc, cur, node, cache); + } + cur = cur->next; + } while (cur); + } + json_renderer_end(renderer); + } else if (strcmp(nodename, "modules") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + + do { + if (cur->type == XML_ELEMENT_NODE && cur->name && strcmp((const char *)cur->name, "module") == 0) { + xmlChar *name = xmlGetProp(cur, XMLSTR("name")); + if (name) { + json_renderer_write_key(renderer, (const char *)name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->properties) { + xmlAttrPtr prop = node->properties; + + do { + xmlChar *value = xmlNodeListGetString(doc, prop->children, 1); + if (value) { + json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } + } while ((prop = prop->next)); + } + json_renderer_end(renderer); + xmlFree(name); + } + } else { + json_renderer_write_key(renderer, "unhandled-child", JSON_RENDERER_FLAGS_NONE); + render_node(renderer, doc, cur, node, cache); + } + cur = cur->next; + } while (cur); + } + json_renderer_end(renderer); + } else { + handled = 0; + } + } + + if (!handled) + render_node_generic(renderer, doc, node, parent, cache); +} static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { @@ -116,7 +211,9 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod href = (const char *)node->ns->href; if (href) { - // TODO: Select alternative renderer here. + if (strcmp(href, "http://icecast.org/specs/legacyresponse-0.0.1") == 0) { + render = render_node_legacyresponse; + } } if (render == NULL) From 3fd16ac834f28a3e49b8ca71707873a9717f5057 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 12:13:20 +0000 Subject: [PATCH 19/42] Feature: Added support for JSON output on -style XML commands --- src/admin.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/admin.c b/src/admin.c index 53606804..41a0144b 100644 --- a/src/admin.c +++ b/src/admin.c @@ -67,15 +67,18 @@ #define FALLBACK_RAW_REQUEST "fallbacks" #define FALLBACK_HTML_REQUEST "fallbacks.xsl" +#define FALLBACK_JSON_REQUEST "fallbacks.json" #define SHOUTCAST_METADATA_REQUEST "admin.cgi" #define METADATA_RAW_REQUEST "metadata" #define METADATA_HTML_REQUEST "metadata.xsl" +#define METADATA_JSON_REQUEST "metadata.json" #define LISTCLIENTS_RAW_REQUEST "listclients" #define LISTCLIENTS_HTML_REQUEST "listclients.xsl" #define STATS_RAW_REQUEST "stats" #define STATS_HTML_REQUEST "stats.xsl" #define QUEUE_RELOAD_RAW_REQUEST "reloadconfig" #define QUEUE_RELOAD_HTML_REQUEST "reloadconfig.xsl" +#define QUEUE_RELOAD_JSON_REQUEST "reloadconfig.json" #define LISTMOUNTS_RAW_REQUEST "listmounts" #define LISTMOUNTS_HTML_REQUEST "listmounts.xsl" #define STREAMLIST_RAW_REQUEST "streamlist" @@ -85,8 +88,10 @@ #define MOVECLIENTS_HTML_REQUEST "moveclients.xsl" #define KILLCLIENT_RAW_REQUEST "killclient" #define KILLCLIENT_HTML_REQUEST "killclient.xsl" +#define KILLCLIENT_JSON_REQUEST "killclient.json" #define KILLSOURCE_RAW_REQUEST "killsource" #define KILLSOURCE_HTML_REQUEST "killsource.xsl" +#define KILLSOURCE_JSON_REQUEST "killsource.json" #define ADMIN_XSL_RESPONSE "response.xsl" #define MANAGEAUTH_RAW_REQUEST "manageauth" #define MANAGEAUTH_HTML_REQUEST "manageauth.xsl" @@ -96,6 +101,7 @@ #define SHOWLOG_HTML_REQUEST "showlog.xsl" #define MARKLOG_RAW_REQUEST "marklog" #define MARKLOG_HTML_REQUEST "marklog.xsl" +#define MARKLOG_JSON_REQUEST "marklog.json" #define DASHBOARD_RAW_REQUEST "dashboard" #define DASHBOARD_HTML_REQUEST "dashboard.xsl" #define DEFAULT_RAW_REQUEST "" @@ -130,8 +136,10 @@ static const admin_command_handler_t handlers[] = { { "*", ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, NULL, NULL}, /* for ACL framework */ { FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_fallback, NULL}, { FALLBACK_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_fallback, NULL}, + { FALLBACK_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_fallback, NULL}, { METADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_metadata, NULL}, { METADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_metadata, NULL}, + { METADATA_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_metadata, NULL}, { SHOUTCAST_METADATA_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_shoutcast_metadata, NULL}, { LISTCLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_show_listeners, NULL}, { LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_show_listeners, NULL}, @@ -140,6 +148,7 @@ static const admin_command_handler_t handlers[] = { { "stats.xml", ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, { QUEUE_RELOAD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_queue_reload, NULL}, { QUEUE_RELOAD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_queue_reload, NULL}, + { QUEUE_RELOAD_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_queue_reload, NULL}, { LISTMOUNTS_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, { LISTMOUNTS_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts, NULL}, { STREAMLIST_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, @@ -149,8 +158,10 @@ static const admin_command_handler_t handlers[] = { { MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_move_clients, NULL}, { KILLCLIENT_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_client, NULL}, { KILLCLIENT_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_client, NULL}, + { KILLCLIENT_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_kill_client, NULL}, { KILLSOURCE_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_source, NULL}, { KILLSOURCE_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_source, NULL}, + { KILLSOURCE_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_kill_source, NULL}, { MANAGEAUTH_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_manageauth, NULL}, { MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_manageauth, NULL}, { UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata, NULL}, @@ -160,6 +171,7 @@ static const admin_command_handler_t handlers[] = { { SHOWLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_show_log, NULL}, { MARKLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_mark_log, NULL}, { MARKLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_mark_log, NULL}, + { MARKLOG_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_mark_log, NULL}, { DASHBOARD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_dashboard, NULL}, { DASHBOARD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_dashboard, NULL}, { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL}, @@ -654,7 +666,7 @@ void admin_handle_request(client_t *client, const char *uri) static void html_success(client_t *client, source_t *source, admin_format_t response, char *message) { - if (client->mode == OMODE_STRICT) { + if (client->mode == OMODE_STRICT || (response != ADMIN_FORMAT_RAW && response != ADMIN_FORMAT_HTML)) { admin_send_response_simple(client, source, response, message, 1); } else { ssize_t ret; From 7a9d42209e39fac664e132f1b9a118bea4c5b585 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 16:43:01 +0000 Subject: [PATCH 20/42] Update: Generify the render_node_legacyresponse() code a bit --- src/xml2json.c | 99 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index 81aca821..5183073b 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -31,9 +31,67 @@ struct xml2json_cache { void (*render)(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); }; +struct nodelist { + xmlNodePtr *nodes; + size_t len; + size_t fill; +}; + static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); +static void handle_textchildnode(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + xmlChar *value = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, (const char *)node->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } +} + +static int handle_node_modules(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + if (node->type == XML_ELEMENT_NODE && strcmp((const char *)node->name, "modules") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + + do { + if (cur->type == XML_ELEMENT_NODE && cur->name && strcmp((const char *)cur->name, "module") == 0) { + xmlChar *name = xmlGetProp(cur, XMLSTR("name")); + if (name) { + json_renderer_write_key(renderer, (const char *)name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->properties) { + xmlAttrPtr prop = node->properties; + + do { + xmlChar *value = xmlNodeListGetString(doc, prop->children, 1); + if (value) { + json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } + } while ((prop = prop->next)); + } + json_renderer_end(renderer); + xmlFree(name); + } + } else { + json_renderer_write_key(renderer, "unhandled-child", JSON_RENDERER_FLAGS_NONE); + render_node(renderer, doc, cur, node, cache); + } + cur = cur->next; + } while (cur); + } + json_renderer_end(renderer); + return 1; + } + + return 0; +} + static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { int handled = 0; @@ -51,12 +109,7 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, if (cur->type == XML_ELEMENT_NODE && cur->name) { if (strcmp((const char *)cur->name, "message") == 0) { - xmlChar *value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); - if (value) { - json_renderer_write_key(renderer, "message", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); - xmlFree(value); - } + handle_textchildnode(renderer, doc, cur, node, cache); } else if (strcmp((const char *)cur->name, "return") == 0) { xmlChar *value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if (value) { @@ -83,39 +136,7 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, } json_renderer_end(renderer); } else if (strcmp(nodename, "modules") == 0) { - json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); - if (node->xmlChildrenNode) { - xmlNodePtr cur = node->xmlChildrenNode; - - do { - if (cur->type == XML_ELEMENT_NODE && cur->name && strcmp((const char *)cur->name, "module") == 0) { - xmlChar *name = xmlGetProp(cur, XMLSTR("name")); - if (name) { - json_renderer_write_key(renderer, (const char *)name, JSON_RENDERER_FLAGS_NONE); - json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); - if (node->properties) { - xmlAttrPtr prop = node->properties; - - do { - xmlChar *value = xmlNodeListGetString(doc, prop->children, 1); - if (value) { - json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); - xmlFree(value); - } - } while ((prop = prop->next)); - } - json_renderer_end(renderer); - xmlFree(name); - } - } else { - json_renderer_write_key(renderer, "unhandled-child", JSON_RENDERER_FLAGS_NONE); - render_node(renderer, doc, cur, node, cache); - } - cur = cur->next; - } while (cur); - } - json_renderer_end(renderer); + handled = handle_node_modules(renderer, doc, node, parent, cache); } else { handled = 0; } From adef1ef38e52fbaed58865a3c939380f3f2dd1cc Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 16:43:58 +0000 Subject: [PATCH 21/42] Feature: Added support for a container object holding a list of XML nodes --- src/xml2json.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index 5183073b..ac9e014b 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -15,6 +15,7 @@ #endif #include +#include #include "xml2json.h" #include "json.h" @@ -40,6 +41,63 @@ struct nodelist { static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache); +static void nodelist_init(struct nodelist *list) +{ + memset(list, 0, sizeof(*list)); +} + +static void nodelist_free(struct nodelist *list) +{ + free(list->nodes); + memset(list, 0, sizeof(*list)); +} + +static void nodelist_push(struct nodelist *list, xmlNodePtr node) +{ + if (list->fill == list->len) { + xmlNodePtr *n = realloc(list->nodes, sizeof(xmlNodePtr)*(list->len + 16)); + if (!n) { + ICECAST_LOG_ERROR("Can not allocate memory for node list. BAD."); + return; + } + + list->nodes = n; + list->len += 16; + } + + list->nodes[list->fill++] = node; +} + +static xmlNodePtr nodelist_get(struct nodelist *list, size_t idx) +{ + if (idx >= list->fill) + return NULL; + return list->nodes[idx]; +} + +static void nodelist_unset(struct nodelist *list, size_t idx) +{ + if (idx >= list->fill) + return; + list->nodes[idx] = NULL; +} + +static size_t nodelist_fill(struct nodelist *list) +{ + return list->fill; +} + +static int nodelist_is_empty(struct nodelist *list) +{ + size_t i; + + for (i = 0; i < list->fill; i++) + if (list->nodes[i]) + return 0; + + return 1; +} + static void handle_textchildnode(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { xmlChar *value = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); From 9be721723cf449a79c66c70d3b45b7af63b5b72e Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 16:54:42 +0000 Subject: [PATCH 22/42] Feature: Added function for writing boolean XML nodes as JSON --- src/xml2json.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index ac9e014b..e234e7b4 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -19,6 +19,7 @@ #include "xml2json.h" #include "json.h" +#include "util.h" /* For XMLSTR() */ #include "cfgfile.h" @@ -108,6 +109,16 @@ static void handle_textchildnode(json_renderer_t *renderer, xmlDocPtr doc, xmlNo } } +static void handle_booleanchildnode(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + xmlChar *value = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, (const char *)node->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_boolean(renderer, util_str_to_bool((const char*)value)); + xmlFree(value); + } +} + static int handle_node_modules(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { if (node->type == XML_ELEMENT_NODE && strcmp((const char *)node->name, "modules") == 0) { From 30f9e89e2661e5b1b7bd5e42470e1ae7e2663d07 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 19:11:13 +0000 Subject: [PATCH 23/42] Feature: Added a basic renderer for --- src/xml2json.c | 214 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index e234e7b4..d292667d 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -215,6 +215,218 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, render_node_generic(renderer, doc, node, parent, cache); } +static int handle_simple_child(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache, xmlNodePtr child, const char * number_keys[], const char * boolean_keys[]) +{ + if (child->type == XML_ELEMENT_NODE && child->name) { + size_t i; + + for (i = 0; number_keys[i]; i++) { + if (strcmp((const char *)child->name, number_keys[i]) == 0) { + xmlChar *value = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, (const char *)child->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_int(renderer, strtoll((const char*)value, NULL, 10)); + xmlFree(value); + return 1; + } + } + } + + for (i = 0; boolean_keys[i]; i++) { + if (strcmp((const char *)child->name, boolean_keys[i]) == 0) { + handle_booleanchildnode(renderer, doc, child, node, cache); + return 1; + } + } + + if (child->xmlChildrenNode && !child->xmlChildrenNode->next && child->xmlChildrenNode->type == XML_TEXT_NODE) { + handle_textchildnode(renderer, doc, child, node, cache); + return 1; + } + } + + return 0; +} + +static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + static const char * number_keys_global[] = { + "listeners", "clients", "client_connections", "connections", "file_connections", "listener_connections", + "source_client_connections", "source_relay_connections", "source_total_connections", "sources", "stats", "stats_connections", NULL + }; + static const char * boolean_keys_global[] = { + NULL + }; + static const char * number_keys_source[] = { + "audio_bitrate", "audio_channels", "audio_samplerate", "ice-bitrate", "listener_peak", "listeners", "slow_listeners", + "total_bytes_read", "total_bytes_sent", NULL + }; + static const char * boolean_keys_source[] = { + "public", NULL + }; + + int handled = 0; + + if (node->type == XML_ELEMENT_NODE) { + const char *nodename = (const char *)node->name; + handled = 1; + if (strcmp(nodename, "icestats") == 0 || strcmp(nodename, "source") == 0) { + int is_icestats = strcmp(nodename, "icestats") == 0; + struct nodelist nodelist; + size_t i; + size_t len; + + nodelist_init(&nodelist); + + if (is_icestats) + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + do { + if (!handle_simple_child(renderer, doc, node, parent, cache, cur, + is_icestats ? number_keys_global : number_keys_source, + is_icestats ? boolean_keys_global : boolean_keys_source + )) { + nodelist_push(&nodelist, cur); + } + cur = cur->next; + } while (cur); + } + + len = nodelist_fill(&nodelist); + for (i = 0; i < len; i++) { + xmlNodePtr cur = nodelist_get(&nodelist, i); + if (cur == NULL) + continue; + + if (cur->type == XML_ELEMENT_NODE && cur->name) { + if (strcmp((const char *)cur->name, "modules") == 0) { + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + handle_node_modules(renderer, doc, cur, node, cache); + nodelist_unset(&nodelist, i); + } else if (strcmp((const char *)cur->name, "source") == 0) { + size_t j; + + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + + for (j = i; j < len; j++) { + xmlNodePtr subcur = nodelist_get(&nodelist, j); + if (subcur == NULL) + continue; + + if (subcur->type == XML_ELEMENT_NODE && subcur->name && strcmp((const char *)cur->name, (const char *)subcur->name) == 0) { + xmlChar *mount = xmlGetProp(subcur, XMLSTR("mount")); + if (mount) { + json_renderer_write_key(renderer, (const char *)mount, JSON_RENDERER_FLAGS_NONE); + xmlFree(mount); + nodelist_unset(&nodelist, j); + render_node_legacystats(renderer, doc, cur, node, cache); + } + } + } + + json_renderer_end(renderer); + nodelist_unset(&nodelist, i); + } else if (strcmp((const char *)cur->name, "metadata") == 0) { + size_t j; + + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + for (j = i; j < len; j++) { + xmlNodePtr subcur = nodelist_get(&nodelist, j); + if (subcur == NULL) + continue; + + if (subcur->type == XML_ELEMENT_NODE && subcur->name && strcmp((const char *)cur->name, (const char *)subcur->name) == 0) { + xmlNodePtr child = subcur->xmlChildrenNode; + while (child) { + handle_textchildnode(renderer, doc, child, subcur, cache); + child = child->next; + } + nodelist_unset(&nodelist, j); + } + } + json_renderer_end(renderer); + } else if (strcmp((const char *)cur->name, "authentication") == 0) { + size_t j; + + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + for (j = i; j < len; j++) { + xmlNodePtr subcur = nodelist_get(&nodelist, j); + if (subcur == NULL) + continue; + + if (subcur->type == XML_ELEMENT_NODE && subcur->name && strcmp((const char *)cur->name, (const char *)subcur->name) == 0) { + xmlNodePtr child = subcur->xmlChildrenNode; + while (child) { + render_node_legacystats(renderer, doc, child, subcur, cache); + child = child->next; + } + nodelist_unset(&nodelist, j); + } + } + json_renderer_end(renderer); + } else if (strcmp((const char *)cur->name, "playlist") == 0) { + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + render_node(renderer, doc, cur, node, cache); + nodelist_unset(&nodelist, i); + } + } + //render_node_generic(renderer, doc, node, parent, cache); + } + + if (!nodelist_is_empty(&nodelist)) { + json_renderer_write_key(renderer, "unhandled-child", JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + len = nodelist_fill(&nodelist); + for (i = 0; i < len; i++) { + xmlNodePtr cur = nodelist_get(&nodelist, i); + if (cur == NULL) + continue; + + render_node(renderer, doc, cur, node, cache); + } + json_renderer_end(renderer); + } + + json_renderer_end(renderer); + if (is_icestats) + json_renderer_end(renderer); + + nodelist_free(&nodelist); + } else if (strcmp(nodename, "role") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->properties) { + xmlAttrPtr cur = node->properties; + + do { + const char *name = (const char*)cur->name; + xmlChar *value = xmlNodeListGetString(doc, cur->children, 1); + + if (value) { + json_renderer_write_key(renderer, name, JSON_RENDERER_FLAGS_NONE); + if (strncmp(name, "can-", 4) == 0) { + json_renderer_write_boolean(renderer, util_str_to_bool((const char *)value)); + } else { + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + } + xmlFree(value); + } + } while ((cur = cur->next)); + } + json_renderer_end(renderer); + } else { + handled = 0; + } + } + + if (!handled) + render_node_generic(renderer, doc, node, parent, cache); +} + static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -303,6 +515,8 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod if (href) { if (strcmp(href, "http://icecast.org/specs/legacyresponse-0.0.1") == 0) { render = render_node_legacyresponse; + } else if (strcmp(href, "http://icecast.org/specs/legacystats-0.0.1") == 0) { + render = render_node_legacystats; } } From b9652444ff0da3ffe1eaf6131551a290e2e85217 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 19:23:12 +0000 Subject: [PATCH 24/42] Feature: Workaround for xmlns set as property --- src/xml2json.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/xml2json.c b/src/xml2json.c index d292667d..c2307352 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -502,8 +502,9 @@ static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNod static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { void (*render)(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) = NULL; + xmlChar * workaroundProp = xmlGetProp(node, XMLSTR("xmlns")); - if (node->ns == cache->ns) + if (node->ns == cache->ns && !workaroundProp) render = cache->render; if (render == NULL) { @@ -512,6 +513,10 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod if (node->ns && node->ns->href) href = (const char *)node->ns->href; + if (!href) { + href = (const char *)workaroundProp; + } + if (href) { if (strcmp(href, "http://icecast.org/specs/legacyresponse-0.0.1") == 0) { render = render_node_legacyresponse; @@ -527,6 +532,9 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod cache->render = render; } + if (workaroundProp) + xmlFree(workaroundProp); + render(renderer, doc, node, parent, cache); } From 758a67c475f11b47b514a45eb925f842cbf589f8 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 19:55:21 +0000 Subject: [PATCH 25/42] Update: More aggressively use workaround for xmlns as property --- src/xml2json.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index c2307352..a7ecc518 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -513,15 +513,16 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod if (node->ns && node->ns->href) href = (const char *)node->ns->href; - if (!href) { + if (workaroundProp) href = (const char *)workaroundProp; - } if (href) { if (strcmp(href, "http://icecast.org/specs/legacyresponse-0.0.1") == 0) { render = render_node_legacyresponse; } else if (strcmp(href, "http://icecast.org/specs/legacystats-0.0.1") == 0) { render = render_node_legacystats; + } else if (strcmp(href, "http://xspf.org/ns/0/") == 0) { + render = render_node_xspf; } } From 3af23324f07ffe7643ce4a4c551098bdee3c89f6 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 20:14:22 +0000 Subject: [PATCH 26/42] Feature: Added that much of a XSPF -> JSPF renderer as we need --- src/xml2json.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index a7ecc518..b3d1c7d4 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -427,6 +427,91 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm render_node_generic(renderer, doc, node, parent, cache); } +static void render_node_xspf(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + const char * text_keys[] = {"title", "creator", "annotation", "info", "identifier", "image", "date", "license", "album", NULL}; + const char * uint_keys[] = {"trackNum", "duration", NULL}; + int handled = 0; + + if (node->type == XML_ELEMENT_NODE) { + const char *nodename = (const char *)node->name; + int handle_childs = 0; + int close_after_me = 0; + size_t i; + + handled = 1; + + for (i = 0; text_keys[i]; i++) { + if (strcmp(nodename, text_keys[i]) == 0) { + handle_textchildnode(renderer, doc, node, parent, cache); + return; + } + } + + for (i = 0; uint_keys[i]; i++) { + if (strcmp(nodename, uint_keys[i]) == 0) { + xmlChar *value = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + if (value) { + long long int val = strtoll((const char*)value, NULL, 10); + if (val < 0) + return; + + json_renderer_write_key(renderer, nodename, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_uint(renderer, val); + xmlFree(value); + return ; + } + return; + } + } + + for (i = 0; text_keys[i]; i++) { + if (strcmp(nodename, text_keys[i]) == 0) { + handle_textchildnode(renderer, doc, node, parent, cache); + return; + } + } + + if (strcmp(nodename, "playlist") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + json_renderer_write_key(renderer, "playlist", JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + + handle_childs = 1; + close_after_me = 2; + } else if (strcmp(nodename, "trackList") == 0) { + json_renderer_write_key(renderer, "track", JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + + handle_childs = 1; + close_after_me = 1; + } else if (strcmp(nodename, "track") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + + handle_childs = 1; + close_after_me = 1; + } else { + handled = 0; + } + + if (handled) { + if (handle_childs) { + xmlNodePtr child = node->xmlChildrenNode; + while (child) { + render_node_xspf(renderer, doc, child, node, cache); + child = child->next; + }; + } + + for (; close_after_me; close_after_me--) + json_renderer_end(renderer); + } + } + + if (!handled) + render_node_generic(renderer, doc, node, parent, cache); +} + static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); From 61d68a13b4d8be29695f9f60721df4d1865d87ad Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 20:15:49 +0000 Subject: [PATCH 27/42] Fix: Report sources correctly --- src/xml2json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xml2json.c b/src/xml2json.c index b3d1c7d4..f166e867 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -322,7 +322,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm json_renderer_write_key(renderer, (const char *)mount, JSON_RENDERER_FLAGS_NONE); xmlFree(mount); nodelist_unset(&nodelist, j); - render_node_legacystats(renderer, doc, cur, node, cache); + render_node_legacystats(renderer, doc, subcur, cur, cache); } } } From 02e9c048d0183ac9e39394485e9bf843de946177 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 20:19:52 +0000 Subject: [PATCH 28/42] Fix: Handle bytes >= 0x80 correctly --- src/json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/json.c b/src/json.c index 7a001b9c..c5fdf9ff 100644 --- a/src/json.c +++ b/src/json.c @@ -290,7 +290,7 @@ static void write_string(json_renderer_t *renderer, const char *string, unsigned return; } - if (*string < 0x20) { + if (((unsigned char)*string) < 0x20) { char buf[7]; snprintf(buf, sizeof(buf), "\\u%.4x", (unsigned int)*string); write_raw(renderer, buf, 0); From 2a4ed04671c4dd391022af2cdb313ca39536a04f Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Wed, 7 Oct 2020 20:58:28 +0000 Subject: [PATCH 29/42] Feature: Handle and nicer --- src/xml2json.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index f166e867..02a07edb 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -218,13 +218,14 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, static int handle_simple_child(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache, xmlNodePtr child, const char * number_keys[], const char * boolean_keys[]) { if (child->type == XML_ELEMENT_NODE && child->name) { + const char *childname = (const char *)child->name; size_t i; for (i = 0; number_keys[i]; i++) { - if (strcmp((const char *)child->name, number_keys[i]) == 0) { + if (strcmp(childname, number_keys[i]) == 0) { xmlChar *value = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); if (value) { - json_renderer_write_key(renderer, (const char *)child->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_key(renderer, childname, JSON_RENDERER_FLAGS_NONE); json_renderer_write_int(renderer, strtoll((const char*)value, NULL, 10)); xmlFree(value); return 1; @@ -233,12 +234,36 @@ static int handle_simple_child(json_renderer_t *renderer, xmlDocPtr doc, xmlNode } for (i = 0; boolean_keys[i]; i++) { - if (strcmp((const char *)child->name, boolean_keys[i]) == 0) { + if (strcmp(childname, boolean_keys[i]) == 0) { handle_booleanchildnode(renderer, doc, child, node, cache); return 1; } } + if (strcmp(childname, "max_listeners") == 0) { + xmlChar *value = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, childname, JSON_RENDERER_FLAGS_NONE); + if (strcmp((const char *)value, "unlimited") == 0) { + json_renderer_write_null(renderer); + } else { + json_renderer_write_int(renderer, strtoll((const char*)value, NULL, 10)); + } + xmlFree(value); + return 1; + } + } + + if (strcmp(childname, "authenticator") == 0) { + xmlChar *value = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (value) { + json_renderer_write_key(renderer, childname, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_boolean(renderer, strlen((const char *)value)); + xmlFree(value); + return 1; + } + } + if (child->xmlChildrenNode && !child->xmlChildrenNode->next && child->xmlChildrenNode->type == XML_TEXT_NODE) { handle_textchildnode(renderer, doc, child, node, cache); return 1; From 6ba8ab1db53ad6d1f6924712531bcddc0b3ff2ce Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Fri, 9 Oct 2020 08:10:39 +0000 Subject: [PATCH 30/42] Update: Use different xmlns depending on root node --- src/admin.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/admin.c b/src/admin.c index 41a0144b..e55aae0e 100644 --- a/src/admin.c +++ b/src/admin.c @@ -449,7 +449,17 @@ void admin_send_response(xmlDocPtr doc, xmlDocDumpMemory(doc, &buff, &len); content_type = "text/xml"; } else { - char *json = xml2json_render_doc_simple(doc, "http://icecast.org/specs/legacyresponse-0.0.1"); + xmlNodePtr xmlroot = xmlDocGetRootElement(doc); + const char *ns; + char *json; + + if (strcmp((const char *)xmlroot->name, "iceresponse") == 0) { + ns = "http://icecast.org/specs/legacyresponse-0.0.1"; + } else { + ns = "http://icecast.org/specs/legacystats-0.0.1"; + } + + json = xml2json_render_doc_simple(doc, ns); buff = xmlStrdup(XMLSTR(json)); len = strlen(json); free(json); From f52d7ae07721995a5fc4c9f7b7ac5d31a391cf70 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Fri, 9 Oct 2020 08:11:09 +0000 Subject: [PATCH 31/42] Feature: Added experimental STATS_JSON_REQUEST "stats.json" --- src/admin.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/admin.c b/src/admin.c index e55aae0e..d1736be2 100644 --- a/src/admin.c +++ b/src/admin.c @@ -76,6 +76,7 @@ #define LISTCLIENTS_HTML_REQUEST "listclients.xsl" #define STATS_RAW_REQUEST "stats" #define STATS_HTML_REQUEST "stats.xsl" +#define STATS_JSON_REQUEST "stats.json" #define QUEUE_RELOAD_RAW_REQUEST "reloadconfig" #define QUEUE_RELOAD_HTML_REQUEST "reloadconfig.xsl" #define QUEUE_RELOAD_JSON_REQUEST "reloadconfig.json" @@ -145,6 +146,7 @@ static const admin_command_handler_t handlers[] = { { LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_show_listeners, NULL}, { STATS_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, { STATS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL}, + { STATS_JSON_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_JSON, command_stats, NULL}, { "stats.xml", ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, { QUEUE_RELOAD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_queue_reload, NULL}, { QUEUE_RELOAD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_queue_reload, NULL}, From a8a3207ccef32fce87fc90a8ac087bbbfb75fbec Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Fri, 9 Oct 2020 19:40:52 +0000 Subject: [PATCH 32/42] Feature: Added node for node identification --- src/xml2json.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/xml2json.c b/src/xml2json.c index 02a07edb..fd150ebb 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -303,8 +303,15 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm nodelist_init(&nodelist); - if (is_icestats) + if (is_icestats) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, "icestats", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, "http://icecast.org/specs/legacystats-0.0.1", JSON_RENDERER_FLAGS_NONE); + json_renderer_end(renderer); + } json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); if (node->xmlChildrenNode) { xmlNodePtr cur = node->xmlChildrenNode; From cc13aba182273b5735e25cda841f1a9f3698daa2 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 07:44:26 +0000 Subject: [PATCH 33/42] Feature: Added constants for XML namespaces --- src/admin.c | 4 ++-- src/icecasttypes.h | 7 +++++++ src/playlist.c | 2 +- src/xml2json.c | 8 ++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/admin.c b/src/admin.c index d1736be2..ecfb23c9 100644 --- a/src/admin.c +++ b/src/admin.c @@ -456,9 +456,9 @@ void admin_send_response(xmlDocPtr doc, char *json; if (strcmp((const char *)xmlroot->name, "iceresponse") == 0) { - ns = "http://icecast.org/specs/legacyresponse-0.0.1"; + ns = XMLNS_LEGACY_RESPONSE; } else { - ns = "http://icecast.org/specs/legacystats-0.0.1"; + ns = XMLNS_LEGACY_STATS; } json = xml2json_render_doc_simple(doc, ns); diff --git a/src/icecasttypes.h b/src/icecasttypes.h index d7374c05..4b340e72 100644 --- a/src/icecasttypes.h +++ b/src/icecasttypes.h @@ -15,6 +15,13 @@ #include "compat.h" +/* ---[ * ]--- */ +/* XML namespaces */ +#define XMLNS_REPORTXML "http://icecast.org/specs/reportxml-0.0.1" +#define XMLNS_XSPF "http://xspf.org/ns/0/" +#define XMLNS_LEGACY_STATS "http://icecast.org/specs/legacystats-0.0.1" +#define XMLNS_LEGACY_RESPONSE "http://icecast.org/specs/legacyresponse-0.0.1" + /* ---[ client.[ch] ]--- */ typedef struct _client_tag client_t; diff --git a/src/playlist.c b/src/playlist.c index a9d8b73e..4f653cce 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -165,7 +165,7 @@ xmlNodePtr playlist_render_xspf(playlist_t *playlist) rootnode = xmlNewNode(NULL, XMLSTR("playlist")); xmlSetProp(rootnode, XMLSTR("version"), XMLSTR("1")); - xmlSetProp(rootnode, XMLSTR("xmlns"), XMLSTR("http://xspf.org/ns/0/")); + xmlSetProp(rootnode, XMLSTR("xmlns"), XMLSTR(XMLNS_XSPF)); tracklist = xmlNewNode(NULL, XMLSTR("trackList")); xmlAddChild(rootnode, tracklist); diff --git a/src/xml2json.c b/src/xml2json.c index fd150ebb..b9ff9509 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -309,7 +309,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); json_renderer_write_string(renderer, "icestats", JSON_RENDERER_FLAGS_NONE); json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, "http://icecast.org/specs/legacystats-0.0.1", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, XMLNS_LEGACY_STATS, JSON_RENDERER_FLAGS_NONE); json_renderer_end(renderer); } json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -634,11 +634,11 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod href = (const char *)workaroundProp; if (href) { - if (strcmp(href, "http://icecast.org/specs/legacyresponse-0.0.1") == 0) { + if (strcmp(href, XMLNS_LEGACY_RESPONSE) == 0) { render = render_node_legacyresponse; - } else if (strcmp(href, "http://icecast.org/specs/legacystats-0.0.1") == 0) { + } else if (strcmp(href, XMLNS_LEGACY_STATS) == 0) { render = render_node_legacystats; - } else if (strcmp(href, "http://xspf.org/ns/0/") == 0) { + } else if (strcmp(href, XMLNS_XSPF) == 0) { render = render_node_xspf; } } From 36ff987e896aa5b2291eb4c1d7314eee3c2da214 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 07:55:46 +0000 Subject: [PATCH 34/42] Feature: Unified node identification in a single function --- src/xml2json.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index b9ff9509..1550a941 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -99,6 +99,24 @@ static int nodelist_is_empty(struct nodelist *list) return 1; } +static void handle_node_identification(json_renderer_t *renderer, const char *name, const char *ns, const char *id) +{ + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (name) { + json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, name, JSON_RENDERER_FLAGS_NONE); + } + if (ns) { + json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, ns, JSON_RENDERER_FLAGS_NONE); + } + if (id) { + json_renderer_write_key(renderer, "id", JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, id, JSON_RENDERER_FLAGS_NONE); + } + json_renderer_end(renderer); +} + static void handle_textchildnode(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { xmlChar *value = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); @@ -305,12 +323,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm if (is_icestats) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); - json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); - json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, "icestats", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, XMLNS_LEGACY_STATS, JSON_RENDERER_FLAGS_NONE); - json_renderer_end(renderer); + handle_node_identification(renderer, "icestats", XMLNS_LEGACY_STATS, NULL); } json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); if (node->xmlChildrenNode) { From 4a54efbc46641d69d5280f2af9fdec084adef0af Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 08:00:01 +0000 Subject: [PATCH 35/42] Feature: Include node identification for messages --- src/xml2json.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index 1550a941..f05ce7b7 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -187,6 +187,8 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, const char *nodename = (const char *)node->name; handled = 1; if (strcmp(nodename, "iceresponse") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + handle_node_identification(renderer, "iceresponse", XMLNS_LEGACY_RESPONSE, NULL); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); if (node->xmlChildrenNode) { xmlNodePtr cur = node->xmlChildrenNode; @@ -222,6 +224,7 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, } while (cur); } json_renderer_end(renderer); + json_renderer_end(renderer); } else if (strcmp(nodename, "modules") == 0) { handled = handle_node_modules(renderer, doc, node, parent, cache); } else { From c900f0b18f9a90c6cd6d6c65c298ca931b3357a0 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 09:33:36 +0000 Subject: [PATCH 36/42] Fix: Set xmlns for in reports --- src/client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.c b/src/client.c index 38b5caf9..ee32a96b 100644 --- a/src/client.c +++ b/src/client.c @@ -712,6 +712,7 @@ static void client_get_reportxml__add_basic_stats(reportxml_t *report) refobject_unref(rootnode); xmlroot = xmlNewNode(NULL, XMLSTR("icestats")); + xmlSetProp(xmlroot, XMLSTR("xmlns"), XMLSTR(XMLNS_LEGACY_STATS)); modules = module_container_get_modulelist_as_xml(global.modulecontainer); xmlAddChild(xmlroot, modules); From ea5b21b58c9ce5b232389de334514552f43d4bfb Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 09:39:02 +0000 Subject: [PATCH 37/42] Feature: Added support for definition, and akindof to handle_node_identification() --- src/xml2json.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index f05ce7b7..1662d089 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -99,21 +99,21 @@ static int nodelist_is_empty(struct nodelist *list) return 1; } -static void handle_node_identification(json_renderer_t *renderer, const char *name, const char *ns, const char *id) +static void handle_node_identification(json_renderer_t *renderer, const char *name, const char *ns, const char *id, const char *definition, const char *akindof) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); - if (name) { - json_renderer_write_key(renderer, "name", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, name, JSON_RENDERER_FLAGS_NONE); - } - if (ns) { - json_renderer_write_key(renderer, "ns", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, ns, JSON_RENDERER_FLAGS_NONE); - } - if (id) { - json_renderer_write_key(renderer, "id", JSON_RENDERER_FLAGS_NONE); - json_renderer_write_string(renderer, id, JSON_RENDERER_FLAGS_NONE); +#define handle_node_identification__single(name) \ + if ((name)) { \ + json_renderer_write_key(renderer, ( # name ), JSON_RENDERER_FLAGS_NONE); \ + json_renderer_write_string(renderer, (name), JSON_RENDERER_FLAGS_NONE); \ } + + handle_node_identification__single(name) + handle_node_identification__single(ns) + handle_node_identification__single(id) + handle_node_identification__single(definition) + handle_node_identification__single(akindof) + json_renderer_end(renderer); } @@ -188,7 +188,7 @@ static void render_node_legacyresponse(json_renderer_t *renderer, xmlDocPtr doc, handled = 1; if (strcmp(nodename, "iceresponse") == 0) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); - handle_node_identification(renderer, "iceresponse", XMLNS_LEGACY_RESPONSE, NULL); + handle_node_identification(renderer, "iceresponse", XMLNS_LEGACY_RESPONSE, NULL, NULL, NULL); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); if (node->xmlChildrenNode) { xmlNodePtr cur = node->xmlChildrenNode; @@ -326,7 +326,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm if (is_icestats) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); - handle_node_identification(renderer, "icestats", XMLNS_LEGACY_STATS, NULL); + handle_node_identification(renderer, "icestats", XMLNS_LEGACY_STATS, NULL, NULL, NULL); } json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); if (node->xmlChildrenNode) { From 77ea98730be8194b6d0aec84c5a84410c0291ffc Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 09:50:35 +0000 Subject: [PATCH 38/42] Feature: Added support for JSON rendering of report xml --- src/xml2json.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/xml2json.c b/src/xml2json.c index 1662d089..17b7b7a3 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -99,6 +99,31 @@ static int nodelist_is_empty(struct nodelist *list) return 1; } +static int has_ns_changed(xmlNodePtr node, xmlNodePtr parent) +{ + xmlChar *xmlns; + + if (parent == NULL) + return 1; + + if (node->ns != parent->ns) { + if (node->ns && parent->ns && node->ns->href && parent->ns->href) { + if (strcmp((const char *)node->ns->href, (const char *)parent->ns->href) != 0) + return 1; + } else { + return 1; + } + } + + xmlns = xmlGetProp(node, XMLSTR("xmlns")); + if (xmlns) { + xmlFree(xmlns); + return 1; + } + + return 0; +} + static void handle_node_identification(json_renderer_t *renderer, const char *name, const char *ns, const char *id, const char *definition, const char *akindof) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -560,6 +585,85 @@ static void render_node_xspf(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePt render_node_generic(renderer, doc, node, parent, cache); } +static void render_node_reportxml(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) +{ + static const char * valid_tags[] = { + "report", "definition", "incident", "state", "backtrace", "position", "more", "fix", + "action", "reason", "text", "timestamp", "resource", "value", "reference", "extension", NULL + }; + static const char * skip_props[] = { + "id", "definition", "akindof", NULL + }; + + if (node->type == XML_ELEMENT_NODE) { + const char *nodename = (const char *)node->name; + size_t i; + + for (i = 0; valid_tags[i]; i++) { + if (strcmp(nodename, valid_tags[i]) == 0) { + xmlChar *id = xmlGetProp(node, XMLSTR("id")); + xmlChar *definition = xmlGetProp(node, XMLSTR("definition")); + xmlChar *akindof = xmlGetProp(node, XMLSTR("akindof")); + + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + handle_node_identification(renderer, nodename, has_ns_changed(node, parent) ? XMLNS_REPORTXML : NULL, (const char *)id, (const char *)definition, (const char *)akindof); + + if (id) + xmlFree(id); + if (definition) + xmlFree(definition); + if (akindof) + xmlFree(akindof); + + if (node->properties || node->xmlChildrenNode) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->properties) { + xmlAttrPtr cur = node->properties; + + do { + int found = 0; + size_t j; + + for (j = 0; skip_props[j]; j++) { + if (strcmp((const char*)cur->name, skip_props[j]) == 0) { + found = 1; + break; + } + } + + if (!found) { + xmlChar *value = xmlNodeListGetString(doc, cur->children, 1); + if (value) { + json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); + xmlFree(value); + } + } + } while ((cur = cur->next)); + } + json_renderer_end(renderer); + } + + if (node->xmlChildrenNode) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + xmlNodePtr child = node->xmlChildrenNode; + while (child) { + render_node(renderer, doc, child, node, cache); + child = child->next; + }; + json_renderer_end(renderer); + } + + json_renderer_end(renderer); + + return; + } + } + } + + render_node_generic(renderer, doc, node, parent, cache); +} + static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -656,6 +760,8 @@ static void render_node(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr nod render = render_node_legacystats; } else if (strcmp(href, XMLNS_XSPF) == 0) { render = render_node_xspf; + } else if (strcmp(href, XMLNS_REPORTXML) == 0) { + render = render_node_reportxml; } } From 4cec193029242fc7fc44d92ad23108c7053e3507 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 10:00:23 +0000 Subject: [PATCH 39/42] Feature: Unify generic xml2json rendering with , , and report xml --- src/xml2json.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/xml2json.c b/src/xml2json.c index 17b7b7a3..e5e7da4c 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -666,6 +666,7 @@ static void render_node_reportxml(json_renderer_t *renderer, xmlDocPtr doc, xmlN static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNodePtr node, xmlNodePtr parent, struct xml2json_cache *cache) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); json_renderer_write_key(renderer, "type", JSON_RENDERER_FLAGS_NONE); @@ -703,27 +704,28 @@ static void render_node_generic(json_renderer_t *renderer, xmlDocPtr doc, xmlNod } else { json_renderer_write_null(renderer); } + json_renderer_end(renderer); - if (node->properties) { + if (node->properties || node->xmlChildrenNode) { xmlAttrPtr cur = node->properties; - - json_renderer_write_key(renderer, "properties", JSON_RENDERER_FLAGS_NONE); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); - do { + + while (cur) { xmlChar *value = xmlNodeListGetString(doc, cur->children, 1); if (value) { json_renderer_write_key(renderer, (const char*)cur->name, JSON_RENDERER_FLAGS_NONE); json_renderer_write_string(renderer, (const char*)value, JSON_RENDERER_FLAGS_NONE); xmlFree(value); } - } while ((cur = cur->next)); + + cur = cur->next; + } json_renderer_end(renderer); } if (node->xmlChildrenNode) { xmlNodePtr cur = node->xmlChildrenNode; - json_renderer_write_key(renderer, "children", JSON_RENDERER_FLAGS_NONE); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); do { render_node(renderer, doc, cur, node, cache); From 25f23c4192dd8f915f06c8abb78b646c3c8d1907 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sat, 10 Oct 2020 11:01:06 +0000 Subject: [PATCH 40/42] Feature: Added JSON output for admin commands showlog and dashboard --- src/admin.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/admin.c b/src/admin.c index ecfb23c9..5e079fe6 100644 --- a/src/admin.c +++ b/src/admin.c @@ -100,11 +100,13 @@ #define UPDATEMETADATA_HTML_REQUEST "updatemetadata.xsl" #define SHOWLOG_RAW_REQUEST "showlog" #define SHOWLOG_HTML_REQUEST "showlog.xsl" +#define SHOWLOG_JSON_REQUEST "showlog.json" #define MARKLOG_RAW_REQUEST "marklog" #define MARKLOG_HTML_REQUEST "marklog.xsl" #define MARKLOG_JSON_REQUEST "marklog.json" #define DASHBOARD_RAW_REQUEST "dashboard" #define DASHBOARD_HTML_REQUEST "dashboard.xsl" +#define DASHBOARD_JSON_REQUEST "dashboard.json" #define DEFAULT_RAW_REQUEST "" #define DEFAULT_HTML_REQUEST "" #define BUILDM3U_RAW_REQUEST "buildm3u" @@ -171,11 +173,13 @@ static const admin_command_handler_t handlers[] = { { BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_buildm3u, NULL}, { SHOWLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_show_log, NULL}, { SHOWLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_show_log, NULL}, + { SHOWLOG_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_show_log, NULL}, { MARKLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_mark_log, NULL}, { MARKLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_mark_log, NULL}, { MARKLOG_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_mark_log, NULL}, { DASHBOARD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_dashboard, NULL}, { DASHBOARD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_dashboard, NULL}, + { DASHBOARD_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_dashboard, NULL}, { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL}, { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL} }; From 5e9feb611ae2faa614c4bc9016ae2448de65b007 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 11 Oct 2020 08:18:55 +0000 Subject: [PATCH 41/42] Feature: Added JSON output for admin commands listclients, listmounts, streamlist, moveclients, and updatemetadata --- src/admin.c | 33 +++++++++++++++++++++++++-------- src/xml2json.c | 22 ++++++++++++++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/admin.c b/src/admin.c index 5e079fe6..6424b37b 100644 --- a/src/admin.c +++ b/src/admin.c @@ -74,6 +74,7 @@ #define METADATA_JSON_REQUEST "metadata.json" #define LISTCLIENTS_RAW_REQUEST "listclients" #define LISTCLIENTS_HTML_REQUEST "listclients.xsl" +#define LISTCLIENTS_JSON_REQUEST "listclients.json" #define STATS_RAW_REQUEST "stats" #define STATS_HTML_REQUEST "stats.xsl" #define STATS_JSON_REQUEST "stats.json" @@ -82,11 +83,14 @@ #define QUEUE_RELOAD_JSON_REQUEST "reloadconfig.json" #define LISTMOUNTS_RAW_REQUEST "listmounts" #define LISTMOUNTS_HTML_REQUEST "listmounts.xsl" +#define LISTMOUNTS_JSON_REQUEST "listmounts.json" #define STREAMLIST_RAW_REQUEST "streamlist" #define STREAMLIST_HTML_REQUEST "streamlist.xsl" +#define STREAMLIST_JSON_REQUEST "streamlist.json" #define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt" #define MOVECLIENTS_RAW_REQUEST "moveclients" #define MOVECLIENTS_HTML_REQUEST "moveclients.xsl" +#define MOVECLIENTS_JSON_REQUEST "moveclients.json" #define KILLCLIENT_RAW_REQUEST "killclient" #define KILLCLIENT_HTML_REQUEST "killclient.xsl" #define KILLCLIENT_JSON_REQUEST "killclient.json" @@ -98,6 +102,7 @@ #define MANAGEAUTH_HTML_REQUEST "manageauth.xsl" #define UPDATEMETADATA_RAW_REQUEST "updatemetadata" #define UPDATEMETADATA_HTML_REQUEST "updatemetadata.xsl" +#define UPDATEMETADATA_JSON_REQUEST "updatemetadata.json" #define SHOWLOG_RAW_REQUEST "showlog" #define SHOWLOG_HTML_REQUEST "showlog.xsl" #define SHOWLOG_JSON_REQUEST "showlog.json" @@ -146,6 +151,7 @@ static const admin_command_handler_t handlers[] = { { SHOUTCAST_METADATA_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_shoutcast_metadata, NULL}, { LISTCLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_show_listeners, NULL}, { LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_show_listeners, NULL}, + { LISTCLIENTS_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_show_listeners, NULL}, { STATS_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, { STATS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL}, { STATS_JSON_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_JSON, command_stats, NULL}, @@ -155,11 +161,14 @@ static const admin_command_handler_t handlers[] = { { QUEUE_RELOAD_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_queue_reload, NULL}, { LISTMOUNTS_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, { LISTMOUNTS_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts, NULL}, + { LISTMOUNTS_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_list_mounts, NULL}, { STREAMLIST_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, { STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_PLAINTEXT, command_list_mounts, NULL}, { STREAMLIST_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts, NULL}, + { STREAMLIST_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_list_mounts, NULL}, { MOVECLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_move_clients, NULL}, { MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_move_clients, NULL}, + { MOVECLIENTS_JSON_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_JSON, command_move_clients, NULL}, { KILLCLIENT_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_client, NULL}, { KILLCLIENT_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_client, NULL}, { KILLCLIENT_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_kill_client, NULL}, @@ -170,6 +179,7 @@ static const admin_command_handler_t handlers[] = { { MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_manageauth, NULL}, { UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata, NULL}, { UPDATEMETADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_updatemetadata, NULL}, + { UPDATEMETADATA_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_updatemetadata, NULL}, { BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_buildm3u, NULL}, { SHOWLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_show_log, NULL}, { SHOWLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_show_log, NULL}, @@ -371,7 +381,7 @@ xmlNodePtr admin_build_rootnode(xmlDocPtr doc, const char *name) /* build an XML doc containing information about currently running sources. * If a mountpoint is passed then that source will not be added to the XML * doc even if the source is running */ -xmlDocPtr admin_build_sourcelist(const char *mount) +xmlDocPtr admin_build_sourcelist(const char *mount, client_t *client, admin_format_t format) { avl_node *node; source_t *source; @@ -406,9 +416,12 @@ xmlDocPtr admin_build_sourcelist(const char *mount) srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL); xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount)); - xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"), - (source->fallback_mount != NULL)? - XMLSTR(source->fallback_mount):XMLSTR("")); + if (source->fallback_mount) { + xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"), XMLSTR(source->fallback_mount)); + } else { + if (format == ADMIN_FORMAT_RAW && client->mode != OMODE_STRICT) + xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"), XMLSTR("")); + } snprintf(buf, sizeof(buf), "%lu", source->listeners); xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf)); @@ -428,7 +441,11 @@ xmlDocPtr admin_build_sourcelist(const char *mount) if (source->client) { snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - source->con->con_time)); - xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf)); + if (format == ADMIN_FORMAT_RAW && client->mode != OMODE_STRICT) { + xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf)); + } else { + xmlNewTextChild(srcnode, NULL, XMLSTR("connected"), XMLSTR(buf)); + } } xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"), XMLSTR(source->format->contenttype)); @@ -741,7 +758,7 @@ static void command_move_clients(client_t *client, } ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed); if (!parameters_passed) { - xmlDocPtr doc = admin_build_sourcelist(source->mount); + xmlDocPtr doc = admin_build_sourcelist(source->mount, client, response); if (idtext) { xmlNodePtr root = xmlDocGetRootElement(doc); @@ -1100,7 +1117,7 @@ static void command_fallback(client_t *client, if (client->mode == OMODE_STRICT) { if (!(COMMAND_OPTIONAL(client, "fallback", fallback))) { - xmlDocPtr doc = admin_build_sourcelist(source->mount); + xmlDocPtr doc = admin_build_sourcelist(source->mount, client, response); admin_send_response(doc, client, response, FALLBACK_HTML_REQUEST); xmlFreeDoc(doc); return; @@ -1273,7 +1290,7 @@ static void command_list_mounts(client_t *client, source_t *source, admin_format } else { xmlDocPtr doc; avl_tree_rlock(global.source_tree); - doc = admin_build_sourcelist(NULL); + doc = admin_build_sourcelist(NULL, client, response); avl_tree_unlock(global.source_tree); admin_send_response(doc, client, response, diff --git a/src/xml2json.c b/src/xml2json.c index e5e7da4c..e0d819f4 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -330,7 +330,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm }; static const char * number_keys_source[] = { "audio_bitrate", "audio_channels", "audio_samplerate", "ice-bitrate", "listener_peak", "listeners", "slow_listeners", - "total_bytes_read", "total_bytes_sent", NULL + "total_bytes_read", "total_bytes_sent", "connected", NULL }; static const char * boolean_keys_source[] = { "public", NULL @@ -341,7 +341,7 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm if (node->type == XML_ELEMENT_NODE) { const char *nodename = (const char *)node->name; handled = 1; - if (strcmp(nodename, "icestats") == 0 || strcmp(nodename, "source") == 0) { + if (strcmp(nodename, "icestats") == 0 || strcmp(nodename, "source") == 0 || strcmp(nodename, "listener") == 0) { int is_icestats = strcmp(nodename, "icestats") == 0; struct nodelist nodelist; size_t i; @@ -402,6 +402,24 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm json_renderer_end(renderer); nodelist_unset(&nodelist, i); + } else if (strcmp((const char *)cur->name, "listener") == 0) { + size_t j; + + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + + for (j = i; j < len; j++) { + xmlNodePtr subcur = nodelist_get(&nodelist, j); + if (subcur == NULL) + continue; + + if (subcur->type == XML_ELEMENT_NODE && subcur->name && strcmp((const char *)cur->name, (const char *)subcur->name) == 0) { + nodelist_unset(&nodelist, j); + render_node_legacystats(renderer, doc, subcur, cur, cache); + } + } + + json_renderer_end(renderer); } else if (strcmp((const char *)cur->name, "metadata") == 0) { size_t j; From 9ad34f97ad7d18b3a4283ca604f25afda31e35a7 Mon Sep 17 00:00:00 2001 From: Philipp Schafft Date: Sun, 11 Oct 2020 08:51:38 +0000 Subject: [PATCH 42/42] Feature: Added JSON output for admin command manageauth --- src/admin.c | 2 ++ src/xml2json.c | 47 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/admin.c b/src/admin.c index 6424b37b..aace3423 100644 --- a/src/admin.c +++ b/src/admin.c @@ -100,6 +100,7 @@ #define ADMIN_XSL_RESPONSE "response.xsl" #define MANAGEAUTH_RAW_REQUEST "manageauth" #define MANAGEAUTH_HTML_REQUEST "manageauth.xsl" +#define MANAGEAUTH_JSON_REQUEST "manageauth.json" #define UPDATEMETADATA_RAW_REQUEST "updatemetadata" #define UPDATEMETADATA_HTML_REQUEST "updatemetadata.xsl" #define UPDATEMETADATA_JSON_REQUEST "updatemetadata.json" @@ -177,6 +178,7 @@ static const admin_command_handler_t handlers[] = { { KILLSOURCE_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_kill_source, NULL}, { MANAGEAUTH_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_manageauth, NULL}, { MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_manageauth, NULL}, + { MANAGEAUTH_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, command_manageauth, NULL}, { UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata, NULL}, { UPDATEMETADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_updatemetadata, NULL}, { UPDATEMETADATA_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, command_updatemetadata, NULL}, diff --git a/src/xml2json.c b/src/xml2json.c index e0d819f4..3e48152c 100644 --- a/src/xml2json.c +++ b/src/xml2json.c @@ -378,9 +378,13 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); handle_node_modules(renderer, doc, cur, node, cache); nodelist_unset(&nodelist, i); - } else if (strcmp((const char *)cur->name, "source") == 0) { + } else if (strcmp((const char *)cur->name, "source") == 0 || strcmp((const char *)cur->name, "role") == 0) { + const char *key = "id"; size_t j; + if (strcmp((const char *)cur->name, "source") == 0) + key = "mount"; + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); @@ -390,10 +394,10 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm continue; if (subcur->type == XML_ELEMENT_NODE && subcur->name && strcmp((const char *)cur->name, (const char *)subcur->name) == 0) { - xmlChar *mount = xmlGetProp(subcur, XMLSTR("mount")); - if (mount) { - json_renderer_write_key(renderer, (const char *)mount, JSON_RENDERER_FLAGS_NONE); - xmlFree(mount); + xmlChar *keyval = xmlGetProp(subcur, XMLSTR(key)); + if (keyval) { + json_renderer_write_key(renderer, (const char *)keyval, JSON_RENDERER_FLAGS_NONE); + xmlFree(keyval); nodelist_unset(&nodelist, j); render_node_legacystats(renderer, doc, subcur, cur, cache); } @@ -401,7 +405,6 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm } json_renderer_end(renderer); - nodelist_unset(&nodelist, i); } else if (strcmp((const char *)cur->name, "listener") == 0) { size_t j; @@ -508,6 +511,38 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm } } while ((cur = cur->next)); } + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + do { + if (cur->type == XML_ELEMENT_NODE && cur->name) { + if (strcmp((const char *)cur->name, "users") == 0) { + json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY); + if (cur->xmlChildrenNode) { + xmlNodePtr subcur = cur->xmlChildrenNode; + do { + render_node_legacystats(renderer, doc, subcur, cur, cache); + subcur = subcur->next; + } while (subcur); + } + json_renderer_end(renderer); + } + } + cur = cur->next; + } while (cur); + } + json_renderer_end(renderer); + } else if (strcmp(nodename, "user") == 0) { + json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT); + if (node->xmlChildrenNode) { + xmlNodePtr cur = node->xmlChildrenNode; + do { + if (cur->xmlChildrenNode && !cur->xmlChildrenNode->next && cur->xmlChildrenNode->type == XML_TEXT_NODE) { + handle_textchildnode(renderer, doc, cur, node, cache); + } + cur = cur->next; + } while (cur); + } json_renderer_end(renderer); } else { handled = 0;