1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2024-06-23 06:25:24 +00:00

Merge branch 'feature-json-renderer'

This commit is contained in:
Philipp Schafft 2020-10-11 09:01:01 +00:00
commit 5ca6747db0
11 changed files with 1480 additions and 26 deletions

View File

@ -35,6 +35,8 @@ noinst_HEADERS = \
module.h \
reportxml.h \
reportxml_helper.h \
json.h \
xml2json.h \
listensocket.h \
fastevent.h \
event.h \
@ -82,6 +84,8 @@ icecast_SOURCES = \
module.c \
reportxml.c \
reportxml_helper.c \
json.c \
xml2json.c \
listensocket.c \
fastevent.c \
format.c \

View File

@ -37,6 +37,7 @@
#include "errors.h"
#include "reportxml.h"
#include "reportxml_helper.h"
#include "xml2json.h"
#include "format.h"
@ -66,37 +67,52 @@
#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 LISTCLIENTS_JSON_REQUEST "listclients.json"
#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"
#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"
#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"
#define MANAGEAUTH_JSON_REQUEST "manageauth.json"
#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"
#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"
@ -129,38 +145,53 @@ 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},
{ 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},
{ "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},
{ 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},
{ 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},
{ 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},
{ 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}
};
@ -352,7 +383,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;
@ -387,9 +418,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));
@ -409,7 +443,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));
@ -425,13 +463,34 @@ 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 {
xmlNodePtr xmlroot = xmlDocGetRootElement(doc);
const char *ns;
char *json;
if (strcmp((const char *)xmlroot->name, "iceresponse") == 0) {
ns = XMLNS_LEGACY_RESPONSE;
} else {
ns = XMLNS_LEGACY_STATS;
}
json = xml2json_render_doc_simple(doc, ns);
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 +501,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 +518,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.");
@ -642,7 +701,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;
@ -701,7 +760,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);
@ -1060,7 +1119,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;
@ -1233,7 +1292,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,

View File

@ -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:
@ -574,21 +577,31 @@ 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.");
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, NULL);
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.");
@ -699,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);
@ -756,7 +770,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 +778,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;
}

View File

@ -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;
@ -33,7 +40,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] ]--- */

362
src/json.c Normal file
View File

@ -0,0 +1,362 @@
/* 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 <lion@lion.leolix.org>,
*/
/**
* This file contains functions for rendering JSON.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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)
have--;
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;
renderer->bufferlen = want;
}
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 **rendererptr)
{
json_renderer_t *renderer;
char *ret;
if (!rendererptr)
return NULL;
renderer = *rendererptr;
*rendererptr = NULL;
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 (((unsigned char)*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);
}

111
src/json.h Normal file
View File

@ -0,0 +1,111 @@
/* 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 <lion@lion.leolix.org>,
*/
/* This file contains functions for rendering JSON. */
#ifndef __JSON_H__
#define __JSON_H__
#include <stdint.h>
/*
* 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

View File

@ -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);

View File

@ -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,13 +599,14 @@ 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, int set_namespace)
{
xmlNodePtr ret;
ssize_t child_count;
ssize_t xml_child_count;
size_t i;
xmlChar *definition;
xmlChar *xmlns;
if (!node)
return NULL;
@ -628,6 +630,16 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node)
xmlFree(definition);
}
xmlns = xmlGetProp(ret, XMLSTR("xmlns"));
if (xmlns) {
xmlUnsetProp(ret, XMLSTR("xmlns"));
ns = xmlNewNs(ret, xmlns, NULL);
xmlFree(xmlns);
}
if (ns && set_namespace)
xmlSetNs(ret, ns);
for (i = 0; i < (size_t)child_count; i++) {
reportxml_node_t *child = reportxml_node_get_child(node, i);
xmlNodePtr xmlchild;
@ -637,7 +649,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, set_namespace);
refobject_unref(child);
if (!xmlchild) {
xmlFreeNode(ret);
@ -673,6 +685,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, 1);
}
reportxml_node_type_t reportxml_node_get_type(reportxml_node_t *node)
{
if (!node)

View File

@ -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 ]--- */

859
src/xml2json.c Normal file
View File

@ -0,0 +1,859 @@
/* 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 <lion@lion.leolix.org>,
*/
/**
* This file contains functions for rendering XML as JSON.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "xml2json.h"
#include "json.h"
#include "util.h"
/* For XMLSTR() */
#include "cfgfile.h"
#include "logging.h"
#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);
};
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 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 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);
#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);
}
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 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) {
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;
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_ARRAY);
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;
do {
int handled_child = 1;
if (cur->type == XML_ELEMENT_NODE && cur->name) {
if (strcmp((const char *)cur->name, "message") == 0) {
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) {
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);
json_renderer_end(renderer);
} else if (strcmp(nodename, "modules") == 0) {
handled = handle_node_modules(renderer, doc, node, parent, cache);
} else {
handled = 0;
}
}
if (!handled)
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) {
const char *childname = (const char *)child->name;
size_t i;
for (i = 0; number_keys[i]; i++) {
if (strcmp(childname, number_keys[i]) == 0) {
xmlChar *value = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
if (value) {
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;
}
}
}
for (i = 0; boolean_keys[i]; i++) {
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;
}
}
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", "connected", 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 || strcmp(nodename, "listener") == 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);
handle_node_identification(renderer, "icestats", XMLNS_LEGACY_STATS, NULL, NULL, NULL);
}
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 || 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);
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 *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);
}
}
}
json_renderer_end(renderer);
} 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;
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));
}
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;
}
}
if (!handled)
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_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_ARRAY);
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);
}
json_renderer_end(renderer);
if (node->properties || node->xmlChildrenNode) {
xmlAttrPtr cur = node->properties;
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT);
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);
}
cur = cur->next;
}
json_renderer_end(renderer);
}
if (node->xmlChildrenNode) {
xmlNodePtr cur = node->xmlChildrenNode;
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY);
do {
render_node(renderer, doc, cur, node, cache);
cur = cur->next;
} while (cur);
json_renderer_end(renderer);
}
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;
xmlChar * workaroundProp = xmlGetProp(node, XMLSTR("xmlns"));
if (node->ns == cache->ns && !workaroundProp)
render = cache->render;
if (render == NULL) {
const char *href = cache->default_namespace;
if (node->ns && node->ns->href)
href = (const char *)node->ns->href;
if (workaroundProp)
href = (const char *)workaroundProp;
if (href) {
if (strcmp(href, XMLNS_LEGACY_RESPONSE) == 0) {
render = render_node_legacyresponse;
} else if (strcmp(href, XMLNS_LEGACY_STATS) == 0) {
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;
}
}
if (render == NULL)
render = render_node_generic;
cache->ns = node->ns;
cache->render = render;
}
if (workaroundProp)
xmlFree(workaroundProp);
render(renderer, doc, node, parent, cache);
}
char * xml2json_render_doc_simple(xmlDocPtr doc, const char *default_namespace)
{
struct xml2json_cache cache;
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;
memset(&cache, 0, sizeof(cache));
cache.default_namespace = default_namespace;
render_node(renderer, doc, xmlroot, NULL, &cache);
return json_renderer_finish(&renderer);
}

18
src/xml2json.h Normal file
View File

@ -0,0 +1,18 @@
/* 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 <lion@lion.leolix.org>,
*/
/* This file contains functions for rendering XML as JSON. */
#ifndef __XML2JSON_H__
#define __XML2JSON_H__
#include <libxml/tree.h>
char * xml2json_render_doc_simple(xmlDocPtr doc, const char *default_namespace);
#endif