mirror of
https://github.com/strophe/libstrophe.git
synced 2026-07-18 03:46:21 +00:00
309 lines
8.4 KiB
C
309 lines
8.4 KiB
C
/* parser.c
|
|
** strophe XMPP client library -- xml parser handlers and utility functions
|
|
**
|
|
** Copyright (C) 2005-2009 Collecta, Inc.
|
|
**
|
|
** This software is provided AS-IS with no warranty, either express
|
|
** or implied.
|
|
**
|
|
** This program is dual licensed under the MIT and GPLv3 licenses.
|
|
*/
|
|
|
|
/** @file
|
|
* XML parser handlers.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
|
|
#include "strophe.h"
|
|
#include "common.h"
|
|
#include "parser.h"
|
|
|
|
struct _parser_t {
|
|
xmpp_ctx_t *ctx;
|
|
xmlParserCtxtPtr xmlctx;
|
|
xmlSAXHandler handlers;
|
|
parser_start_callback startcb;
|
|
parser_end_callback endcb;
|
|
parser_stanza_callback stanzacb;
|
|
void *userdata;
|
|
int depth;
|
|
xmpp_stanza_t *stanza;
|
|
};
|
|
|
|
static void
|
|
_set_attributes(xmpp_stanza_t *stanza, int nattrs, const xmlChar **attrs)
|
|
{
|
|
int i, len;
|
|
char *value;
|
|
|
|
if (!attrs)
|
|
return;
|
|
|
|
/* SAX2 uses array of localname/prefix/uri/value_begin/value_end */
|
|
for (i = 0; i < nattrs * 5; i += 5) {
|
|
len = attrs[i + 4] - attrs[i + 3];
|
|
value = strophe_alloc(stanza->ctx, len + 1);
|
|
if (value) {
|
|
memcpy(value, attrs[i + 3], len);
|
|
value[len] = '\0';
|
|
xmpp_stanza_set_attribute(stanza, (const char *)attrs[i], value);
|
|
strophe_free(stanza->ctx, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* SAX2 gives us the attrs in an incredibly inconvenient array,
|
|
* convert it to what the start callback is expecting */
|
|
static char **
|
|
_convert_attrs(parser_t *parser, int nattrs, const xmlChar **attrs)
|
|
{
|
|
int c, i, o, len;
|
|
char *value;
|
|
char **ret;
|
|
|
|
if (!attrs)
|
|
return NULL;
|
|
|
|
ret = strophe_alloc(parser->ctx, (nattrs + 1) * 2 * sizeof(char *));
|
|
if (!ret)
|
|
return NULL;
|
|
memset(ret, 0, (nattrs + 1) * 2 * sizeof(char *));
|
|
|
|
for (c = 0; c < nattrs; c++) {
|
|
i = c * 5;
|
|
o = c * 2;
|
|
|
|
len = attrs[i + 4] - attrs[i + 3];
|
|
value = strophe_alloc(parser->ctx, len + 1);
|
|
if (value) {
|
|
memcpy(value, attrs[i + 3], len);
|
|
value[len] = '\0';
|
|
ret[o] = strophe_strdup(parser->ctx, (char *)attrs[i]);
|
|
ret[o + 1] = value;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void _free_cbattrs(parser_t *parser, char **attrs)
|
|
{
|
|
int i;
|
|
|
|
if (!attrs)
|
|
return;
|
|
|
|
for (i = 0; attrs[i]; i += 2) {
|
|
if (attrs[i])
|
|
strophe_free(parser->ctx, attrs[i]);
|
|
if (attrs[i + 1])
|
|
strophe_free(parser->ctx, attrs[i + 1]);
|
|
}
|
|
|
|
strophe_free(parser->ctx, attrs);
|
|
}
|
|
|
|
static void _start_element(void *userdata,
|
|
const xmlChar *name,
|
|
const xmlChar *prefix,
|
|
const xmlChar *uri,
|
|
int nnamespaces,
|
|
const xmlChar **namespaces,
|
|
int nattrs,
|
|
int ndefaulted,
|
|
const xmlChar **attrs)
|
|
{
|
|
parser_t *parser = (parser_t *)userdata;
|
|
xmpp_stanza_t *child;
|
|
char **cbattrs;
|
|
|
|
UNUSED(prefix);
|
|
UNUSED(nnamespaces);
|
|
UNUSED(namespaces);
|
|
UNUSED(ndefaulted);
|
|
|
|
if (parser->depth == 0) {
|
|
/* notify the owner */
|
|
if (parser->startcb) {
|
|
cbattrs = _convert_attrs(parser, nattrs, attrs);
|
|
parser->startcb((char *)name, cbattrs, parser->userdata);
|
|
_free_cbattrs(parser, cbattrs);
|
|
}
|
|
} else {
|
|
/* build stanzas at depth 1 */
|
|
if (!parser->stanza && parser->depth != 1) {
|
|
/* something terrible happened */
|
|
/* FIXME: we should probably trigger a disconnect */
|
|
strophe_error(parser->ctx, "parser",
|
|
"oops, where did our stanza go?");
|
|
} else if (!parser->stanza) {
|
|
/* starting a new toplevel stanza */
|
|
parser->stanza = xmpp_stanza_new(parser->ctx);
|
|
if (!parser->stanza) {
|
|
/* FIXME: can't allocate, disconnect */
|
|
}
|
|
xmpp_stanza_set_name(parser->stanza, (char *)name);
|
|
_set_attributes(parser->stanza, nattrs, attrs);
|
|
if (uri)
|
|
xmpp_stanza_set_ns(parser->stanza, (char *)uri);
|
|
} else {
|
|
/* starting a child of conn->stanza */
|
|
child = xmpp_stanza_new(parser->ctx);
|
|
if (!child) {
|
|
/* FIXME: can't allocate, disconnect */
|
|
}
|
|
xmpp_stanza_set_name(child, (char *)name);
|
|
_set_attributes(child, nattrs, attrs);
|
|
if (uri)
|
|
xmpp_stanza_set_ns(child, (char *)uri);
|
|
|
|
/* add child to parent */
|
|
xmpp_stanza_add_child(parser->stanza, child);
|
|
|
|
/* the child is owned by the toplevel stanza now */
|
|
xmpp_stanza_release(child);
|
|
|
|
/* make child the current stanza */
|
|
parser->stanza = child;
|
|
}
|
|
}
|
|
|
|
parser->depth++;
|
|
}
|
|
|
|
static void _end_element(void *userdata,
|
|
const xmlChar *name,
|
|
const xmlChar *prefix,
|
|
const xmlChar *uri)
|
|
{
|
|
parser_t *parser = (parser_t *)userdata;
|
|
|
|
UNUSED(prefix);
|
|
UNUSED(uri);
|
|
|
|
parser->depth--;
|
|
|
|
if (parser->depth == 0) {
|
|
/* notify owner */
|
|
if (parser->endcb)
|
|
parser->endcb((char *)name, parser->userdata);
|
|
} else {
|
|
if (parser->stanza->parent) {
|
|
/* we're finishing a child stanza, so set current to the parent */
|
|
parser->stanza = parser->stanza->parent;
|
|
} else {
|
|
if (parser->stanzacb)
|
|
parser->stanzacb(parser->stanza, parser->userdata);
|
|
xmpp_stanza_release(parser->stanza);
|
|
parser->stanza = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _characters(void *userdata, const xmlChar *chr, int len)
|
|
{
|
|
parser_t *parser = (parser_t *)userdata;
|
|
xmpp_stanza_t *stanza;
|
|
|
|
/* skip unimportant whitespace, etc */
|
|
if (parser->depth < 2)
|
|
return;
|
|
|
|
/* create and populate stanza */
|
|
stanza = xmpp_stanza_new(parser->ctx);
|
|
if (!stanza) {
|
|
/* FIXME: allocation error, disconnect */
|
|
return;
|
|
}
|
|
xmpp_stanza_set_text_with_size(stanza, (char *)chr, len);
|
|
|
|
xmpp_stanza_add_child(parser->stanza, stanza);
|
|
xmpp_stanza_release(stanza);
|
|
}
|
|
|
|
/* create a new parser */
|
|
parser_t *parser_new(xmpp_ctx_t *ctx,
|
|
parser_start_callback startcb,
|
|
parser_end_callback endcb,
|
|
parser_stanza_callback stanzacb,
|
|
void *userdata)
|
|
{
|
|
parser_t *parser;
|
|
|
|
parser = strophe_alloc(ctx, sizeof(parser_t));
|
|
if (parser != NULL) {
|
|
parser->ctx = ctx;
|
|
parser->xmlctx = NULL;
|
|
memset(&parser->handlers, 0, sizeof(xmlSAXHandler));
|
|
parser->handlers.initialized = XML_SAX2_MAGIC;
|
|
parser->handlers.startElementNs = _start_element;
|
|
parser->handlers.endElementNs = _end_element;
|
|
parser->handlers.characters = _characters;
|
|
parser->startcb = startcb;
|
|
parser->endcb = endcb;
|
|
parser->stanzacb = stanzacb;
|
|
parser->userdata = userdata;
|
|
parser->depth = 0;
|
|
parser->stanza = NULL;
|
|
|
|
parser_reset(parser);
|
|
}
|
|
|
|
return parser;
|
|
}
|
|
|
|
char *parser_attr_name(xmpp_ctx_t *ctx, char *nsname)
|
|
{
|
|
return strophe_strdup(ctx, nsname);
|
|
}
|
|
|
|
static void _free_parent_stanza(xmpp_stanza_t *stanza)
|
|
{
|
|
xmpp_stanza_t *parent;
|
|
|
|
for (parent = stanza; parent->parent != NULL; parent = parent->parent)
|
|
;
|
|
xmpp_stanza_release(parent);
|
|
}
|
|
|
|
/* free a parser */
|
|
void parser_free(parser_t *parser)
|
|
{
|
|
if (parser->xmlctx)
|
|
xmlFreeParserCtxt(parser->xmlctx);
|
|
if (parser->stanza)
|
|
_free_parent_stanza(parser->stanza);
|
|
strophe_free(parser->ctx, parser);
|
|
}
|
|
|
|
/* shuts down and restarts XML parser. true on success */
|
|
int parser_reset(parser_t *parser)
|
|
{
|
|
if (parser->xmlctx)
|
|
xmlFreeParserCtxt(parser->xmlctx);
|
|
if (parser->stanza)
|
|
_free_parent_stanza(parser->stanza);
|
|
|
|
parser->stanza = NULL;
|
|
parser->depth = 0;
|
|
|
|
parser->xmlctx =
|
|
xmlCreatePushParserCtxt(&parser->handlers, parser, NULL, 0, NULL);
|
|
|
|
return parser->xmlctx ? 1 : 0;
|
|
}
|
|
|
|
/* feed a chunk of data to the parser */
|
|
int parser_feed(parser_t *parser, char *chunk, int len)
|
|
{
|
|
/* xmlParseChunk API returns 0 on success which is opposite logic to
|
|
the status returned by parser_feed */
|
|
return !xmlParseChunk(parser->xmlctx, chunk, len, 0);
|
|
}
|