diff --git a/.gitignore b/.gitignore index 54cf14f..2479024 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ autom4te.cache src/*.o examples/*.o tests/*.o +tests/check_parser *.a examples/basic examples/active diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..7a5cd57 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,60 @@ +AUTOMAKE_OPTIONS = subdir-objects + +CFLAGS = -g -Wall + +builtin_CFLAGS = -I$(top_srcdir)/expat/lib +builtin_LIBS = libexpat.a + +PARSER_CFLAGS=@PARSER_CFLAGS@ +PARSER_LIBS=@PARSER_LIBS@ + +STROPHE_FLAGS = -I$(top_srcdir) +STROPHE_LIBS = libstrophe.a $(PARSER_LIBS) -lssl -lresolv + +## Main build targets +if BUILD_EXPAT +lib_LIBRARIES = libstrophe.a libexpat.a +else +lib_LIBRARIES = libstrophe.a +endif + +libstrophe_a_CFLAGS=$(STROPHE_FLAGS) $(PARSER_CFLAGS) +libstrophe_a_SOURCES = src/auth.c src/conn.c src/ctx.c \ + src/event.c src/handler.c src/hash.c \ + src/jid.c src/md5.c src/sasl.c src/sha1.c \ + src/snprintf.c src/sock.c src/stanza.c src/thread.c \ + src/tls_openssl.c src/util.c +if PARSER_EXPAT +libstrophe_a_SOURCES += src/parser_expat.c +else +libstrophe_a_SOURCES += src/parser_libxml2.c +endif + +libexpat_a_CFLAGS=-DXML_DTD -DXML_NS -DXML_CONTEXT_BYTES=1024 -DXML_STATIC \ + -I$(top_srcdir)/expat/lib +libexpat_a_SOURCES=expat/lib/xmlparse.c expat/lib/xmltok.c expat/lib/xmlrole.c + + +## Examples +noinst_PROGRAMS = examples/active examples/roster examples/basic examples/bot +examples_active_SOURCES = examples/active.c +examples_active_CFLAGS = $(STROPHE_FLAGS) +examples_active_LDADD = $(STROPHE_LIBS) +examples_roster_SOURCES = examples/roster.c +examples_roster_CFLAGS = $(STROPHE_FLAGS) +examples_roster_LDADD = $(STROPHE_LIBS) +examples_basic_SOURCES = examples/basic.c +examples_basic_CFLAGS = $(STROPHE_FLAGS) +examples_basic_LDADD = $(STROPHE_LIBS) +examples_bot_SOURCES = examples/bot.c +examples_bot_CFLAGS = $(STROPHE_FLAGS) +examples_bot_LDADD = $(STROPHE_LIBS) + + +## Tests +TESTS = tests/check_parser +check_PROGRAMS = tests/check_parser +tests_check_parser_SOURCES = tests/check_parser.c +tests_check_parser_CFLAGS = @check_CFLAGS@ $(PARSER_CFLAGS) $(STROPHE_FLAGS) \ + -I$(top_srcdir)/src +tests_check_parser_LDADD = @check_LIBS@ $(STROPHE_LIBS) diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..c546bd9 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +aclocal -I /usr/local/share/aclocal +automake --add-missing --foreign --copy +autoconf \ No newline at end of file diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..2f1ac77 --- /dev/null +++ b/configure.ac @@ -0,0 +1,44 @@ +AC_INIT([libstrophe], [trunk], [jack@metajack.im]) +AM_INIT_AUTOMAKE + +AC_PROG_CC +AC_PROG_RANLIB + +PKG_CHECK_MODULES([check], [check >= 0.9.4]) + +AC_ARG_WITH([libxml2], + [AS_HELP_STRING([--with-libxml2], [use libxml2 for XML parsing])], + [with_libxml2=check], + [with_libxml2=no]) + +if test "x$with_libxml2" != xno; then + PKG_CHECK_MODULES([libxml2], [libxml-2.0 >= 2.7], + [with_libxml2=yes], + [AC_MSG_ERROR([couldn't find libxml2])]) +else + AC_CHECK_FUNCS(memmove) + AC_C_BIGENDIAN([byteorder=1234], [byteorder=4321], [], []) +fi + +if test "x$with_libxml2" = xyes; then + with_parser=libxml2 + PARSER_NAME=libxml2 + PARSER_CFLAGS=\$\(libxml2_CFLAGS\) + PARSER_LIBS=\$\(libxml2_LIBS\) +else + with_parser=builtin + PARSER_NAME=expat + PARSER_CFLAGS=\$\(builtin_CFLAGS\) + PARSER_LIBS=\$\(builtin_LIBS\) +fi + +AC_MSG_NOTICE([libstrophe will use the $with_parser XML parser]) + +AM_CONDITIONAL([BUILD_EXPAT], [test x$with_parser = xbuiltin]) +AM_CONDITIONAL([PARSER_EXPAT], [test x$with_parser != xlibxml2]) +AC_DEFINE_UNQUOTED([BYTEORDER], [$byteorder]) +AC_SUBST(PARSER_NAME) +AC_SUBST(PARSER_CFLAGS) +AC_SUBST(PARSER_LIBS) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT \ No newline at end of file diff --git a/src/auth.c b/src/auth.c index dd0b9ee..260da39 100644 --- a/src/auth.c +++ b/src/auth.c @@ -208,9 +208,11 @@ static int _handle_features(xmpp_conn_t * const conn, xmpp_timed_handler_delete(conn, _handle_missing_features); /* check for TLS */ - child = xmpp_stanza_get_child_by_name(stanza, "starttls"); - if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_TLS) == 0)) - conn->tls_support = 1; + if (!conn->secured) { + child = xmpp_stanza_get_child_by_name(stanza, "starttls"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_TLS) == 0)) + conn->tls_support = 1; + } /* check for SASL */ child = xmpp_stanza_get_child_by_name(stanza, "mechanisms"); @@ -277,7 +279,8 @@ static int _handle_proceedtls_default(xmpp_conn_t * const conn, } else { - parser_prepare_reset(conn, _handle_open_tls); + conn->secured = 1; + conn_prepare_reset(conn, auth_handle_open); conn_open_stream(conn); } @@ -307,7 +310,7 @@ static int _handle_sasl_result(xmpp_conn_t * const conn, (char *)userdata); /* reset parser */ - parser_prepare_reset(conn, _handle_open_sasl); + conn_prepare_reset(conn, _handle_open_sasl); /* send stream tag */ conn_open_stream(conn); @@ -692,16 +695,6 @@ void auth_handle_open(xmpp_conn_t * const conn) FEATURES_TIMEOUT, NULL); } -/* called when stream:stream tag received after TLS connection */ -static void _handle_open_tls(xmpp_conn_t * const conn) -{ - xmpp_debug(conn->ctx, "xmpp", "TLS successful, proceeding with SASL"); - - /* go straight to SASL auth */ - _auth(conn); -} - - /* called when stream:stream tag received after SASL auth */ static void _handle_open_sasl(xmpp_conn_t * const conn) { diff --git a/src/common.h b/src/common.h index 8ecbb78..c8fb4a5 100644 --- a/src/common.h +++ b/src/common.h @@ -31,8 +31,7 @@ #include "tls.h" #include "hash.h" #include "util.h" - -#include "expat.h" +#include "parser.h" /** run-time context **/ @@ -167,6 +166,7 @@ struct _xmpp_conn_t { int tls_failed; /* set when tls fails, so we don't try again */ int sasl_support; /* if true, field is a bitfield of supported mechanisms */ + int secured; /* set when stream is secured with TLS */ /* if server returns or we must do them */ int bind_required; @@ -189,9 +189,7 @@ struct _xmpp_conn_t { /* xml parser */ int reset_parser; - XML_Parser parser; - int depth; - xmpp_stanza_t *stanza; + parser_t *parser; /* timeouts */ unsigned int connect_timeout; @@ -217,6 +215,8 @@ struct _xmpp_conn_t { void conn_disconnect(xmpp_conn_t * const conn); void conn_disconnect_clean(xmpp_conn_t * const conn); void conn_open_stream(xmpp_conn_t * const conn); +void conn_prepare_reset(xmpp_conn_t * const conn, xmpp_open_handler handler); +void conn_parser_reset(xmpp_conn_t * const conn); typedef enum { @@ -241,19 +241,6 @@ struct _xmpp_stanza_t { hash_t *attributes; }; -int xmpp_stanza_set_attributes(xmpp_stanza_t * const stanza, - const char * const * const attr); - -/* parser functions */ -void parser_handle_start(void *userdata, - const XML_Char *name, - const XML_Char **attr); -void parser_handle_character(void *userdata, const XML_Char *s, int len); -void parser_handle_end(void *userdata, const XML_Char *name); -void parser_prepare_reset(xmpp_conn_t * const conn, - xmpp_open_handler handler); -int parser_reset(xmpp_conn_t * const conn); - /* handler management */ void handler_fire_stanza(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza); diff --git a/src/conn.c b/src/conn.c index 96fe693..af69cba 100644 --- a/src/conn.c +++ b/src/conn.c @@ -23,9 +23,11 @@ #include #include -#include "strophe.h" +#include + #include "common.h" #include "util.h" +#include "parser.h" #ifndef DEFAULT_SEND_QUEUE_MAX /** @def DEFAULT_SEND_QUEUE_MAX @@ -51,6 +53,13 @@ static int _disconnect_cleanup(xmpp_conn_t * const conn, void * const userdata); +static void _handle_stream_start(char *name, char **attrs, + void * const userdata); +static void _handle_stream_end(char *name, + void * const userdata); +static void _handle_stream_stanza(xmpp_stanza_t *stanza, + void * const userdata); + /** Create a new Strophe connection object. * * @param ctx a Strophe context object @@ -100,13 +109,18 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx) conn->tls_support = 0; conn->tls_failed = 0; conn->sasl_support = 0; + conn->secured = 0; conn->bind_required = 0; conn->session_required = 0; - conn->parser = NULL; - conn->stanza = NULL; - parser_prepare_reset(conn, auth_handle_open); + conn->parser = parser_new(conn->ctx, + _handle_stream_start, + _handle_stream_end, + _handle_stream_stanza, + conn); + conn->reset_parser = 0; + conn_prepare_reset(conn, auth_handle_open); conn->authenticated = 0; conn->conn_handler = NULL; @@ -127,7 +141,7 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx) if (!item) { xmpp_error(conn->ctx, "xmpp", "failed to allocate memory"); xmpp_free(conn->ctx, conn->lang); - XML_ParserFree(conn->parser); + parser_free(conn->parser); xmpp_free(conn->ctx, conn); conn = NULL; } else { @@ -249,7 +263,7 @@ int xmpp_conn_release(xmpp_conn_t * const conn) xmpp_free(ctx, conn->stream_error); } - XML_ParserFree(conn->parser); + parser_free(conn->parser); if (conn->domain) xmpp_free(ctx, conn->domain); if (conn->jid) xmpp_free(ctx, conn->jid); @@ -361,7 +375,7 @@ int xmpp_connect_client(xmpp_conn_t * const conn, { char connectdomain[2048]; int connectport; - char *domain; + const char * domain; conn->type = XMPP_CLIENT; @@ -434,6 +448,21 @@ void conn_disconnect(xmpp_conn_t * const conn) conn->stream_error, conn->userdata); } +/* prepares a parser reset. this is called from handlers. we can't + * reset the parser immediately as it is not re-entrant. */ +void conn_prepare_reset(xmpp_conn_t * const conn, xmpp_open_handler handler) +{ + conn->reset_parser = 1; + conn->open_handler = handler; +} + +/* reset the parser */ +void conn_parser_reset(xmpp_conn_t * const conn) +{ + conn->reset_parser = 0; + parser_reset(conn->parser); +} + /* timed handler for cleanup if normal disconnect procedure takes too long */ static int _disconnect_cleanup(xmpp_conn_t * const conn, void * const userdata) @@ -607,3 +636,97 @@ void conn_open_stream(xmpp_conn_t * const conn) conn->type == XMPP_CLIENT ? XMPP_NS_CLIENT : XMPP_NS_COMPONENT, XMPP_NS_STREAMS); } + +static void _log_open_tag(xmpp_conn_t *conn, char **attrs) +{ + char buf[4096]; + size_t len, pos; + int i; + + if (!attrs) return; + + pos = 0; + len = xmpp_snprintf(buf, 4096, ""); + if (len < 0) return; + + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); +} + +static char *_get_stream_attribute(char **attrs, char *name) +{ + int i; + + if (!attrs) return NULL; + + for (i = 0; attrs[i]; i += 2) + if (strcmp(name, attrs[i]) == 0) + return attrs[i+1]; + + return NULL; +} + +static void _handle_stream_start(char *name, char **attrs, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + char *id; + + if (strcmp(name, "stream:stream") != 0) { + printf("name = %s\n", name); + xmpp_error(conn->ctx, "conn", "Server did not open valid stream."); + conn_disconnect(conn); + } else { + _log_open_tag(conn, attrs); + + if (conn->stream_id) xmpp_free(conn->ctx, conn->stream_id); + + id = _get_stream_attribute(attrs, "id"); + if (id) + conn->stream_id = xmpp_strdup(conn->ctx, id); + + if (!conn->stream_id) { + xmpp_error(conn->ctx, "conn", "Memory allocation failed."); + conn_disconnect(conn); + } + } + + /* call stream open handler */ + conn->open_handler(conn); +} + +static void _handle_stream_end(char *name, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + + /* stream is over */ + xmpp_debug(conn->ctx, "xmpp", "RECV: "); + conn_disconnect_clean(conn); +} + +static void _handle_stream_stanza(xmpp_stanza_t *stanza, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + char *buf; + size_t len; + + if (xmpp_stanza_to_text(stanza, &buf, &len) == 0) { + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); + xmpp_free(conn->ctx, buf); + } + + handler_fire_stanza(conn, stanza); +} diff --git a/src/event.c b/src/event.c index 571a7ee..556b672 100644 --- a/src/event.c +++ b/src/event.c @@ -47,8 +47,9 @@ #define ECONNABORTED WSAECONNABORTED #endif -#include "strophe.h" +#include #include "common.h" +#include "parser.h" #ifndef DEFAULT_TIMEOUT /** @def DEFAULT_TIMEOUT @@ -168,7 +169,7 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) /* reset parsers if needed */ for (connitem = ctx->connlist; connitem; connitem = connitem->next) { if (connitem->conn->reset_parser) - parser_reset(connitem->conn); + conn_parser_reset(connitem->conn); } @@ -267,7 +268,7 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) } if (ret > 0) { - ret = XML_Parse(conn->parser, buf, ret, 0); + ret = parser_feed(conn->parser, buf, ret); if (!ret) { /* parse error, we need to shut down */ /* FIXME */ diff --git a/src/parser.c b/src/parser.c deleted file mode 100644 index 55ca56b..0000000 --- a/src/parser.c +++ /dev/null @@ -1,206 +0,0 @@ -/* parser.c -** strophe XMPP client library -- xml parser handlers and utility functions -** -** Copyright (C) 2005-2008 OGG, LLC. All rights reserved. -** -** This software is provided AS-IS with no warranty, either express -** or implied. -** -** This software is distributed under license and may not be copied, -** modified or distributed except as expressly authorized under the -** terms of the license contained in the file LICENSE.txt in this -** distribution. -*/ - -/** @file - * XML parser handlers. - */ - -#include -#include -#include - -#include "expat.h" - -#include "strophe.h" -#include "common.h" - -static void _log_open_tag(xmpp_conn_t * const conn, - const XML_Char **attr) -{ - char buf[4096]; - size_t len, pos; - int i; - - pos = 0; - len = xmpp_snprintf(buf, 4096, ""); - if (len < 0) return; - - xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); -} - -void parser_handle_start(void *userdata, - const XML_Char *name, - const XML_Char **attr) -{ - xmpp_conn_t *conn = (xmpp_conn_t *)userdata; - xmpp_stanza_t *child; - - if (conn->depth == 0) { - /* we're expecting a stream:stream tag. */ - if (strcmp(name, "stream:stream") != 0) { - xmpp_error(conn->ctx, "xmpp", - "Server did not open valid stream."); - conn_disconnect(conn); - } else { - _log_open_tag(conn, attr); - - if (conn->stream_id) xmpp_free(conn->ctx, conn->stream_id); - conn->stream_id = xmpp_strdup(conn->ctx, "foo"); - if (!conn->stream_id) { - xmpp_error(conn->ctx, "xmpp", - "Memory allocation failure."); - conn_disconnect(conn); - } - - /* call stream open handler */ - conn->open_handler(conn); - } - } else { - /* build stanzas at depth 1 */ - if (!conn->stanza && conn->depth != 1) { - /* something terrible happened */ - /* FIXME: shutdown disconnect */ - xmpp_debug(conn->ctx, "xmpp", "oops, where did our stanza go?"); - } else if (!conn->stanza) { - /* starting a new toplevel stanza */ - conn->stanza = xmpp_stanza_new(conn->ctx); - if (!conn->stanza) { - /* FIXME: can't allocate, disconnect */ - } - xmpp_stanza_set_name(conn->stanza, name); - xmpp_stanza_set_attributes(conn->stanza, attr); - } else { - /* starting a child of conn->stanza */ - child = xmpp_stanza_new(conn->ctx); - if (!child) { - /* FIXME: can't allocate, disconnect */ - } - xmpp_stanza_set_name(child, name); - xmpp_stanza_set_attributes(child, attr); - - /* add child to parent */ - xmpp_stanza_add_child(conn->stanza, child); - - /* the child is owned by the toplevel stanza now */ - xmpp_stanza_release(child); - - /* make child the current stanza */ - conn->stanza = child; - } - } - - conn->depth++; -} - -void parser_handle_end(void *userdata, const XML_Char *name) -{ - xmpp_conn_t *conn = (xmpp_conn_t *)userdata; - char *buf; - size_t len; - xmpp_stanza_t *stanza; - - conn->depth--; - - if (conn->depth == 0) { - /* got a closing stream tag */ - xmpp_debug(conn->ctx, "xmpp", "RECV: "); - conn_disconnect_clean(conn); - } else { - if (conn->stanza->parent) { - /* we're finishing a child stanza, so set current to the parent */ - conn->stanza = conn->stanza->parent; - } else { - /* we're finishing a toplevel stanza, so fire off handler */ - if (xmpp_stanza_to_text(conn->stanza, &buf, &len) == 0) { - xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); - xmpp_free(conn->ctx, buf); - } - - stanza = xmpp_stanza_clone(conn->stanza); - xmpp_stanza_release(conn->stanza); - conn->stanza = NULL; - - /* fire handlers */ - handler_fire_stanza(conn, stanza); - - xmpp_stanza_release(stanza); - } - } -} - -void parser_handle_character(void *userdata, const XML_Char *s, int len) -{ - xmpp_conn_t *conn = (xmpp_conn_t *)userdata; - xmpp_stanza_t *stanza; - - if (conn->depth < 2) return; - - /* create and populate stanza */ - stanza = xmpp_stanza_new(conn->ctx); - if (!stanza) { - /* FIXME: allocation error, disconnect */ - return; - } - xmpp_stanza_set_text_with_size(stanza, s, len); - - xmpp_stanza_add_child(conn->stanza, stanza); - xmpp_stanza_release(stanza); -} - -/* prepares a parser reset. this is called from handlers. we can't - * reset the parser immediately as it is not reentrant. */ -void parser_prepare_reset(xmpp_conn_t * const conn, - xmpp_open_handler handler) -{ - conn->reset_parser = 1; - conn->open_handler = handler; -} - -/* shuts down and restarts XML parser. true on success */ -int parser_reset(xmpp_conn_t * const conn) -{ - conn->reset_parser = 0; - - if (conn->parser) - XML_ParserFree(conn->parser); - - if (conn->stanza) - xmpp_stanza_release(conn->stanza); - - conn->parser = XML_ParserCreate(NULL); - if (!conn->parser) return 0; - - conn->depth = 0; - conn->stanza = NULL; - XML_SetUserData(conn->parser, conn); - XML_SetElementHandler(conn->parser, parser_handle_start, - parser_handle_end); - XML_SetCharacterDataHandler(conn->parser, parser_handle_character); - - return 1; -} - diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..f8bfaec --- /dev/null +++ b/src/parser.h @@ -0,0 +1,43 @@ +/* parser.h +** strophe XMPP client library -- parser structures and functions +** +** Copyright (C) 2005-2009 Stanziq, Inc. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express or +** implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +/** @file + * Internally used functions and structures. + */ + +#ifndef __LIBSTROPHE_PARSER_H__ +#define __LIBSTROPHE_PARSER_H__ + +#include "strophe.h" + +typedef struct _parser_t parser_t; + +typedef void (*parser_start_callback)(char *name, + char **attrs, + void * const userdata); +typedef void (*parser_end_callback)(char *name, void * const userdata); +typedef void (*parser_stanza_callback)(xmpp_stanza_t *stanza, + void * const userdata); + + +parser_t *parser_new(xmpp_ctx_t *ctx, + parser_start_callback startcb, + parser_end_callback endcb, + parser_stanza_callback stanzacb, + void *userdata); +void parser_free(parser_t * const parser); +int parser_reset(parser_t *parser); +int parser_feed(parser_t *parser, char *chunk, int len); + +#endif /* __LIBSTROPHE_PARSER_H__ */ diff --git a/src/parser_expat.c b/src/parser_expat.c new file mode 100644 index 0000000..94859b9 --- /dev/null +++ b/src/parser_expat.c @@ -0,0 +1,202 @@ +/* parser.c +** strophe XMPP client library -- xml parser handlers and utility functions +** +** Copyright (C) 2005-2008 OGG, LLC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +/** @file + * XML parser handlers. + */ + +#include +#include +#include + +#include + +#include +#include "common.h" +#include "parser.h" + +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; +}; + +static void _set_attributes(xmpp_stanza_t *stanza, const XML_Char **attrs) +{ + int i; + + if (!attrs) return; + + for (i = 0; attrs[i]; i += 2) { + xmpp_stanza_set_attribute(stanza, attrs[i], attrs[i+1]); + } +} + +static void _start_element(void *userdata, + const XML_Char *name, + const XML_Char **attrs) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *child; + + 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); + } 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); + + /* 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 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(xmpp_stanza_clone(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_ParserCreate(NULL); + 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); +} diff --git a/src/parser_libxml2.c b/src/parser_libxml2.c new file mode 100644 index 0000000..1d9d271 --- /dev/null +++ b/src/parser_libxml2.c @@ -0,0 +1,206 @@ +/* parser.c +** strophe XMPP client library -- xml parser handlers and utility functions +** +** Copyright (C) 2005-2008 OGG, LLC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +/** @file + * XML parser handlers. + */ + +#include +#include +#include + +#include +#include + +#include +#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, const xmlChar **attrs) +{ + int i; + + if (!attrs) return; + + for (i = 0; attrs[i]; i += 2) { + xmpp_stanza_set_attribute(stanza, attrs[i], attrs[i+1]); + } +} + +static void _start_element(void *userdata, + const xmlChar *name, const xmlChar **attrs) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *child; + + 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: we should probably trigger a 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, (char *)name); + _set_attributes(parser->stanza, attrs); + } 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, attrs); + + /* 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) +{ + parser_t *parser = (parser_t *)userdata; + + 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(xmpp_stanza_clone(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 = xmpp_alloc(ctx, sizeof(parser_t)); + if (parser != NULL) { + parser->ctx = ctx; + parser->xmlctx = NULL; + memset(&parser->handlers, 0, sizeof(xmlSAXHandler)); + parser->handlers.startElement = _start_element; + parser->handlers.endElement = _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; +} + +/* free a parser */ +void parser_free(parser_t *parser) +{ + if (parser->xmlctx) + xmlFreeParserCtxt(parser->xmlctx); + xmpp_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) + xmpp_stanza_release(parser->stanza); + + parser->xmlctx = xmlCreatePushParserCtxt(&parser->handlers, + parser, NULL, 0, NULL); + if (!parser->xmlctx) return 0; + + parser->depth = 0; + parser->stanza = NULL; + + return 1; +} + +/* feed a chunk of data to the parser */ +int parser_feed(parser_t *parser, char *chunk, int len) +{ + return xmlParseChunk(parser->xmlctx, chunk, len, 0); +} diff --git a/src/stanza.c b/src/stanza.c index 24ca1a5..064385e 100644 --- a/src/stanza.c +++ b/src/stanza.c @@ -400,45 +400,6 @@ char *xmpp_stanza_get_name(xmpp_stanza_t * const stanza) return stanza->data; } -/** Set or replace attributes on a stanza. - * This function replaces all previous attributes (if any) with the - * attributes given. It is used primarily by the XML parser during - * stanza creation. All strings in the array are copied before placing them - * inside the stanza object. - * - * @param stanza a Strophe stanza object - * @param attr an array of strings with the attributes in the following - * format: attr[i] = attribute name, attr[i+1] = attribute value - * - * @return XMPP_EOK on success, a number less than 0 on failure (XMPP_EMEM, - * XMPP_EINVOP) - * - * @ingroup Stanza - */ -int xmpp_stanza_set_attributes(xmpp_stanza_t * const stanza, - const char * const * const attr) -{ - int i; - char *value; - - if (stanza->attributes != NULL) - hash_release(stanza->attributes); - - stanza->attributes = hash_new(stanza->ctx, 8, xmpp_free); - if (!stanza->attributes) return XMPP_EMEM; - - for (i = 0; attr[i]; i += 2) { - value = xmpp_strdup(stanza->ctx, attr[i + 1]); - if (!value) { - /* FIXME: memory allocation error */ - continue; - } - hash_add(stanza->attributes, attr[i], value); - } - - return XMPP_EOK; -} - /** Count the attributes in a stanza object. * * @param stanza a Strophe stanza object diff --git a/tests/check_parser.c b/tests/check_parser.c new file mode 100644 index 0000000..d2dae66 --- /dev/null +++ b/tests/check_parser.c @@ -0,0 +1,93 @@ +/* check_parser.h +** strophe XMPP client library -- parser tests +** +** Copyright (C) 2005-2009 Stanziq, Inc. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express or +** implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include + +#include + +#include +#include "parser.h" + +#include "test.h" + +START_TEST(create_destroy) +{ + xmpp_ctx_t *ctx; + parser_t *parser; + + ctx = xmpp_ctx_new(NULL, NULL); + parser = parser_new(ctx, NULL, NULL, NULL, NULL); + fail_unless(parser != NULL, "Parser creation failed."); + parser_free(parser); + xmpp_ctx_free(ctx); +} +END_TEST + +int cbtest_got_start = 0; +void cbtest_handle_start(char *name, char **attrs, void *userdata) +{ + if (strcmp(name, "stream:stream") == 0) + cbtest_got_start = 1; +} + +int cbtest_got_end = 0; +void cbtest_handle_end(char *name, void *userdata) +{ + if (strcmp(name, "stream:stream") == 0) + cbtest_got_end = 1; +} + +int cbtest_got_stanza = 0; +void cbtest_handle_stanza(xmpp_stanza_t *stanza, void *userdata) +{ + if (strcmp(xmpp_stanza_get_name(stanza), "message") == 0) + cbtest_got_stanza = 1; +} + +START_TEST(callbacks) +{ + xmpp_ctx_t *ctx; + parser_t *parser; + int ret; + + ctx = xmpp_ctx_new(NULL, NULL); + parser = parser_new(ctx, + cbtest_handle_start, + cbtest_handle_end, + cbtest_handle_stanza, NULL); + + ret = parser_feed(parser, "", 15); + ret = parser_feed(parser, "", 10); + parser_feed(parser, "", 16); + + fail_unless(cbtest_got_start == 1); + fail_unless(cbtest_got_end == 1); + fail_unless(cbtest_got_stanza == 1); + + parser_free(parser); + xmpp_ctx_free(ctx); +} +END_TEST + +Suite *parser_suite(void) +{ + Suite *s = suite_create("Parser"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test(tc_core, create_destroy); + tcase_add_test(tc_core, callbacks); + suite_add_tcase(s, tc_core); + return s; +} + +TEST_MAIN diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..134d5b4 --- /dev/null +++ b/tests/test.h @@ -0,0 +1,16 @@ +#ifndef __LIBSTROPHE_TEST_H__ +#define __LIBSTROPHE_TEST_H__ + +#define TEST_MAIN \ + int main(int argc, char **argv) {\ + int num_failed;\ + Suite *s = parser_suite();\ + SRunner *sr = srunner_create(s);\ + srunner_run_all(sr, CK_NORMAL);\ + num_failed = srunner_ntests_failed(sr);\ + srunner_free(sr);\ + return (num_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;\ + }\ + + +#endif /* __LIBSTROPHE_TEST_H__ */