1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2025-02-02 15:07:36 -05:00

Merge branch 'feature-geoip' into devel-phschafft

This commit is contained in:
Philipp Schafft 2024-01-20 02:13:38 +00:00
commit 6752809c20
17 changed files with 404 additions and 1 deletions

View File

@ -25,6 +25,7 @@
<th>Role</th> <th>Role</th>
<th>Sec. connected</th> <th>Sec. connected</th>
<th>User Agent</th> <th>User Agent</th>
<th>Location</th>
<th class="actions">Action</th> <th class="actions">Action</th>
</tr> </tr>
</thead> </thead>
@ -36,6 +37,10 @@
<td><xsl:value-of select="role" /></td> <td><xsl:value-of select="role" /></td>
<td><xsl:value-of select="connected" /></td> <td><xsl:value-of select="connected" /></td>
<td><xsl:value-of select="useragent" /></td> <td><xsl:value-of select="useragent" /></td>
<td>
<xsl:value-of select="geoip/country/@iso-alpha-2" />
<xsl:if test="geoip/location/@latitude and geoip/location/@longitude">&#160;<a href="https://www.openstreetmap.org/?mlat={geoip/location/@latitude}&amp;mlon={geoip/location/@longitude}&amp;zoom=7">On OSM</a></xsl:if>
</td>
<td class="actions"> <td class="actions">
<a class="critical" href="/admin/ui/confirmkillclient.xsl?mount={../@mount}&amp;id={id}">Kick</a> <a class="critical" href="/admin/ui/confirmkillclient.xsl?mount={../@mount}&amp;id={id}">Kick</a>
<a href="/admin/moveclients.xsl?mount={../@mount}&amp;id={id}">Move</a> <a href="/admin/moveclients.xsl?mount={../@mount}&amp;id={id}">Move</a>

View File

@ -243,6 +243,14 @@ PKG_HAVE_WITH_MODULES([OPENSSL], [openssl >= 1.1.0], [
LIBS="${LIBS} ${OPENSSL_LIBS}" LIBS="${LIBS} ${OPENSSL_LIBS}"
]) ])
dnl
dnl libmaxminddb
dnl
PKG_HAVE_WITH_MODULES([MAXMINDDB], [libmaxminddb >= 1.3.2], [
CFLAGS="${CFLAGS} ${MAXMINDDB_CFLAGS}"
LIBS="${LIBS} ${MAXMINDDB_LIBS}"
])
dnl dnl
dnl librhash - first try pkgconfig and then basic search dnl librhash - first try pkgconfig and then basic search
dnl since the function is defined in rhash.h we need to check for that first, dnl since the function is defined in rhash.h we need to check for that first,

View File

@ -34,6 +34,7 @@ noinst_HEADERS = \
prng.h \ prng.h \
matchfile.h \ matchfile.h \
tls.h \ tls.h \
geoip.h \
refobject.h \ refobject.h \
module.h \ module.h \
reportxml.h \ reportxml.h \
@ -88,6 +89,7 @@ icecast_SOURCES = \
prng.c \ prng.c \
matchfile.c \ matchfile.c \
tls.c \ tls.c \
geoip.c \
refobject.c \ refobject.c \
module.c \ module.c \
reportxml.c \ reportxml.c \

View File

