mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
[misc] Add misc_xml.c to wrap mxml, fixes XML reading of CDATA (et al)
The change removes all direct calls to mxml from the modules that need an XML parser (lastfm.c, pipe.c, rssscanner.c and httpd_rsp.c). Even with the help of mxml, reading XML is hard, so a layer is added which helps deal with stuff like whitespace and CDATA. This should make OwnTone more resilient to any XML variations it might receive. The changes fixes issue #1677.
This commit is contained in:
parent
83ac327d7f
commit
65c72c484b
@ -107,6 +107,7 @@ owntone_SOURCES = main.c \
|
|||||||
artwork.c artwork.h \
|
artwork.c artwork.h \
|
||||||
misc.c misc.h \
|
misc.c misc.h \
|
||||||
misc_json.c misc_json.h \
|
misc_json.c misc_json.h \
|
||||||
|
misc_xml.c misc_xml.h \
|
||||||
rng.c rng.h \
|
rng.c rng.h \
|
||||||
smartpl_query.c smartpl_query.h \
|
smartpl_query.c smartpl_query.h \
|
||||||
player.c player.h \
|
player.c player.h \
|
||||||
@ -128,7 +129,6 @@ owntone_SOURCES = main.c \
|
|||||||
$(MPD_SRC) \
|
$(MPD_SRC) \
|
||||||
listener.c listener.h \
|
listener.c listener.h \
|
||||||
commands.c commands.h \
|
commands.c commands.h \
|
||||||
mxml-compat.h \
|
|
||||||
outputs/plist_wrap.h \
|
outputs/plist_wrap.h \
|
||||||
$(LIBWEBSOCKETS_SRC) \
|
$(LIBWEBSOCKETS_SRC) \
|
||||||
$(GPERF_SRC) \
|
$(GPERF_SRC) \
|
||||||
|
285
src/httpd_rsp.c
285
src/httpd_rsp.c
@ -30,13 +30,12 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "mxml-compat.h"
|
|
||||||
|
|
||||||
#include "httpd_internal.h"
|
#include "httpd_internal.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
#include "misc_xml.h"
|
||||||
#include "transcode.h"
|
#include "transcode.h"
|
||||||
#include "parsers/rsp_parser.h"
|
#include "parsers/rsp_parser.h"
|
||||||
|
|
||||||
@ -120,12 +119,12 @@ static const struct field_map rsp_fields[] =
|
|||||||
/* -------------------------------- HELPERS --------------------------------- */
|
/* -------------------------------- HELPERS --------------------------------- */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mxml_to_evbuf(struct evbuffer *evbuf, mxml_node_t *tree)
|
xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
|
||||||
{
|
{
|
||||||
char *xml;
|
char *xml;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
xml = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);
|
xml = xml_to_string(tree);
|
||||||
if (!xml)
|
if (!xml)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
|
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
|
||||||
@ -143,37 +142,33 @@ mxml_to_evbuf(struct evbuffer *evbuf, mxml_node_t *tree)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rsp_xml_response_new(xml_node **xml_ptr, xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
|
||||||
|
{
|
||||||
|
xml_node *xml = xml_new_node(NULL, RSP_XML_ROOT, NULL);
|
||||||
|
xml_node *response = xml_new_node(xml, "response", NULL);
|
||||||
|
xml_node *status = xml_new_node(response, "status", NULL);
|
||||||
|
|
||||||
|
xml_new_node_textf(status, "errorcode", "%d", errorcode);
|
||||||
|
xml_new_node(status, "errorstring", errorstring);
|
||||||
|
xml_new_node_textf(status, "records", "%d", records);
|
||||||
|
xml_new_node_textf(status, "totalrecords", "%d", totalrecords);
|
||||||
|
|
||||||
|
if (response_ptr)
|
||||||
|
*response_ptr = response;
|
||||||
|
if (xml_ptr)
|
||||||
|
*xml_ptr = xml;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rsp_send_error(struct httpd_request *hreq, char *errmsg)
|
rsp_send_error(struct httpd_request *hreq, char *errmsg)
|
||||||
{
|
{
|
||||||
mxml_node_t *reply;
|
xml_node *xml;
|
||||||
mxml_node_t *status;
|
|
||||||
mxml_node_t *node;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* We'd use mxmlNewXML(), but then we can't put any attributes
|
rsp_xml_response_new(&xml, NULL, 1, errmsg, 0, 0);
|
||||||
* on the root node and we need some.
|
ret = xml_to_evbuf(hreq->out_body, xml);
|
||||||
*/
|
xml_free(xml);
|
||||||
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
|
|
||||||
|
|
||||||
node = mxmlNewElement(reply, "response");
|
|
||||||
status = mxmlNewElement(node, "status");
|
|
||||||
|
|
||||||
/* Status block */
|
|
||||||
node = mxmlNewElement(status, "errorcode");
|
|
||||||
mxmlNewText(node, 0, "1");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "errorstring");
|
|
||||||
mxmlNewText(node, 0, errmsg);
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "records");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "totalrecords");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
ret = mxml_to_evbuf(hreq->out_body, reply);
|
|
||||||
mxmlDelete(reply);
|
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -259,12 +254,12 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply)
|
rsp_send_reply(struct httpd_request *hreq, xml_node *reply)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = mxml_to_evbuf(hreq->out_body, reply);
|
ret = xml_to_evbuf(hreq->out_body, reply);
|
||||||
mxmlDelete(reply);
|
xml_free(reply);
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -310,10 +305,9 @@ rsp_request_authorize(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
rsp_reply_info(struct httpd_request *hreq)
|
rsp_reply_info(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
mxml_node_t *reply;
|
xml_node *xml;
|
||||||
mxml_node_t *status;
|
xml_node *response;
|
||||||
mxml_node_t *info;
|
xml_node *info;
|
||||||
mxml_node_t *node;
|
|
||||||
cfg_t *lib;
|
cfg_t *lib;
|
||||||
char *library;
|
char *library;
|
||||||
uint32_t songcount;
|
uint32_t songcount;
|
||||||
@ -323,43 +317,16 @@ rsp_reply_info(struct httpd_request *hreq)
|
|||||||
lib = cfg_getsec(cfg, "library");
|
lib = cfg_getsec(cfg, "library");
|
||||||
library = cfg_getstr(lib, "name");
|
library = cfg_getstr(lib, "name");
|
||||||
|
|
||||||
/* We'd use mxmlNewXML(), but then we can't put any attributes
|
rsp_xml_response_new(&xml, &response, 0, "", 0, 0);
|
||||||
* on the root node and we need some.
|
|
||||||
*/
|
|
||||||
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
|
|
||||||
|
|
||||||
node = mxmlNewElement(reply, "response");
|
info = xml_new_node(response, "info", NULL);
|
||||||
status = mxmlNewElement(node, "status");
|
|
||||||
info = mxmlNewElement(node, "info");
|
|
||||||
|
|
||||||
/* Status block */
|
xml_new_node_textf(info, "count", "%d", (int)songcount);
|
||||||
node = mxmlNewElement(status, "errorcode");
|
xml_new_node(info, "rsp-version", RSP_VERSION);
|
||||||
mxmlNewText(node, 0, "0");
|
xml_new_node(info, "server-version", VERSION);
|
||||||
|
xml_new_node(info, "name", library);
|
||||||
node = mxmlNewElement(status, "errorstring");
|
|
||||||
mxmlNewText(node, 0, "");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "records");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "totalrecords");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
/* Info block */
|
|
||||||
node = mxmlNewElement(info, "count");
|
|
||||||
mxmlNewTextf(node, 0, "%d", (int)songcount);
|
|
||||||
|
|
||||||
node = mxmlNewElement(info, "rsp-version");
|
|
||||||
mxmlNewText(node, 0, RSP_VERSION);
|
|
||||||
|
|
||||||
node = mxmlNewElement(info, "server-version");
|
|
||||||
mxmlNewText(node, 0, VERSION);
|
|
||||||
|
|
||||||
node = mxmlNewElement(info, "name");
|
|
||||||
mxmlNewText(node, 0, library);
|
|
||||||
|
|
||||||
rsp_send_reply(hreq, reply);
|
|
||||||
|
|
||||||
|
rsp_send_reply(hreq, xml);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,11 +336,10 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct db_playlist_info dbpli;
|
struct db_playlist_info dbpli;
|
||||||
char **strval;
|
char **strval;
|
||||||
mxml_node_t *reply;
|
xml_node *xml;
|
||||||
mxml_node_t *status;
|
xml_node *response;
|
||||||
mxml_node_t *pls;
|
xml_node *pls;
|
||||||
mxml_node_t *pl;
|
xml_node *pl;
|
||||||
mxml_node_t *node;
|
|
||||||
int i;
|
int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -391,27 +357,9 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We'd use mxmlNewXML(), but then we can't put any attributes
|
rsp_xml_response_new(&xml, &response, 0, "", qp.results, qp.results);
|
||||||
* on the root node and we need some.
|
|
||||||
*/
|
|
||||||
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
|
|
||||||
|
|
||||||
node = mxmlNewElement(reply, "response");
|
pls = xml_new_node(response, "playlists", NULL);
|
||||||
status = mxmlNewElement(node, "status");
|
|
||||||
pls = mxmlNewElement(node, "playlists");
|
|
||||||
|
|
||||||
/* Status block */
|
|
||||||
node = mxmlNewElement(status, "errorcode");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "errorstring");
|
|
||||||
mxmlNewText(node, 0, "");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "records");
|
|
||||||
mxmlNewTextf(node, 0, "%d", qp.results);
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "totalrecords");
|
|
||||||
mxmlNewTextf(node, 0, "%d", qp.results);
|
|
||||||
|
|
||||||
/* Playlists block (all playlists) */
|
/* Playlists block (all playlists) */
|
||||||
while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
|
while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
|
||||||
@ -421,7 +369,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Playlist block (one playlist) */
|
/* Playlist block (one playlist) */
|
||||||
pl = mxmlNewElement(pls, "playlist");
|
pl = xml_new_node(pls, "playlist", NULL);
|
||||||
|
|
||||||
for (i = 0; pl_fields[i].field; i++)
|
for (i = 0; pl_fields[i].field; i++)
|
||||||
{
|
{
|
||||||
@ -429,8 +377,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
strval = (char **) ((char *)&dbpli + pl_fields[i].offset);
|
strval = (char **) ((char *)&dbpli + pl_fields[i].offset);
|
||||||
|
|
||||||
node = mxmlNewElement(pl, pl_fields[i].field);
|
xml_new_node(pl, pl_fields[i].field, *strval);
|
||||||
mxmlNewText(node, 0, *strval);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,7 +386,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
mxmlDelete(reply);
|
xml_free(xml);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -451,11 +398,11 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
mxmlNewText(pls, 0, "");
|
xml_new_text(pls, "");
|
||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, reply);
|
rsp_send_reply(hreq, xml);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -469,11 +416,10 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
const char *ua;
|
const char *ua;
|
||||||
const char *client_codecs;
|
const char *client_codecs;
|
||||||
char **strval;
|
char **strval;
|
||||||
mxml_node_t *reply;
|
xml_node *xml;
|
||||||
mxml_node_t *status;
|
xml_node *response;
|
||||||
mxml_node_t *items;
|
xml_node *items;
|
||||||
mxml_node_t *item;
|
xml_node *item;
|
||||||
mxml_node_t *node;
|
|
||||||
int mode;
|
int mode;
|
||||||
int records;
|
int records;
|
||||||
int transcode;
|
int transcode;
|
||||||
@ -537,27 +483,9 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
if (qp.limit && (records > qp.limit))
|
if (qp.limit && (records > qp.limit))
|
||||||
records = qp.limit;
|
records = qp.limit;
|
||||||
|
|
||||||
/* We'd use mxmlNewXML(), but then we can't put any attributes
|
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
||||||
* on the root node and we need some.
|
|
||||||
*/
|
|
||||||
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
|
|
||||||
|
|
||||||
node = mxmlNewElement(reply, "response");
|
items = xml_new_node(response, "items", NULL);
|
||||||
status = mxmlNewElement(node, "status");
|
|
||||||
items = mxmlNewElement(node, "items");
|
|
||||||
|
|
||||||
/* Status block */
|
|
||||||
node = mxmlNewElement(status, "errorcode");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "errorstring");
|
|
||||||
mxmlNewText(node, 0, "");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "records");
|
|
||||||
mxmlNewTextf(node, 0, "%d", records);
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "totalrecords");
|
|
||||||
mxmlNewTextf(node, 0, "%d", qp.results);
|
|
||||||
|
|
||||||
/* Items block (all items) */
|
/* Items block (all items) */
|
||||||
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
||||||
@ -568,7 +496,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
|
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
|
||||||
|
|
||||||
/* Item block (one item) */
|
/* Item block (one item) */
|
||||||
item = mxmlNewElement(items, "item");
|
item = xml_new_node(items, "item", NULL);
|
||||||
|
|
||||||
for (i = 0; rsp_fields[i].field; i++)
|
for (i = 0; rsp_fields[i].field; i++)
|
||||||
{
|
{
|
||||||
@ -580,44 +508,41 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
if (!(*strval) || (strlen(*strval) == 0))
|
if (!(*strval) || (strlen(*strval) == 0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
node = mxmlNewElement(item, rsp_fields[i].field);
|
|
||||||
|
|
||||||
if (!transcode)
|
if (!transcode)
|
||||||
mxmlNewText(node, 0, *strval);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
switch (rsp_fields[i].offset)
|
xml_new_node(item, rsp_fields[i].field, *strval);
|
||||||
{
|
continue;
|
||||||
case dbmfi_offsetof(type):
|
}
|
||||||
mxmlNewText(node, 0, "wav");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dbmfi_offsetof(bitrate):
|
switch (rsp_fields[i].offset)
|
||||||
bitrate = 0;
|
{
|
||||||
ret = safe_atoi32(dbmfi.samplerate, &bitrate);
|
case dbmfi_offsetof(type):
|
||||||
if ((ret < 0) || (bitrate == 0))
|
xml_new_node(item, rsp_fields[i].field, "wav");
|
||||||
bitrate = 1411;
|
break;
|
||||||
else
|
|
||||||
bitrate = (bitrate * 8) / 250;
|
|
||||||
|
|
||||||
mxmlNewTextf(node, 0, "%d", bitrate);
|
case dbmfi_offsetof(bitrate):
|
||||||
break;
|
bitrate = 0;
|
||||||
|
ret = safe_atoi32(dbmfi.samplerate, &bitrate);
|
||||||
|
if ((ret < 0) || (bitrate == 0))
|
||||||
|
bitrate = 1411;
|
||||||
|
else
|
||||||
|
bitrate = (bitrate * 8) / 250;
|
||||||
|
|
||||||
case dbmfi_offsetof(description):
|
xml_new_node_textf(item, rsp_fields[i].field, "%d", bitrate);
|
||||||
mxmlNewText(node, 0, "wav audio file");
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case dbmfi_offsetof(codectype):
|
case dbmfi_offsetof(description):
|
||||||
mxmlNewText(node, 0, "wav");
|
xml_new_node(item, rsp_fields[i].field, "wav audio file");
|
||||||
|
break;
|
||||||
|
|
||||||
node = mxmlNewElement(item, "original_codec");
|
case dbmfi_offsetof(codectype):
|
||||||
mxmlNewText(node, 0, *strval);
|
xml_new_node(item, rsp_fields[i].field, "wav");
|
||||||
break;
|
xml_new_node(item, "original_codec", *strval);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
mxmlNewText(node, 0, *strval);
|
xml_new_node(item, rsp_fields[i].field, *strval);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -629,7 +554,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
mxmlDelete(reply);
|
xml_free(xml);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -641,11 +566,11 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
mxmlNewText(items, 0, "");
|
xml_new_text(items, "");
|
||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, reply);
|
rsp_send_reply(hreq, xml);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -655,10 +580,9 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
char *browse_item;
|
char *browse_item;
|
||||||
mxml_node_t *reply;
|
xml_node *xml;
|
||||||
mxml_node_t *status;
|
xml_node *response;
|
||||||
mxml_node_t *items;
|
xml_node *items;
|
||||||
mxml_node_t *node;
|
|
||||||
int records;
|
int records;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -719,33 +643,14 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
if (qp.limit && (records > qp.limit))
|
if (qp.limit && (records > qp.limit))
|
||||||
records = qp.limit;
|
records = qp.limit;
|
||||||
|
|
||||||
/* We'd use mxmlNewXML(), but then we can't put any attributes
|
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
||||||
* on the root node and we need some.
|
|
||||||
*/
|
|
||||||
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
|
|
||||||
|
|
||||||
node = mxmlNewElement(reply, "response");
|
items = xml_new_node(response, "items", NULL);
|
||||||
status = mxmlNewElement(node, "status");
|
|
||||||
items = mxmlNewElement(node, "items");
|
|
||||||
|
|
||||||
/* Status block */
|
|
||||||
node = mxmlNewElement(status, "errorcode");
|
|
||||||
mxmlNewText(node, 0, "0");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "errorstring");
|
|
||||||
mxmlNewText(node, 0, "");
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "records");
|
|
||||||
mxmlNewTextf(node, 0, "%d", records);
|
|
||||||
|
|
||||||
node = mxmlNewElement(status, "totalrecords");
|
|
||||||
mxmlNewTextf(node, 0, "%d", qp.results);
|
|
||||||
|
|
||||||
/* Items block (all items) */
|
/* Items block (all items) */
|
||||||
while (((ret = db_query_fetch_string(&browse_item, &qp)) == 0) && (browse_item))
|
while (((ret = db_query_fetch_string(&browse_item, &qp)) == 0) && (browse_item))
|
||||||
{
|
{
|
||||||
node = mxmlNewElement(items, "item");
|
xml_new_node(items, "item", browse_item);
|
||||||
mxmlNewText(node, 0, browse_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qp.filter)
|
if (qp.filter)
|
||||||
@ -755,7 +660,7 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
mxmlDelete(reply);
|
xml_free(xml);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -767,11 +672,11 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
mxmlNewText(items, 0, "");
|
xml_new_text(items, "");
|
||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, reply);
|
rsp_send_reply(hreq, xml);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,9 @@
|
|||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
|
|
||||||
#include "mxml-compat.h"
|
|
||||||
|
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
#include "misc_xml.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
@ -531,35 +530,33 @@ log_incoming(int severity, const char *msg, uint32_t type, uint32_t code, int da
|
|||||||
DPRINTF(severity, L_PLAYER, "%s (type=%s, code=%s, len=%d)\n", msg, typestr, codestr, data_len);
|
DPRINTF(severity, L_PLAYER, "%s (type=%s, code=%s, len=%d)\n", msg, typestr, codestr, data_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Example of xml item:
|
||||||
|
|
||||||
|
<item><type>73736e63</type><code>6d647374</code><length>9</length>
|
||||||
|
<data encoding="base64">
|
||||||
|
NDE5OTg3OTU0</data></item>
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
parse_item_xml(uint32_t *type, uint32_t *code, uint8_t **data, int *data_len, const char *item)
|
parse_item_xml(uint32_t *type, uint32_t *code, uint8_t **data, int *data_len, const char *item)
|
||||||
{
|
{
|
||||||
mxml_node_t *xml;
|
xml_node *xml;
|
||||||
mxml_node_t *haystack;
|
|
||||||
mxml_node_t *needle;
|
|
||||||
const char *s;
|
const char *s;
|
||||||
|
|
||||||
xml = mxmlNewXML("1.0");
|
// DPRINTF(E_DBG, L_PLAYER, "Got pipe metadata item: '%s'\n", item);
|
||||||
|
|
||||||
|
xml = xml_from_string(item);
|
||||||
if (!xml)
|
if (!xml)
|
||||||
return -1;
|
|
||||||
|
|
||||||
// DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", item);
|
|
||||||
|
|
||||||
haystack = mxmlLoadString(xml, item, MXML_NO_CALLBACK);
|
|
||||||
if (!haystack)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata: %s\n", item);
|
DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata item: %s\n", item);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
*type = 0;
|
*type = 0;
|
||||||
if ( (needle = mxmlFindElement(haystack, haystack, "type", NULL, NULL, MXML_DESCEND)) &&
|
if ((s = xml_get_val(xml, "item/type")))
|
||||||
(s = mxmlGetText(needle, NULL)) )
|
|
||||||
sscanf(s, "%8x", type);
|
sscanf(s, "%8x", type);
|
||||||
|
|
||||||
*code = 0;
|
*code = 0;
|
||||||
if ( (needle = mxmlFindElement(haystack, haystack, "code", NULL, NULL, MXML_DESCEND)) &&
|
if ((s = xml_get_val(xml, "item/code")))
|
||||||
(s = mxmlGetText(needle, NULL)) )
|
|
||||||
sscanf(s, "%8x", code);
|
sscanf(s, "%8x", code);
|
||||||
|
|
||||||
if (*type == 0 || *code == 0)
|
if (*type == 0 || *code == 0)
|
||||||
@ -570,8 +567,7 @@ parse_item_xml(uint32_t *type, uint32_t *code, uint8_t **data, int *data_len, co
|
|||||||
|
|
||||||
*data = NULL;
|
*data = NULL;
|
||||||
*data_len = 0;
|
*data_len = 0;
|
||||||
if ( (needle = mxmlFindElement(haystack, haystack, "data", NULL, NULL, MXML_DESCEND)) &&
|
if ((s = xml_get_val(xml, "item/data")))
|
||||||
(s = mxmlGetText(needle, NULL)) )
|
|
||||||
{
|
{
|
||||||
*data = b64_decode(data_len, s);
|
*data = b64_decode(data_len, s);
|
||||||
if (*data == NULL)
|
if (*data == NULL)
|
||||||
@ -583,11 +579,11 @@ parse_item_xml(uint32_t *type, uint32_t *code, uint8_t **data, int *data_len, co
|
|||||||
|
|
||||||
log_incoming(E_SPAM, "Read Shairport metadata", *type, *code, *data_len);
|
log_incoming(E_SPAM, "Read Shairport metadata", *type, *code, *data_len);
|
||||||
|
|
||||||
mxmlDelete(xml);
|
xml_free(xml);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
mxmlDelete(xml);
|
xml_free(xml);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
88
src/lastfm.c
88
src/lastfm.c
@ -34,14 +34,13 @@
|
|||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
|
|
||||||
#include "mxml-compat.h"
|
|
||||||
|
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
#include "lastfm.h"
|
#include "lastfm.h"
|
||||||
#include "listener.h"
|
#include "listener.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
#include "misc_xml.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
// LastFM becomes disabled if we get a scrobble, try initialising session,
|
// LastFM becomes disabled if we get a scrobble, try initialising session,
|
||||||
@ -119,15 +118,44 @@ param_sign(struct keyval *kv)
|
|||||||
|
|
||||||
/* --------------------------------- MAIN --------------------------------- */
|
/* --------------------------------- MAIN --------------------------------- */
|
||||||
|
|
||||||
|
/* Example responses
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lfm status="ok">
|
||||||
|
<session>
|
||||||
|
<name>myname</name>
|
||||||
|
<key>dsfjDFDS22</key>
|
||||||
|
<subscriber>0</subscriber>
|
||||||
|
</session>
|
||||||
|
</lfm>
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lfm status="failed">
|
||||||
|
<error code="4">Authentication Failed - You do not have permissions to access the service</error>
|
||||||
|
</lfm>
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lfm status="ok">
|
||||||
|
<scrobbles ignored="0" accepted="1">
|
||||||
|
<scrobble>
|
||||||
|
<track corrected="0">Hard Place</track>
|
||||||
|
<artist corrected="0">My Artist</artist>
|
||||||
|
<album corrected="0">My Album</album>
|
||||||
|
<albumArtist corrected="0">My Album Artist</albumArtist>
|
||||||
|
<timestamp>1699649816</timestamp>
|
||||||
|
<ignoredMessage code="0"></ignoredMessage>
|
||||||
|
</scrobble>
|
||||||
|
</scrobbles>
|
||||||
|
</lfm>
|
||||||
|
*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
response_process(struct http_client_ctx *ctx, char **errmsg)
|
response_process(struct http_client_ctx *ctx, char **errmsg)
|
||||||
{
|
{
|
||||||
mxml_node_t *tree;
|
xml_node *tree;
|
||||||
mxml_node_t *s_node;
|
const char *error;
|
||||||
mxml_node_t *e_node;
|
|
||||||
char *body;
|
char *body;
|
||||||
char *sk;
|
char *sk;
|
||||||
int ret;
|
|
||||||
|
|
||||||
// NULL-terminate the buffer
|
// NULL-terminate the buffer
|
||||||
evbuffer_add(ctx->input_body, "", 1);
|
evbuffer_add(ctx->input_body, "", 1);
|
||||||
@ -139,67 +167,55 @@ response_process(struct http_client_ctx *ctx, char **errmsg)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
tree = mxmlLoadString(NULL, body, MXML_OPAQUE_CALLBACK);
|
tree = xml_from_string(body);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Failed to parse LastFM response:\n%s\n", body);
|
DPRINTF(E_LOG, L_LASTFM, "Failed to parse LastFM response:\n%s\n", body);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for errors
|
error = xml_get_val(tree, "lfm/error");
|
||||||
e_node = mxmlFindElement(tree, tree, "error", NULL, NULL, MXML_DESCEND);
|
if (error)
|
||||||
if (e_node)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Request to LastFM failed: %s\n", mxmlGetOpaque(e_node));
|
DPRINTF(E_LOG, L_LASTFM, "Request to LastFM failed: %s\n", error);
|
||||||
DPRINTF(E_DBG, L_LASTFM, "LastFM response:\n%s\n", body);
|
DPRINTF(E_DBG, L_LASTFM, "LastFM response:\n%s\n", body);
|
||||||
|
|
||||||
if (errmsg)
|
if (errmsg)
|
||||||
*errmsg = atrim(mxmlGetOpaque(e_node));
|
*errmsg = atrim(error);
|
||||||
|
|
||||||
mxmlDelete(tree);
|
xml_free(tree);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_SPAM, L_LASTFM, "LastFM response:\n%s\n", body);
|
DPRINTF(E_SPAM, L_LASTFM, "LastFM response:\n%s\n", body);
|
||||||
|
|
||||||
// Was it a scrobble request? Then do nothing. TODO: Check for error messages
|
// Was it a scrobble request? Then do nothing. TODO: Check for error messages
|
||||||
s_node = mxmlFindElement(tree, tree, "scrobbles", NULL, NULL, MXML_DESCEND);
|
if (xml_get_node(tree, "lfm/scrobbles/scrobble"))
|
||||||
if (s_node)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_LASTFM, "Scrobble callback\n");
|
DPRINTF(E_DBG, L_LASTFM, "Scrobble callback\n");
|
||||||
mxmlDelete(tree);
|
xml_free(tree);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise an auth request, so get the session key
|
// Otherwise an auth request, so get the session key
|
||||||
s_node = mxmlFindElement(tree, tree, "key", NULL, NULL, MXML_DESCEND);
|
sk = atrim(xml_get_val(tree, "lfm/session/key"));
|
||||||
if (!s_node)
|
if (!sk)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Session key not found\n");
|
DPRINTF(E_LOG, L_LASTFM, "Session key not found\n");
|
||||||
mxmlDelete(tree);
|
xml_free(tree);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sk = atrim(mxmlGetOpaque(s_node));
|
DPRINTF(E_INFO, L_LASTFM, "Got session key from LastFM: %s\n", sk);
|
||||||
if (sk)
|
db_admin_set(DB_ADMIN_LASTFM_SESSION_KEY, sk);
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk);
|
|
||||||
db_admin_set(DB_ADMIN_LASTFM_SESSION_KEY, sk);
|
|
||||||
|
|
||||||
if (lastfm_session_key)
|
free(lastfm_session_key);
|
||||||
free(lastfm_session_key);
|
|
||||||
|
|
||||||
lastfm_session_key = sk;
|
lastfm_session_key = sk;
|
||||||
lastfm_disabled = false;
|
lastfm_disabled = false;
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
mxmlDelete(tree);
|
xml_free(tree);
|
||||||
return ret;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -40,14 +40,13 @@
|
|||||||
|
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
|
|
||||||
#include "mxml-compat.h"
|
|
||||||
|
|
||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "misc_json.h"
|
#include "misc_json.h"
|
||||||
|
#include "misc_xml.h"
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "library/filescanner.h"
|
#include "library/filescanner.h"
|
||||||
|
|
||||||
@ -233,12 +232,12 @@ playlist_fetch(bool *is_new, const char *path)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static mxml_node_t *
|
static xml_node *
|
||||||
rss_xml_get(const char *url)
|
rss_xml_get(const char *url)
|
||||||
{
|
{
|
||||||
struct http_client_ctx ctx = { 0 };
|
struct http_client_ctx ctx = { 0 };
|
||||||
const char *raw = NULL;
|
const char *raw = NULL;
|
||||||
mxml_node_t *xml = NULL;
|
xml_node *xml = NULL;
|
||||||
char *feedurl;
|
char *feedurl;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -267,7 +266,7 @@ rss_xml_get(const char *url)
|
|||||||
|
|
||||||
raw = (const char*)evbuffer_pullup(ctx.input_body, -1);
|
raw = (const char*)evbuffer_pullup(ctx.input_body, -1);
|
||||||
|
|
||||||
xml = mxmlLoadString(NULL, raw, MXML_OPAQUE_CALLBACK);
|
xml = xml_from_string(raw);
|
||||||
if (!xml)
|
if (!xml)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LIB, "Failed to parse RSS XML from '%s'\n", ctx.url);
|
DPRINTF(E_LOG, L_LIB, "Failed to parse RSS XML from '%s'\n", ctx.url);
|
||||||
@ -281,85 +280,39 @@ rss_xml_get(const char *url)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
rss_xml_parse_feed(const char **feed_title, const char **feed_author, const char **feed_artwork, mxml_node_t *xml)
|
feed_metadata_from_xml(const char **feed_title, const char **feed_author, const char **feed_artwork, xml_node *xml)
|
||||||
{
|
{
|
||||||
mxml_node_t *channel;
|
xml_node *channel = xml_get_node(xml, "rss/channel");
|
||||||
mxml_node_t *node;
|
|
||||||
|
|
||||||
channel = mxmlFindElement(xml, xml, "channel", NULL, NULL, MXML_DESCEND);
|
|
||||||
if (!channel)
|
if (!channel)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml, missing 'channel' node\n");
|
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml, missing 'channel' node\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
node = mxmlFindElement(channel, channel, "title", NULL, NULL, MXML_DESCEND_FIRST);
|
*feed_title = xml_get_val(channel, "title");
|
||||||
if (!node)
|
if (!*feed_title)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml, missing 'title' node\n");
|
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml, missing 'title' node\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
*feed_title = mxmlGetOpaque(node);
|
|
||||||
|
|
||||||
node = mxmlFindElement(channel, channel, "itunes:author", NULL, NULL, MXML_DESCEND_FIRST);
|
*feed_author = xml_get_val(channel, "itunes:author");
|
||||||
*feed_author = node ? mxmlGetOpaque(node) : NULL;
|
*feed_artwork = xml_get_val(channel, "image/url");
|
||||||
|
|
||||||
*feed_artwork = NULL;
|
|
||||||
node = mxmlFindElement(channel, channel, "image", NULL, NULL, MXML_DESCEND_FIRST);
|
|
||||||
if (node)
|
|
||||||
{
|
|
||||||
node = mxmlFindElement(node, node, "url", NULL, NULL, MXML_DESCEND_FIRST);
|
|
||||||
*feed_artwork = node ? mxmlGetOpaque(node) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
rss_xml_parse_item(struct rss_item_info *ri, mxml_node_t *xml, void **saveptr)
|
ri_from_item(struct rss_item_info *ri, xml_node *item)
|
||||||
{
|
{
|
||||||
mxml_node_t *item;
|
|
||||||
mxml_node_t *node;
|
|
||||||
const char *s;
|
|
||||||
|
|
||||||
if (*saveptr)
|
|
||||||
{
|
|
||||||
item = (mxml_node_t *)(*saveptr);
|
|
||||||
while ( (item = mxmlGetNextSibling(item)) )
|
|
||||||
{
|
|
||||||
s = mxmlGetElement(item);
|
|
||||||
if (s && strcmp(s, "item") == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*saveptr = item;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = mxmlFindElement(xml, xml, "item", NULL, NULL, MXML_DESCEND);
|
|
||||||
*saveptr = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return -1; // No more items
|
|
||||||
|
|
||||||
memset(ri, 0, sizeof(struct rss_item_info));
|
memset(ri, 0, sizeof(struct rss_item_info));
|
||||||
|
|
||||||
node = mxmlFindElement(item, item, "title", NULL, NULL, MXML_DESCEND_FIRST);
|
ri->title = xml_get_val(item, "title");
|
||||||
ri->title = mxmlGetOpaque(node);
|
ri->pubdate = xml_get_val(item, "pubDate");
|
||||||
|
ri->link = xml_get_val(item, "link");
|
||||||
|
|
||||||
node = mxmlFindElement(item, item, "pubDate", NULL, NULL, MXML_DESCEND_FIRST);
|
ri->url = xml_get_attr(item, "enclosure", "url");
|
||||||
ri->pubdate = mxmlGetOpaque(node);
|
ri->type = xml_get_attr(item, "enclosure", "type");
|
||||||
|
|
||||||
node = mxmlFindElement(item, item, "link", NULL, NULL, MXML_DESCEND_FIRST);
|
|
||||||
ri->link = mxmlGetOpaque(node);
|
|
||||||
|
|
||||||
node = mxmlFindElement(item, item, "enclosure", NULL, NULL, MXML_DESCEND_FIRST);
|
|
||||||
ri->url = mxmlElementGetAttr(node, "url");
|
|
||||||
ri->type = mxmlElementGetAttr(node, "type");
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_LIB, "RSS/xml item: title '%s' pubdate: '%s' link: '%s' url: '%s' type: '%s'\n", ri->title, ri->pubdate, ri->link, ri->url, ri->type);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The RSS spec states:
|
// The RSS spec states:
|
||||||
@ -411,14 +364,14 @@ mfi_metadata_fixup(struct media_file_info *mfi, struct rss_item_info *ri, const
|
|||||||
static int
|
static int
|
||||||
rss_save(struct playlist_info *pli, int *count, enum rss_scan_type scan_type)
|
rss_save(struct playlist_info *pli, int *count, enum rss_scan_type scan_type)
|
||||||
{
|
{
|
||||||
mxml_node_t *xml;
|
xml_node *xml;
|
||||||
|
xml_node *item;
|
||||||
const char *feed_title;
|
const char *feed_title;
|
||||||
const char *feed_author;
|
const char *feed_author;
|
||||||
const char *feed_artwork;
|
const char *feed_artwork;
|
||||||
struct media_file_info mfi = { 0 };
|
struct media_file_info mfi = { 0 };
|
||||||
struct rss_item_info ri;
|
struct rss_item_info ri;
|
||||||
uint32_t time_added;
|
uint32_t time_added;
|
||||||
void *ptr = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
xml = rss_xml_get(pli->path);
|
xml = rss_xml_get(pli->path);
|
||||||
@ -428,11 +381,11 @@ rss_save(struct playlist_info *pli, int *count, enum rss_scan_type scan_type)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = rss_xml_parse_feed(&feed_title, &feed_author, &feed_artwork, xml);
|
ret = feed_metadata_from_xml(&feed_title, &feed_author, &feed_artwork, xml);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml received from '%s' (id %d)\n", pli->path, pli->id);
|
DPRINTF(E_LOG, L_LIB, "Invalid RSS/xml received from '%s' (id %d)\n", pli->path, pli->id);
|
||||||
mxmlDelete(xml);
|
xml_free(xml);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,21 +408,24 @@ rss_save(struct playlist_info *pli, int *count, enum rss_scan_type scan_type)
|
|||||||
*count = 0;
|
*count = 0;
|
||||||
db_transaction_begin();
|
db_transaction_begin();
|
||||||
db_pl_clear_items(pli->id);
|
db_pl_clear_items(pli->id);
|
||||||
while ((ret = rss_xml_parse_item(&ri, xml, &ptr)) == 0 && (*count < pli->query_limit))
|
for (item = xml_get_node(xml, "rss/channel/item"); item && (*count < pli->query_limit); item = xml_get_next(xml, item))
|
||||||
{
|
{
|
||||||
if (library_is_exiting())
|
if (library_is_exiting())
|
||||||
{
|
{
|
||||||
db_transaction_rollback();
|
db_transaction_rollback();
|
||||||
mxmlDelete(xml);
|
xml_free(xml);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ri_from_item(&ri, item);
|
||||||
if (!ri.url)
|
if (!ri.url)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_LIB, "Missing URL for item '%s' (date %s) in RSS feed '%s'\n", ri.title, ri.pubdate, feed_title);
|
DPRINTF(E_WARN, L_LIB, "Missing URL for item '%s' (date %s) in RSS feed '%s'\n", ri.title, ri.pubdate, feed_title);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_LIB, "RSS/xml item: title '%s' pubdate: '%s' link: '%s' url: '%s' type: '%s'\n", ri.title, ri.pubdate, ri.link, ri.url, ri.type);
|
||||||
|
|
||||||
db_pl_add_item_bypath(pli->id, ri.url);
|
db_pl_add_item_bypath(pli->id, ri.url);
|
||||||
(*count)++;
|
(*count)++;
|
||||||
|
|
||||||
@ -499,7 +455,7 @@ rss_save(struct playlist_info *pli, int *count, enum rss_scan_type scan_type)
|
|||||||
}
|
}
|
||||||
|
|
||||||
db_transaction_end();
|
db_transaction_end();
|
||||||
mxmlDelete(xml);
|
xml_free(xml);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
400
src/misc_xml.c
Normal file
400
src/misc_xml.c
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Espen Jurgensen
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* About pipe.c
|
||||||
|
* --------------
|
||||||
|
* This module will read a PCM16 stream from a named pipe and write it to the
|
||||||
|
* input buffer. The user may start/stop playback from a pipe by selecting it
|
||||||
|
* through a client. If the user has configured pipe_autostart, then pipes in
|
||||||
|
* the library will also be watched for data, and playback will start/stop
|
||||||
|
* automatically.
|
||||||
|
*
|
||||||
|
* The module will also look for pipes with a .metadata suffix, and if found,
|
||||||
|
* the metadata will be parsed and fed to the player. The metadata must be in
|
||||||
|
* the format Shairport uses for this purpose.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h> // fopen
|
||||||
|
#include <stdarg.h> // va_*
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include <mxml.h>
|
||||||
|
|
||||||
|
typedef mxml_node_t xml_node;
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------------- Compability with older versions of mxml ----------------- */
|
||||||
|
|
||||||
|
// mxml 2.10 has a memory leak in mxmlDelete, see https://github.com/michaelrsweet/mxml/issues/183
|
||||||
|
// - and since this is the version in Ubuntu 18.04 LTS and Raspian Stretch, we
|
||||||
|
// fix it by including a fixed mxmlDelete here. It should be removed once the
|
||||||
|
// major distros no longer have 2.10. The below code is msweet's fixed mxml.
|
||||||
|
#if (MXML_MAJOR_VERSION == 2) && (MXML_MINOR_VERSION <= 10)
|
||||||
|
|
||||||
|
#define mxmlDelete compat_mxmlDelete
|
||||||
|
|
||||||
|
static void
|
||||||
|
compat_mxml_free(mxml_node_t *node)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
switch (node->type)
|
||||||
|
{
|
||||||
|
case MXML_ELEMENT :
|
||||||
|
if (node->value.element.name)
|
||||||
|
free(node->value.element.name);
|
||||||
|
|
||||||
|
if (node->value.element.num_attrs)
|
||||||
|
{
|
||||||
|
for (i = 0; i < node->value.element.num_attrs; i ++)
|
||||||
|
{
|
||||||
|
if (node->value.element.attrs[i].name)
|
||||||
|
free(node->value.element.attrs[i].name);
|
||||||
|
if (node->value.element.attrs[i].value)
|
||||||
|
free(node->value.element.attrs[i].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(node->value.element.attrs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MXML_INTEGER :
|
||||||
|
break;
|
||||||
|
case MXML_OPAQUE :
|
||||||
|
if (node->value.opaque)
|
||||||
|
free(node->value.opaque);
|
||||||
|
break;
|
||||||
|
case MXML_REAL :
|
||||||
|
break;
|
||||||
|
case MXML_TEXT :
|
||||||
|
if (node->value.text.string)
|
||||||
|
free(node->value.text.string);
|
||||||
|
break;
|
||||||
|
case MXML_CUSTOM :
|
||||||
|
if (node->value.custom.data &&
|
||||||
|
node->value.custom.destroy)
|
||||||
|
(*(node->value.custom.destroy))(node->value.custom.data);
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((unused)) static void
|
||||||
|
compat_mxmlDelete(mxml_node_t *node)
|
||||||
|
{
|
||||||
|
mxml_node_t *current,
|
||||||
|
*next;
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mxmlRemove(node);
|
||||||
|
for (current = node->child; current; current = next)
|
||||||
|
{
|
||||||
|
if ((next = current->child) != NULL)
|
||||||
|
{
|
||||||
|
current->child = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((next = current->next) == NULL)
|
||||||
|
{
|
||||||
|
if ((next = current->parent) == node)
|
||||||
|
next = NULL;
|
||||||
|
}
|
||||||
|
compat_mxml_free(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
compat_mxml_free(node);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* For compability with mxml 2.6 */
|
||||||
|
#ifndef HAVE_MXMLGETTEXT
|
||||||
|
__attribute__((unused)) static const char * /* O - Text string or NULL */
|
||||||
|
mxmlGetText(mxml_node_t *node, /* I - Node to get */
|
||||||
|
int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */
|
||||||
|
{
|
||||||
|
if (node->type == MXML_TEXT)
|
||||||
|
return (node->value.text.string);
|
||||||
|
else if (node->type == MXML_ELEMENT &&
|
||||||
|
node->child &&
|
||||||
|
node->child->type == MXML_TEXT)
|
||||||
|
return (node->child->value.text.string);
|
||||||
|
else
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_MXMLGETOPAQUE
|
||||||
|
__attribute__((unused)) static const char * /* O - Opaque string or NULL */
|
||||||
|
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
|
||||||
|
{
|
||||||
|
if (!node)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
if (node->type == MXML_OPAQUE)
|
||||||
|
return (node->value.opaque);
|
||||||
|
else if (node->type == MXML_ELEMENT &&
|
||||||
|
node->child &&
|
||||||
|
node->child->type == MXML_OPAQUE)
|
||||||
|
return (node->child->value.opaque);
|
||||||
|
else
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_MXMLGETFIRSTCHILD
|
||||||
|
__attribute__((unused)) static mxml_node_t * /* O - First child or NULL */
|
||||||
|
mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */
|
||||||
|
{
|
||||||
|
if (!node || node->type != MXML_ELEMENT)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
return (node->child);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_MXMLGETTYPE
|
||||||
|
__attribute__((unused)) static mxml_type_t /* O - Type of node */
|
||||||
|
mxmlGetType(mxml_node_t *node) /* I - Node to get */
|
||||||
|
{
|
||||||
|
return (node->type);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* --------------------------------- Helpers -------------------------------- */
|
||||||
|
|
||||||
|
// We get values from mxml via GetOpaque, but that means they can whitespace,
|
||||||
|
// thus we trim them. A bit dirty, since the values are in principle const.
|
||||||
|
static const char *
|
||||||
|
trim(const char *str)
|
||||||
|
{
|
||||||
|
char *term;
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while (isspace(*str))
|
||||||
|
str++;
|
||||||
|
|
||||||
|
term = (char *)str + strlen(str);
|
||||||
|
while (term != str && isspace(*(term - 1)))
|
||||||
|
term--;
|
||||||
|
|
||||||
|
// Dirty write to the const string from mxml
|
||||||
|
*term = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------- Wrapper implementation ------------------------ */
|
||||||
|
|
||||||
|
char *
|
||||||
|
xml_to_string(xml_node *top)
|
||||||
|
{
|
||||||
|
return mxmlSaveAllocString(top, MXML_NO_CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This works both for well-formed xml strings (beginning with <?xml..) and for
|
||||||
|
// those that get straight down to business (<foo...)
|
||||||
|
xml_node *
|
||||||
|
xml_from_string(const char *string)
|
||||||
|
{
|
||||||
|
mxml_node_t *top;
|
||||||
|
mxml_node_t *node;
|
||||||
|
|
||||||
|
top = mxmlNewXML("1.0");
|
||||||
|
if (!top)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
node = mxmlLoadString(top, string, MXML_OPAQUE_CALLBACK);
|
||||||
|
if (!node)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
return top;
|
||||||
|
|
||||||
|
error:
|
||||||
|
mxmlDelete(top);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_from_file(const char *path)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
mxml_node_t *top;
|
||||||
|
mxml_node_t *node;
|
||||||
|
|
||||||
|
top = mxmlNewXML("1.0");
|
||||||
|
if (!top)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
fp = fopen(path, "r");
|
||||||
|
node = mxmlLoadFile(top, fp, MXML_OPAQUE_CALLBACK);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
return top;
|
||||||
|
|
||||||
|
error:
|
||||||
|
mxmlDelete(top);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
xml_free(xml_node *top)
|
||||||
|
{
|
||||||
|
mxmlDelete(top);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_get_node(xml_node *top, const char *path)
|
||||||
|
{
|
||||||
|
mxml_node_t *node;
|
||||||
|
mxml_type_t type;
|
||||||
|
|
||||||
|
// This example shows why we can't just return the result of mxmlFindPath:
|
||||||
|
// <?xml version="1.0""?><rss>
|
||||||
|
// <channel>
|
||||||
|
// <title><![CDATA[Tissages]]></title>
|
||||||
|
// mxmlFindPath(top, "rss/channel") will return an OPAQUE node where the
|
||||||
|
// opaque value is just the whitespace. What we want is the ELEMENT parent,
|
||||||
|
// because that's the one we can use to search for children nodes ("title").
|
||||||
|
node = mxmlFindPath(top, path);
|
||||||
|
type = mxmlGetType(node);
|
||||||
|
if (type == MXML_ELEMENT)
|
||||||
|
return node;
|
||||||
|
|
||||||
|
return mxmlGetParent(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_get_next(xml_node *top, xml_node *node)
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
name = mxmlGetElement(node);
|
||||||
|
if (!name)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while ( (node = mxmlGetNextSibling(node)) )
|
||||||
|
{
|
||||||
|
s = mxmlGetElement(node);
|
||||||
|
if (s && strcmp(s, name) == 0)
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walks through the children of the "path" node until it finds one that is
|
||||||
|
// not just whitespace and returns a trimmed value (except for CDATA). Means
|
||||||
|
// that these variations will all give the same result:
|
||||||
|
//
|
||||||
|
// <foo>FOO FOO</foo><bar>\nBAR BAR \n</bar>
|
||||||
|
// <foo>FOO FOO</foo><bar><![CDATA[BAR BAR]]></bar>
|
||||||
|
// <foo>\nFOO FOO\n</foo><bar>\n<![CDATA[BAR BAR]]></bar>
|
||||||
|
const char *
|
||||||
|
xml_get_val(xml_node *top, const char *path)
|
||||||
|
{
|
||||||
|
mxml_node_t *parent;
|
||||||
|
mxml_node_t *node;
|
||||||
|
mxml_type_t type;
|
||||||
|
const char *s = "";
|
||||||
|
|
||||||
|
parent = xml_get_node(top, path);
|
||||||
|
if (!parent)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (node = mxmlGetFirstChild(parent); node; node = mxmlGetNextSibling(node))
|
||||||
|
{
|
||||||
|
type = mxmlGetType(node);
|
||||||
|
if (type == MXML_OPAQUE)
|
||||||
|
s = trim(mxmlGetOpaque(node));
|
||||||
|
else if (type == MXML_ELEMENT)
|
||||||
|
s = mxmlGetCDATA(node);
|
||||||
|
|
||||||
|
if (s && *s != '\0')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
xml_get_attr(xml_node *top, const char *path, const char *name)
|
||||||
|
{
|
||||||
|
mxml_node_t *node = mxmlFindPath(top, path);
|
||||||
|
|
||||||
|
return mxmlElementGetAttr(node, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_new_node(xml_node *parent, const char *name, const char *val)
|
||||||
|
{
|
||||||
|
if (!parent)
|
||||||
|
parent = MXML_NO_PARENT;
|
||||||
|
|
||||||
|
mxml_node_t *node = mxmlNewElement(parent, name);
|
||||||
|
if (!val)
|
||||||
|
return node; // We're done, caller gets an ELEMENT to use as parent
|
||||||
|
|
||||||
|
mxmlNewText(node, 0, val);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...)
|
||||||
|
{
|
||||||
|
char *s = NULL;
|
||||||
|
va_list va;
|
||||||
|
mxml_node_t *node;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
va_start(va, format);
|
||||||
|
ret = vasprintf(&s, format, va);
|
||||||
|
va_end(va);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
node = xml_new_node(parent, name, s);
|
||||||
|
|
||||||
|
free(s);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
xml_new_text(xml_node *parent, const char *val)
|
||||||
|
{
|
||||||
|
mxmlNewText(parent, 0, val);
|
||||||
|
}
|
56
src/misc_xml.h
Normal file
56
src/misc_xml.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#ifndef SRC_MISC_XML_H_
|
||||||
|
#define SRC_MISC_XML_H_
|
||||||
|
|
||||||
|
// This wraps mxml and adds some convenience functions. This also means that
|
||||||
|
// callers don't need to concern themselves with changes and bugs in various
|
||||||
|
// versions of mxml.
|
||||||
|
|
||||||
|
typedef void xml_node;
|
||||||
|
|
||||||
|
// Wraps mxmlSaveAllocString. Returns NULL on error.
|
||||||
|
char *
|
||||||
|
xml_to_string(xml_node *top);
|
||||||
|
|
||||||
|
// Wraps mxmlNewXML and mxmlLoadString, so creates an xml struct with the parsed
|
||||||
|
// content of string. Returns NULL on error.
|
||||||
|
xml_node *
|
||||||
|
xml_from_string(const char *string);
|
||||||
|
|
||||||
|
// Wraps mxmlNewXML and mxmlLoadFile, so creates an xml struct with the parsed
|
||||||
|
// content of string. Returns NULL on error.
|
||||||
|
xml_node *
|
||||||
|
xml_from_file(const char *path);
|
||||||
|
|
||||||
|
// Wraps mxmlDelete, which will free node + underlying nodes
|
||||||
|
void
|
||||||
|
xml_free(xml_node *top);
|
||||||
|
|
||||||
|
// Wraps mxmlFindPath.
|
||||||
|
xml_node *
|
||||||
|
xml_get_node(xml_node *top, const char *path);
|
||||||
|
|
||||||
|
// Wraps mxmlGetNextSibling, but only returns sibling nodes that have the same
|
||||||
|
// name as input node.
|
||||||
|
xml_node *
|
||||||
|
xml_get_next(xml_node *top, xml_node *node);
|
||||||
|
|
||||||
|
// Wraps mxmlFindPath and mxmlGetOpaque + mxmlGetCDATA. Returns NULL if nothing
|
||||||
|
// can be found.
|
||||||
|
const char *
|
||||||
|
xml_get_val(xml_node *top, const char *path);
|
||||||
|
|
||||||
|
// Wraps mxmlFindPath and mxmlElementGetAttr. Returns NULL if nothing can be
|
||||||
|
// found.
|
||||||
|
const char *
|
||||||
|
xml_get_attr(xml_node *top, const char *path, const char *name);
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_new_node(xml_node *parent, const char *name, const char *val);
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...);
|
||||||
|
|
||||||
|
void
|
||||||
|
xml_new_text(xml_node *parent, const char *val);
|
||||||
|
|
||||||
|
#endif /* SRC_MISC_XML_H_ */
|
@ -1,178 +0,0 @@
|
|||||||
#ifndef __MXML_COMPAT_H__
|
|
||||||
#define __MXML_COMPAT_H__
|
|
||||||
|
|
||||||
#include <mxml.h>
|
|
||||||
|
|
||||||
// mxml 2.10 has a memory leak in mxmlDelete, see https://github.com/michaelrsweet/mxml/issues/183
|
|
||||||
// - and since this is the version in Ubuntu 18.04 LTS and Raspian Stretch, we
|
|
||||||
// fix it by including a fixed mxmlDelete here. It should be removed once the
|
|
||||||
// major distros no longer have 2.10. The below code is msweet's fixed mxml.
|
|
||||||
#if (MXML_MAJOR_VERSION == 2) && (MXML_MINOR_VERSION <= 10)
|
|
||||||
|
|
||||||
#define mxmlDelete compat_mxmlDelete
|
|
||||||
|
|
||||||
static void
|
|
||||||
compat_mxml_free(mxml_node_t *node)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
switch (node->type)
|
|
||||||
{
|
|
||||||
case MXML_ELEMENT :
|
|
||||||
if (node->value.element.name)
|
|
||||||
free(node->value.element.name);
|
|
||||||
|
|
||||||
if (node->value.element.num_attrs)
|
|
||||||
{
|
|
||||||
for (i = 0; i < node->value.element.num_attrs; i ++)
|
|
||||||
{
|
|
||||||
if (node->value.element.attrs[i].name)
|
|
||||||
free(node->value.element.attrs[i].name);
|
|
||||||
if (node->value.element.attrs[i].value)
|
|
||||||
free(node->value.element.attrs[i].value);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(node->value.element.attrs);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MXML_INTEGER :
|
|
||||||
break;
|
|
||||||
case MXML_OPAQUE :
|
|
||||||
if (node->value.opaque)
|
|
||||||
free(node->value.opaque);
|
|
||||||
break;
|
|
||||||
case MXML_REAL :
|
|
||||||
break;
|
|
||||||
case MXML_TEXT :
|
|
||||||
if (node->value.text.string)
|
|
||||||
free(node->value.text.string);
|
|
||||||
break;
|
|
||||||
case MXML_CUSTOM :
|
|
||||||
if (node->value.custom.data &&
|
|
||||||
node->value.custom.destroy)
|
|
||||||
(*(node->value.custom.destroy))(node->value.custom.data);
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((unused)) static void
|
|
||||||
compat_mxmlDelete(mxml_node_t *node)
|
|
||||||
{
|
|
||||||
mxml_node_t *current,
|
|
||||||
*next;
|
|
||||||
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mxmlRemove(node);
|
|
||||||
for (current = node->child; current; current = next)
|
|
||||||
{
|
|
||||||
if ((next = current->child) != NULL)
|
|
||||||
{
|
|
||||||
current->child = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((next = current->next) == NULL)
|
|
||||||
{
|
|
||||||
if ((next = current->parent) == node)
|
|
||||||
next = NULL;
|
|
||||||
}
|
|
||||||
compat_mxml_free(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
compat_mxml_free(node);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Debian 10.x amd64 w/mxml 2.12 has a mxmlNewTextf that causes segfault when
|
|
||||||
// mxmlSaveString or mxmlSaveAllocString is called,
|
|
||||||
// ref https://github.com/owntone/owntone-server/issues/938
|
|
||||||
#if (MXML_MAJOR_VERSION == 2) && (MXML_MINOR_VERSION == 12)
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
#define mxmlNewTextf compat_mxmlNewTextf
|
|
||||||
|
|
||||||
__attribute__((unused)) static mxml_node_t *
|
|
||||||
compat_mxmlNewTextf(mxml_node_t *parent, int whitespace, const char *format, ...)
|
|
||||||
{
|
|
||||||
char *s = NULL;
|
|
||||||
va_list va;
|
|
||||||
mxml_node_t *node;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
va_start(va, format);
|
|
||||||
ret = vasprintf(&s, format, va);
|
|
||||||
va_end(va);
|
|
||||||
|
|
||||||
if (ret < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
node = mxmlNewText(parent, whitespace, s);
|
|
||||||
|
|
||||||
free(s);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* For compability with mxml 2.6 */
|
|
||||||
#ifndef HAVE_MXMLGETTEXT
|
|
||||||
__attribute__((unused)) static const char * /* O - Text string or NULL */
|
|
||||||
mxmlGetText(mxml_node_t *node, /* I - Node to get */
|
|
||||||
int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */
|
|
||||||
{
|
|
||||||
if (node->type == MXML_TEXT)
|
|
||||||
return (node->value.text.string);
|
|
||||||
else if (node->type == MXML_ELEMENT &&
|
|
||||||
node->child &&
|
|
||||||
node->child->type == MXML_TEXT)
|
|
||||||
return (node->child->value.text.string);
|
|
||||||
else
|
|
||||||
return (NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETOPAQUE
|
|
||||||
__attribute__((unused)) static const char * /* O - Opaque string or NULL */
|
|
||||||
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
if (!node)
|
|
||||||
return (NULL);
|
|
||||||
|
|
||||||
if (node->type == MXML_OPAQUE)
|
|
||||||
return (node->value.opaque);
|
|
||||||
else if (node->type == MXML_ELEMENT &&
|
|
||||||
node->child &&
|
|
||||||
node->child->type == MXML_OPAQUE)
|
|
||||||
return (node->child->value.opaque);
|
|
||||||
else
|
|
||||||
return (NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETFIRSTCHILD
|
|
||||||
__attribute__((unused)) static mxml_node_t * /* O - First child or NULL */
|
|
||||||
mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
if (!node || node->type != MXML_ELEMENT)
|
|
||||||
return (NULL);
|
|
||||||
|
|
||||||
return (node->child);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETTYPE
|
|
||||||
__attribute__((unused)) static mxml_type_t /* O - Type of node */
|
|
||||||
mxmlGetType(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
return (node->type);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* !__MXML_COMPAT_H__ */
|
|
Loading…
Reference in New Issue
Block a user