Commit 5ca6747d authored by Philipp Schafft's avatar Philipp Schafft 🦁

Merge branch 'feature-json-renderer'

parents 96d6190d 9ad34f97
Pipeline #2182 canceled with stage
......@@ -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 \
......
......@@ -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,
......
......@@ -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] ]--- */
......
/* 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)