@ -150,6 +150,12 @@
#define DEFAULT_HTML_REQUEST "" #define DEFAULT_HTML_REQUEST ""
#define BUILDM3U_RAW_REQUEST "buildm3u" #define BUILDM3U_RAW_REQUEST "buildm3u"
typedef struct {
size_t listeners;
size_t tls;
size_t ipv6;
} country_t;
typedef struct { typedef struct {
const char *prefix; const char *prefix;
size_t length; size_t length;
@ -577,6 +583,8 @@ xmlDocPtr admin_build_sourcelist(const char *mount, client_t *client, admin_form
snprintf(buf, sizeof(buf), "%"PRIu64, source->dumpfile_written); snprintf(buf, sizeof(buf), "%"PRIu64, source->dumpfile_written);
xmlNewTextChild(srcnode, NULL, XMLSTR("dumpfile_written"), XMLSTR(buf)); xmlNewTextChild(srcnode, NULL, XMLSTR("dumpfile_written"), XMLSTR(buf));
admin_add_geoip_to_mount(source, srcnode, client->mode);
} }
node = avl_get_next(node); node = avl_get_next(node);
} }
@ -922,6 +930,30 @@ static inline xmlNodePtr __add_listener(client_t *client,
xmlNewTextChild(node, NULL, XMLSTR("protocol"), XMLSTR(client_protocol_to_string(client->protocol))); xmlNewTextChild(node, NULL, XMLSTR("protocol"), XMLSTR(client_protocol_to_string(client->protocol)));
if (client->con) {
connection_t *con = client->con;
if (*con->geoip.iso_3166_1_alpha_2 || con->geoip.have_latitude || con->geoip.have_longitude) {
xmlNodePtr geoip = xmlNewChild(node, NULL, XMLSTR("geoip"), NULL);
if (*con->geoip.iso_3166_1_alpha_2) {
xmlNodePtr country = xmlNewChild(geoip, NULL, XMLSTR("country"), NULL);
xmlSetProp(country, XMLSTR("iso-alpha-2"), XMLSTR(con->geoip.iso_3166_1_alpha_2));
}
if (con->geoip.have_latitude || con->geoip.have_longitude) {
xmlNodePtr location = xmlNewChild(geoip, NULL, XMLSTR("location"), NULL);
if (con->geoip.have_latitude) {
snprintf(buf, sizeof(buf), "%f", con->geoip.latitude);
xmlSetProp(location, XMLSTR("latitude"), XMLSTR(buf));
}
if (con->geoip.have_longitude) {
snprintf(buf, sizeof(buf), "%f", con->geoip.longitude);
xmlSetProp(location, XMLSTR("longitude"), XMLSTR(buf));
}
}
}
}
do { do {
xmlNodePtr history = xmlNewChild(node, NULL, XMLSTR("history"), NULL); xmlNodePtr history = xmlNewChild(node, NULL, XMLSTR("history"), NULL);
size_t i; size_t i;
@ -950,6 +982,80 @@ void admin_add_listeners_to_mount(source_t *source,
avl_tree_unlock(source->client_tree); avl_tree_unlock(source->client_tree);
} }
static void admin_add_geoip_to_mount__country(source_t *source,
xmlNodePtr parent,
operation_mode mode,
const char *code,
country_t *country)
{
if (country->listeners) {
xmlNodePtr node = xmlNewChild(parent, NULL, XMLSTR("country"), NULL);
char buf[22];
if (code)
xmlSetProp(node, XMLSTR("iso-alpha-2"), XMLSTR(code));
snprintf(buf, sizeof(buf), "%llu", (long long unsigned)country->listeners);
xmlNewTextChild(node, NULL, XMLSTR("listeners"), XMLSTR(buf));
snprintf(buf, sizeof(buf), "%llu", (long long unsigned)country->tls);
xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR(buf));
snprintf(buf, sizeof(buf), "%llu", (long long unsigned)country->ipv6);
xmlNewTextChild(node, NULL, XMLSTR("ipv6"), XMLSTR(buf));
}
}
void admin_add_geoip_to_mount(source_t *source,
xmlNodePtr parent,
operation_mode mode)
{
avl_node *client_node;
xmlNodePtr geoip = xmlNewChild(parent, NULL, XMLSTR("geoip"), NULL);
country_t countries[26][26];
country_t default_country;
memset(countries, 0, sizeof(countries));
memset(&default_country, 0, sizeof(default_country));
avl_tree_rlock(source->client_tree);
client_node = avl_get_first(source->client_tree);
while(client_node) {
client_t *client = client_node->key;
connection_t *con = client->con;
country_t *country = &default_country;
if (con && *con->geoip.iso_3166_1_alpha_2) {
const char *iso = client->con->geoip.iso_3166_1_alpha_2;
if ((iso[0] >= 'a' && iso[0] <= 'z') && (iso[1] >= 'a' && iso[1] <= 'z')) {
country = &(countries[iso[0] - 'a'][iso[1] - 'a']);
}
}
country->listeners++;
if (con) {
if (con->tls)
country->tls++;
if (con->ip && strchr(con->ip, ':'))
country->ipv6++;
}
client_node = avl_get_next(client_node);
}
avl_tree_unlock(source->client_tree);
for (size_t idx_a = 0; idx_a < 26; idx_a++) {
for (size_t idx_b = 0; idx_b < 26; idx_b++) {
const char code[3] = {idx_a + 'a', idx_b + 'a', 0};
admin_add_geoip_to_mount__country(source, geoip, mode, code, &(countries[idx_a][idx_b]));
}
}
admin_add_geoip_to_mount__country(source, geoip, mode, NULL, &default_country);
}
static void command_show_listeners(client_t *client, static void command_show_listeners(client_t *client,
source_t *source, source_t *source,
admin_format_t response) admin_format_t response)
@ -970,6 +1076,7 @@ static void command_show_listeners(client_t *client,
xmlNewTextChild(srcnode, NULL, XMLSTR(client->mode == OMODE_LEGACY ? "Listeners" : "listeners"), XMLSTR(buf)); xmlNewTextChild(srcnode, NULL, XMLSTR(client->mode == OMODE_LEGACY ? "Listeners" : "listeners"), XMLSTR(buf));
admin_add_listeners_to_mount(source, srcnode, client->mode); admin_add_listeners_to_mount(source, srcnode, client->mode);
admin_add_geoip_to_mount(source, srcnode, client->mode);
admin_send_response(doc, client, response, admin_send_response(doc, client, response,
LISTCLIENTS_HTML_REQUEST); LISTCLIENTS_HTML_REQUEST);

View File

@ -61,6 +61,11 @@ void admin_add_listeners_to_mount(source_t *source,
xmlNodePtr parent, xmlNodePtr parent,
operation_mode mode); operation_mode mode);
void admin_add_geoip_to_mount(source_t *source,
xmlNodePtr parent,
operation_mode mode);
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent); xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent);
admin_command_id_t admin_get_command(const char *command); admin_command_id_t admin_get_command(const char *command);

