From 09757da5df1e963ae2acfbcbd60405e316b1dcf6 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 12:10:26 +0100 Subject: [PATCH 1/7] feat: Improve validation of JIDs Add new `jid_is_valid()` function and improve the adherence to XMPP RFCs in jid_create(). Add unit tests for them as well. --- src/xmpp/jid.c | 96 ++++++++++++++++++++++++-------- src/xmpp/jid.h | 1 + tests/unittests/unittests.c | 8 +++ tests/unittests/xmpp/test_jid.c | 99 +++++++++++++++++++++++++++++++++ tests/unittests/xmpp/test_jid.h | 8 +++ 5 files changed, 189 insertions(+), 23 deletions(-) diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c index 96c65f1c..449e1c92 100644 --- a/src/xmpp/jid.c +++ b/src/xmpp/jid.c @@ -43,23 +43,31 @@ #include "common.h" #include "xmpp/jid.h" +#define JID_MAX_PART_LEN 1023 +#define JID_MAX_TOTAL_LEN 3071 + +static gboolean +_is_invalid_local_char(gunichar c) +{ + // RFC 6122: " & ' / : < > @ + // Also space is forbidden. + return (c == ' ' || c == '"' || c == '&' || c == '\'' || c == '/' || c == ':' || c == '<' || c == '>' || c == '@'); +} + Jid* jid_create(const gchar* const str) { - Jid* result = NULL; + if (str == NULL) { + return NULL; + } - /* if str is NULL g_strdup returns NULL */ gchar* trimmed = g_strdup(str); if (trimmed == NULL) { return NULL; } - if (strlen(trimmed) == 0) { - g_free(trimmed); - return NULL; - } - - if (g_str_has_prefix(trimmed, "/") || g_str_has_prefix(trimmed, "@")) { + size_t len = strlen(trimmed); + if (len == 0 || len > JID_MAX_TOTAL_LEN) { g_free(trimmed); return NULL; } @@ -69,39 +77,81 @@ jid_create(const gchar* const str) return NULL; } - result = g_new0(Jid, 1); + Jid* result = g_new0(Jid, 1); result->refcnt = 1; + result->str = trimmed; - gchar* atp = g_utf8_strchr(trimmed, -1, '@'); gchar* slashp = g_utf8_strchr(trimmed, -1, '/'); - gchar* domain_start = trimmed; - - if (atp) { - result->localpart = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, atp)); - domain_start = atp + 1; - } + auto_gchar gchar* bare = NULL; if (slashp) { result->resourcepart = g_strdup(slashp + 1); - result->domainpart = g_utf8_substring(domain_start, 0, g_utf8_pointer_to_offset(domain_start, slashp)); - auto_gchar gchar* barejidraw = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, slashp)); - result->barejid = g_utf8_strdown(barejidraw, -1); + bare = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, slashp)); result->fulljid = g_strdup(trimmed); } else { - result->domainpart = g_strdup(domain_start); - result->barejid = g_utf8_strdown(trimmed, -1); + bare = g_strdup(trimmed); } - if (result->domainpart == NULL) { + // Validate resourcepart + if (result->resourcepart) { + size_t rlen = strlen(result->resourcepart); + if (rlen == 0 || rlen > JID_MAX_PART_LEN) { + jid_destroy(result); + return NULL; + } + } + + gchar* atp = g_utf8_strchr(bare, -1, '@'); + gchar* domain_start = bare; + + if (atp) { + result->localpart = g_utf8_substring(bare, 0, g_utf8_pointer_to_offset(bare, atp)); + domain_start = atp + 1; + + // Validate localpart + size_t llen = strlen(result->localpart); + if (llen == 0 || llen > JID_MAX_PART_LEN) { + jid_destroy(result); + return NULL; + } + + const gchar* p = result->localpart; + while (*p) { + gunichar c = g_utf8_get_char(p); + if (_is_invalid_local_char(c)) { + jid_destroy(result); + return NULL; + } + p = g_utf8_next_char(p); + } + } + + result->domainpart = g_strdup(domain_start); + result->barejid = g_utf8_strdown(bare, -1); + + // Validate domainpart + size_t dlen = result->domainpart ? strlen(result->domainpart) : 0; + if (dlen == 0 || dlen > JID_MAX_PART_LEN) { jid_destroy(result); return NULL; } - result->str = trimmed; + // Domainpart cannot contain @ + if (g_utf8_strchr(result->domainpart, -1, '@')) { + jid_destroy(result); + return NULL; + } return result; } +gboolean +jid_is_valid(const gchar* const str) +{ + auto_jid Jid* jid = jid_create(str); + return (jid != NULL); +} + Jid* jid_create_from_bare_and_resource(const gchar* const barejid, const gchar* const resource) { diff --git a/src/xmpp/jid.h b/src/xmpp/jid.h index ea738a63..2d1aa127 100644 --- a/src/xmpp/jid.h +++ b/src/xmpp/jid.h @@ -52,6 +52,7 @@ struct jid_t typedef struct jid_t Jid; Jid* jid_create(const gchar* const str); +gboolean jid_is_valid(const gchar* const str); Jid* jid_create_from_bare_and_resource(const gchar* const barejid, const gchar* const resource); void jid_destroy(Jid* jid); void jid_ref(Jid* jid); diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index 688a7742..2f1cce20 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -150,6 +150,14 @@ main(int argc, char* argv[]) cmocka_unit_test(jid_create__returns__correct_parts_with_trailing_slash), cmocka_unit_test(jid_fulljid_or_barejid__returns__fulljid_when_exists), cmocka_unit_test(jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists), + cmocka_unit_test(jid_create__returns__null_from_invalid_localpart_chars), + cmocka_unit_test(jid_create__returns__null_from_empty_parts), + cmocka_unit_test(jid_create__returns__null_from_multiple_at_in_bare), + cmocka_unit_test(jid_create__returns__correct_parts_with_at_in_resource_only), + cmocka_unit_test(jid_is_valid__is__true_for_valid_jid), + cmocka_unit_test(jid_is_valid__is__false_for_invalid_jid), + cmocka_unit_test(jid_is_valid__is__false_for_null), + cmocka_unit_test(jid_is_valid__is__false_for_empty_string), cmocka_unit_test(parse_args__returns__null_from_null), cmocka_unit_test(parse_args__returns__null_from_empty), diff --git a/tests/unittests/xmpp/test_jid.c b/tests/unittests/xmpp/test_jid.c index 51f66f08..99bbd231 100644 --- a/tests/unittests/xmpp/test_jid.c +++ b/tests/unittests/xmpp/test_jid.c @@ -234,3 +234,102 @@ jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists(void** state) jid_destroy(jid); } + +void +jid_create__returns__null_from_invalid_localpart_chars(void** state) +{ + // Space + Jid* result = jid_create("user name@domain.com"); + assert_null(result); + + // Double quote + result = jid_create("user\"name@domain.com"); + assert_null(result); + + // Ampersand + result = jid_create("user&name@domain.com"); + assert_null(result); + + // Single quote + result = jid_create("user'name@domain.com"); + assert_null(result); + + // Colon + result = jid_create("user:name@domain.com"); + assert_null(result); + + // Less than + result = jid_create("username@domain.com"); + assert_null(result); +} + +void +jid_create__returns__null_from_empty_parts(void** state) +{ + // Empty local + Jid* result = jid_create("@domain.com"); + assert_null(result); + + // Empty domain (trailing @) + result = jid_create("user@"); + assert_null(result); + + // Empty resource (trailing /) + result = jid_create("user@domain.com/"); + assert_null(result); + + // Empty domain in full jid + result = jid_create("user@/resource"); + assert_null(result); +} + +void +jid_create__returns__null_from_multiple_at_in_bare(void** state) +{ + Jid* result = jid_create("user@domain@other.com"); + assert_null(result); +} + +void +jid_create__returns__correct_parts_with_at_in_resource_only(void** state) +{ + // domain.com/user@host (no localpart) + Jid* result = jid_create("domain.com/user@host"); + assert_non_null(result); + assert_null(result->localpart); + assert_string_equal("domain.com", result->domainpart); + assert_string_equal("user@host", result->resourcepart); + jid_destroy(result); +} + +void +jid_is_valid__is__true_for_valid_jid(void** state) +{ + assert_true(jid_is_valid("myuser@mydomain/laptop")); + assert_true(jid_is_valid("myuser@mydomain")); + assert_true(jid_is_valid("mydomain/laptop")); + assert_true(jid_is_valid("mydomain")); +} + +void +jid_is_valid__is__false_for_invalid_jid(void** state) +{ + assert_false(jid_is_valid("@mydomain")); + assert_false(jid_is_valid("/laptop")); +} + +void +jid_is_valid__is__false_for_null(void** state) +{ + assert_false(jid_is_valid(NULL)); +} + +void +jid_is_valid__is__false_for_empty_string(void** state) +{ + assert_false(jid_is_valid("")); +} diff --git a/tests/unittests/xmpp/test_jid.h b/tests/unittests/xmpp/test_jid.h index 4afccf23..92828758 100644 --- a/tests/unittests/xmpp/test_jid.h +++ b/tests/unittests/xmpp/test_jid.h @@ -26,5 +26,13 @@ void jid_create__returns__correct_parts_with_at_and_slash_in_resource(void** sta void jid_create__returns__correct_parts_with_trailing_slash(void** state); void jid_fulljid_or_barejid__returns__fulljid_when_exists(void** state); void jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists(void** state); +void jid_create__returns__null_from_invalid_localpart_chars(void** state); +void jid_create__returns__null_from_empty_parts(void** state); +void jid_create__returns__null_from_multiple_at_in_bare(void** state); +void jid_create__returns__correct_parts_with_at_in_resource_only(void** state); +void jid_is_valid__is__true_for_valid_jid(void** state); +void jid_is_valid__is__false_for_invalid_jid(void** state); +void jid_is_valid__is__false_for_null(void** state); +void jid_is_valid__is__false_for_empty_string(void** state); #endif From 5bcf859e923317effad7835b998cfed452a16ba2 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 12:35:30 +0100 Subject: [PATCH 2/7] feat: Add jid_is_valid_user_jid() to check for valid user JIDs This one will need a localpart as well. It should be used when we want to message a user for example. While jid_is_valid() also returns TRUE for domain/server JIDs. --- src/xmpp/jid.c | 7 +++++++ src/xmpp/jid.h | 1 + tests/unittests/unittests.c | 3 +++ tests/unittests/xmpp/test_jid.c | 23 +++++++++++++++++++++++ tests/unittests/xmpp/test_jid.h | 3 +++ 5 files changed, 37 insertions(+) diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c index 449e1c92..64425116 100644 --- a/src/xmpp/jid.c +++ b/src/xmpp/jid.c @@ -152,6 +152,13 @@ jid_is_valid(const gchar* const str) return (jid != NULL); } +gboolean +jid_is_valid_user_jid(const gchar* const str) +{ + auto_jid Jid* jid = jid_create(str); + return (jid != NULL && jid->localpart != NULL); +} + Jid* jid_create_from_bare_and_resource(const gchar* const barejid, const gchar* const resource) { diff --git a/src/xmpp/jid.h b/src/xmpp/jid.h index 2d1aa127..938b27aa 100644 --- a/src/xmpp/jid.h +++ b/src/xmpp/jid.h @@ -53,6 +53,7 @@ typedef struct jid_t Jid; Jid* jid_create(const gchar* const str); gboolean jid_is_valid(const gchar* const str); +gboolean jid_is_valid_user_jid(const gchar* const str); Jid* jid_create_from_bare_and_resource(const gchar* const barejid, const gchar* const resource); void jid_destroy(Jid* jid); void jid_ref(Jid* jid); diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index 2f1cce20..e2372745 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -154,6 +154,9 @@ main(int argc, char* argv[]) cmocka_unit_test(jid_create__returns__null_from_empty_parts), cmocka_unit_test(jid_create__returns__null_from_multiple_at_in_bare), cmocka_unit_test(jid_create__returns__correct_parts_with_at_in_resource_only), + cmocka_unit_test(jid_is_valid_user_jid__is__true_for_valid_user_jid), + cmocka_unit_test(jid_is_valid_user_jid__is__false_for_domain_jid), + cmocka_unit_test(jid_is_valid_user_jid__is__false_for_invalid_jid), cmocka_unit_test(jid_is_valid__is__true_for_valid_jid), cmocka_unit_test(jid_is_valid__is__false_for_invalid_jid), cmocka_unit_test(jid_is_valid__is__false_for_null), diff --git a/tests/unittests/xmpp/test_jid.c b/tests/unittests/xmpp/test_jid.c index 99bbd231..421a9321 100644 --- a/tests/unittests/xmpp/test_jid.c +++ b/tests/unittests/xmpp/test_jid.c @@ -306,6 +306,29 @@ jid_create__returns__correct_parts_with_at_in_resource_only(void** state) jid_destroy(result); } +void +jid_is_valid_user_jid__is__true_for_valid_user_jid(void** state) +{ + assert_true(jid_is_valid_user_jid("myuser@mydomain/laptop")); + assert_true(jid_is_valid_user_jid("myuser@mydomain")); +} + +void +jid_is_valid_user_jid__is__false_for_domain_jid(void** state) +{ + assert_false(jid_is_valid_user_jid("mydomain/laptop")); + assert_false(jid_is_valid_user_jid("mydomain")); +} + +void +jid_is_valid_user_jid__is__false_for_invalid_jid(void** state) +{ + assert_false(jid_is_valid_user_jid("@mydomain")); + assert_false(jid_is_valid_user_jid("/laptop")); + assert_false(jid_is_valid_user_jid(NULL)); + assert_false(jid_is_valid_user_jid("")); +} + void jid_is_valid__is__true_for_valid_jid(void** state) { diff --git a/tests/unittests/xmpp/test_jid.h b/tests/unittests/xmpp/test_jid.h index 92828758..8f582413 100644 --- a/tests/unittests/xmpp/test_jid.h +++ b/tests/unittests/xmpp/test_jid.h @@ -30,6 +30,9 @@ void jid_create__returns__null_from_invalid_localpart_chars(void** state); void jid_create__returns__null_from_empty_parts(void** state); void jid_create__returns__null_from_multiple_at_in_bare(void** state); void jid_create__returns__correct_parts_with_at_in_resource_only(void** state); +void jid_is_valid_user_jid__is__true_for_valid_user_jid(void** state); +void jid_is_valid_user_jid__is__false_for_domain_jid(void** state); +void jid_is_valid_user_jid__is__false_for_invalid_jid(void** state); void jid_is_valid__is__true_for_valid_jid(void** state); void jid_is_valid__is__false_for_invalid_jid(void** state); void jid_is_valid__is__false_for_null(void** state); From d6705362a2f9a25e823ce1b513e99ad2532bad19 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 12:36:53 +0100 Subject: [PATCH 3/7] fix: Only allow /msg with valid JIDs Only allow to message someone who has a nick in roster or when we pass a user JID. Otherwise notify the user. --- src/command/cmd_funcs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index d8fb6b4c..7f574aff 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -2155,7 +2155,11 @@ cmd_msg(ProfWin* window, const char* const command, gchar** args) barejid = usr; } - _cmd_msg_chatwin(barejid, msg); + if (jid_is_valid_user_jid(barejid)) { + _cmd_msg_chatwin(barejid, msg); + } else { + cons_show_error("Not a contact or valid JID: %s", barejid); + } return TRUE; } From fa64b135df9915638ae59a741662127085481eab Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 13:03:18 +0100 Subject: [PATCH 4/7] refactor: Centralize validation within jid_is_valid --- src/xmpp/jid.c | 153 ++++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 59 deletions(-) diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c index 64425116..76aab2bf 100644 --- a/src/xmpp/jid.c +++ b/src/xmpp/jid.c @@ -57,7 +57,7 @@ _is_invalid_local_char(gunichar c) Jid* jid_create(const gchar* const str) { - if (str == NULL) { + if (!jid_is_valid(str)) { return NULL; } @@ -66,17 +66,6 @@ jid_create(const gchar* const str) return NULL; } - size_t len = strlen(trimmed); - if (len == 0 || len > JID_MAX_TOTAL_LEN) { - g_free(trimmed); - return NULL; - } - - if (!g_utf8_validate(trimmed, -1, NULL)) { - g_free(trimmed); - return NULL; - } - Jid* result = g_new0(Jid, 1); result->refcnt = 1; result->str = trimmed; @@ -85,78 +74,124 @@ jid_create(const gchar* const str) auto_gchar gchar* bare = NULL; if (slashp) { - result->resourcepart = g_strdup(slashp + 1); + if (strlen(slashp + 1) > 0) { + result->resourcepart = g_strdup(slashp + 1); + result->fulljid = g_strdup(trimmed); + } bare = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, slashp)); - result->fulljid = g_strdup(trimmed); } else { bare = g_strdup(trimmed); } - // Validate resourcepart - if (result->resourcepart) { - size_t rlen = strlen(result->resourcepart); - if (rlen == 0 || rlen > JID_MAX_PART_LEN) { - jid_destroy(result); - return NULL; - } - } - gchar* atp = g_utf8_strchr(bare, -1, '@'); gchar* domain_start = bare; if (atp) { - result->localpart = g_utf8_substring(bare, 0, g_utf8_pointer_to_offset(bare, atp)); + if (g_utf8_pointer_to_offset(bare, atp) > 0) { + result->localpart = g_utf8_substring(bare, 0, g_utf8_pointer_to_offset(bare, atp)); + } domain_start = atp + 1; - - // Validate localpart - size_t llen = strlen(result->localpart); - if (llen == 0 || llen > JID_MAX_PART_LEN) { - jid_destroy(result); - return NULL; - } - - const gchar* p = result->localpart; - while (*p) { - gunichar c = g_utf8_get_char(p); - if (_is_invalid_local_char(c)) { - jid_destroy(result); - return NULL; - } - p = g_utf8_next_char(p); - } } - result->domainpart = g_strdup(domain_start); + if (strlen(domain_start) > 0) { + result->domainpart = g_strdup(domain_start); + } + result->barejid = g_utf8_strdown(bare, -1); - // Validate domainpart - size_t dlen = result->domainpart ? strlen(result->domainpart) : 0; - if (dlen == 0 || dlen > JID_MAX_PART_LEN) { - jid_destroy(result); - return NULL; - } - - // Domainpart cannot contain @ - if (g_utf8_strchr(result->domainpart, -1, '@')) { - jid_destroy(result); - return NULL; - } - return result; } gboolean jid_is_valid(const gchar* const str) { - auto_jid Jid* jid = jid_create(str); - return (jid != NULL); + if (str == NULL) { + return FALSE; + } + + size_t total_len = strlen(str); + if (total_len == 0 || total_len > JID_MAX_TOTAL_LEN) { + return FALSE; + } + + if (!g_utf8_validate(str, -1, NULL)) { + return FALSE; + } + + const gchar* slash = g_utf8_strchr(str, -1, '/'); + const gchar* at = NULL; + + // Find the first '@' that is NOT in the resourcepart + // and ensure there is at most one '@' in the bare JID + const gchar* p = str; + while (*p && (!slash || p < slash)) { + if (g_utf8_get_char(p) == '@') { + if (at == NULL) { + at = p; + } else { + // More than one '@' in bare JID + return FALSE; + } + } + p = g_utf8_next_char(p); + } + + const gchar* domain_start = str; + size_t domain_len = 0; + + // Localpart validation + if (at) { + size_t local_len = at - str; + if (local_len == 0 || local_len > JID_MAX_PART_LEN) { + return FALSE; + } + + const gchar* lp = str; + while (lp < at) { + gunichar c = g_utf8_get_char(lp); + if (_is_invalid_local_char(c)) { + return FALSE; + } + lp = g_utf8_next_char(lp); + } + domain_start = at + 1; + } + + // Resourcepart validation + if (slash) { + domain_len = slash - domain_start; + size_t resource_len = strlen(slash + 1); + if (resource_len == 0 || resource_len > JID_MAX_PART_LEN) { + return FALSE; + } + } else { + domain_len = strlen(domain_start); + } + + // Domainpart validation + if (domain_len == 0 || domain_len > JID_MAX_PART_LEN) { + return FALSE; + } + + return TRUE; } gboolean jid_is_valid_user_jid(const gchar* const str) { - auto_jid Jid* jid = jid_create(str); - return (jid != NULL && jid->localpart != NULL); + if (!jid_is_valid(str)) { + return FALSE; + } + + const gchar* slash = g_utf8_strchr(str, -1, '/'); + const gchar* at = g_utf8_strchr(str, -1, '@'); + + // A user JID must have an '@' before any '/' + if (at && (!slash || at < slash)) { + return TRUE; + } + + return FALSE; } Jid* From 35a2f2990ae4880a8c92cdacd3925db9a0c38f65 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 13:09:49 +0100 Subject: [PATCH 5/7] fix: Correct handling of create_fulljid without resource If resource was NULL/"" we otherwise had a trailing slash. --- src/xmpp/jid.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c index 76aab2bf..26dfabdc 100644 --- a/src/xmpp/jid.c +++ b/src/xmpp/jid.c @@ -250,7 +250,11 @@ gchar* create_fulljid(const gchar* const barejid, const gchar* const resource) { auto_gchar gchar* barejidlower = g_utf8_strdown(barejid, -1); - return g_strdup_printf("%s/%s", barejidlower, resource); + if (resource && strlen(resource) > 0) { + return g_strdup_printf("%s/%s", barejidlower, resource); + } else { + return g_strdup(barejidlower); + } } /* From f251cc8bb30e4f22c454080e56a8ed91a7ffe45b Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 13:17:13 +0100 Subject: [PATCH 6/7] feat: handle and allow JIDs with trailing slashes Make jid_is_valid() allow JIDs ending with a slash (`user@domain/`). In these cases, the parser now treats the input as a Bare JID with no resource part, rather than rejecting it as invalid. We will then just get `user@domain`. --- src/xmpp/jid.c | 4 ++-- tests/unittests/unittests.c | 1 + tests/unittests/xmpp/test_jid.c | 14 ++++++++++---- tests/unittests/xmpp/test_jid.h | 1 + 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c index 26dfabdc..58b602bd 100644 --- a/src/xmpp/jid.c +++ b/src/xmpp/jid.c @@ -157,11 +157,11 @@ jid_is_valid(const gchar* const str) domain_start = at + 1; } - // Resourcepart validation + // Resourcepart validation if present if (slash) { domain_len = slash - domain_start; size_t resource_len = strlen(slash + 1); - if (resource_len == 0 || resource_len > JID_MAX_PART_LEN) { + if (resource_len > JID_MAX_PART_LEN) { return FALSE; } } else { diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index e2372745..ee81d4f8 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -150,6 +150,7 @@ main(int argc, char* argv[]) cmocka_unit_test(jid_create__returns__correct_parts_with_trailing_slash), cmocka_unit_test(jid_fulljid_or_barejid__returns__fulljid_when_exists), cmocka_unit_test(jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists), + cmocka_unit_test(jid_create__returns__bare_from_trailing_slash), cmocka_unit_test(jid_create__returns__null_from_invalid_localpart_chars), cmocka_unit_test(jid_create__returns__null_from_empty_parts), cmocka_unit_test(jid_create__returns__null_from_multiple_at_in_bare), diff --git a/tests/unittests/xmpp/test_jid.c b/tests/unittests/xmpp/test_jid.c index 421a9321..7a643d68 100644 --- a/tests/unittests/xmpp/test_jid.c +++ b/tests/unittests/xmpp/test_jid.c @@ -235,6 +235,16 @@ jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists(void** state) jid_destroy(jid); } +void +jid_create__returns__bare_from_trailing_slash(void** state) +{ + Jid* result = jid_create("myuser@mydomain/"); + assert_non_null(result); + assert_string_equal("myuser@mydomain", result->barejid); + assert_null(result->resourcepart); + jid_destroy(result); +} + void jid_create__returns__null_from_invalid_localpart_chars(void** state) { @@ -278,10 +288,6 @@ jid_create__returns__null_from_empty_parts(void** state) result = jid_create("user@"); assert_null(result); - // Empty resource (trailing /) - result = jid_create("user@domain.com/"); - assert_null(result); - // Empty domain in full jid result = jid_create("user@/resource"); assert_null(result); diff --git a/tests/unittests/xmpp/test_jid.h b/tests/unittests/xmpp/test_jid.h index 8f582413..f8c6cb99 100644 --- a/tests/unittests/xmpp/test_jid.h +++ b/tests/unittests/xmpp/test_jid.h @@ -26,6 +26,7 @@ void jid_create__returns__correct_parts_with_at_and_slash_in_resource(void** sta void jid_create__returns__correct_parts_with_trailing_slash(void** state); void jid_fulljid_or_barejid__returns__fulljid_when_exists(void** state); void jid_fulljid_or_barejid__returns__barejid_when_fulljid_not_exists(void** state); +void jid_create__returns__bare_from_trailing_slash(void** state); void jid_create__returns__null_from_invalid_localpart_chars(void** state); void jid_create__returns__null_from_empty_parts(void** state); void jid_create__returns__null_from_multiple_at_in_bare(void** state); From 40963c702e8e284b694195d6dd16d050eab182f4 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Fri, 6 Mar 2026 14:49:26 +0100 Subject: [PATCH 7/7] fix: database return NULL if no history limits are found log_database_get_limits_info should return NULL when a contact has no history in the database. We hit a bug with `/msg adsf@asd` before. --- src/database.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/database.c b/src/database.c index c30d5271..efeb6d32 100644 --- a/src/database.c +++ b/src/database.c @@ -309,6 +309,9 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las msg->stanzaid = _db_strdup(archive_id); msg->timestamp = g_date_time_new_from_iso8601(date, NULL); + } else { + message_free(msg); + msg = NULL; } sqlite3_finalize(stmt);