1
0
Fork 0
icecast-server/src/reportxml.c

1487 lines
45 KiB
C

/* 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>,
*/
/**
* Special fast event functions
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "reportxml.h"
#include "refobject.h"
/* For XMLSTR() */
#include "cfgfile.h"
#include "logging.h"
#define CATMODULE "reportxml"
/* The report XML document type */
struct reportxml_tag {
/* base object */
refobject_base_t __base;
/* the root report XML node of the document */
reportxml_node_t *root;
};
/* The report XML node type */
struct reportxml_node_tag {
/* base object */
refobject_base_t __base;
/* an XML node used to store the attributes */
xmlNodePtr xmlnode;
/* the type of the node */
reportxml_node_type_t type;
/* the report XML childs */
reportxml_node_t **childs;
size_t childs_len;
/* the XML childs (used by <extension>) */
xmlNodePtr *xml_childs;
size_t xml_childs_len;
/* the node's text content (used by <text>) */
char *content;
};
/* The report XML database type */
struct reportxml_database_tag {
/* base object */
refobject_base_t __base;
/* the lock used to ensure the database object is thread safe. */
mutex_t lock;
/* The tree of definitions */
avl_tree *definitions;
};
/* The nodeattr structure is used to store definition of node attributes */
struct nodeattr;
struct nodeattr {
/* name of the attribute */
const char *name;
/* the type of the attribute. This is based on the DTD */
const char *type;
/* the default value for the attribute if any */
const char *def;
/* whether the attribute is required or not */
int required;
/* a function that can be used to check the content of the attribute if any */
int (*checker)(const struct nodeattr *attr, const char *str);
/* NULL terminated list of possible values (if enum) */
const char *values[32];
};
/* The type of the content an node has */
enum nodecontent {
/* The node may not have any content */
NC_NONE,
/* The node may have children */
NC_CHILDS,
/* The node may have a text content */
NC_CONTENT,
/* The node may have XML children */
NC_XML
};
/* This structure is used to define a node */
struct nodedef {
/* the type of the node */
reportxml_node_type_t type;
/* the name of the corresponding XML node */
const char *name;
/* The type of the content the node may have */
enum nodecontent content;
/* __attr__eol terminated list of attributes the node may have */
const struct nodeattr *attr[12];
/* REPORTXML_NODE_TYPE__ERROR terminated list of child node types the node may have */
const reportxml_node_type_t childs[12];
};
/* Prototypes */
static int __attach_copy_of_node_or_definition(reportxml_node_t *parent, reportxml_node_t *node, reportxml_database_t *db, ssize_t depth);
static reportxml_node_t * __reportxml_database_build_node_ext(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t *acst_type_ret);
static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace);
/* definition of known attributes */
static const struct nodeattr __attr__eol[1] = {{NULL, NULL, NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_version[1] = {{"version", "CDATA", "0.0.1", 1, NULL, {"0.0.1", NULL}}};
static const struct nodeattr __attr_xmlns[1] = {{"xmlns", "URI", "http://icecast.org/specs/reportxml-0.0.1", 1, NULL, {"http://icecast.org/specs/reportxml-0.0.1", NULL}}};
static const struct nodeattr __attr_id[1] = {{"id", "ID", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_definition[1] = {{"definition", "UUID", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr__definition[1] = {{"_definition", "UUID", NULL, 0, NULL, {NULL}}}; /* for internal use only */
static const struct nodeattr __attr_akindof[1] = {{"akindof", "UUIDs", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_lang[1] = {{"lang", "LanguageCode", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_dir[1] = {{"dir", NULL, NULL, 0, NULL, {"ltr", "rtl", NULL}}};
static const struct nodeattr __attr_template[1] = {{"template", "UUID", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_defines[1] = {{"defines", "UUID", NULL, 1, NULL, {NULL}}};
static const struct nodeattr __attr_function[1] = {{"function", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_filename[1] = {{"filename", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_line[1] = {{"line", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_binary[1] = {{"binary", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_offset[1] = {{"offset", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_absolute[1] = {{"absolute", "iso8601", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_relative[1] = {{"relative", "iso8601", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_name[1] = {{"name", "CDATA", NULL, 1, NULL, {NULL}}};
static const struct nodeattr __attr_member[1] = {{"member", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_value[1] = {{"value", "CDATA", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_state[1] = {{"state", NULL, "set", 1, NULL, {"declared", "set", "unset", "uninitialized", "missing", NULL}}};
static const struct nodeattr __attr_href[1] = {{"href", "URI", NULL, 0, NULL, {NULL}}};
static const struct nodeattr __attr_application[1] = {{"application", "URI", NULL, 1, NULL, {NULL}}};
static const struct nodeattr __attr__action_type[1] = {{"type", NULL, NULL, 1, NULL, {"retry", "choice", "see-other", "authenticate", "pay", "change-protocol", "slow-down", "ask-user", "ask-admin", "bug", NULL}}};
static const struct nodeattr __attr__resource_type[1] = {{"type", NULL, NULL, 1, NULL, {"actor", "manipulation-target", "helper", "related", "result", "parameter", NULL}}};
static const struct nodeattr __attr__value_type[1] = {{"type", NULL, NULL, 1, NULL, {"null", "int", "float", "uuid", "string", "structure", "uri", "pointer", "version", "protocol", "nodename", "username", "password", "boolean", "list", "unordered-list", "bit-field", "enum", "flag", "blob", "blob-base64", "timestamp", NULL}}};
static const struct nodeattr __attr__reference_type[1] = {{"type", NULL, NULL, 1, NULL, {"documentation", "log", "report", "related", NULL}}};
/* definition of known nodes */
/* Helper:
* grep '^ *REPORTXML_NODE_TYPE_' reportxml.h | sed 's/^\( *REPORTXML_NODE_TYPE_\([^,]*\)\),*$/\1 \2/;' | while read l s; do c=$(tr A-Z a-z <<<"$s"); printf " {%-32s \"%-16s 0, {__attr__eol}},\n" "$l," "$c\","; done
*/
#define __BASIC_ELEMENT __attr_id, __attr_definition, __attr_akindof, __attr__definition
static const struct nodedef __nodedef[] = {
{REPORTXML_NODE_TYPE_REPORT, "report", NC_CHILDS, {__attr_id, __attr_version, __attr_xmlns, __attr__eol},
{REPORTXML_NODE_TYPE_INCIDENT, REPORTXML_NODE_TYPE_DEFINITION, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_DEFINITION, "definition", NC_CHILDS, {__BASIC_ELEMENT, __attr_template, __attr_defines, __attr__eol},
{REPORTXML_NODE_TYPE_INCIDENT, REPORTXML_NODE_TYPE_STATE, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_FIX, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REASON, REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_INCIDENT, "incident", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_STATE, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_FIX, REPORTXML_NODE_TYPE_REASON, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_STATE, "state", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_BACKTRACE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_BACKTRACE, "backtrace", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_POSITION, REPORTXML_NODE_TYPE_MORE, REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_POSITION, "position", NC_CHILDS, {__BASIC_ELEMENT, __attr_function, __attr_filename, __attr_line, __attr_binary, __attr_offset, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_MORE, "more", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_FIX, "fix", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_ACTION, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_ACTION, "action", NC_CHILDS, {__BASIC_ELEMENT, __attr__action_type, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_REASON, "reason", NC_CHILDS, {__BASIC_ELEMENT, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_TEXT, "text", NC_CONTENT, {__BASIC_ELEMENT, __attr_lang, __attr_dir, __attr__eol},
{REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_TIMESTAMP, "timestamp", NC_NONE, {__BASIC_ELEMENT, __attr_absolute, __attr_relative, __attr__eol},
{REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_RESOURCE, "resource", NC_CHILDS, {__BASIC_ELEMENT, __attr__resource_type, __attr_name, __attr__eol},
{REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_VALUE, "value", NC_CHILDS, {__BASIC_ELEMENT, __attr_member, __attr_value, __attr_state, __attr__value_type, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_POSITION, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_REFERENCE, "reference", NC_CHILDS, {__BASIC_ELEMENT, __attr__reference_type, __attr_href, __attr__eol},
{REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
{REPORTXML_NODE_TYPE_EXTENSION, "extension", NC_XML, {__BASIC_ELEMENT, __attr_application, __attr__eol},
{REPORTXML_NODE_TYPE__ERROR}}
};
static const struct nodedef * __get_nodedef(reportxml_node_type_t type)
{
size_t i;
for (i = 0; i < (sizeof(__nodedef)/sizeof(*__nodedef)); i++) {
if (__nodedef[i].type == type) {
return &(__nodedef[i]);
}
}
return NULL;
}
static const struct nodedef * __get_nodedef_by_name(const char *name)
{
size_t i;
for (i = 0; i < (sizeof(__nodedef)/sizeof(*__nodedef)); i++) {
if (strcmp(__nodedef[i].name, name) == 0) {
return &(__nodedef[i]);
}
}
return NULL;
}
static const struct nodeattr * __get_nodeattr(const struct nodedef * nodedef, const char *key)
{
size_t i;
if (!nodedef || !key)
return NULL;
for (i = 0; i < (sizeof(nodedef->attr)/sizeof(*nodedef->attr)); i++) {
if (nodedef->attr[i]->name == NULL) {
/* we reached the end of the list */
return NULL;
}
if (strcmp(nodedef->attr[i]->name, key) == 0) {
return nodedef->attr[i];
}
}
return NULL;
}
static void __report_free(refobject_t self, void **userdata)
{
reportxml_t *report = REFOBJECT_TO_TYPE(self, reportxml_t *);
refobject_unref(report->root);
}
static int __report_new(refobject_t self, const refobject_type_t *type, va_list ap)
{
reportxml_t *ret = REFOBJECT_TO_TYPE(self, reportxml_t*);
reportxml_node_t *root = reportxml_node_new(REPORTXML_NODE_TYPE_REPORT, NULL, NULL, NULL);
if (!root)
return -1;
ret->root = root;
return 0;
}
REFOBJECT_DEFINE_TYPE(reportxml_t,
REFOBJECT_DEFINE_TYPE_FREE(__report_free),
REFOBJECT_DEFINE_TYPE_NEW(__report_new)
);
static reportxml_t * reportxml_new_with_root(reportxml_node_t *root)
{
reportxml_t *ret;
if (!root)
return NULL;
ret = refobject_new__new(reportxml_t, NULL, NULL, NULL);
ret->root = root;
return ret;
}
reportxml_t * reportxml_new(void)
{
return refobject_new(reportxml_t);
}
reportxml_node_t * reportxml_get_root_node(reportxml_t *report)
{
if (!report)
return NULL;
if (refobject_ref(report->root) != 0)
return NULL;
return report->root;
}
reportxml_node_t * reportxml_get_node_by_attribute(reportxml_t *report, const char *key, const char *value, int include_definitions)
{
if (!report || !key || !value)
return NULL;
return reportxml_node_get_child_by_attribute(report->root, key, value, include_definitions);
}
reportxml_node_t * reportxml_get_node_by_type(reportxml_t *report, reportxml_node_type_t type, int include_definitions)
{
if (!report)
return NULL;
return reportxml_node_get_child_by_type(report->root, type, include_definitions);
}
reportxml_t * reportxml_parse_xmldoc(xmlDocPtr doc)
{
reportxml_node_t *root;
reportxml_t *ret;
xmlNodePtr xmlroot;
if (!doc)
return NULL;
xmlroot = xmlDocGetRootElement(doc);
if (!xmlroot)
return NULL;
root = reportxml_node_parse_xmlnode(xmlroot);
if (!root)
return NULL;
if (reportxml_node_get_type(root) != REPORTXML_NODE_TYPE_REPORT) {
refobject_unref(root);
return NULL;
}
ret = reportxml_new_with_root(root);
if (!ret) {
refobject_unref(root);
return NULL;
}
return ret;
}
xmlDocPtr reportxml_render_xmldoc(reportxml_t *report, int set_namespace)
{
xmlDocPtr ret;
xmlNodePtr node;
if (!report)
return NULL;
node = reportxml_node_render_xmlnode_with_ns(report->root, NULL, set_namespace);
if (!node)
return NULL;
ret = xmlNewDoc(XMLSTR("1.0"));
if (!ret) {
xmlFreeNode(node);
return NULL;
}
xmlDocSetRootElement(ret, node);
return ret;
}
static void __report_node_free(refobject_t self, void **userdata)
{
size_t i;
reportxml_node_t *node = REFOBJECT_TO_TYPE(self, reportxml_node_t *);
xmlFreeNode(node->xmlnode);
for (i = 0; i < node->childs_len; i++) {
refobject_unref(node->childs[i]);
}
for (i = 0; i < node->xml_childs_len; i++) {
xmlFreeNode(node->xml_childs[i]);
}
free(node->content);
free(node->childs);
free(node->xml_childs);
}
REFOBJECT_DEFINE_TYPE(reportxml_node_t,
REFOBJECT_DEFINE_TYPE_FREE(__report_node_free)
);
reportxml_node_t * reportxml_node_new(reportxml_node_type_t type, const char *id, const char *definition, const char *akindof)
{
reportxml_node_t *ret;
const struct nodedef *nodedef = __get_nodedef(type);
size_t i;
if (!nodedef)
return NULL;
ret = refobject_new__new(reportxml_node_t, NULL, NULL, NULL);
if (ret == NULL)
return NULL;
ret->type = type;
ret->xmlnode = xmlNewNode(NULL, XMLSTR(nodedef->name));
if (!ret->xmlnode) {
refobject_unref(ret);
return NULL;
}
for (i = 0; i < (sizeof(nodedef->attr)/sizeof(*nodedef->attr)); i++) {
const struct nodeattr *nodeattr = nodedef->attr[i];
if (nodeattr->name == NULL)
break;
if (nodeattr->def) {
if (reportxml_node_set_attribute(ret, nodeattr->name, nodeattr->def) != 0) {
refobject_unref(ret);
return NULL;
}
}
}
#define _set_attr(x) \
if ((x)) { \
if (reportxml_node_set_attribute(ret, # x , (x)) != 0) { \
refobject_unref(ret); \
return NULL; \
} \
}
_set_attr(id)
_set_attr(definition)
_set_attr(akindof)
return ret;
}
reportxml_node_t * reportxml_node_parse_xmlnode(xmlNodePtr xmlnode)
{
reportxml_node_t *node;
const struct nodedef *nodedef;
if (!xmlnode)
return NULL;
nodedef = __get_nodedef_by_name((const char *)xmlnode->name);
if (!nodedef) {
ICECAST_LOG_ERROR("Can not parse XML node: Unknown name <%s>", xmlnode->name);
return NULL;
}
node = reportxml_node_new(nodedef->type, NULL, NULL, NULL);
if (!node)
return NULL;
if (xmlnode->properties) {
xmlAttrPtr cur = xmlnode->properties;
do {
xmlChar *value = xmlNodeListGetString(xmlnode->doc, cur->children, 1);
if (!value) {
refobject_unref(node);
return NULL;
}
if (reportxml_node_set_attribute(node, (const char*)cur->name, (const char*)value) != 0) {
refobject_unref(node);
ICECAST_LOG_DEBUG("Can not parse XML node: attribute \"%H\" value \"%H\" is invalid on node of type <%s>", cur->name, value, nodedef->name);
return NULL;
}
xmlFree(value);
} while ((cur = cur->next));
}
if (xmlnode->xmlChildrenNode) {
xmlNodePtr cur = xmlnode->xmlChildrenNode;
do {
if (nodedef->content == NC_XML) {
if (reportxml_node_add_xml_child(node, cur) != 0) {
refobject_unref(node);
return NULL;
}
} else {
reportxml_node_t *child;
if (xmlIsBlankNode(cur))
continue;
if (cur->type == XML_COMMENT_NODE)
continue;
if (cur->type == XML_TEXT_NODE) {
xmlChar *value = xmlNodeListGetString(xmlnode->doc, cur, 1);
if (!value) {
refobject_unref(node);
return NULL;
}
if (reportxml_node_set_content(node, (const char *)value) != 0) {
refobject_unref(node);
return NULL;
}
xmlFree(value);
continue;
}
child = reportxml_node_parse_xmlnode(cur);
if (!child) {
refobject_unref(node);
return NULL;
}
if (reportxml_node_add_child(node, child) != 0) {
refobject_unref(child);
refobject_unref(node);
return NULL;
}
refobject_unref(child);
}
} while ((cur = cur->next));
}
return node;
}
static reportxml_node_t * __reportxml_node_copy_with_db(reportxml_node_t *node, reportxml_database_t *db, ssize_t depth)
{
reportxml_node_t *ret;
ssize_t child_count;
ssize_t xml_child_count;
size_t i;
if (!node)
return NULL;
child_count = reportxml_node_count_child(node);
if (child_count < 0)
return NULL;
xml_child_count = reportxml_node_count_xml_child(node);
if (xml_child_count < 0)
return NULL;
ret = reportxml_node_parse_xmlnode(node->xmlnode);
if (!ret)
return NULL;
if (node->content) {
if (reportxml_node_set_content(ret, node->content) != 0) {
refobject_unref(ret);
return NULL;
}
}
for (i = 0; i < (size_t)child_count; i++) {
reportxml_node_t *child = reportxml_node_get_child(node, i);
if (db && depth > 0) {
if (__attach_copy_of_node_or_definition(ret, child, db, depth) != 0) {
refobject_unref(child);
refobject_unref(ret);
return NULL;
}
refobject_unref(child);
} else {
reportxml_node_t *copy = __reportxml_node_copy_with_db(child, NULL, -1);
refobject_unref(child);
if (!copy) {
refobject_unref(ret);
return NULL;
}
if (reportxml_node_add_child(ret, copy) != 0) {
refobject_unref(copy);
refobject_unref(ret);
return NULL;
}
refobject_unref(copy);
}
}
for (i = 0; i < (size_t)xml_child_count; i++) {
xmlNodePtr child = reportxml_node_get_xml_child(node, i);
if (reportxml_node_add_xml_child(ret, child) != 0) {
xmlFreeNode(child);
refobject_unref(ret);
return NULL;
}
xmlFreeNode(child);
}
return ret;
}
reportxml_node_t * reportxml_node_copy(reportxml_node_t *node)
{
return __reportxml_node_copy_with_db(node, NULL, -1);
}
static xmlNodePtr reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace)
{
xmlNodePtr ret;
ssize_t child_count;
ssize_t xml_child_count;
size_t i;
xmlChar *definition;
xmlChar *xmlns;
if (!node)
return NULL;
child_count = reportxml_node_count_child(node);
if (child_count < 0)
return NULL;
xml_child_count = reportxml_node_count_xml_child(node);
if (xml_child_count < 0)
return NULL;
ret = xmlCopyNode(node->xmlnode, 2);
if (!ret)
return NULL;
definition = xmlGetProp(ret, XMLSTR("_definition"));
if (definition) {
xmlSetProp(ret, XMLSTR("definition"), definition);
xmlUnsetProp(ret, XMLSTR("_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++) {
reportxml_node_t *child = reportxml_node_get_child(node, i);
xmlNodePtr xmlchild;
if (!child) {
xmlFreeNode(ret);
return NULL;
}
xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns, set_namespace);
refobject_unref(child);
if (!xmlchild) {
xmlFreeNode(ret);
return NULL;
}
xmlAddChild(ret, xmlchild);
}
if (node->content) {
xmlNodePtr xmlchild = xmlNewText(XMLSTR(node->content));
if (!xmlchild) {
xmlFreeNode(ret);
return NULL;
}
xmlAddChild(ret, xmlchild);
}
for (i = 0; i < (size_t)xml_child_count; i++) {
xmlNodePtr xmlchild = reportxml_node_get_xml_child(node, i);
if (!xmlchild) {
xmlFreeNode(ret);
return NULL;
}
xmlAddChild(ret, xmlchild);
}
return ret;
}
xmlNodePtr reportxml_node_render_xmlnode(reportxml_node_t *node)
{
return reportxml_node_render_xmlnode_with_ns(node, NULL, 1);
}
reportxml_node_type_t reportxml_node_get_type(reportxml_node_t *node)
{
if (!node)
return REPORTXML_NODE_TYPE__ERROR;
return node->type;
}
int reportxml_node_set_attribute(reportxml_node_t *node, const char *key, const char *value)
{
const struct nodedef *nodedef;
const struct nodeattr *nodeattr;
size_t i;
if (!node || !key || !value)
return -1;
nodedef = __get_nodedef(node->type);
nodeattr = __get_nodeattr(nodedef, key);
if (!nodeattr)
return -1;
if (nodeattr->values[0] != NULL) {
int found = 0;
for (i = 0; i < (sizeof(nodeattr->values)/sizeof(*nodeattr->values)); i++) {
if (nodeattr->values[i] == NULL)
break;
if (strcmp(nodeattr->values[i], value) == 0) {
found = 1;
break;
}
}
if (!found)
return -1;
}
if (nodeattr->checker) {
if (nodeattr->checker(nodeattr, value) != 0) {
return -1;
}
}
xmlSetProp(node->xmlnode, XMLSTR(key), XMLSTR(value));
return 0;
}
char * reportxml_node_get_attribute(reportxml_node_t *node, const char *key)
{
xmlChar *k;
char *n;
if (!node || !key)
return NULL;
/* We do this super complicated thing as we do not know if we can use free() to free a xmlChar* object. */
k = xmlGetProp(node->xmlnode, XMLSTR(key));
if (!k)
return NULL;
n = strdup((char*)k);
xmlFree(k);
return n;
}
int reportxml_node_add_child(reportxml_node_t *node, reportxml_node_t *child)
{
const struct nodedef *nodedef;
reportxml_node_t **n;
size_t i;
int found;
if (!node || !child)
return -1;
nodedef = __get_nodedef(node->type);
if (nodedef->content != NC_CHILDS)
return -1;
found = 0;
for (i = 0; nodedef->childs[i] != REPORTXML_NODE_TYPE__ERROR; i++) {
if (nodedef->childs[i] == child->type) {
found = 1;
break;
}
}
if (!found)
return -1;
n = realloc(node->childs, sizeof(*n)*(node->childs_len + 1));
if (!n)
return -1;
node->childs = n;
if (refobject_ref(child) != 0)
return -1;
node->childs[node->childs_len++] = child;
// ICECAST_LOG_DEBUG("Child %p added to Node %p", child, node);
return 0;
}
ssize_t reportxml_node_count_child(reportxml_node_t *node)
{
if (!node)
return -1;
return node->childs_len;
}
reportxml_node_t * reportxml_node_get_child(reportxml_node_t *node, size_t idx)
{
if (!node)
return NULL;
if (idx >= node->childs_len)
return NULL;
if (refobject_ref(node->childs[idx]) != 0)
return NULL;
return node->childs[idx];
}
reportxml_node_t * reportxml_node_get_child_by_attribute(reportxml_node_t *node, const char *key, const char *value, int include_definitions)
{
reportxml_node_t *ret;
xmlChar *k;
size_t i;
if (!node || !key ||!value)
return NULL;
k = xmlGetProp(node->xmlnode, XMLSTR(key));
if (k != NULL) {
if (strcmp((const char*)k, value) == 0) {
xmlFree(k);
if (refobject_ref(node) != 0)
return NULL;
return node;
}
xmlFree(k);
}
if (node->type == REPORTXML_NODE_TYPE_DEFINITION && !include_definitions)
return NULL;
for (i = 0; i < node->childs_len; i++) {
ret = reportxml_node_get_child_by_attribute(node->childs[i], key, value, include_definitions);
if (ret != NULL)
return ret;
}
return NULL;
}
reportxml_node_t * reportxml_node_get_child_by_type(reportxml_node_t *node, reportxml_node_type_t type, int include_definitions)
{
size_t i;
if (!node)
return NULL;
if (node->type == type) {
if (refobject_ref(node) != 0)
return NULL;
return node;
}
if (node->type == REPORTXML_NODE_TYPE_DEFINITION && !include_definitions)
return NULL;
for (i = 0; i < node->childs_len; i++) {
reportxml_node_t *ret;
ret = reportxml_node_get_child_by_type(node->childs[i], type, include_definitions);
if (ret != NULL)
return ret;
}
return NULL;
}
int reportxml_node_set_content(reportxml_node_t *node, const char *value)
{
const struct nodedef *nodedef;
char *n = NULL;
if (!node)
return -1;
nodedef = __get_nodedef(node->type);
if (nodedef->content != NC_CONTENT)
return -1;
if (value) {
n = strdup(value);
if (!n)
return -1;
}
free(node->content);
node->content = n;
return 0;
}
char * reportxml_node_get_content(reportxml_node_t *node)
{
if (!node)
return NULL;
if (node->content) {
return strdup(node->content);
} else {
return NULL;
}
}
int reportxml_node_add_xml_child(reportxml_node_t *node, xmlNodePtr child)
{
const struct nodedef *nodedef;
xmlNodePtr *n;
if (!node || !child)
return -1;
nodedef = __get_nodedef(node->type);
if (nodedef->content != NC_XML)
return -1;
n = realloc(node->xml_childs, sizeof(*n)*(node->xml_childs_len + 1));
if (!n)
return -1;
node->xml_childs = n;
node->xml_childs[node->xml_childs_len] = xmlCopyNode(child, 1);
if (node->xml_childs[node->xml_childs_len] == NULL)
return -1;
node->xml_childs_len++;
return 0;
}
ssize_t reportxml_node_count_xml_child(reportxml_node_t *node)
{
if (!node)
return -1;
return node->xml_childs_len;
}
xmlNodePtr reportxml_node_get_xml_child(reportxml_node_t *node, size_t idx)
{
xmlNodePtr ret;
if (!node)
return NULL;
if (idx >= node->xml_childs_len)
return NULL;
ret = xmlCopyNode(node->xml_childs[idx], 1);
return ret;
}
static void __database_free(refobject_t self, void **userdata)
{
reportxml_database_t *db = REFOBJECT_TO_TYPE(self, reportxml_database_t *);
if (db->definitions)
avl_tree_free(db->definitions, (avl_free_key_fun_type)refobject_unref);
thread_mutex_destroy(&(db->lock));
}
static int __compare_definitions(void *arg, void *a, void *b)
{
char *id_a, *id_b;
int ret = 0;
id_a = reportxml_node_get_attribute(a, "defines");
id_b = reportxml_node_get_attribute(b, "defines");
if (!id_a || !id_b || id_a == id_b) {
ret = 0;
} else {
ret = strcmp(id_a, id_b);
}
free(id_a);
free(id_b);
return ret;
}
static int __database_new(refobject_t self, const refobject_type_t *type, va_list ap)
{
reportxml_database_t *ret = REFOBJECT_TO_TYPE(self, reportxml_database_t*);
thread_mutex_create(&(ret->lock));
ret->definitions = avl_tree_new(__compare_definitions, NULL);
if (!ret->definitions)
return -1;
return 0;
}
REFOBJECT_DEFINE_TYPE(reportxml_database_t,
REFOBJECT_DEFINE_TYPE_FREE(__database_free),
REFOBJECT_DEFINE_TYPE_NEW(__database_new)
);
reportxml_database_t * reportxml_database_new(void)
{
return refobject_new(reportxml_database_t);
}
int reportxml_database_add_report(reportxml_database_t *db, reportxml_t *report)
{
reportxml_node_t *root;
ssize_t count;
size_t i;
if (!db || !report)
return -1;
root = reportxml_get_root_node(report);
if (!root)
return -1;
count = reportxml_node_count_child(root);
if (count < 0)
return -1;
thread_mutex_lock(&(db->lock));
for (i = 0; i < (size_t)count; i++) {
reportxml_node_t *node = reportxml_node_get_child(root, i);
reportxml_node_t *copy;
if (reportxml_node_get_type(node) != REPORTXML_NODE_TYPE_DEFINITION) {
refobject_unref(node);
continue;
}
copy = reportxml_node_copy(node);
refobject_unref(node);
if (!copy)
continue;
avl_insert(db->definitions, copy);
}
thread_mutex_unlock(&(db->lock));
refobject_unref(root);
return 0;
}
static int __attach_copy_of_node_or_definition(reportxml_node_t *parent, reportxml_node_t *node, reportxml_database_t *db, ssize_t depth)
{
reportxml_node_t *copy;
reportxml_node_t *def = NULL;
char *definition;
if (!parent || !node || !db)
return -1;
if (depth >= 2) {
definition = reportxml_node_get_attribute(node, "definition");
if (definition) {
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, definition=\"%H\"", parent, node, depth, definition);
def = reportxml_database_build_node(db, definition, depth - 1);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Definition for \"%H\" at %p", parent, node, depth, definition, def);
free(definition);
}
}
if (def) {
ssize_t count = reportxml_node_count_child(def);
size_t i;
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Found definition.", parent, node, depth);
if (count < 0) {
refobject_unref(def);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
return -1;
}
for (i = 0; i < (size_t)count; i++) {
reportxml_node_t *child = reportxml_node_get_child(def, i);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Itering, child #%zu (%p)", parent, node, depth, i, child);
if (__attach_copy_of_node_or_definition(parent, child, db, depth - 1) != 0) {
refobject_unref(child);
refobject_unref(def);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
return -1;
}
refobject_unref(child);
}
refobject_unref(def);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- 0", parent, node, depth);
return 0;
} else {
int ret;
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Found no definition.", parent, node, depth);
copy = __reportxml_node_copy_with_db(node, db, depth - 1);
if (!copy) {
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
return -1;
}
ret = reportxml_node_add_child(parent, copy);
refobject_unref(copy);
ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- %i", parent, node, depth, ret);
return 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)
{
reportxml_node_t *search;
reportxml_node_t *found;
reportxml_node_t *ret;
enum {
ACST_FIRST,
ACST_YES,
ACST_NO,
} all_childs_same_type = ACST_FIRST;
reportxml_node_type_t acst_type = REPORTXML_NODE_TYPE__ERROR;
char *template;
ssize_t count;
size_t i;
if (!db || !id)
return NULL;
/* Assign default depth in case it's set to -1 */
if (depth < 0)
depth = 8;
ICECAST_LOG_DEBUG("Looking up \"%H\" in database %p with depth %zu", id, db, depth);
if (!depth)
return NULL;
search = reportxml_node_new(REPORTXML_NODE_TYPE_DEFINITION, NULL, NULL, NULL);
if (!search)
return NULL;
if (reportxml_node_set_attribute(search, "defines", id) != 0) {
refobject_unref(search);
return NULL;
}
thread_mutex_lock(&(db->lock));
if (avl_get_by_key(db->definitions, REFOBJECT_TO_TYPE(search, void *), (void**)&found) != 0) {
thread_mutex_unlock(&(db->lock));
refobject_unref(search);
return NULL;
}
refobject_unref(search);
if (refobject_ref(found) != 0) {
thread_mutex_unlock(&(db->lock));
return NULL;
}
thread_mutex_unlock(&(db->lock));
count = reportxml_node_count_child(found);
if (count < 0) {
refobject_unref(found);
return NULL;
}
template = reportxml_node_get_attribute(found, "template");
if (template) {
reportxml_node_t *tpl = reportxml_database_build_node(db, template, depth - 1);
free(template);
if (tpl) {
ret = reportxml_node_copy(tpl);
refobject_unref(tpl);
} else {
ret = NULL;
}
} else {
ret = reportxml_node_new(REPORTXML_NODE_TYPE_DEFINITION, NULL, NULL, NULL);
}
if (!ret) {
refobject_unref(found);
return NULL;
}
for (i = 0; i < (size_t)count; i++) {
/* TODO: Look up definitions of our childs and childs' childs. */
reportxml_node_t *node = reportxml_node_get_child(found, i);
reportxml_node_type_t type = reportxml_node_get_type(node);
switch (all_childs_same_type) {
case ACST_FIRST:
acst_type = type;
all_childs_same_type = ACST_YES;
break;
case ACST_YES:
if (acst_type != type)
all_childs_same_type = ACST_NO;
break;
case ACST_NO:
/* noop */
break;
}
/* We want depth, not depth - 1 here. __attach_copy_of_node_or_definition() takes care of this for us. */
if (__attach_copy_of_node_or_definition(ret, node, db, depth) != 0) {
refobject_unref(node);
refobject_unref(found);
refobject_unref(ret);
ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p) in report. BAD.", i, node, ret);
return NULL;
}
refobject_unref(node);
}
refobject_unref(found);
if (all_childs_same_type == ACST_YES) {
count = reportxml_node_count_child(ret);
if (count < 0) {
refobject_unref(ret);
return NULL;
}
for (i = 0; i < (size_t)count; i++) {
reportxml_node_t *node = reportxml_node_get_child(ret, i);
if (!node) {
refobject_unref(ret);
return NULL;
}
if (reportxml_node_set_attribute(node, "_definition", id) != 0) {
refobject_unref(node);
refobject_unref(ret);
return NULL;
}
refobject_unref(node);
}
}
if (acst_type_ret) {
if (all_childs_same_type == ACST_YES) {
*acst_type_ret = acst_type;
} else {
*acst_type_ret = REPORTXML_NODE_TYPE__ERROR;
}
}
return ret;
}
reportxml_node_t * reportxml_database_build_node(reportxml_database_t *db, const char *id, ssize_t depth)
{
return __reportxml_database_build_node_ext(db, id, depth, NULL);
}
/* We try to build a a report from the definition. Exat structure depends on what is defined. */
reportxml_t * reportxml_database_build_report(reportxml_database_t *db, const char *id, ssize_t depth)
{
reportxml_node_t *definition;
reportxml_node_t *child;
reportxml_node_t *root;
reportxml_node_t *attach_to;
reportxml_node_type_t type;
reportxml_t *ret;
ssize_t count;
size_t i;
if (!db || !id)
return NULL;
/* first find the definition itself. This will be some REPORTXML_NODE_TYPE_DEFINITION node. */
definition = __reportxml_database_build_node_ext(db, id, depth, &type);
if (!definition) {
ICECAST_LOG_WARN("No matching definition for \"%H\"", id);
return NULL;
}
/* Let's see how many children we have. */
count = reportxml_node_count_child(definition);
if (count < 0) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Can not get child count from definition. BAD.");
return NULL;
} else if (count == 0) {
/* Empty definition? Not exactly an exciting report... */
ICECAST_LOG_WARN("Empty definition for \"%H\". Returning empty report. This is likely an error.", id);
refobject_unref(definition);
return refobject_new(reportxml_t);
}
if (type == REPORTXML_NODE_TYPE__ERROR) {
/* Now the hard part: find out what level we are. */
child = reportxml_node_get_child(definition, 0);
if (!child) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Can not get first child. BAD.");
return NULL;
}
type = reportxml_node_get_type(child);
refobject_unref(child);
}
/* check for supported configurations */
switch (type) {
case REPORTXML_NODE_TYPE_INCIDENT:
case REPORTXML_NODE_TYPE_STATE:
break;
default:
refobject_unref(definition);
ICECAST_LOG_WARN("Unsupported type of first child.");
return NULL;
break;
}
ret = refobject_new(reportxml_t);
if (!ret) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Can not allocate new report. BAD.");
return NULL;
}
root = reportxml_get_root_node(ret);
if (!root) {
refobject_unref(definition);
refobject_unref(ret);
ICECAST_LOG_ERROR("Can not get root node from report. BAD.");
return NULL;
}
if (type == REPORTXML_NODE_TYPE_INCIDENT) {
refobject_ref(attach_to = root);
} else if (type == REPORTXML_NODE_TYPE_STATE) {
attach_to = reportxml_node_new(REPORTXML_NODE_TYPE_INCIDENT, NULL, NULL, NULL);
if (attach_to) {
if (reportxml_node_add_child(root, attach_to) != 0) {
refobject_unref(attach_to);
attach_to = NULL;
}
}
} else {
attach_to = NULL;
}
refobject_unref(root);
if (!attach_to) {
refobject_unref(definition);
refobject_unref(ret);
ICECAST_LOG_ERROR("No point to attach to in report. BAD.");
return NULL;
}
/* now move nodes. */
for (i = 0; i < (size_t)count; i++) {
child = reportxml_node_get_child(definition, i);
if (reportxml_node_get_type(child) == type) {
/* Attach definition to all childs that are the same type.
* As long as we work to-the-specs all childs are of the same type.
* But if we work in relaxed mode, there might be other tags.
*/
reportxml_node_set_attribute(child, "definition", id);
}
/* we can directly attach as it's a already a copy. */
if (reportxml_node_add_child(attach_to, child) != 0) {
refobject_unref(definition);
refobject_unref(attach_to);
refobject_unref(ret);
ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p) in report. BAD.", i, child, attach_to);
return NULL;
}
refobject_unref(child);
}
refobject_unref(definition);
refobject_unref(attach_to);
return ret;
}
reportxml_node_t * reportxml_database_build_fragment(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t type)
{
reportxml_node_t *definition;
reportxml_node_t *ret;
reportxml_node_type_t got;
ssize_t count;
size_t i;
if (!db || !id)
return NULL;
/* first find the definition itself. This will be some REPORTXML_NODE_TYPE_DEFINITION node. */
definition = __reportxml_database_build_node_ext(db, id, depth, &got);
if (!definition) {
ICECAST_LOG_WARN("No matching definition for \"%H\"", id);
return NULL;
}
/* Let's see how many children we have. */
count = reportxml_node_count_child(definition);
if (count < 0) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Can not get child count from definition. BAD.");
return NULL;
} else if (count == 0) {
/* Empty definition? Not exactly an exciting report... */
ICECAST_LOG_WARN("Empty definition for \"%H\". Returning empty node. This is likely an error.", id);
refobject_unref(definition);
return reportxml_node_new(type, NULL, id, NULL);
} else if (count == 1 && got == type) {
ret = reportxml_node_get_child(definition, 0);
refobject_unref(definition);
return ret;
} else if (got == type) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Definition lists multiple children of target type. BAD.");
return NULL;
}
ret = reportxml_node_new(type, NULL, NULL, NULL);
if (!ret) {
refobject_unref(definition);
ICECAST_LOG_ERROR("Can not allocate new node. BAD.");
return NULL;
}
for (i = 0; i < (size_t)count; i++) {
reportxml_node_t *child = reportxml_node_get_child(definition, i);
/* we can directly attach as it's a already a copy. */
if (reportxml_node_add_child(ret, child) != 0) {
refobject_unref(definition);
refobject_unref(ret);
ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p). BAD.", i, child, ret);
return NULL;
}
refobject_unref(child);
}
refobject_unref(definition);
return ret;
}