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 \
|
module.h \
|
||||||
reportxml.h \
|
reportxml.h \
|
||||||
reportxml_helper.h \
|
reportxml_helper.h \
|
||||||
|
json.h \
|
||||||
|
xml2json.h \
|
||||||
listensocket.h \
|
listensocket.h \
|
||||||
fastevent.h \
|
fastevent.h \
|
||||||
event.h \
|
event.h \
|
||||||
@ -82,6 +84,8 @@ icecast_SOURCES = \
|
|||||||
module.c \
|
module.c \
|
||||||
reportxml.c \
|
reportxml.c \
|
||||||
reportxml_helper.c \
|
reportxml_helper.c \
|
||||||
|
json.c \
|
||||||
|
xml2json.c \
|
||||||
listensocket.c \
|
listensocket.c \
|
||||||
fastevent.c \
|
fastevent.c \
|
||||||
format.c \
|
format.c \
|
||||||
|
85
src/admin.c
85
src/admin.c
@ -37,6 +37,7 @@
|
|||||||
#include "errors.h"
|
#include "errors.h"
|
||||||
#include "reportxml.h"
|
#include "reportxml.h"
|
||||||
#include "reportxml_helper.h"
|
#include "reportxml_helper.h"
|
||||||
|
#include "xml2json.h"
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
@ -66,37 +67,52 @@
|
|||||||
|
|
||||||
#define FALLBACK_RAW_REQUEST "fallbacks"
|
#define FALLBACK_RAW_REQUEST "fallbacks"
|
||||||
#define FALLBACK_HTML_REQUEST "fallbacks.xsl"
|
#define FALLBACK_HTML_REQUEST "fallbacks.xsl"
|
||||||
|
#define FALLBACK_JSON_REQUEST "fallbacks.json"
|
||||||
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
|
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
|
||||||
#define METADATA_RAW_REQUEST "metadata"
|
#define METADATA_RAW_REQUEST "metadata"
|
||||||
#define METADATA_HTML_REQUEST "metadata.xsl"
|
#define METADATA_HTML_REQUEST "metadata.xsl"
|
||||||
|
#define METADATA_JSON_REQUEST "metadata.json"
|
||||||
#define LISTCLIENTS_RAW_REQUEST "listclients"
|
#define LISTCLIENTS_RAW_REQUEST "listclients"
|
||||||
#define LISTCLIENTS_HTML_REQUEST "listclients.xsl"
|
#define LISTCLIENTS_HTML_REQUEST "listclients.xsl"
|
||||||
|
#define LISTCLIENTS_JSON_REQUEST "listclients.json"
|
||||||
#define STATS_RAW_REQUEST "stats"
|
#define STATS_RAW_REQUEST "stats"
|
||||||
#define STATS_HTML_REQUEST "stats.xsl"
|
#define STATS_HTML_REQUEST "stats.xsl"
|
||||||
|
#define STATS_JSON_REQUEST "stats.json"
|
||||||
#define QUEUE_RELOAD_RAW_REQUEST "reloadconfig"
|
#define QUEUE_RELOAD_RAW_REQUEST "reloadconfig"
|
||||||
#define QUEUE_RELOAD_HTML_REQUEST "reloadconfig.xsl"
|
#define QUEUE_RELOAD_HTML_REQUEST "reloadconfig.xsl"
|
||||||
|
#define QUEUE_RELOAD_JSON_REQUEST "reloadconfig.json"
|
||||||
#define LISTMOUNTS_RAW_REQUEST "listmounts"
|
#define LISTMOUNTS_RAW_REQUEST "listmounts"
|
||||||
#define LISTMOUNTS_HTML_REQUEST "listmounts.xsl"
|
#define LISTMOUNTS_HTML_REQUEST "listmounts.xsl"
|
||||||
|
#define LISTMOUNTS_JSON_REQUEST "listmounts.json"
|
||||||
#define STREAMLIST_RAW_REQUEST "streamlist"
|
#define STREAMLIST_RAW_REQUEST "streamlist"
|
||||||
#define STREAMLIST_HTML_REQUEST "streamlist.xsl"
|
#define STREAMLIST_HTML_REQUEST "streamlist.xsl"
|
||||||
|
#define STREAMLIST_JSON_REQUEST "streamlist.json"
|
||||||
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
|
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
|
||||||
#define MOVECLIENTS_RAW_REQUEST "moveclients"
|
#define MOVECLIENTS_RAW_REQUEST "moveclients"
|
||||||
#define MOVECLIENTS_HTML_REQUEST "moveclients.xsl"
|
#define MOVECLIENTS_HTML_REQUEST "moveclients.xsl"
|
||||||
|
#define MOVECLIENTS_JSON_REQUEST "moveclients.json"
|
||||||
#define KILLCLIENT_RAW_REQUEST "killclient"
|
#define KILLCLIENT_RAW_REQUEST "killclient"
|
||||||
#define KILLCLIENT_HTML_REQUEST "killclient.xsl"
|
#define KILLCLIENT_HTML_REQUEST "killclient.xsl"
|
||||||
|
#define KILLCLIENT_JSON_REQUEST "killclient.json"
|
||||||
#define KILLSOURCE_RAW_REQUEST "killsource"
|
#define KILLSOURCE_RAW_REQUEST "killsource"
|
||||||
#define KILLSOURCE_HTML_REQUEST "killsource.xsl"
|
#define KILLSOURCE_HTML_REQUEST "killsource.xsl"
|
||||||
|
#define KILLSOURCE_JSON_REQUEST "killsource.json"
|
||||||
#define ADMIN_XSL_RESPONSE "response.xsl"
|
#define ADMIN_XSL_RESPONSE "response.xsl"
|
||||||
#define MANAGEAUTH_RAW_REQUEST "manageauth"
|
#define MANAGEAUTH_RAW_REQUEST "manageauth"
|
||||||
#define MANAGEAUTH_HTML_REQUEST "manageauth.xsl"
|
#define MANAGEAUTH_HTML_REQUEST "manageauth.xsl"
|
||||||
|
#define MANAGEAUTH_JSON_REQUEST "manageauth.json"
|
||||||
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
|
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
|
||||||
#define UPDATEMETADATA_HTML_REQUEST "updatemetadata.xsl"
|
#define UPDATEMETADATA_HTML_REQUEST "updatemetadata.xsl"
|
||||||
|
#define UPDATEMETADATA_JSON_REQUEST "updatemetadata.json"
|
||||||
#define SHOWLOG_RAW_REQUEST "showlog"
|
#define SHOWLOG_RAW_REQUEST "showlog"
|
||||||
#define SHOWLOG_HTML_REQUEST "showlog.xsl"
|
#define SHOWLOG_HTML_REQUEST "showlog.xsl"
|
||||||
|
#define SHOWLOG_JSON_REQUEST "showlog.json"
|
||||||
#define MARKLOG_RAW_REQUEST "marklog"
|
#define MARKLOG_RAW_REQUEST "marklog"
|
||||||
#define MARKLOG_HTML_REQUEST "marklog.xsl"
|
#define MARKLOG_HTML_REQUEST "marklog.xsl"
|
||||||
|
#define MARKLOG_JSON_REQUEST "marklog.json"
|
||||||
#define DASHBOARD_RAW_REQUEST "dashboard"
|
#define DASHBOARD_RAW_REQUEST "dashboard"
|
||||||
#define DASHBOARD_HTML_REQUEST "dashboard.xsl"
|
#define DASHBOARD_HTML_REQUEST "dashboard.xsl"
|
||||||
|
#define DASHBOARD_JSON_REQUEST "dashboard.json"
|
||||||
#define DEFAULT_RAW_REQUEST ""
|
#define DEFAULT_RAW_REQUEST ""
|
||||||
#define DEFAULT_HTML_REQUEST ""
|
#define DEFAULT_HTML_REQUEST ""
|
||||||
#define BUILDM3U_RAW_REQUEST "buildm3u"
|
#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 */
|
{ "*", ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, NULL, NULL}, /* for ACL framework */
|
||||||
{ FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_fallback, NULL},
|
{ FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_fallback, NULL},
|
||||||
{ FALLBACK_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_metadata, NULL},
|
||||||
{ METADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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},
|
{ 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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_show_listeners, NULL},
|
||||||
{ LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL},
|
||||||
{ STATS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, 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},
|
{ "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_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_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_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL},
|
||||||
{ LISTMOUNTS_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL},
|
||||||
{ STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_PLAINTEXT, 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_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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_move_clients, NULL},
|
||||||
{ MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_client, NULL},
|
||||||
{ KILLCLIENT_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_source, NULL},
|
||||||
{ KILLSOURCE_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_manageauth, NULL},
|
||||||
{ MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata, NULL},
|
||||||
{ UPDATEMETADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, 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},
|
{ BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_buildm3u, NULL},
|
||||||
{ SHOWLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_show_log, 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_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_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_mark_log, NULL},
|
||||||
{ MARKLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, 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_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_dashboard, NULL},
|
||||||
{ DASHBOARD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, 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_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL},
|
||||||
{ DEFAULT_RAW_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.
|
/* 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
|
* If a mountpoint is passed then that source will not be added to the XML
|
||||||
* doc even if the source is running */
|
* 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;
|
avl_node *node;
|
||||||
source_t *source;
|
source_t *source;
|
||||||
@ -387,9 +418,12 @@ xmlDocPtr admin_build_sourcelist(const char *mount)
|
|||||||
srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
|
srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
|
||||||
xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
|
xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
|
||||||
|
|
||||||
xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"),
|
if (source->fallback_mount) {
|
||||||
(source->fallback_mount != NULL)?
|
xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"), XMLSTR(source->fallback_mount));
|
||||||
XMLSTR(source->fallback_mount):XMLSTR(""));
|
} else {
|
||||||
|
if (format == ADMIN_FORMAT_RAW && client->mode != OMODE_STRICT)
|
||||||
|
xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"), XMLSTR(""));
|
||||||
|
}
|
||||||
snprintf(buf, sizeof(buf), "%lu", source->listeners);
|
snprintf(buf, sizeof(buf), "%lu", source->listeners);
|
||||||
xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
|
xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
|
||||||
|
|
||||||
@ -409,7 +443,11 @@ xmlDocPtr admin_build_sourcelist(const char *mount)
|
|||||||
if (source->client) {
|
if (source->client) {
|
||||||
snprintf(buf, sizeof(buf), "%lu",
|
snprintf(buf, sizeof(buf), "%lu",
|
||||||
(unsigned long)(now - source->con->con_time));
|
(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"),
|
xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
|
||||||
XMLSTR(source->format->contenttype));
|
XMLSTR(source->format->contenttype));
|
||||||
@ -425,13 +463,34 @@ void admin_send_response(xmlDocPtr doc,
|
|||||||
admin_format_t response,
|
admin_format_t response,
|
||||||
const char *xslt_template)
|
const char *xslt_template)
|
||||||
{
|
{
|
||||||
if (response == ADMIN_FORMAT_RAW) {
|
if (response == ADMIN_FORMAT_RAW || response == ADMIN_FORMAT_JSON) {
|
||||||
xmlChar *buff = NULL;
|
xmlChar *buff = NULL;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
size_t buf_len;
|
size_t buf_len;
|
||||||
ssize_t ret;
|
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;
|
buf_len = len + 1024;
|
||||||
if (buf_len < 4096)
|
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,
|
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
||||||
0, 200, NULL,
|
0, 200, NULL,
|
||||||
"text/xml", "utf-8",
|
content_type, "utf-8",
|
||||||
NULL, NULL, client);
|
NULL, NULL, client);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
|
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;
|
client->refbuf->len = buf_len;
|
||||||
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
||||||
0, 200, NULL,
|
0, 200, NULL,
|
||||||
"text/xml", "utf-8",
|
content_type, "utf-8",
|
||||||
NULL, NULL, client);
|
NULL, NULL, client);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
|
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)
|
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);
|
admin_send_response_simple(client, source, response, message, 1);
|
||||||
} else {
|
} else {
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
@ -701,7 +760,7 @@ static void command_move_clients(client_t *client,
|
|||||||
}
|
}
|
||||||
ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
|
ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
|
||||||
if (!parameters_passed) {
|
if (!parameters_passed) {
|
||||||
xmlDocPtr doc = admin_build_sourcelist(source->mount);
|
xmlDocPtr doc = admin_build_sourcelist(source->mount, client, response);
|
||||||
|
|
||||||
if (idtext) {
|
if (idtext) {
|
||||||
xmlNodePtr root = xmlDocGetRootElement(doc);
|
xmlNodePtr root = xmlDocGetRootElement(doc);
|
||||||
@ -1060,7 +1119,7 @@ static void command_fallback(client_t *client,
|
|||||||
|
|
||||||
if (client->mode == OMODE_STRICT) {
|
if (client->mode == OMODE_STRICT) {
|
||||||
if (!(COMMAND_OPTIONAL(client, "fallback", fallback))) {
|
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);
|
admin_send_response(doc, client, response, FALLBACK_HTML_REQUEST);
|
||||||
xmlFreeDoc(doc);
|
xmlFreeDoc(doc);
|
||||||
return;
|
return;
|
||||||
@ -1233,7 +1292,7 @@ static void command_list_mounts(client_t *client, source_t *source, admin_format
|
|||||||
} else {
|
} else {
|
||||||
xmlDocPtr doc;
|
xmlDocPtr doc;
|
||||||
avl_tree_rlock(global.source_tree);
|
avl_tree_rlock(global.source_tree);
|
||||||
doc = admin_build_sourcelist(NULL);
|
doc = admin_build_sourcelist(NULL, client, response);
|
||||||
avl_tree_unlock(global.source_tree);
|
avl_tree_unlock(global.source_tree);
|
||||||
|
|
||||||
admin_send_response(doc, client, response,
|
admin_send_response(doc, client, response,
|
||||||
|
28
src/client.c
28
src/client.c
@ -43,6 +43,7 @@
|
|||||||
#include "reportxml.h"
|
#include "reportxml.h"
|
||||||
#include "refobject.h"
|
#include "refobject.h"
|
||||||
#include "xslt.h"
|
#include "xslt.h"
|
||||||
|
#include "xml2json.h"
|
||||||
#include "source.h"
|
#include "source.h"
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
@ -363,6 +364,7 @@ static inline void _client_send_report(client_t *client, const char *uuid, const
|
|||||||
|
|
||||||
switch (admin_format) {
|
switch (admin_format) {
|
||||||
case ADMIN_FORMAT_RAW:
|
case ADMIN_FORMAT_RAW:
|
||||||
|
case ADMIN_FORMAT_JSON:
|
||||||
xslt = NULL;
|
xslt = NULL;
|
||||||
break;
|
break;
|
||||||
case ADMIN_FORMAT_HTML:
|
case ADMIN_FORMAT_HTML:
|
||||||
@ -554,6 +556,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai
|
|||||||
if (!xsl) {
|
if (!xsl) {
|
||||||
switch (admin_format) {
|
switch (admin_format) {
|
||||||
case ADMIN_FORMAT_RAW:
|
case ADMIN_FORMAT_RAW:
|
||||||
|
case ADMIN_FORMAT_JSON:
|
||||||
/* noop, we don't need to set xsl */
|
/* noop, we don't need to set xsl */
|
||||||
break;
|
break;
|
||||||
case ADMIN_FORMAT_HTML:
|
case ADMIN_FORMAT_HTML:
|
||||||
@ -574,21 +577,31 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = reportxml_render_xmldoc(report);
|
doc = reportxml_render_xmldoc(report, admin_format == ADMIN_FORMAT_RAW || admin_format == ADMIN_FORMAT_JSON);
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
ICECAST_LOG_ERROR("Can not render XML Document from report. Sending 500 to client %p", client);
|
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.");
|
client_send_500(client, "Can not render XML Document from report.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (admin_format == ADMIN_FORMAT_RAW) {
|
if (admin_format == ADMIN_FORMAT_RAW || admin_format == ADMIN_FORMAT_JSON) {
|
||||||
xmlChar *buff = NULL;
|
xmlChar *buff = NULL;
|
||||||
size_t location_length = 0;
|
size_t location_length = 0;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
size_t buf_len;
|
size_t buf_len;
|
||||||
ssize_t ret;
|
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) {
|
if (location) {
|
||||||
location_length = strlen(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,
|
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
||||||
0, status, NULL,
|
0, status, NULL,
|
||||||
"text/xml", "utf-8",
|
content_type, "utf-8",
|
||||||
NULL, NULL, client);
|
NULL, NULL, client);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
|
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;
|
client->refbuf->len = buf_len;
|
||||||
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
ret = util_http_build_header(client->refbuf->data, buf_len, 0,
|
||||||
0, status, NULL,
|
0, status, NULL,
|
||||||
"text/xml", "utf-8",
|
content_type, "utf-8",
|
||||||
NULL, NULL, client);
|
NULL, NULL, client);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
|
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);
|
refobject_unref(rootnode);
|
||||||
|
|
||||||
xmlroot = xmlNewNode(NULL, XMLSTR("icestats"));
|
xmlroot = xmlNewNode(NULL, XMLSTR("icestats"));
|
||||||
|
xmlSetProp(xmlroot, XMLSTR("xmlns"), XMLSTR(XMLNS_LEGACY_STATS));
|
||||||
modules = module_container_get_modulelist_as_xml(global.modulecontainer);
|
modules = module_container_get_modulelist_as_xml(global.modulecontainer);
|
||||||
xmlAddChild(xmlroot, modules);
|
xmlAddChild(xmlroot, modules);
|
||||||
|
|
||||||
@ -756,7 +770,7 @@ admin_format_t client_get_admin_format_by_content_negotiation(client_t *client)
|
|||||||
if (!client || !client->parser)
|
if (!client || !client->parser)
|
||||||
return CLIENT_DEFAULT_ADMIN_FORMAT;
|
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) {
|
if (strcmp(pref, "text/xml") == 0) {
|
||||||
return ADMIN_FORMAT_RAW;
|
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;
|
return ADMIN_FORMAT_HTML;
|
||||||
} else if (strcmp(pref, "text/plain") == 0) {
|
} else if (strcmp(pref, "text/plain") == 0) {
|
||||||
return ADMIN_FORMAT_PLAINTEXT;
|
return ADMIN_FORMAT_PLAINTEXT;
|
||||||
|
} else if (strcmp(pref, "application/json") == 0) {
|
||||||
|
return ADMIN_FORMAT_JSON;
|
||||||
} else {
|
} else {
|
||||||
return CLIENT_DEFAULT_ADMIN_FORMAT;
|
return CLIENT_DEFAULT_ADMIN_FORMAT;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
|
|
||||||
#include "compat.h"
|
#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] ]--- */
|
/* ---[ client.[ch] ]--- */
|
||||||
|
|
||||||
typedef struct _client_tag client_t;
|
typedef struct _client_tag client_t;
|
||||||
@ -33,7 +40,8 @@ typedef enum {
|
|||||||
ADMIN_FORMAT_AUTO,
|
ADMIN_FORMAT_AUTO,
|
||||||
ADMIN_FORMAT_RAW,
|
ADMIN_FORMAT_RAW,
|
||||||
ADMIN_FORMAT_HTML,
|
ADMIN_FORMAT_HTML,
|
||||||
ADMIN_FORMAT_PLAINTEXT
|
ADMIN_FORMAT_PLAINTEXT,
|
||||||
|
ADMIN_FORMAT_JSON
|
||||||
} admin_format_t;
|
} admin_format_t;
|
||||||
|
|
||||||
/* ---[ acl.[ch] ]--- */
|
/* ---[ 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"));
|
rootnode = xmlNewNode(NULL, XMLSTR("playlist"));
|
||||||
xmlSetProp(rootnode, XMLSTR("version"), XMLSTR("1"));
|
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"));
|
tracklist = xmlNewNode(NULL, XMLSTR("trackList"));
|
||||||
xmlAddChild(rootnode, tracklist);
|
xmlAddChild(rootnode, tracklist);
|
||||||
|
@ -110,6 +110,7 @@ struct nodedef {
|
|||||||
/* Prototypes */
|
/* 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 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 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 */
|
/* definition of known attributes */
|
||||||
static const struct nodeattr __attr__eol[1] = {{NULL, NULL, NULL, 0, NULL, {NULL}}};
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlDocPtr reportxml_render_xmldoc(reportxml_t *report)
|
xmlDocPtr reportxml_render_xmldoc(reportxml_t *report, int set_namespace)
|
||||||
{
|
{
|
||||||
xmlDocPtr ret;
|
xmlDocPtr ret;
|
||||||
xmlNodePtr node;
|
xmlNodePtr node;
|
||||||
@ -338,7 +339,7 @@ xmlDocPtr reportxml_render_xmldoc(reportxml_t *report)
|
|||||||
if (!report)
|
if (!report)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
node = reportxml_node_render_xmlnode(report->root);
|
node = reportxml_node_render_xmlnode_with_ns(report->root, NULL, set_namespace);
|
||||||
if (!node)
|
if (!node)
|
||||||
return NULL;
|
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);
|
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;
|
xmlNodePtr ret;
|
||||||
ssize_t child_count;
|
ssize_t child_count;
|
||||||
ssize_t xml_child_count;
|
ssize_t xml_child_count;
|
||||||
size_t i;
|
size_t i;
|
||||||
xmlChar *definition;
|
xmlChar *definition;
|
||||||
|
xmlChar *xmlns;
|
||||||
|
|
||||||
if (!node)
|
if (!node)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -628,6 +630,16 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node)
|
|||||||
xmlFree(definition);
|
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++) {
|
for (i = 0; i < (size_t)child_count; i++) {
|
||||||
reportxml_node_t *child = reportxml_node_get_child(node, i);
|
reportxml_node_t *child = reportxml_node_get_child(node, i);
|
||||||
xmlNodePtr xmlchild;
|
xmlNodePtr xmlchild;
|
||||||
@ -637,7 +649,7 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlchild = reportxml_node_render_xmlnode(child);
|
xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns, set_namespace);
|
||||||
refobject_unref(child);
|
refobject_unref(child);
|
||||||
if (!xmlchild) {
|
if (!xmlchild) {
|
||||||
xmlFreeNode(ret);
|
xmlFreeNode(ret);
|
||||||
@ -673,6 +685,11 @@ xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node)
|
|||||||
return ret;
|
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)
|
reportxml_node_type_t reportxml_node_get_type(reportxml_node_t *node)
|
||||||
{
|
{
|
||||||
if (!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 */
|
/* This function parses an XML document and returns the parst report XML document */
|
||||||
reportxml_t * reportxml_parse_xmldoc(xmlDocPtr doc);
|
reportxml_t * reportxml_parse_xmldoc(xmlDocPtr doc);
|
||||||
/* This function renders an report XML document as XML structure */
|
/* 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 ]--- */
|
/* ---[ 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