View File

@ -55,6 +55,7 @@
#include "slave.h" #include "slave.h"
#include "xslt.h" #include "xslt.h"
#include "prng.h" #include "prng.h"
#include "geoip.h"
#define CATMODULE "CONFIG" #define CATMODULE "CONFIG"
#define RANGE_PORT 1, 65535 #define RANGE_PORT 1, 65535
@ -914,6 +915,7 @@ void config_clear(ice_config_t *c)
if (c->log_dir) xmlFree(c->log_dir); if (c->log_dir) xmlFree(c->log_dir);
if (c->webroot_dir) xmlFree(c->webroot_dir); if (c->webroot_dir) xmlFree(c->webroot_dir);
if (c->adminroot_dir) xmlFree(c->adminroot_dir); if (c->adminroot_dir) xmlFree(c->adminroot_dir);
if (c->geoipdbfile) xmlFree(c->geoipdbfile);
if (c->null_device) xmlFree(c->null_device); if (c->null_device) xmlFree(c->null_device);
if (c->pidfile) xmlFree(c->pidfile); if (c->pidfile) xmlFree(c->pidfile);
if (c->banfile) xmlFree(c->banfile); if (c->banfile) xmlFree(c->banfile);
@ -1004,6 +1006,8 @@ void config_reread_config(void)
restart_logging(config); restart_logging(config);
prng_configure(config); prng_configure(config);
main_config_reload(config); main_config_reload(config);
igloo_ro_unref(&global.geoip_db);
global.geoip_db = geoip_db_new(config->geoipdbfile);
connection_reread_config(config); connection_reread_config(config);
yp_recheck_config(config); yp_recheck_config(config);
fserve_recheck_mime_types(config); fserve_recheck_mime_types(config);
@ -2719,6 +2723,14 @@ static void _parse_paths(xmlDocPtr doc,
} }
} }
xmlFree(temp); xmlFree(temp);
} else if (xmlStrcmp(node->name, XMLSTR("geoipdb")) == 0) {
if (!(temp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1))) {
ICECAST_LOG_WARN("<geoipdb> setting must not be empty.");
continue;
}
if (configuration->geoipdbfile)
xmlFree(configuration->geoipdbfile);
configuration->geoipdbfile = (char *)temp;
} else if (xmlStrcmp(node->name, XMLSTR("resource")) == 0 || xmlStrcmp(node->name, XMLSTR("alias")) == 0) { } else if (xmlStrcmp(node->name, XMLSTR("resource")) == 0 || xmlStrcmp(node->name, XMLSTR("alias")) == 0) {
_parse_resource(doc, node, configuration); _parse_resource(doc, node, configuration);
} else { } else {

View File

@ -288,6 +288,7 @@ struct ice_config_tag {
char *allowfile; char *allowfile;
char *webroot_dir; char *webroot_dir;
char *adminroot_dir; char *adminroot_dir;
char *geoipdbfile;
prng_seed_config_t *prng_seed; prng_seed_config_t *prng_seed;
resource_t *resources; resource_t *resources;
reportxml_database_t *reportxml_db; reportxml_database_t *reportxml_db;

View File

@ -57,6 +57,7 @@
#include "acl.h" #include "acl.h"
#include "listensocket.h" #include "listensocket.h"
#include "fastevent.h" #include "fastevent.h"
#include "geoip.h"
/* for ADMIN_COMMAND_ERROR, and ADMIN_ICESTATS_LEGACY_EXTENSION_APPLICATION */ /* for ADMIN_COMMAND_ERROR, and ADMIN_ICESTATS_LEGACY_EXTENSION_APPLICATION */
#include "admin.h" #include "admin.h"
@ -189,6 +190,8 @@ int client_create(client_t **c_ptr, connection_t *con, http_parser_t *parser)
fastevent_emit(FASTEVENT_TYPE_CLIENT_CREATE, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client); fastevent_emit(FASTEVENT_TYPE_CLIENT_CREATE, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client);
geoip_lookup_client(global.geoip_db, client);
return ret; return ret;
} }

