mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-12-04 14:46:30 -05:00
Merge branch 'feature-json-renderer'
This commit is contained in:
commit
5ca6747db0
@ -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 \
|
||||
|
85
src/admin.c
85
src/admin.c
@ -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,
|
||||
|
28
src/client.c
28
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:
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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
362
src/json.c
Normal 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
111
src/json.h
Normal 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
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
859
src/xml2json.c
Normal 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
18
src/xml2json.h
Normal 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
|
Loading…
Reference in New Issue
Block a user