Files
libstrophe-gh/src/parser_expat.c
Steffen Jaeckel d7d7435f21 less calls to xmpp_stanza_release()
Instead of using the "cloning version" `xmpp_stanza_add_child()` use the
version that takes ownership, so we don't have to release directly
afterwards.

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
2023-01-09 15:55:23 +01:00

371 lines
9.7 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 <expat.h>
#include "strophe.h"
#include "common.h"
#include "parser.h"
/* Allocate inner text by this number bytes more. Expat splits string
* "new\nline" into 3 strings: "new" "\n" "line". Expecting this pattern,
* we can leave few bytes in the inner_text for "\n". It should reduce
* number of re-allocations in 2 times for multi-line texts. */
#define INNER_TEXT_PADDING 2
struct _parser_t {
xmpp_ctx_t *ctx;
XML_Parser expat;
parser_start_callback startcb;
parser_end_callback endcb;
parser_stanza_callback stanzacb;
void *userdata;
int depth;
xmpp_stanza_t *stanza;
char *inner_text;
/* number of allocated bytes */
int inner_text_size;
/* excluding terminal '\0' */
int inner_text_used;
};
/* Use the Unit Separator to delimit namespace and name in our XML */
const XML_Char namespace_sep = '\x1F';
/*
* Cached strophe ctx. It is used for memory suite.
* Note, expat doesn't support userdata in memory suite, therefore,
* we can support only one strophe context. If user creates more than one
* context, this module will fallback to default library allocator for all
* contexts other than mem_ctx.
*/
static xmpp_ctx_t *mem_ctx = NULL;
static void *parser_mem_malloc(size_t size)
{
if (mem_ctx != NULL)
return strophe_alloc(mem_ctx, size);
else
return NULL;
}
static void *parser_mem_realloc(void *ptr, size_t size)
{
if (mem_ctx != NULL)
return strophe_realloc(mem_ctx, ptr, size);
else
return NULL;
}
static void parser_mem_free(void *ptr)
{
if (mem_ctx != NULL)
strophe_free(mem_ctx, ptr);
}
static const XML_Memory_Handling_Suite parser_mem_suite = {
.malloc_fcn = &parser_mem_malloc,
.realloc_fcn = &parser_mem_realloc,
.free_fcn = &parser_mem_free,
};
/* return allocated string with the name from a delimited
* namespace/name string */
static char *_xml_name(xmpp_ctx_t *ctx, const char *nsname)
{
char *result = NULL;
const char *c;
size_t len;
c = strchr(nsname, namespace_sep);
if (c == NULL)
return strophe_strdup(ctx, nsname);
c++;
len = strlen(c);
result = strophe_alloc(ctx, len + 1);
if (result != NULL) {
memcpy(result, c, len);
result[len] = '\0';
}
return result;
}
/* return allocated string with the namespace from a delimited string */
static char *_xml_namespace(xmpp_ctx_t *ctx, const char *nsname)
{
char *result = NULL;
const char *c;
c = strchr(nsname, namespace_sep);
if (c != NULL) {
result = strophe_alloc(ctx, (c - nsname) + 1);
if (result != NULL) {
memcpy(result, nsname, (c - nsname));
result[c - nsname] = '\0';
}
}
return result;
}
static void _set_attributes(xmpp_stanza_t *stanza, const XML_Char **attrs)
{
char *attr;
int i;
if (!attrs)
return;
for (i = 0; attrs[i]; i += 2) {
/* namespaced attributes aren't used in xmpp, discard namespace */
attr = _xml_name(stanza->ctx, attrs[i]);
xmpp_stanza_set_attribute(stanza, attr, attrs[i + 1]);
strophe_free(stanza->ctx, attr);
}
}
static void complete_inner_text(parser_t *parser)
{
xmpp_stanza_t *stanza;
if (parser->inner_text) {
/* create and populate stanza */
stanza = xmpp_stanza_new(parser->ctx);
/* FIXME: disconnect on allocation error */
if (stanza) {
xmpp_stanza_set_text(stanza, parser->inner_text);
xmpp_stanza_add_child_ex(parser->stanza, stanza, 0);
}
strophe_free(parser->ctx, parser->inner_text);
parser->inner_text = NULL;
parser->inner_text_size = 0;
parser->inner_text_used = 0;
}
}
static void
_start_element(void *userdata, const XML_Char *nsname, const XML_Char **attrs)
{
parser_t *parser = (parser_t *)userdata;
xmpp_stanza_t *child;
char *ns, *name;
ns = _xml_namespace(parser->ctx, nsname);
name = _xml_name(parser->ctx, nsname);
if (parser->depth == 0) {
/* notify the owner */
if (parser->startcb)
parser->startcb(name, (char **)attrs, parser->userdata);
} else {
/* build stanzas at depth 1 */
if (!parser->stanza && parser->depth != 1) {
/* something terrible happened */
/* FIXME: shutdown disconnect */
strophe_error(parser->ctx, "parser",
"oops, where did our stanza go?");
} else {
child = xmpp_stanza_new(parser->ctx);
if (!child) {
/* FIXME: can't allocate, disconnect */
}
xmpp_stanza_set_name(child, name);
_set_attributes(child, attrs);
if (ns)
xmpp_stanza_set_ns(child, ns);
if (parser->stanza != NULL) {
complete_inner_text(parser);
xmpp_stanza_add_child_ex(parser->stanza, child, 0);
}
parser->stanza = child;
}
}
if (ns)
strophe_free(parser->ctx, ns);
if (name)
strophe_free(parser->ctx, name);
parser->depth++;
}
static void _end_element(void *userdata, const XML_Char *name)
{
parser_t *parser = (parser_t *)userdata;
parser->depth--;
if (parser->depth == 0) {
/* notify the owner */
if (parser->endcb)
parser->endcb((char *)name, parser->userdata);
} else {
complete_inner_text(parser);
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 XML_Char *s, int len)
{
parser_t *parser = (parser_t *)userdata;
char *p;
if (parser->depth < 2)
return;
/* Join all parts to a single resulting string. Stanza is created in
* _start_element() and _end_element(). */
if (parser->inner_text_used + len >= parser->inner_text_size) {
parser->inner_text_size =
parser->inner_text_used + len + 1 + INNER_TEXT_PADDING;
p = strophe_realloc(parser->ctx, parser->inner_text,
parser->inner_text_size);
if (p == NULL) {
strophe_free(parser->ctx, parser->inner_text);
parser->inner_text = NULL;
parser->inner_text_used = 0;
parser->inner_text_size = 0;
return;
}
parser->inner_text = p;
parser->inner_text[parser->inner_text_used] = '\0';
}
parser->inner_text_used += len;
strncat(parser->inner_text, s, len);
}
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->expat = NULL;
parser->startcb = startcb;
parser->endcb = endcb;
parser->stanzacb = stanzacb;
parser->userdata = userdata;
parser->depth = 0;
parser->stanza = NULL;
parser->inner_text = NULL;
parser->inner_text_size = 0;
parser->inner_text_used = 0;
parser_reset(parser);
}
return parser;
}
char *parser_attr_name(xmpp_ctx_t *ctx, char *nsname)
{
return _xml_name(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->expat)
XML_ParserFree(parser->expat);
if (parser->stanza) {
_free_parent_stanza(parser->stanza);
parser->stanza = NULL;
}
if (parser->inner_text) {
strophe_free(parser->ctx, parser->inner_text);
parser->inner_text = NULL;
}
strophe_free(parser->ctx, parser);
}
/* shuts down and restarts XML parser. true on success */
int parser_reset(parser_t *parser)
{
XML_Bool ret;
const XML_Memory_Handling_Suite *mem = NULL;
if (parser->expat) {
ret = XML_ParserReset(parser->expat, NULL);
if (ret != XML_TRUE) {
XML_ParserFree(parser->expat);
parser->expat = NULL;
}
} else {
if (mem_ctx == NULL)
mem_ctx = parser->ctx;
if (parser->ctx == mem_ctx)
mem = &parser_mem_suite;
parser->expat = XML_ParserCreate_MM(NULL, mem, &namespace_sep);
}
if (parser->stanza) {
_free_parent_stanza(parser->stanza);
parser->stanza = NULL;
}
if (parser->inner_text) {
strophe_free(parser->ctx, parser->inner_text);
parser->inner_text = NULL;
}
if (!parser->expat)
return 0;
parser->depth = 0;
XML_SetUserData(parser->expat, parser);
XML_SetElementHandler(parser->expat, _start_element, _end_element);
XML_SetCharacterDataHandler(parser->expat, _characters);
return 1;
}
int parser_feed(parser_t *parser, char *chunk, int len)
{
return XML_Parse(parser->expat, chunk, len, 0);
}