View File

@ -16,6 +16,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <time.h> #include <time.h>
#include <stdbool.h>
#include "tls.h" #include "tls.h"
@ -64,6 +65,14 @@ struct connection_tag {
/* IP Address of the client as seen by the server */ /* IP Address of the client as seen by the server */
char *ip; char *ip;
struct {
double latitude;
double longitude;
bool have_latitude;
bool have_longitude;
char iso_3166_1_alpha_2[3]; /* 2 bytes plus \0 */
} geoip;
}; };
void connection_initialize(void); void connection_initialize(void);

128
src/geoip.c Normal file
View File

@ -0,0 +1,128 @@
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2023 , Philipp Schafft <lion@lion.leolix.org>,
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "icecasttypes.h"
#ifdef HAVE_MAXMINDDB
#include <maxminddb.h>
#include <string.h>
#include <errno.h>
#endif
#include <igloo/error.h>
#include <igloo/ro.h>
#include "geoip.h"
#include "global.h"
#include "client.h"
#include "connection.h"
#include "util_string.h"
#include "logging.h"
#define CATMODULE "geoip"
struct geoip_db_tag {
igloo_ro_full_t __parent;
#ifdef HAVE_MAXMINDDB
MMDB_s mmdb;
#endif
};
static void geoip_db_free(igloo_ro_t self)
{
#ifdef HAVE_MAXMINDDB
geoip_db_t *db = igloo_ro_to_type(self, geoip_db_t);
MMDB_close(&(db->mmdb));
#endif
}
igloo_RO_PUBLIC_TYPE(geoip_db_t, igloo_ro_full_t,
igloo_RO_TYPEDECL_FREE(geoip_db_free)
);
#ifdef HAVE_MAXMINDDB
geoip_db_t * geoip_db_new(const char *filename)
{
geoip_db_t *ret;
MMDB_s mmdb;
int status;
status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb);
if (status != MMDB_SUCCESS) {
if (status == MMDB_IO_ERROR) {
ICECAST_LOG_ERROR("Cannot open geoip database: %s: %s", MMDB_strerror(status), strerror(errno));
} else {
ICECAST_LOG_ERROR("Cannot open geoip database: %s", MMDB_strerror(status));
}
return NULL;
}
if (igloo_ro_new_raw(&ret, geoip_db_t, igloo_instance) != igloo_ERROR_NONE)
return NULL;
ret->mmdb = mmdb;
ICECAST_LOG_INFO("Loaded geoip database: %s", filename);
return ret;
}
void geoip_lookup_client(geoip_db_t *self, client_t * client)
{
int gai_error, mmdb_error;
MMDB_lookup_result_s result;
connection_t *con;
if (!self || !client)
return;
if (!client->con && !client->con->ip)
return;
con = client->con;
result = MMDB_lookup_string(&(self->mmdb), client->con->ip, &gai_error, &mmdb_error);
if (gai_error || mmdb_error != MMDB_SUCCESS)
return;
if (result.found_entry) {
MMDB_entry_data_s entry_data;
int status;
status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", (const char*)NULL);
if (status == MMDB_SUCCESS && entry_data.has_data) {
if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
if (entry_data.data_size < sizeof(con->geoip.iso_3166_1_alpha_2)) {
memcpy(con->geoip.iso_3166_1_alpha_2, entry_data.utf8_string, entry_data.data_size);
con->geoip.iso_3166_1_alpha_2[entry_data.data_size] = 0;
util_strtolower(con->geoip.iso_3166_1_alpha_2);
ICECAST_LOG_DINFO("FOUND: <%zu> <%H>", (size_t)entry_data.data_size, con->geoip.iso_3166_1_alpha_2);
}
}
}
status = MMDB_get_value(&result.entry, &entry_data, "location", "latitude", (const char*)NULL);
if (status == MMDB_SUCCESS && entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE) {
con->geoip.latitude = entry_data.double_value;
con->geoip.have_latitude = true;
}
status = MMDB_get_value(&result.entry, &entry_data, "location", "longitude", (const char*)NULL);
if (status == MMDB_SUCCESS && entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE) {
con->geoip.longitude = entry_data.double_value;
con->geoip.have_longitude = true;
}
}
}
#endif

