owntone-server/src/rxml.c

331 lines
8.1 KiB
C

/*
* $Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rxml.h"
/* Typedefs/Defines */
#define RXML_ERROR(a,b) { (a)->stdio_errno=errno; (a)->ecode=(b); return 0; };
#define RXML_MAX_LINE 1024
#define RXML_MAX_TEXT 1024
#define RXML_MAX_TAG 256
typedef struct _RXML {
RXML_EVTHANDLER handler;
char *fname;
FILE *fhandle;
void *udata;
int stdio_errno;
int ecode;
int line;
char *estring;
} RXML;
#ifndef FALSE
# define FALSE 0
# define TRUE 1
#endif /* FALSE */
#define E_RXML_SUCCESS 0x00
#define E_RXML_OPEN 0x01 | 0x80
#define E_RXML_READ 0x02 | 0x80
#define E_RXML_NEST 0x03
#define E_RXML_SPLIT 0x04
#define E_RXML_CLOSE 0x05
#define E_RXML_TAGSIZE 0x06
#define E_RXML_ENTITY 0x07
char *rxml_estrings[] = {
"Success",
"Could not open xml file: ",
"Error reading file: ",
"Parse error: Nested tags",
"Parse error: Tag split over multiple lines",
"Parse error: Unexpected '>'",
"Parse error: tag too big",
"Parse error: bad entity encoding",
NULL
};
/* Forwards */
static int rxml_decode_string(char *string);
/**
* decode an xml entity-encoded string in-place
*
* @param string string to encode
* @returns 1 on success, 0 on error
*/
int rxml_decode_string(char *string) {
char *src, *dst;
int len;
int cval;
src=dst=string;
while(*src) {
if((*src) == '&') {
len = (int)strlen(src);
if((len > 3) && (strncmp(src,"&gt;",4) == 0)) {
*dst++ = '>';
src += 4;
} else if((len > 3) && (strncmp(src,"&lt;",4) == 0)) {
*dst++ = '<';
src += 4;
} else if((len > 4) && (strncmp(src,"&amp;",5) == 0)) {
*dst++ = '&';
src += 5;
} else if((len > 5) && (strncmp(src,"&quot;",6) == 0)) {
*dst++ = '"';
src += 6;
} else if((len > 5) && (strncmp(src,"&apos;",6) == 0)) {
*dst ++ = '\'';
src += 6;
} else {
/* &#xx; */
if(!sscanf((char*)&src[2],"%d;",&cval))
return FALSE;
*dst++ = cval;
if(src[3] == ';') {
src += 4;
} else if(src[4] == ';') {
src += 5;
} else if(src[5] == ';') {
src += 6;
} else {
return FALSE;
}
}
} else {
*dst++=*src++;
}
}
*dst = '\0';
return TRUE;
}
/**
* open a file
*
* @param vp pointer to the opaque xml struct
* @param file filename of file to open
* @param handler handler function for events
* @param udata opaque data structure to pass to the callback
*/
int rxml_open(RXMLHANDLE *vp, char *file,
RXML_EVTHANDLER handler, void *udata) {
RXML *pnew;
pnew=(RXML*)malloc(sizeof(RXML));
if(!pnew) {
*vp = NULL;
return FALSE;
}
memset(pnew,0x0,sizeof(RXML));
*vp = pnew;
pnew->handler = handler;
pnew->fname = file;
pnew->fhandle = fopen(file,"rb");
pnew->udata = udata;
pnew->line = 0;
if(!pnew->fhandle)
RXML_ERROR(pnew,E_RXML_OPEN);
if(pnew->handler)
pnew->handler(RXML_EVT_OPEN, pnew->udata, pnew->fname);
return TRUE;
}
/**
* close the currently open file
*
* @param vp xml struct containing info about the file to close
*/
int rxml_close(RXMLHANDLE vp) {
RXML *ph = (RXML*)vp;
if(ph->handler) ph->handler(RXML_EVT_CLOSE,ph->udata,ph->fname);
if(ph->fhandle) fclose(ph->fhandle);
if(ph->estring) free(ph->estring);
free(ph);
return TRUE;
}
/**
* return a string describing the last error
*
* @param vp handle of the document with the error
*/
char *rxml_errorstring(RXMLHANDLE vp) {
RXML *ph = (RXML*)vp;
int len;
char *estring=NULL;
if(!ph) {
return "Malloc error";
}
if(ph->estring) free(ph->estring);
len = (int)strlen(rxml_estrings[ph->ecode]) + 16;
if((ph->ecode & 0x80)) {
estring=strerror(ph->stdio_errno);
len += (int)strlen(estring);
}
ph->estring=(char*)malloc(len);
if(!ph->estring)
return "Double-fault malloc error";
if((ph->ecode & 0x80)) {
snprintf(ph->estring,len,"%s%s",rxml_estrings[ph->ecode],estring);
} else {
if(strncmp(rxml_estrings[ph->ecode],"Parse",5)==0) {
snprintf(ph->estring, len, "%s (Line:%d)",
rxml_estrings[ph->ecode], ph->line);
} else {
snprintf(ph->estring,len, "%s", rxml_estrings[ph->ecode]);
}
}
return ph->estring;
}
/**
* walk through the xml file, sending events to the
* callback handler.
*
* @param vp opaque doc struct of the doc to parse
*/
int rxml_parse(RXMLHANDLE vp) {
char linebuffer[RXML_MAX_LINE];
char tagbuffer[RXML_MAX_TAG];
char textbuffer[RXML_MAX_TEXT];
int in_text=0;
int text_offset=0;
int size;
int offset;
int in_tag=0;
int tag_end=0;
int tag_start=0;
int single_tag;
RXML *ph = (RXML*)vp;
ph->line = 0;
textbuffer[0] = '\0';
/* walk through and read row by row */
while(fgets(linebuffer,sizeof(linebuffer),ph->fhandle) != NULL) {
ph->line++;
offset=0;
size=(int)strlen(linebuffer);
in_text=0;
text_offset=0;
while(offset < size) {
switch(linebuffer[offset]) {
case '<':
if(in_tag)
RXML_ERROR(ph, E_RXML_NEST);
in_tag=TRUE;
tag_start=offset+1;
tag_end=FALSE;
if(linebuffer[tag_start] == '/') {
tag_end = TRUE;
offset++;
tag_start++;
}
offset++;
in_text=0;
break;
case '>':
if(!in_tag)
RXML_ERROR(ph, E_RXML_CLOSE);
in_tag=FALSE;
if((offset - tag_start) > RXML_MAX_TAG)
RXML_ERROR(ph, E_RXML_TAGSIZE);
strncpy(tagbuffer,&linebuffer[tag_start],offset-tag_start);
tagbuffer[offset-tag_start] = '\0';
if(tag_end) {
/* send the text before the tag end */
if((ph->handler) && (strlen(textbuffer))) {
if(!rxml_decode_string(textbuffer))
RXML_ERROR(ph,E_RXML_ENTITY);
ph->handler(RXML_EVT_TEXT,ph->udata,textbuffer);
}
}
in_text=1;
text_offset=0;
textbuffer[0] = '\0';
single_tag=0;
if(tagbuffer[strlen(tagbuffer)-1] == '/') {
tagbuffer[strlen(tagbuffer)-1] = '\0';
single_tag=1;
}
if(ph->handler)
ph->handler(tag_end ? RXML_EVT_END : RXML_EVT_BEGIN,
ph->udata,tagbuffer);
/* send a follow-up end on a <tag/> - style tag */
if((single_tag) && (ph->handler))
ph->handler(RXML_EVT_END,ph->udata,tagbuffer);
offset++;
break;
default:
if((in_text) && (text_offset < (sizeof(textbuffer)-1))) {
/* get rid of EOL */
if((linebuffer[offset] != '\r') &&
(linebuffer[offset] != '\n')) {
textbuffer[text_offset] = linebuffer[offset];
text_offset++;
textbuffer[text_offset] = '\x0';
}
} else if (in_text) {
/* should warn of an overflow */
}
offset++;
break;
}
}
}
ph->stdio_errno=errno;
if(ferror(ph->fhandle))
RXML_ERROR(ph,E_RXML_READ);
return TRUE;
}