add xmpp_stanza_get_child_by_path()

... to provide an easier way to find long paths.

After looking at some code in profanity-im/profanity#1605 I thought this
could help to make libstrophe-user code easier to write (and read).

Initially it started off as `xmpp_stanza_get_child_by_xpath()` but then
I was annoyed when it came to handling all those potential corner cases
like e.g. escaping URL's in the ns ... so here we go with a
vararg approach :)

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
This commit is contained in:
Steffen Jaeckel
2021-12-01 15:55:35 +01:00
parent b0631e322f
commit 28f3ce19b8
4 changed files with 140 additions and 0 deletions

View File

@@ -1,5 +1,7 @@
0.12.0
- Add code coverage support
- New API:
- xmpp_stanza_get_child_by_path()
0.11.0
- SASL EXTERNAL support (XEP-0178)

View File

@@ -844,6 +844,82 @@ const char *xmpp_stanza_get_from(xmpp_stanza_t *stanza)
return xmpp_stanza_get_attribute(stanza, "from");
}
/** Get the first child of stanza following a path-like list of names.
* This function searches the children and their children that match
* the given path.
*
* * "name" - Search 'name'
*
* * "name[@ns='foo']" - Search 'name' which is in the namespace 'foo'
*
* The Syntax to pass namespaces is inspired by the XPATH way of passing
* attributes.
*
* The namespace syntax only supports single quotes `'`.
*
* The \ref XMPP_STANZA_NAME_IN_NS macro is provided as a helper for names
* in namespaces.
*
* @param stanza a Strophe stanza object
* @param ... a var-args list that must be terminated by a NULL entry
*
* @return the matching child stanza object or NULL if no match was found
*
* @ingroup Stanza
*/
xmpp_stanza_t *xmpp_stanza_get_child_by_path(xmpp_stanza_t *stanza, ...)
{
xmpp_stanza_t *child = NULL;
char *p, *tok, *attr, *saveattr, *ns = NULL;
const char *xmlns;
va_list ap;
va_start(ap, stanza);
while ((p = va_arg(ap, char *)) != NULL) {
tok = xmpp_strdup(stanza->ctx, p);
if (!tok) {
child = NULL;
break;
}
saveattr = ns = NULL;
attr = xmpp_strtok_r(tok, "[", &saveattr);
if (attr) {
attr = xmpp_strtok_r(NULL, "]", &saveattr);
if (attr) {
if (!strncmp(attr, "@ns='", 5)) {
ns = attr + 5;
xmpp_strtok_r(ns, "'", &saveattr);
}
}
}
if (!child) {
if (strcmp(xmpp_stanza_get_name(stanza), tok))
goto error_out;
if (ns) {
xmlns = xmpp_stanza_get_ns(stanza);
if (!xmlns || strcmp(xmlns, ns))
goto error_out;
}
child = stanza;
} else {
if (!ns)
child = xmpp_stanza_get_child_by_name(child, tok);
else
child = xmpp_stanza_get_child_by_name_and_ns(child, tok, ns);
}
error_out:
xmpp_free(stanza->ctx, tok);
if (!child)
break;
}
va_end(ap);
return p == NULL ? child : NULL;
}
/** Get the first child of stanza with name.
* This function searches all the immediate children of stanza for a child
* stanza that matches the name. The first matching child is returned.

View File

@@ -406,6 +406,9 @@ xmpp_stanza_t *xmpp_stanza_get_child_by_ns(xmpp_stanza_t *stanza,
xmpp_stanza_t *xmpp_stanza_get_child_by_name_and_ns(xmpp_stanza_t *stanza,
const char *name,
const char *ns);
/* helper macro for names with a namespace */
#define XMPP_STANZA_NAME_IN_NS(name, ns) name "[@ns='" ns "']"
xmpp_stanza_t *xmpp_stanza_get_child_by_path(xmpp_stanza_t *stanza, ...);
xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t *stanza);
int xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child);
int xmpp_stanza_add_child_ex(xmpp_stanza_t *stanza,

View File

@@ -123,6 +123,7 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
xmpp_stanza_t *stanza;
xmpp_stanza_t *error;
xmpp_stanza_t *item;
xmpp_stanza_t *mood;
char *buf;
size_t buflen;
const char *attr[10];
@@ -135,12 +136,29 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
static const char *str_error =
"<error type=\"cancel\"><service-unavailable "
"xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/></error>";
// clang-format off
static const char *str_mood =
"<iq from='juliet@capulet.lit/balcony' id='publish1' type='set'>"
"<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
"<publish node='http://jabber.org/protocol/mood'>"
"<item>"
"<mood xmlns='http://jabber.org/protocol/mood'>"
"<annoyed/>"
"<text>curse my nurse!</text>"
"</mood>"
"</item>"
"</publish>"
"</pubsub>"
"</iq>";
// clang-format on
stanza = xmpp_stanza_new_from_string(ctx, str);
assert(stanza != NULL);
error =
xmpp_stanza_reply_error(stanza, "cancel", "service-unavailable", NULL);
assert(error != NULL);
mood = xmpp_stanza_new_from_string(ctx, str_mood);
assert(stanza != NULL);
assert(xmpp_stanza_get_to(error) != NULL);
COMPARE("romeo@montague.lit/home", xmpp_stanza_get_to(error));
@@ -151,6 +169,46 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
assert(xmpp_stanza_get_type(error) != NULL);
COMPARE("error", xmpp_stanza_get_type(error));
/* FAIL - no list given */
item = xmpp_stanza_get_child_by_path(mood, NULL);
assert(item == NULL);
/* FAIL - first entry doesn't match */
item = xmpp_stanza_get_child_by_path(mood, "foo", NULL);
assert(item == NULL);
/* FAIL - 'iq' has no namespace */
item = xmpp_stanza_get_child_by_path(
mood, XMPP_STANZA_NAME_IN_NS("iq", "foobar"),
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/pubsub"),
"publish", "item", "mood", NULL);
assert(item == NULL);
/* FAIL - 'pubsub' is in another namespace */
item = xmpp_stanza_get_child_by_path(
mood, "iq",
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/foobar"),
"publish", "item", "mood", NULL);
assert(item == NULL);
item = xmpp_stanza_get_child_by_path(mood, "iq", "pubsub", "publish",
"item", "mood", NULL);
assert(item != NULL);
assert(xmpp_stanza_get_children(item) != NULL);
assert(xmpp_stanza_get_name(xmpp_stanza_get_children(item)) != NULL);
COMPARE("annoyed", xmpp_stanza_get_name(xmpp_stanza_get_children(item)));
item = xmpp_stanza_get_child_by_path(
mood, "iq",
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/pubsub"),
"publish", "item",
XMPP_STANZA_NAME_IN_NS("mood", "http://jabber.org/protocol/mood"),
NULL);
assert(item != NULL);
assert(xmpp_stanza_get_children(item) != NULL);
assert(xmpp_stanza_get_name(xmpp_stanza_get_children(item)) != NULL);
COMPARE("annoyed", xmpp_stanza_get_name(xmpp_stanza_get_children(item)));
ret = xmpp_stanza_get_attributes(error, attr, attrlen);
/* attr contains both attribute name and value. */
assert(ret == 8);
@@ -163,6 +221,7 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
COMPARE(str_error, buf);
xmpp_free(ctx, buf);
xmpp_stanza_release(mood);
xmpp_stanza_release(stanza);
xmpp_stanza_release(error);
}