summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2021-09-25 16:16:32 +0300
committerdefanor <defanor@uberspace.net>2021-09-25 16:16:32 +0300
commit2705c070071e54431811bf0b5d4025e04bce50c6 (patch)
tree74c867541d52b1aa471d2eed0a179be83538bc12
parent6efd6ba8e375b25ac34224e16b191ea11fd5ca0f (diff)
Add recursive feature search (rexmpp_disco_find_feature)
-rw-r--r--README3
-rw-r--r--src/rexmpp.c177
-rw-r--r--src/rexmpp.h31
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