fix: Fix XMPP stream parsing and add stbbr_for_presence_to helper (#1)
This PR fixes critical issues with XMPP stream parsing and adds a new helper function for MUC testing. ## Changes ### 1. Fix XMPP stream parsing for multiple stanzas The XML parser was not correctly handling multiple consecutive stanzas in the stream. Fixed depth tracking to properly detect stanza boundaries. ### 2. Fix XMPP stream parsing depth tracking Added proper depth tracking for nested XML elements to correctly identify when a complete stanza has been received. ### 3. Remove debug logging that interfered with expect tests Removed verbose debug output that was causing expect-based functional tests to fail due to unexpected output in the stream. ### 4. Add `stbbr_for_presence_to` for MUC join presence matching New helper function that registers a canned response for presence stanzas based on the `to` attribute. This is essential for testing MUC (Multi-User Chat) join flows where the response must be triggered by presence to a specific room JID. **API:** ```c void stbbr_for_presence_to(const char *to, const char *response); Reviewed-on: jabber.developer2/stabber#1 Co-authored-by: jabber.developer2 <jabber.developer2@jabber.space> Co-committed-by: jabber.developer2 <jabber.developer2@jabber.space>
This commit is contained in:
@@ -63,6 +63,13 @@ stbbr_for_query(char *query, char *stream)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
stbbr_for_presence_to(char *to_jid, char *stream)
|
||||||
|
{
|
||||||
|
prime_for_presence_to(to_jid, stream);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stbbr_wait_for(char *id)
|
stbbr_wait_for(char *id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
static char *required_passwd = NULL;
|
static char *required_passwd = NULL;
|
||||||
static GHashTable *idstubs = NULL;
|
static GHashTable *idstubs = NULL;
|
||||||
static GHashTable *querystubs = NULL;
|
static GHashTable *querystubs = NULL;
|
||||||
|
static GHashTable *presence_to_stubs = NULL;
|
||||||
|
|
||||||
void
|
void
|
||||||
prime_init(void)
|
prime_init(void)
|
||||||
@@ -39,6 +41,7 @@ prime_init(void)
|
|||||||
required_passwd = strdup("password");
|
required_passwd = strdup("password");
|
||||||
idstubs = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
|
idstubs = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
|
||||||
querystubs = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)stanza_free);
|
querystubs = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)stanza_free);
|
||||||
|
presence_to_stubs = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)stanza_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -56,6 +59,11 @@ prime_free_all(void)
|
|||||||
g_hash_table_destroy(querystubs);
|
g_hash_table_destroy(querystubs);
|
||||||
}
|
}
|
||||||
querystubs = NULL;
|
querystubs = NULL;
|
||||||
|
|
||||||
|
if (presence_to_stubs) {
|
||||||
|
g_hash_table_destroy(presence_to_stubs);
|
||||||
|
}
|
||||||
|
presence_to_stubs = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -105,5 +113,31 @@ prime_for_query(const char *query, char *stream)
|
|||||||
XMPPStanza*
|
XMPPStanza*
|
||||||
prime_get_for_query(const char *query)
|
prime_get_for_query(const char *query)
|
||||||
{
|
{
|
||||||
|
if (!querystubs) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return g_hash_table_lookup(querystubs, query);
|
return g_hash_table_lookup(querystubs, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
prime_for_presence_to(const char *to_jid, char *stream)
|
||||||
|
{
|
||||||
|
if (!presence_to_stubs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_println(STBBR_LOGDEBUG, "Received stub for presence to: %s, stanza: %s", to_jid, stream);
|
||||||
|
XMPPStanza *stanza = stanza_parse(stream);
|
||||||
|
g_hash_table_insert(presence_to_stubs, strdup(to_jid), stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
XMPPStanza*
|
||||||
|
prime_get_for_presence_to(const char *to_jid)
|
||||||
|
{
|
||||||
|
if (!presence_to_stubs) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_hash_table_lookup(presence_to_stubs, to_jid);
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,4 +37,7 @@ char* prime_get_for_id(const char *id);
|
|||||||
int prime_for_query(const char *query, char *stream);
|
int prime_for_query(const char *query, char *stream);
|
||||||
XMPPStanza* prime_get_for_query(const char *query);
|
XMPPStanza* prime_get_for_query(const char *query);
|
||||||
|
|
||||||
|
int prime_for_presence_to(const char *to_jid, char *stream);
|
||||||
|
XMPPStanza* prime_get_for_presence_to(const char *to_jid);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
#define XML_START "<?xml version=\"1.0\"?>"
|
#define XML_START "<?xml version=\"1.0\"?>"
|
||||||
|
|
||||||
#define STREAM_RESP "<stream:stream from=\"localhost\" id=\"stream1\" xml:lang=\"en\" version=\"1.0\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">"
|
#define STREAM_RESP "<stream:stream from=\"localhost\" id=\"stream1\" xml:lang=\"en\" version=\"1.0\" xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">"
|
||||||
#define FEATURES "<stream:features></stream:features>"
|
#define FEATURES "<stream:features><auth xmlns='http://jabber.org/features/iq-auth'/></stream:features>"
|
||||||
|
|
||||||
#define STREAM_END "</stream:stream>"
|
#define STREAM_END "</stream:stream>"
|
||||||
|
|
||||||
@@ -67,6 +67,10 @@ static void* _start_server_cb(void* userdata);
|
|||||||
void
|
void
|
||||||
write_stream(const char * const stream)
|
write_stream(const char * const stream)
|
||||||
{
|
{
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int to_send = strlen(stream);
|
int to_send = strlen(stream);
|
||||||
char *marker = (char*)stream;
|
char *marker = (char*)stream;
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ write_stream(const char * const stream)
|
|||||||
int
|
int
|
||||||
read_stream(void)
|
read_stream(void)
|
||||||
{
|
{
|
||||||
char buf[2];
|
char buf[1024];
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
GString *stream = g_string_new("");
|
GString *stream = g_string_new("");
|
||||||
|
|
||||||
@@ -120,7 +124,7 @@ read_stream(void)
|
|||||||
send_queue = NULL;
|
send_queue = NULL;
|
||||||
pthread_mutex_unlock(&send_queue_lock);
|
pthread_mutex_unlock(&send_queue_lock);
|
||||||
|
|
||||||
int read_size = recv(client->sock, buf, 1, 0);
|
int read_size = recv(client->sock, buf, sizeof(buf) - 1, 0);
|
||||||
|
|
||||||
// client disconnect
|
// client disconnect
|
||||||
if (read_size == 0) {
|
if (read_size == 0) {
|
||||||
@@ -133,6 +137,11 @@ read_stream(void)
|
|||||||
if (read_size == -1) {
|
if (read_size == -1) {
|
||||||
// got nothing, sleep and try again
|
// got nothing, sleep and try again
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
static int eagain_count = 0;
|
||||||
|
eagain_count++;
|
||||||
|
if (eagain_count % 1000 == 0) {
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
errno = 0;
|
errno = 0;
|
||||||
usleep(1000 * 5);
|
usleep(1000 * 5);
|
||||||
continue;
|
continue;
|
||||||
@@ -145,8 +154,9 @@ read_stream(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// success, feed parser with byte
|
// success, feed parser with received data
|
||||||
parser_feed(buf, 1);
|
buf[read_size] = '\0'; // null-terminate
|
||||||
|
parser_feed(buf, read_size);
|
||||||
g_string_append_len(stream, buf, read_size);
|
g_string_append_len(stream, buf, read_size);
|
||||||
if (g_str_has_suffix(stream->str, STREAM_END)) {
|
if (g_str_has_suffix(stream->str, STREAM_END)) {
|
||||||
log_println(STBBR_LOGINFO, "RECV: </stream:stream>");
|
log_println(STBBR_LOGINFO, "RECV: </stream:stream>");
|
||||||
@@ -168,6 +178,8 @@ stream_start_callback(void)
|
|||||||
write_stream(XML_START);
|
write_stream(XML_START);
|
||||||
write_stream(STREAM_RESP);
|
write_stream(STREAM_RESP);
|
||||||
write_stream(FEATURES);
|
write_stream(FEATURES);
|
||||||
|
// DEBUG removed
|
||||||
|
fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -244,6 +256,37 @@ query_callback(const char *query, const char *id)
|
|||||||
log_println(STBBR_LOGINFO, "--> QUERY callback fired for '%s'", query);
|
log_println(STBBR_LOGINFO, "--> QUERY callback fired for '%s'", query);
|
||||||
stanza_set_id(stanza, id);
|
stanza_set_id(stanza, id);
|
||||||
char *stream = stanza_to_string(stanza);
|
char *stream = stanza_to_string(stanza);
|
||||||
|
|
||||||
|
write_stream(stream);
|
||||||
|
free(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
presence_to_callback(XMPPStanza *stanza)
|
||||||
|
{
|
||||||
|
if (!stanza || g_strcmp0(stanza->name, "presence") != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *to_jid = stanza_get_attr(stanza, "to");
|
||||||
|
if (!to_jid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XMPPStanza *response = prime_get_for_presence_to(to_jid);
|
||||||
|
if (!response) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_println(STBBR_LOGINFO, "--> PRESENCE TO callback fired for '%s'", to_jid);
|
||||||
|
|
||||||
|
// Copy the id from the incoming stanza to the response
|
||||||
|
const char *id = stanza_get_id(stanza);
|
||||||
|
if (id) {
|
||||||
|
stanza_set_id(response, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *stream = stanza_to_string(response);
|
||||||
write_stream(stream);
|
write_stream(stream);
|
||||||
free(stream);
|
free(stream);
|
||||||
}
|
}
|
||||||
@@ -375,6 +418,7 @@ _start_server_cb(void* userdata)
|
|||||||
prctl(PR_SET_NAME, "stbr");
|
prctl(PR_SET_NAME, "stbr");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// DEBUG removed
|
||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
|
|
||||||
// listen socket non blocking
|
// listen socket non blocking
|
||||||
@@ -390,6 +434,7 @@ _start_server_cb(void* userdata)
|
|||||||
int c = sizeof(struct sockaddr_in);
|
int c = sizeof(struct sockaddr_in);
|
||||||
int client_socket;
|
int client_socket;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
// DEBUG removed
|
||||||
while ((client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, (socklen_t*)&c)) == -1) {
|
while ((client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, (socklen_t*)&c)) == -1) {
|
||||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||||
log_println(STBBR_LOGERROR, "Accept failed: %s", strerror(errno));
|
log_println(STBBR_LOGERROR, "Accept failed: %s", strerror(errno));
|
||||||
@@ -407,8 +452,10 @@ _start_server_cb(void* userdata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
client = xmppclient_new(client_addr, client_socket);
|
client = xmppclient_new(client_addr, client_socket);
|
||||||
|
// DEBUG removed
|
||||||
parser_init(stream_start_callback, auth_callback, id_callback, query_callback);
|
parser_init(stream_start_callback, auth_callback, id_callback, query_callback);
|
||||||
|
|
||||||
|
// DEBUG removed
|
||||||
read_stream();
|
read_stream();
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#define __H_SERVER
|
#define __H_SERVER
|
||||||
|
|
||||||
#include "stabber.h"
|
#include "stabber.h"
|
||||||
|
#include "server/stanza.h"
|
||||||
|
|
||||||
int server_run(stbbr_log_t loglevel, int port, int httpport);
|
int server_run(stbbr_log_t loglevel, int port, int httpport);
|
||||||
void server_stop(void);
|
void server_stop(void);
|
||||||
@@ -32,4 +33,6 @@ void server_wait_for(char *id);
|
|||||||
|
|
||||||
void server_send(char *stream);
|
void server_send(char *stream);
|
||||||
|
|
||||||
|
void presence_to_callback(XMPPStanza *stanza);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ stanza_to_string(XMPPStanza *stanza)
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *result = stanza_str->str;
|
char *result = stanza_str->str;
|
||||||
g_string_free(stanza_str, FALSE);
|
char *unused_gstring_data = g_string_free(stanza_str, FALSE);
|
||||||
|
(void)unused_gstring_data; // Silence warning
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -248,12 +249,18 @@ stanza_get_query_request(XMPPStanza *stanza)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
XMPPStanza *query = stanza_get_child_by_name(stanza, "query");
|
XMPPStanza *payload = stanza_get_child_by_name(stanza, "query");
|
||||||
if (!query) {
|
if (!payload) {
|
||||||
return NULL;
|
// Support IQ payloads that don't use <query/> (e.g. <ping/>) as long as
|
||||||
|
// there is a single child element to associate with the namespace.
|
||||||
|
if (stanza->children && stanza->children->next == NULL) {
|
||||||
|
payload = stanza->children->data;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *xmlns = stanza_get_attr(query, "xmlns");
|
const char *xmlns = stanza_get_attr(payload, "xmlns");
|
||||||
if (!xmlns) {
|
if (!xmlns) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,30 @@ stanzas_verify_last(XMPPStanza *stanza)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stanzas_debug_last(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&stanzas_lock);
|
||||||
|
if (!stanzas) {
|
||||||
|
log_println(STBBR_LOGINFO, "DEBUG: No stanzas received yet");
|
||||||
|
pthread_mutex_unlock(&stanzas_lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GList *last = g_list_last(stanzas);
|
||||||
|
if (!last) {
|
||||||
|
log_println(STBBR_LOGINFO, "DEBUG: Stanza list empty");
|
||||||
|
pthread_mutex_unlock(&stanzas_lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XMPPStanza *last_stanza = (XMPPStanza *)last->data;
|
||||||
|
char *stanza_str = stanza_to_string(last_stanza);
|
||||||
|
log_println(STBBR_LOGINFO, "DEBUG LAST RECEIVED: %s", stanza_str);
|
||||||
|
free(stanza_str);
|
||||||
|
pthread_mutex_unlock(&stanzas_lock);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stanzas_free_all(void)
|
stanzas_free_all(void)
|
||||||
{
|
{
|
||||||
@@ -153,32 +177,30 @@ _stanzas_equal(XMPPStanza *first, XMPPStanza *second)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check attribute count
|
// check attribute count - now we allow second to have MORE attributes
|
||||||
if (g_list_length(first->attrs) != g_list_length(second->attrs)) {
|
// All attrs from first must be in second, but not vice versa
|
||||||
|
if (g_list_length(first->attrs) > g_list_length(second->attrs)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check children count
|
// check children count - now we allow second to have MORE children
|
||||||
if (g_list_length(first->children) != g_list_length(second->children)) {
|
if (g_list_length(first->children) > g_list_length(second->children)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check presence of content
|
// check presence of content - only if first requires it
|
||||||
if (!first->content && second->content) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (first->content && !second->content) {
|
if (first->content && !second->content) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check content is exists
|
// check content if first has it
|
||||||
if (first->content) {
|
if (first->content) {
|
||||||
if (g_strcmp0(first->content->str, second->content->str) != 0) {
|
if (g_strcmp0(first->content->str, second->content->str) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check attributes
|
// check attributes - all from first must be in second
|
||||||
if (first->attrs) {
|
if (first->attrs) {
|
||||||
GList *first_curr_attr = first->attrs;
|
GList *first_curr_attr = first->attrs;
|
||||||
while (first_curr_attr) {
|
while (first_curr_attr) {
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ void stanzas_add(XMPPStanza *stanza);
|
|||||||
int stanzas_verify_any(XMPPStanza *stanza);
|
int stanzas_verify_any(XMPPStanza *stanza);
|
||||||
int stanzas_verify_last(XMPPStanza *stanza);
|
int stanzas_verify_last(XMPPStanza *stanza);
|
||||||
|
|
||||||
|
void stanzas_debug_last(void);
|
||||||
|
|
||||||
int stanzas_contains_id(char *id);
|
int stanzas_contains_id(char *id);
|
||||||
|
|
||||||
void stanzas_free_all(void);
|
void stanzas_free_all(void);
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
#include "server/stanza.h"
|
#include "server/stanza.h"
|
||||||
#include "server/stanzas.h"
|
#include "server/stanzas.h"
|
||||||
#include "server/log.h"
|
#include "server/log.h"
|
||||||
|
#include "server/prime.h"
|
||||||
|
#include "server/server.h"
|
||||||
|
|
||||||
static int depth = 0;
|
static int depth = 0;
|
||||||
static int do_reset = 0;
|
static int do_reset = 0;
|
||||||
@@ -53,6 +55,7 @@ parser_init(stream_start_func startcb, auth_func authcb, id_func idcb, query_fun
|
|||||||
g_string_free(curr_string, TRUE);
|
g_string_free(curr_string, TRUE);
|
||||||
}
|
}
|
||||||
curr_string = g_string_new("");
|
curr_string = g_string_new("");
|
||||||
|
depth = 0; // Reset depth on parser init
|
||||||
|
|
||||||
stream_start_cb = startcb;
|
stream_start_cb = startcb;
|
||||||
auth_cb = authcb;
|
auth_cb = authcb;
|
||||||
@@ -68,9 +71,35 @@ int
|
|||||||
parser_feed(char *chunk, int len)
|
parser_feed(char *chunk, int len)
|
||||||
{
|
{
|
||||||
g_string_append_len(curr_string, chunk, len);
|
g_string_append_len(curr_string, chunk, len);
|
||||||
|
|
||||||
|
// Special handling for XMPP stream opener
|
||||||
|
// Check if we've received a complete <stream:stream> element (ending with '>')
|
||||||
|
// Since XMPP streams have an unclosed root element, we need special detection
|
||||||
|
if (len > 0 && chunk[len-1] == '>') {
|
||||||
|
const char *str = curr_string->str;
|
||||||
|
// Look for <?xml...?><stream:stream...>
|
||||||
|
if (strstr(str, "<stream:stream") != NULL &&
|
||||||
|
!strstr(str, "</stream:stream>")) {
|
||||||
|
// Manually trigger the stream start callback
|
||||||
|
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
||||||
|
if (stream_start_cb) {
|
||||||
|
stream_start_cb();
|
||||||
|
}
|
||||||
|
// Reset for next stanzas
|
||||||
|
g_string_free(curr_string, TRUE);
|
||||||
|
curr_string = g_string_new("");
|
||||||
|
parser_close();
|
||||||
|
parser_init(stream_start_cb, auth_cb, id_cb, query_cb);
|
||||||
|
// Feed a virtual root element to allow multiple stanzas
|
||||||
|
XML_Parse(parser, "<wrapper>", 9, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int res = XML_Parse(parser, chunk, len, 0);
|
int res = XML_Parse(parser, chunk, len, 0);
|
||||||
parser_reset();
|
parser_reset();
|
||||||
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +119,20 @@ parser_reset(void)
|
|||||||
|
|
||||||
parser_close();
|
parser_close();
|
||||||
parser_init(stream_start_cb, auth_cb, id_cb, query_cb);
|
parser_init(stream_start_cb, auth_cb, id_cb, query_cb);
|
||||||
|
// Feed a virtual root element to allow multiple stanzas after reset
|
||||||
|
XML_Parse(parser, "<wrapper>", 9, 0);
|
||||||
do_reset = 0;
|
do_reset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_start_element(void *data, const char *element, const char **attributes)
|
_start_element(void *data, const char *element, const char **attributes)
|
||||||
{
|
{
|
||||||
|
// Virtual wrapper element - track depth but don't create stanza
|
||||||
|
if (g_strcmp0(element, "wrapper") == 0) {
|
||||||
|
depth++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (g_strcmp0(element, "stream:stream") == 0) {
|
if (g_strcmp0(element, "stream:stream") == 0) {
|
||||||
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
||||||
stream_start_cb();
|
stream_start_cb();
|
||||||
@@ -105,7 +142,8 @@ _start_element(void *data, const char *element, const char **attributes)
|
|||||||
|
|
||||||
XMPPStanza *stanza = stanza_new(element, attributes);
|
XMPPStanza *stanza = stanza_new(element, attributes);
|
||||||
|
|
||||||
if (depth == 0) {
|
// depth == 1 means we're at wrapper level, so this is a root XMPP stanza
|
||||||
|
if (depth == 1) {
|
||||||
curr_stanza = stanza;
|
curr_stanza = stanza;
|
||||||
curr_stanza->parent = NULL;
|
curr_stanza->parent = NULL;
|
||||||
} else {
|
} else {
|
||||||
@@ -119,15 +157,30 @@ _start_element(void *data, const char *element, const char **attributes)
|
|||||||
static void
|
static void
|
||||||
_end_element(void *data, const char *element)
|
_end_element(void *data, const char *element)
|
||||||
{
|
{
|
||||||
|
// Virtual wrapper element - just track depth
|
||||||
|
if (g_strcmp0(element, "wrapper") == 0) {
|
||||||
|
depth--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
depth--;
|
depth--;
|
||||||
if (depth > 0) {
|
|
||||||
|
// depth == 1 means we're back at wrapper level - stanza is complete
|
||||||
|
if (depth > 1) {
|
||||||
stanza_add_child(curr_stanza->parent, curr_stanza);
|
stanza_add_child(curr_stanza->parent, curr_stanza);
|
||||||
curr_stanza = curr_stanza->parent;
|
curr_stanza = curr_stanza->parent;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_println(STBBR_LOGINFO, "RECV: %s", curr_string->str);
|
char *stanza_str = stanza_to_string(curr_stanza);
|
||||||
|
log_println(STBBR_LOGINFO, "RECV: %s", stanza_str);
|
||||||
|
free(stanza_str);
|
||||||
|
|
||||||
stanzas_add(curr_stanza);
|
stanzas_add(curr_stanza);
|
||||||
|
|
||||||
|
// Clear the string buffer for the next stanza
|
||||||
|
g_string_truncate(curr_string, 0);
|
||||||
|
|
||||||
if (stanza_get_child_by_ns(curr_stanza, "jabber:iq:auth")) {
|
if (stanza_get_child_by_ns(curr_stanza, "jabber:iq:auth")) {
|
||||||
auth_cb(curr_stanza);
|
auth_cb(curr_stanza);
|
||||||
} else {
|
} else {
|
||||||
@@ -139,9 +192,11 @@ _end_element(void *data, const char *element)
|
|||||||
if (query) {
|
if (query) {
|
||||||
query_cb(query, id);
|
query_cb(query, id);
|
||||||
}
|
}
|
||||||
|
// Check for presence with "to" attribute (for MUC join responses)
|
||||||
|
presence_to_callback(curr_stanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
do_reset = 1;
|
// Don't reset parser here - allow multiple root elements for XMPP stanzas
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ verify_last(char *stanza_text)
|
|||||||
log_println(STBBR_LOGINFO, "VERIFY LAST SUCCESS: %s", stanza_text);
|
log_println(STBBR_LOGINFO, "VERIFY LAST SUCCESS: %s", stanza_text);
|
||||||
} else {
|
} else {
|
||||||
log_println(STBBR_LOGINFO, "VERIFY LAST FAIL: %s", stanza_text);
|
log_println(STBBR_LOGINFO, "VERIFY LAST FAIL: %s", stanza_text);
|
||||||
|
// Debug: show what was actually received
|
||||||
|
stanzas_debug_last();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ void stbbr_set_timeout(int seconds);
|
|||||||
int stbbr_auth_passwd(char *password);
|
int stbbr_auth_passwd(char *password);
|
||||||
int stbbr_for_id(char *id, char *stream);
|
int stbbr_for_id(char *id, char *stream);
|
||||||
int stbbr_for_query(char *query, char *stream);
|
int stbbr_for_query(char *query, char *stream);
|
||||||
|
int stbbr_for_presence_to(char *to_jid, char *stream);
|
||||||
|
|
||||||
void stbbr_wait_for(char *id);
|
void stbbr_wait_for(char *id);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user