From 2705c070071e54431811bf0b5d4025e04bce50c6 Mon Sep 17 00:00:00 2001 From: defanor Date: Sat, 25 Sep 2021 16:16:32 +0300 Subject: Add recursive feature search (rexmpp_disco_find_feature) --- README | 3 +- src/rexmpp.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++------------- src/rexmpp.h | 31 ++++++++++- 3 files changed, 170 insertions(+), 41 deletions(-) diff --git a/README b/README index 0a4095e..418e5ac 100644 --- a/README +++ b/README @@ -62,7 +62,8 @@ A rough roadmap: [+] XMPP IM (RFC 6121): roster management (loading and pushes, with versioning and caching) -[+] XEP-0030 v2.5: Service Discovery (replying to queries) +[+] XEP-0030 v2.5: Service Discovery (replying to queries, recursively + searching for features) [+] XEP-0115 v1.5: Entity Capabilities (including into initial presence) [+] XEP-0092 v1.1: Software Version [+] XEP-0172 v1.1: User Nickname diff --git a/src/rexmpp.c b/src/rexmpp.c index b0286c1..6508c9e 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -40,6 +40,16 @@ struct rexmpp_iq_cacher { void *cb_data; }; +struct rexmpp_feature_search { + const char *feature_var; + int max_requests; + int pending; + rexmpp_iq_callback_t cb; + void *cb_data; + int fresh; + int found; +}; + const char *rexmpp_strerror (rexmpp_err_t error) { switch (error) { case REXMPP_SUCCESS: return "No error"; @@ -309,6 +319,12 @@ xmlNodePtr rexmpp_xml_feature (const char *var) { return feature; } +xmlNodePtr rexmpp_xml_new_node (const char *name, const char *namespace) { + xmlNodePtr node = xmlNewNode(NULL, name); + xmlNewNs(node, namespace, NULL); + return node; +} + xmlNodePtr rexmpp_xml_error (const char *type, const char *condition) { xmlNodePtr error = xmlNewNode(NULL, "error"); xmlNewProp(error, "type", type); @@ -318,6 +334,108 @@ xmlNodePtr rexmpp_xml_error (const char *type, const char *condition) { return error; } +void rexmpp_disco_find_feature_cb (rexmpp_t *s, + void *ptr, + xmlNodePtr request, + xmlNodePtr response, + int success) +{ + struct rexmpp_feature_search *search = ptr; + if (! success) { + char *to = xmlGetProp(request, "to"); + xmlNodePtr query = xmlFirstElementChild(request); + rexmpp_log(s, LOG_ERR, "Failed to query %s for %s.", to, query->nsDef->href); + free(to); + } else if (! search->found) { + xmlNodePtr query = xmlFirstElementChild(response); + if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", + "query")) { + xmlNodePtr child = xmlFirstElementChild(query); + while (child != NULL && (! search->found)) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", + "feature")) { + char *var = xmlGetProp(child, "var"); + if (var != NULL) { + if (strcmp(var, search->feature_var) == 0) { + search->cb(s, search->cb_data, request, response, success); + search->found = 1; + } + free(var); + } + } + child = child->next; + } + if ((! search->found) && (search->max_requests > 0)) { + /* Still not found, request items */ + char *jid = xmlGetProp(request, "to"); + if (jid != NULL) { + search->pending++; + search->max_requests--; + xmlNodePtr query = + rexmpp_xml_new_node("query", + "http://jabber.org/protocol/disco#items"); + rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, + search, search->fresh); + free(jid); + } + } + } else if (rexmpp_xml_match(query, + "http://jabber.org/protocol/disco#items", + "query")) { + xmlNodePtr child = xmlFirstElementChild(query); + while (child != NULL && (search->max_requests > 0)) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#items", + "item")) { + char *jid = xmlGetProp(child, "jid"); + if (jid != NULL) { + search->pending++; + search->max_requests--; + xmlNodePtr query = + rexmpp_xml_new_node("query", + "http://jabber.org/protocol/disco#info"); + rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, + search, search->fresh); + } + } + child = child->next; + } + } + } + search->pending--; + if (search->pending == 0) { + if (! search->found) { + search->cb(s, search->cb_data, NULL, NULL, 0); + } + free(search); + } +} + +rexmpp_err_t +rexmpp_disco_find_feature (rexmpp_t *s, + const char *jid, + const char *feature_var, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh, + int max_requests) +{ + struct rexmpp_feature_search *search = + malloc(sizeof(struct rexmpp_feature_search)); + search->max_requests = max_requests - 1; + search->found = 0; + search->pending = 1; + search->cb = cb; + search->cb_data = cb_data; + search->fresh = fresh; + search->feature_var = feature_var; + xmlNodePtr query = + rexmpp_xml_new_node("query", "http://jabber.org/protocol/disco#info"); + return rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, search, fresh); +} + xmlNodePtr rexmpp_disco_info (rexmpp_t *s) { if (s->disco_info != NULL) { return s->disco_info; @@ -390,7 +508,6 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->socks_host = NULL; s->server_host = NULL; s->enable_carbons = 1; - s->enable_service_discovery = 1; s->manage_roster = 1; s->roster_cache_file = NULL; s->track_roster_presence = 1; @@ -714,7 +831,7 @@ int rexmpp_xml_match (xmlNodePtr node, return 0; } } else { - if (strcmp(namespace, node->ns->href) != 0) { + if (strcmp(namespace, node->nsDef->href) != 0) { return 0; } } @@ -1494,40 +1611,22 @@ void rexmpp_pong (rexmpp_t *s, s->ping_requested = 0; } -void rexmpp_iq_discovery_info (rexmpp_t *s, - void *ptr, - xmlNodePtr req, - xmlNodePtr response, - int success) -{ +void rexmpp_disco_carbons_cb (rexmpp_t *s, + void *ptr, + xmlNodePtr req, + xmlNodePtr response, + int success) { (void)ptr; (void)req; - if (! success) { - rexmpp_log(s, LOG_ERR, "Failed to discover features"); - return; - } - xmlNodePtr query = xmlFirstElementChild(response); - if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", - "query")) { - xmlNodePtr child; - for (child = xmlFirstElementChild(query); - child != NULL; - child = xmlNextElementSibling(child)) - { - if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", - "feature")) { - char *var = xmlGetProp(child, "var"); - if (s->enable_carbons && - strcmp(var, "urn:xmpp:carbons:2") == 0) { - xmlNodePtr carbons_enable = xmlNewNode(NULL, "enable"); - xmlNewNs(carbons_enable, "urn:xmpp:carbons:2", NULL); - s->carbons_state = REXMPP_CARBONS_NEGOTIATION; - rexmpp_iq_new(s, "set", NULL, carbons_enable, - rexmpp_carbons_enabled, NULL); - } - free(var); - } - } + (void)response; + if (success) { + xmlNodePtr carbons_enable = + rexmpp_xml_new_node("enable", "urn:xmpp:carbons:2"); + s->carbons_state = REXMPP_CARBONS_NEGOTIATION; + rexmpp_iq_new(s, "set", NULL, carbons_enable, + rexmpp_carbons_enabled, NULL); + } else { + rexmpp_log(s, LOG_WARNING, "Failed to discover the carbons feature."); } } @@ -1535,11 +1634,11 @@ void rexmpp_stream_is_ready(rexmpp_t *s) { s->stream_state = REXMPP_STREAM_READY; rexmpp_resend_stanzas(s); - if (s->enable_service_discovery) { - xmlNodePtr disco_query = xmlNewNode(NULL, "query"); - xmlNewNs(disco_query, "http://jabber.org/protocol/disco#info", NULL); - rexmpp_iq_new(s, "get", s->initial_jid.domain, - disco_query, rexmpp_iq_discovery_info, NULL); + if (s->enable_carbons) { + rexmpp_disco_find_feature (s, s->initial_jid.domain, + "urn:xmpp:carbons:2", + rexmpp_disco_carbons_cb, + NULL, 0, 1); } if (s->manage_roster) { if (s->roster_cache_file != NULL) { diff --git a/src/rexmpp.h b/src/rexmpp.h index addd4d4..627ec50 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -260,7 +260,6 @@ struct rexmpp /* Various knobs (these are used instead of loadable modules). */ int enable_carbons; /* XEP-0280 */ - int enable_service_discovery; /* XEP-0030 */ int manage_roster; const char *roster_cache_file; int track_roster_presence; @@ -422,6 +421,7 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node); @param[in] payload IQ payload, the library assumes ownership of it. @param[in] cb A ::rexmpp_iq_callback_t function to call on reply (or if we will give up on it), can be NULL. + @param[in] cb_data A data pointer to pass to cb. This function is specifically for IQs that should be tracked by the library. If an application wants to track replies on its own, it @@ -547,6 +547,8 @@ xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node, const char *namespace, const char *name); +xmlNodePtr rexmpp_xml_new_node (const char *name, const char *namespace); + /** @brief Finds a PEP event. @param[in] s ::rexmpp @@ -570,4 +572,31 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len); */ const char *rexmpp_strerror (rexmpp_err_t error); + +/** + @brief Recurisevly searches for a given feature, using service + discovery, starting from a given JID. If it finds such a feature, + it call the provided callback, providing it both IQ request and + response for the entity that provided the feature; if the feature + isn't found, it calls the callback with NULL values. + + @param[in,out] s ::rexmpp + @param[in] jid An XMPP address to start searching from. + @param[in] feature_var A feature to search for. + @param[in] cb A ::rexmpp_iq_callback_t function to call on reply. + @param[in] cb_data A data pointer to pass to cb. + @param[in] fresh Force a new request, instead of looking up the + cache. + @param[in] max_requests Maximum number of IQ requests to perform + before giving up. +*/ +rexmpp_err_t +rexmpp_disco_find_feature (rexmpp_t *s, + const char *jid, + const char *feature_var, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh, + int max_requests); + #endif -- cgit v1.2.3