/* 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 #include #include #include #include #include "common.h" #include "parser.h" /* ASCII FF is invalid UTF-8 and should never be present in well-formed XML */ #define NAMESPACE_SEP ('\xFF') 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; }; /* 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; int len; c = strchr(nsname, NAMESPACE_SEP); if (c == NULL) return xmpp_strdup(ctx, nsname); c++; len = strlen(c); result = xmpp_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 = xmpp_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]); xmpp_free(stanza->ctx, attr); } } 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((char *)name, (char **)attrs, parser->userdata); } else { /* build stanzas at depth 1 */ if (!parser->stanza && parser->depth != 1) { /* something terrible happened */ /* FIXME: shutdown disconnect */ xmpp_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, name); _set_attributes(parser->stanza, attrs); if (ns) xmpp_stanza_set_ns(parser->stanza, ns); } else { /* starting a child of parser->stanza */ 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); /* 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; } } if (ns) xmpp_free(parser->ctx, ns); if (name) xmpp_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 { 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; xmpp_stanza_t *stanza; 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, s, len); xmpp_stanza_add_child(parser->stanza, stanza); xmpp_stanza_release(stanza); } 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 = xmpp_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_reset(parser); } return parser; } /* free a parser */ void parser_free(parser_t *parser) { if (parser->expat) XML_ParserFree(parser->expat); xmpp_free(parser->ctx, parser); } /* shuts down and restarts XML parser. true on success */ int parser_reset(parser_t *parser) { if (parser->expat) XML_ParserFree(parser->expat); if (parser->stanza) xmpp_stanza_release(parser->stanza); parser->expat = XML_ParserCreateNS(NULL, NAMESPACE_SEP); if (!parser->expat) return 0; parser->depth = 0; parser->stanza = NULL; 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); }