25
src/geoip.h Normal file
View File

@ -0,0 +1,25 @@
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2023-2023, Philipp Schafft <lion@lion.leolix.org>,
*/
#ifndef __GEOIP_H__
#define __GEOIP_H__
#include "icecasttypes.h"
#include "client.h"
igloo_RO_FORWARD_TYPE(geoip_db_t);
#ifdef HAVE_MAXMINDDB
geoip_db_t * geoip_db_new(const char *filename);
void geoip_lookup_client(geoip_db_t *self, client_t * client);
#else
#define geoip_db_new(filename) NULL
#define geoip_lookup_client(self,client)
#endif
#endif /* __GEOIP_H__ */

View File

@ -51,6 +51,7 @@ void global_shutdown(void)
{ {
thread_mutex_destroy(&_global_mutex); thread_mutex_destroy(&_global_mutex);
igloo_ro_unref(&global.modulecontainer); igloo_ro_unref(&global.modulecontainer);
igloo_ro_unref(&global.geoip_db);
avl_tree_free(global.source_tree, NULL); avl_tree_free(global.source_tree, NULL);
igloo_sp_unref(&_instance_uuid, igloo_instance); igloo_sp_unref(&_instance_uuid, igloo_instance);
} }

View File

@ -53,6 +53,7 @@ typedef struct ice_global_tag
relay_t *master_relays; relay_t *master_relays;
module_container_t *modulecontainer; module_container_t *modulecontainer;
geoip_db_t *geoip_db;
/* state */ /* state */

View File

@ -134,6 +134,10 @@ typedef struct mount_identifier_tag mount_identifier_t;
typedef struct string_renderer_tag string_renderer_t; typedef struct string_renderer_tag string_renderer_t;
/* ---[ geoip.[ch] ]--- */
typedef struct geoip_db_tag geoip_db_t;
/* ---[ event.[ch] ]--- */ /* ---[ event.[ch] ]--- */
typedef struct event_tag event_t; typedef struct event_tag event_t;
@ -160,6 +164,7 @@ typedef void * refobject_t;
/* --- [ For libigloo ]--- */ /* --- [ For libigloo ]--- */
#define igloo_RO_APPTYPES \ #define igloo_RO_APPTYPES \
igloo_RO_TYPE(string_renderer_t) \ igloo_RO_TYPE(string_renderer_t) \
igloo_RO_TYPE(geoip_db_t) \
igloo_RO_TYPE(event_t) \ igloo_RO_TYPE(event_t) \
igloo_RO_TYPE(event_registration_t) \ igloo_RO_TYPE(event_registration_t) \
igloo_RO_TYPE(module_t) \ igloo_RO_TYPE(module_t) \

View File

@ -91,6 +91,7 @@
#include "listensocket.h" #include "listensocket.h"
#include "fastevent.h" #include "fastevent.h"
#include "prng.h" #include "prng.h"
#include "geoip.h"
#include "navigation.h" #include "navigation.h"
#include <libxml/xmlmemory.h> #include <libxml/xmlmemory.h>
@ -755,6 +756,10 @@ int main(int argc, char **argv)
ICECAST_LOG_INFO("Server's PID is %lli", (long long int)getpid()); ICECAST_LOG_INFO("Server's PID is %lli", (long long int)getpid());
__log_system_name(); __log_system_name();
config = config_get_config();
global.geoip_db = geoip_db_new(config->geoipdbfile);
config_release_config();
/* REM 3D Graphics */ /* REM 3D Graphics */
/* let her rip */ /* let her rip */

View File

@ -934,8 +934,10 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, unsigned int flags, const
if (source_real->running) if (source_real->running)
xmlNewTextChild(xmlnode, NULL, XMLSTR("content-type"), XMLSTR(source_real->format->contenttype)); xmlNewTextChild(xmlnode, NULL, XMLSTR("content-type"), XMLSTR(source_real->format->contenttype));
if (flags & STATS_XML_FLAG_SHOW_LISTENERS) if (flags & STATS_XML_FLAG_SHOW_LISTENERS) {
admin_add_listeners_to_mount(source_real, xmlnode, client->mode); admin_add_listeners_to_mount(source_real, xmlnode, client->mode);
admin_add_geoip_to_mount(source_real, xmlnode, client->mode);
}
} }
avl_tree_unlock(global.source_tree); avl_tree_unlock(global.source_tree);

View File

@ -467,6 +467,90 @@ static void render_node_legacystats(json_renderer_t *renderer, xmlDocPtr doc, xm
json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE); json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE);
render_node(renderer, doc, cur, node, cache); render_node(renderer, doc, cur, node, cache);
nodelist_unset(&nodelist, i); nodelist_unset(&nodelist, i);
} else if (strcmp((const char *)cur->name, "geoip") == 0) {
xmlNodePtr geoip = NULL;
for (size_t 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);
geoip = subcur;
}
}
if (geoip) {
json_renderer_write_key(renderer, (const char *)cur->name, JSON_RENDERER_FLAGS_NONE);
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT);
{
xmlNodePtr child = geoip->xmlChildrenNode;
json_renderer_write_key(renderer, "country", JSON_RENDERER_FLAGS_NONE);
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY);
while (child) {
if (child->type == XML_ELEMENT_NODE && child->name && strcmp((const char *)child->name, "country") == 0) {
xmlChar *keyval = xmlGetProp(child, XMLSTR("iso-alpha-2"));
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT);
if (keyval) {
json_renderer_write_key(renderer, "iso-alpha-2", JSON_RENDERER_FLAGS_NONE);
json_renderer_write_string(renderer, (const char *)keyval, JSON_RENDERER_FLAGS_NONE);
xmlFree(keyval);
}
for (xmlNodePtr subchild = child->xmlChildrenNode; subchild; subchild = subchild->next) {
if (subchild->type == XML_ELEMENT_NODE && subchild->name) {
xmlChar *value = xmlNodeListGetString(doc, subchild->xmlChildrenNode, 1);
if (value) {
json_renderer_write_key(renderer, (const char*)subchild->name, JSON_RENDERER_FLAGS_NONE);
json_renderer_write_int(renderer, strtoll((const char*)value, NULL, 10));
xmlFree(value);
}
}
}
json_renderer_end(renderer);
}
child = child->next;
}
json_renderer_end(renderer);
}
{
xmlNodePtr child = geoip->xmlChildrenNode;
json_renderer_write_key(renderer, "location", JSON_RENDERER_FLAGS_NONE);
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_ARRAY);
while (child) {
ICECAST_LOG_INFO("child->name=<%s>", child->name);
if (child->type == XML_ELEMENT_NODE && child->name && strcmp((const char *)child->name, "location") == 0) {
static const char * keys[] = {"latitude", "longitude", NULL};
json_renderer_begin(renderer, JSON_ELEMENT_TYPE_OBJECT);
for (const char **p = keys; *p; p++) {
xmlChar *keyval = xmlGetProp(child, XMLSTR(*p));
if (keyval) {
json_renderer_write_key(renderer, *p, JSON_RENDERER_FLAGS_NONE);
json_renderer_write_string(renderer, (const char *)keyval, JSON_RENDERER_FLAGS_NONE);
xmlFree(keyval);
}
}
json_renderer_end(renderer);
}
child = child->next;
}
json_renderer_end(renderer);
}
json_renderer_end(renderer);
}
} }
} }
//render_node_generic(renderer, doc, node, parent, cache); //render_node_generic(renderer, doc, node, parent, cache);