Compare commits

..

10 Commits

Author SHA1 Message Date
14fb6e3b41 fix(disco): clean up autoping warning message handling
Remove unused return value from _disco_autoping_warning_message by
changing its return type to void. Rename local variable from
users_prefers_warning to user_prefers_warning for correct grammar.

Use g_ascii_strcasecmp instead of g_strcmp0 for the domain match
check, so the warning fires correctly regardless of case differences
between the stanza 'from' field and the bound domain.

Remove duplicate autoping warning display from cons_notify_setting(),
keeping it only in cons_autoping_setting() where it belongs.
2026-06-23 10:57:59 +00:00
19bfad76ba test(autoping): add test for autoping warning exclusion on subdomain services
All checks were successful
CI Code / Check spelling (pull_request) Successful in 15s
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Code Coverage (pull_request) Successful in 3m47s
CI Code / Linux (debian) (pull_request) Successful in 5m11s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m16s
CI Code / Linux (arch) (pull_request) Successful in 7m9s
The new test verifies that disco#info responses from subdomain services
(e.g., conference servers) do not trigger the autoping warning, ensuring
the check is correctly restricted to the server's own domain.
2026-06-22 09:17:49 +00:00
b5bbad8d2c fix(xmpp): prevent autoping warning repetition on disco items
All checks were successful
CI Code / Check spelling (pull_request) Successful in 17s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 4m0s
CI Code / Linux (debian) (pull_request) Successful in 5m5s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m12s
CI Code / Linux (arch) (pull_request) Successful in 6m55s
The previous logic triggered warnings for disco items. This change restricts the check to only the connection domain, ensuring that auto-discovered items do not trigger duplicate warnings.
2026-06-21 15:37:16 +00:00
7fd0cded3d test(autoping): add test for autoping warning exclusion on user JIDs
All checks were successful
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 3m33s
CI Code / Linux (debian) (pull_request) Successful in 5m7s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m12s
CI Code / Linux (arch) (pull_request) Successful in 7m9s
2026-06-20 20:47:05 +00:00
2012e15c0b fix(disco): avoid autoping warning on disco#info responses from users
The autoping availability warning should only trigger for disco#info
responses from the XMPP server (no 'from' attribute), otherwise the
warning message is displayed excessive amount of times.

Refs #80
2026-06-20 19:32:40 +00:00
45b81cf2de test(autoping): cover availability-warning conditions
All checks were successful
CI Code / Check spelling (pull_request) Successful in 15s
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Code Coverage (pull_request) Successful in 3m57s
CI Code / Linux (debian) (pull_request) Successful in 4m58s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m8s
CI Code / Linux (arch) (pull_request) Successful in 6m58s
Functional tests for the on-connect warning: shown when the server advertises
urn:xmpp:ping while autoping is disabled and the warning preference is on;
suppressed when ping is unsupported, when autoping is enabled, or when the user
turned the warning off.

Refs #80
2026-06-20 14:39:32 +03:00
15a1a343d2 fix(autoping): align warning command with on|off
The warning toggle uses _cmd_set_boolean_preference (accepts on|off) and the
subcommand is "warning", but the help syntax, console status lines and warning
text advertised "warning enable|disable" and "/autoping warn", none of which
work.

- cmd_defs: document "/autoping warning on|off"
- console: show "(/autoping warning)" in the status lines
- iq: point the warning text at "/autoping warning off"
- cmd_ac: register "warning" and complete on|off via a dedicated
  _autoping_autocomplete (moved off the generic single-level table)
2026-06-20 14:39:32 +03:00
0f7741fdfa feat: Autoping availability warning 2026-06-20 14:39:32 +03:00
830479cf20 fix: OTR/presence/OMEMO correctness, stanza-id disco gate, build hardening
All checks were successful
CI Code / Check spelling (push) Successful in 17s
CI Code / Check coding style (push) Successful in 32s
CI Code / Code Coverage (push) Successful in 3m45s
CI Code / Linux (debian) (push) Successful in 4m42s
CI Code / Linux (ubuntu) (push) Successful in 4m49s
CI Code / Linux (arch) (push) Successful in 6m25s
- OTR: strip the whitespace tag by shifting the full message tail incl. the NUL, not tag_length bytes, so the body is no longer duplicated for messages longer than the tag.
- presence: snapshot the resource fields before connection_add_available_resource() takes ownership, removing a use-after-free in the own-presence path.
- OMEMO: propagate _omemo_finalize_identity_load() failure on connect (log + cons_show_error + stop) instead of leaving OMEMO silently unavailable.
- OMEMO: guard NULL fingerprint decode in _omemo_fingerprint_decode / omemo_is_trusted_identity / omemo_trust and log every decode failure instead of failing silently.
- stanza-id: gate XEP-0359 dedup on disco urn:xmpp:sid:0 (the `by` JID or its domain), falling back to no-dedup when caps are unknown; add functional tests for trusted vs untrusted server.
- accounts: narrow the group-name sanitizer to the characters GKeyFile forbids in headers ([ ] \n \r), keeping `=` and `#`, so read/write stays symmetric.
- build: re-introduce compiler/sanitizer flags in a Pikaur-safe form (opt-in sanitizers, -Wsign-compare) and fix the resulting -Wsign-compare warnings (incl. proftest _mkdir_recursive).fix: OTR/presence/OMEMO correctness, stanza-id disco gate, build hardening

Author: jabber.developer2 <jabber.developer2@jabber.space>
2026-06-20 10:30:23 +00:00
2d3d1ced71 ref(ui): use MAX utility method for code conciseness and clarity
All checks were successful
CI Code / Check coding style (push) Successful in 1m28s
CI Code / Check spelling (push) Successful in 2m20s
CI Code / Linux (ubuntu) (push) Successful in 5m16s
CI Code / Code Coverage (push) Successful in 3m51s
CI Code / Linux (debian) (push) Successful in 5m52s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Check spelling (pull_request) Successful in 23s
CI Code / Linux (arch) (push) Successful in 8m34s
CI Code / Linux (debian) (pull_request) Successful in 4m25s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m35s
CI Code / Code Coverage (pull_request) Successful in 3m30s
CI Code / Linux (arch) (pull_request) Successful in 8m55s
2026-06-20 09:40:11 +00:00
27 changed files with 445 additions and 59 deletions

View File

@@ -71,6 +71,10 @@ AC_ARG_ENABLE([omemo-qrcode],
[AS_HELP_STRING([--enable-omemo-qrcode], [enable ability to display omemo qr code])])
AC_ARG_ENABLE([coverage],
[AS_HELP_STRING([--enable-coverage], [enable code coverage analysis])])
AC_ARG_ENABLE([sanitizers],
[AS_HELP_STRING([--enable-sanitizers], [enable AddressSanitizer + UndefinedBehaviorSanitizer + unsigned-integer-overflow])])
AC_ARG_ENABLE([hardening],
[AS_HELP_STRING([--enable-hardening], [enable extra hardening warnings (e.g. -Wnull-dereference) that may produce false positives under -O2])])
m4_include([m4/ax_valgrind_check.m4])
AX_VALGRIND_DFLT([drd], [off])
@@ -395,7 +399,7 @@ AC_SUBST([FORKPTY_LIB])
## Default parameters
AM_CFLAGS="$AM_CFLAGS -Wall -Wextra -Wformat=2 -Wno-format-zero-length"
AM_CFLAGS="$AM_CFLAGS -Wno-deprecated-declarations -Wno-unused-parameter -Wno-missing-field-initializers -Wno-sign-compare -Wno-cast-function-type"
AM_CFLAGS="$AM_CFLAGS -Wno-deprecated-declarations -Wno-unused-parameter -Wno-missing-field-initializers -Wno-cast-function-type"
AM_CFLAGS="$AM_CFLAGS -Wpointer-arith"
AM_CFLAGS="$AM_CFLAGS -Wimplicit-function-declaration"
AM_CFLAGS="$AM_CFLAGS -Wundef"
@@ -406,7 +410,8 @@ AM_CFLAGS="$AM_CFLAGS -std=gnu99 -ggdb3"
# GCC-specific warnings (not supported by clang) — test each one
saved_CFLAGS="$CFLAGS"
for _flag in -Wlogical-op -Wduplicated-cond -Wduplicated-branches \
-Wstringop-overflow -Warray-bounds=2 -Walloc-zero; do
-Wstringop-overflow -Warray-bounds=2 -Walloc-zero \
-Wsign-compare; do
AC_MSG_CHECKING([whether $CC supports $_flag])
CFLAGS="$saved_CFLAGS $_flag -Werror"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
@@ -433,11 +438,46 @@ AS_IF([test "x$enable_coverage" = xyes],
AM_LDFLAGS="$AM_LDFLAGS --coverage"
AC_MSG_NOTICE([Code coverage analysis enabled])])
AS_IF([test "x$enable_sanitizers" = xyes],
[SAN_FLAGS="-fsanitize=address,undefined"
saved_CFLAGS="$CFLAGS"
CFLAGS="$saved_CFLAGS -fsanitize=unsigned-integer-overflow -Werror"
AC_MSG_CHECKING([whether $CC supports -fsanitize=unsigned-integer-overflow])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
[AC_MSG_RESULT([yes]); SAN_FLAGS="$SAN_FLAGS,unsigned-integer-overflow"],
[AC_MSG_RESULT([no])])
CFLAGS="$saved_CFLAGS"
AM_CFLAGS="$AM_CFLAGS $SAN_FLAGS -fno-sanitize-recover=all -fno-omit-frame-pointer"
AM_LDFLAGS="$AM_LDFLAGS -fsanitize=address,undefined"
AC_MSG_NOTICE([Sanitizers enabled: $SAN_FLAGS])])
AS_IF([test "x$enable_hardening" = xyes],
[saved_CFLAGS="$CFLAGS"
for _flag in -Wnull-dereference; do
AC_MSG_CHECKING([whether $CC supports $_flag])
CFLAGS="$saved_CFLAGS $_flag -Werror"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
[AC_MSG_RESULT([yes]); AM_CFLAGS="$AM_CFLAGS $_flag"],
[AC_MSG_RESULT([no])])
done
CFLAGS="$saved_CFLAGS"
AC_MSG_NOTICE([Extra hardening warnings enabled])])
# Skip _FORTIFY_SOURCE=2 if env already set one (Pikaur/makepkg) or under coverage -O0 (FORTIFY needs -O>0).
AS_IF([test "x$enable_coverage" != xyes],
[AS_CASE([" ${CPPFLAGS} ${CFLAGS} "],
[*_FORTIFY_SOURCE=*], [AC_MSG_NOTICE([_FORTIFY_SOURCE preset from environment, leaving as-is])],
[AM_CFLAGS="$AM_CFLAGS -D_FORTIFY_SOURCE=2"])])
AS_IF([test "x$PACKAGE_STATUS" = xdevelopment],
[AM_CFLAGS="$AM_CFLAGS -Wunused -Werror"])
AS_IF([test "x$PLATFORM" = xosx],
[AM_CFLAGS="$AM_CFLAGS -Qunused-arguments"])
# Treat glib/gio headers as system to keep their macros out of our -W* set.
glib_CFLAGS=$(echo "$glib_CFLAGS" | sed 's/-I/-isystem /g')
gio_CFLAGS=$(echo "$gio_CFLAGS" | sed 's/-I/-isystem /g')
AM_CFLAGS="$AM_CFLAGS $PTHREAD_CFLAGS $glib_CFLAGS $gio_CFLAGS $curl_CFLAGS ${SQLITE_CFLAGS}"
AM_CFLAGS="$AM_CFLAGS $libnotify_CFLAGS ${GTK_CFLAGS} $python_CFLAGS"
AM_CFLAGS="$AM_CFLAGS -DTHEMES_PATH=\"\\\"$THEMES_PATH\\\"\" -DICONS_PATH=\"\\\"$ICONS_PATH\\\"\" -DGLOBAL_PYTHON_PLUGINS_PATH=\"\\\"$GLOBAL_PYTHON_PLUGINS_PATH\\\"\" -DGLOBAL_C_PLUGINS_PATH=\"\\\"$GLOBAL_C_PLUGINS_PATH\\\"\""

View File

@@ -47,6 +47,7 @@ static char* _notify_autocomplete(ProfWin* window, const char* const input, gboo
static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _autoaway_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _autoping_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _autoconnect_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _who_autocomplete(ProfWin* window, const char* const input, gboolean previous);
@@ -1056,6 +1057,7 @@ cmd_ac_init(void)
autocomplete_add(autoping_ac, "set");
autocomplete_add(autoping_ac, "timeout");
autocomplete_add(autoping_ac, "warning");
autocomplete_add(plugins_ac, "install");
autocomplete_add(plugins_ac, "update");
@@ -1407,6 +1409,7 @@ cmd_ac_init(void)
g_hash_table_insert(ac_funcs, "/alias", _alias_autocomplete);
g_hash_table_insert(ac_funcs, "/autoaway", _autoaway_autocomplete);
g_hash_table_insert(ac_funcs, "/autoconnect", _autoconnect_autocomplete);
g_hash_table_insert(ac_funcs, "/autoping", _autoping_autocomplete);
g_hash_table_insert(ac_funcs, "/avatar", _avatar_autocomplete);
g_hash_table_insert(ac_funcs, "/ban", _ban_autocomplete);
g_hash_table_insert(ac_funcs, "/blocked", _blocked_autocomplete);
@@ -1934,7 +1937,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
{ "/prefs", prefs_ac },
{ "/disco", disco_ac },
{ "/room", room_ac },
{ "/autoping", autoping_ac },
{ "/mainwin", winpos_ac },
{ "/inputwin", winpos_ac },
};
@@ -4264,6 +4266,20 @@ _executable_autocomplete(ProfWin* window, const char* const input, gboolean prev
return result;
}
static char*
_autoping_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
result = autocomplete_param_with_ac(input, "/autoping", autoping_ac, TRUE, previous);
if (result) {
return result;
}
result = autocomplete_param_with_func(input, "/autoping warning", prefs_autocomplete_boolean_choice, previous, NULL);
return result;
}
static char*
_lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{

View File

@@ -1964,12 +1964,14 @@ static const struct cmd_t command_defs[] = {
CMD_TAG_CONNECTION)
CMD_SYN(
"/autoping set <seconds>",
"/autoping timeout <seconds>")
"/autoping timeout <seconds>",
"/autoping warning on|off")
CMD_DESC(
"Set the interval between sending ping requests to the server to ensure the connection is kept alive.")
CMD_ARGS(
{ "set <seconds>", "Number of seconds between sending pings, a value of 0 disables autoping." },
{ "timeout <seconds>", "Seconds to wait for autoping responses, after which the connection is considered broken." })
{ "timeout <seconds>", "Seconds to wait for autoping responses, after which the connection is considered broken." },
{ "warning on|off", "Enable or disable autoping availability warning." })
},
{ CMD_PREAMBLE("/ping",

View File

@@ -6374,6 +6374,8 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
cons_bad_cmd_usage(command);
}
} else if (g_strcmp0(cmd, "warning") == 0) {
_cmd_set_boolean_preference(value, "Autoping availability warning", PREF_AUTOPING_WARNING);
} else {
cons_bad_cmd_usage(command);
}
@@ -10063,8 +10065,14 @@ cmd_vcard_remove(ProfWin* window, const char* const command, gchar** args)
}
if (args[1]) {
vcard_user_remove_element(atoi(args[1]));
cons_show("Removed element at index %d", atoi(args[1]));
int index;
auto_gchar gchar* err_msg = NULL;
if (!strtoi_range(args[1], &index, 0, INT_MAX, &err_msg)) {
cons_show_error("%s", err_msg);
return TRUE;
}
vcard_user_remove_element((unsigned int)index);
cons_show("Removed element at index %d", index);
vcardwin_update();
} else {
cons_bad_cmd_usage(command);

View File

@@ -294,7 +294,7 @@ str_replace(const char* string, const char* substr,
wr = newstr;
while ((tok = strstr(head, substr))) {
size_t l = tok - head;
size_t l = g_diff_to_gsize(tok, head);
memcpy(wr, head, l);
wr += l;
memcpy(wr, replacement, len_replacement);
@@ -309,6 +309,16 @@ str_replace(const char* string, const char* substr,
return newstr;
}
gsize
g_diff_to_gsize(const void* end, const void* start)
{
if (end < start) {
log_error("g_diff_to_gsize: end < start (%p < %p)", end, start);
return 0;
}
return (gsize)((const char*)end - (const char*)start);
}
gboolean
strtoi_range(const char* str, int* saveptr, int min, int max, gchar** err_msg)
{
@@ -359,9 +369,9 @@ string_matches_one_of(const char* what, const char* is, gboolean is_can_be_null,
char errmsg[256] = { 0 };
size_t sz = 0;
int s = snprintf(errmsg, sizeof(errmsg) - sz, "%s must be one of:", what);
if (s < 0 || s + sz >= sizeof(errmsg))
if (s < 0 || (size_t)s + sz >= sizeof(errmsg))
return ret;
sz += s;
sz += (size_t)s;
cur = first;
va_start(ap, first);
@@ -375,12 +385,12 @@ string_matches_one_of(const char* what, const char* is, gboolean is_can_be_null,
errmsg[sz] = '\0';
s = snprintf(errmsg + sz, sizeof(errmsg) - sz, " or '%s'.", cur);
}
if (s < 0 || s + sz >= sizeof(errmsg)) {
if (s < 0 || (size_t)s + sz >= sizeof(errmsg)) {
log_debug("Error message too long or some other error occurred (%d).", s);
s = -1;
break;
}
sz += s;
sz += (size_t)s;
cur = next;
}
va_end(ap);
@@ -431,7 +441,7 @@ str_xml_sanitize(const char* const str)
return NULL;
}
GString* sanitized = g_string_new_len(NULL, strlen(str));
GString* sanitized = g_string_new_len(NULL, (gssize)strlen(str));
const char* curr = str;
while (*curr != '\0') {
@@ -690,7 +700,7 @@ get_file_paths_recursive(const char* path, GSList** contents)
}
gchar*
get_random_string(int length)
get_random_string(size_t length)
{
GRand* prng;
gchar* rand;
@@ -701,7 +711,7 @@ get_random_string(int length)
prng = g_rand_new();
for (int i = 0; i < length; i++) {
for (size_t i = 0; i < length; i++) {
rand[i] = alphabet[g_rand_int_range(prng, 0, endrange)];
}
g_rand_free(prng);

View File

@@ -158,6 +158,7 @@ gboolean create_dir(const char* name);
gboolean copy_file(const char* const src, const char* const target, const gboolean overwrite_existing);
char* str_replace(const char* string, const char* substr, const char* replacement);
gboolean strtoi_range(const char* str, int* saveptr, int min, int max, char** err_msg);
gsize g_diff_to_gsize(const void* end, const void* start);
int utf8_display_len(const char* const str);
gchar* str_xml_sanitize(const char* const str);
@@ -178,7 +179,7 @@ int is_regular_file(const char* path);
int is_dir(const char* path);
void get_file_paths_recursive(const char* directory, GSList** contents);
gchar* get_random_string(int length);
gchar* get_random_string(size_t length);
gboolean call_external(gchar** argv);
gchar** format_call_external_argv(const char* template_fmt, const char* url, const char* filename);

View File

@@ -43,8 +43,8 @@ _sanitize_account_name(const char* const name)
gchar* sanitized = g_strdup(name);
gchar* p = sanitized;
while (*p) {
// GKeyFile special characters: [ ] = # \n \r
if (*p == '[' || *p == ']' || *p == '=' || *p == '#' || *p == '\n' || *p == '\r') {
// GKeyFile group header forbids these characters.
if (*p == '[' || *p == ']' || *p == '\n' || *p == '\r') {
*p = '_';
}
p++;

View File

@@ -2290,6 +2290,7 @@ _get_group(preference_t pref)
case PREF_TRAY:
case PREF_TRAY_READ:
case PREF_ADV_NOTIFY_DISCO_OR_VERSION:
case PREF_AUTOPING_WARNING:
return PREF_GROUP_NOTIFICATIONS;
case PREF_DBLOG:
case PREF_CHLOG:
@@ -2646,6 +2647,8 @@ _get_key(preference_t pref)
return "enabled";
case PREF_SPELLCHECK_LANG:
return "lang";
case PREF_AUTOPING_WARNING:
return "autoping.warning";
default:
return NULL;
}
@@ -2699,6 +2702,7 @@ _get_default_boolean(preference_t pref)
case PREF_MOOD:
case PREF_STROPHE_SM_ENABLED:
case PREF_STROPHE_SM_RESEND:
case PREF_AUTOPING_WARNING:
return TRUE;
case PREF_SPELLCHECK_ENABLE:
case PREF_PGP_PUBKEY_AUTOIMPORT:

View File

@@ -169,6 +169,7 @@ typedef enum {
PREF_FORCE_ENCRYPTION_MODE,
PREF_SPELLCHECK_ENABLE,
PREF_SPELLCHECK_LANG,
PREF_AUTOPING_WARNING
} preference_t;
typedef struct prof_alias_t

View File

@@ -282,7 +282,7 @@ _export_one_contact(const char* contact)
gchar* key = _make_dedup_key(pl->stanza_id, pl->timestamp_str, pl->from_jid, pl->message);
g_hash_table_add(seen_keys, key);
}
int existing_count = g_slist_length(existing);
int existing_count = (int)g_slist_length(existing);
// 2. Query SQLite for this contact — get ALL messages via direct SQL
GSList* sqlite_lines = db_sqlite_get_all_chat(contact);
@@ -581,7 +581,7 @@ log_database_import_from_flatfile(const gchar* const contact_jid)
// Wrap in a transaction for atomicity and performance
int contact_imported = 0;
int contact_skipped = 0;
int total_lines = g_slist_length(ff_lines);
int total_lines = (int)g_slist_length(ff_lines);
db_sqlite_begin_transaction();
gboolean import_ok = TRUE;

View File

@@ -88,7 +88,7 @@ _ff_cache_line_ids(ff_contact_state_t* state, const char* line)
return;
// Split metadata on unescaped '|'
auto_gchar gchar* meta_str = g_strndup(bracket + 1, close - bracket - 1);
auto_gchar gchar* meta_str = g_strndup(bracket + 1, g_diff_to_gsize(close, bracket + 1));
char** parts = ff_split_meta(meta_str);
char* stanza_id = NULL;
@@ -111,9 +111,9 @@ _ff_cache_line_ids(ff_contact_state_t* state, const char* line)
const char* sender_start = close + 2;
const char* colonspace = ff_find_unescaped_colonspace(sender_start);
if (colonspace) {
const char* slash = memchr(sender_start, '/', colonspace - sender_start);
const char* slash = memchr(sender_start, '/', g_diff_to_gsize(colonspace, sender_start));
const char* jid_end = slash ? slash : colonspace;
from_jid = g_strndup(sender_start, jid_end - sender_start);
from_jid = g_strndup(sender_start, g_diff_to_gsize(jid_end, sender_start));
}
}
@@ -140,7 +140,7 @@ _ff_maybe_index_line(ff_contact_state_t* state, const char* buf, off_t pos)
if (!space)
return;
auto_gchar gchar* ts_str = g_strndup(buf, space - buf);
auto_gchar gchar* ts_str = g_strndup(buf, g_diff_to_gsize(space, buf));
GDateTime* dt = g_date_time_new_from_iso8601(ts_str, NULL);
if (!dt) {
log_warning("flatfile: unparsable timestamp in %s at offset %ld: %s",
@@ -771,7 +771,7 @@ _flatfile_get_previous_chat(const gchar* const contact_barejid, const gchar* sta
// For "last N messages": back up from read_to using index
if (!from_start) {
int margin_entries = (MESSAGES_TO_RETRIEVE * 3) / FF_INDEX_STEP + 2;
size_t margin_entries = (MESSAGES_TO_RETRIEVE * 3) / FF_INDEX_STEP + 2;
// Find index entry closest to (but before) read_to
size_t entry_idx = 0;
@@ -781,7 +781,7 @@ _flatfile_get_previous_chat(const gchar* const contact_barejid, const gchar* sta
entry_idx = i;
}
size_t start_entry = entry_idx > (size_t)margin_entries
size_t start_entry = entry_idx > margin_entries
? entry_idx - margin_entries
: 0;
off_t backed = state->entries[start_entry].byte_offset;

View File

@@ -553,7 +553,7 @@ ff_split_meta(const char* meta)
continue;
}
if (*p == '|' || *p == '\0') {
g_ptr_array_add(arr, g_strndup(start, p - start));
g_ptr_array_add(arr, g_strndup(start, g_diff_to_gsize(p, start)));
if (*p == '\0')
break;
start = p + 1;
@@ -710,7 +710,7 @@ ff_parse_line(const char* line)
if (bracket_start && first_space && first_space < bracket_start) {
// Standard format with metadata: {timestamp} [{meta}] {sender}: {msg}
result->timestamp_str = g_strndup(work, first_space - work);
result->timestamp_str = g_strndup(work, g_diff_to_gsize(first_space, work));
// Parse metadata section [...]
const char* bracket_end = ff_find_unescaped_char(bracket_start + 1, ']');
@@ -721,7 +721,7 @@ ff_parse_line(const char* line)
return NULL;
}
auto_gchar gchar* meta_content = g_strndup(bracket_start + 1, bracket_end - bracket_start - 1);
auto_gchar gchar* meta_content = g_strndup(bracket_start + 1, g_diff_to_gsize(bracket_end, bracket_start + 1));
char** parts = ff_split_meta(meta_content);
if (parts) {
_ff_parse_meta_parts(parts, result);
@@ -736,12 +736,12 @@ ff_parse_line(const char* line)
// Find first *unescaped* ': ' which separates sender from message.
const char* colon = ff_find_unescaped_colonspace(after_meta);
if (colon) {
auto_gchar gchar* raw_sender = g_strndup(after_meta, colon - after_meta);
auto_gchar gchar* raw_sender = g_strndup(after_meta, g_diff_to_gsize(colon, after_meta));
// Split sender into jid/resource, then unescape resource
char* slash = strchr(raw_sender, '/');
if (slash) {
result->from_jid = g_strndup(raw_sender, slash - raw_sender);
result->from_jid = g_strndup(raw_sender, g_diff_to_gsize(slash, raw_sender));
result->from_resource = ff_unescape_sender_resource(slash + 1);
} else {
result->from_jid = g_strdup(raw_sender);
@@ -755,7 +755,7 @@ ff_parse_line(const char* line)
}
} else if (first_space) {
// Legacy/simple format without metadata: {timestamp} - {sender}: {msg}
result->timestamp_str = g_strndup(work, first_space - work);
result->timestamp_str = g_strndup(work, g_diff_to_gsize(first_space, work));
result->type = g_strdup("chat");
result->enc = g_strdup("none");
@@ -767,10 +767,10 @@ ff_parse_line(const char* line)
char* colon = strstr(rest, ": ");
if (colon) {
char* sender = g_strndup(rest, colon - rest);
char* sender = g_strndup(rest, g_diff_to_gsize(colon, rest));
char* slash = strchr(sender, '/');
if (slash) {
result->from_jid = g_strndup(sender, slash - sender);
result->from_jid = g_strndup(sender, g_diff_to_gsize(slash, sender));
result->from_resource = g_strdup(slash + 1);
} else {
result->from_jid = g_strdup(sender);

View File

@@ -260,7 +260,12 @@ omemo_on_connect(ProfAccount* account)
return;
}
_omemo_finalize_identity_load(account);
if (!_omemo_finalize_identity_load(account)) {
omemo_ctx.loaded = FALSE;
log_error("[OMEMO] failed to load OMEMO state from disk for %s", account->jid);
cons_show_error("OMEMO: could not load encryption state from disk; OMEMO will be unavailable this session.");
return;
}
wins_omemo_trust_changed(NULL);
}
@@ -1278,8 +1283,12 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint)
.device_id = GPOINTER_TO_UINT(device_id),
};
size_t fingerprint_len;
size_t fingerprint_len = 0;
unsigned char* fingerprint_raw = _omemo_fingerprint_decode(fingerprint, &fingerprint_len);
if (!fingerprint_raw) {
log_error("[OMEMO] omemo_is_trusted_identity: failed to decode fingerprint for %s", jid);
return FALSE;
}
unsigned char djb_type[] = { '\x05' };
signal_buffer* buffer = signal_buffer_create(djb_type, 1);
buffer = signal_buffer_append(buffer, fingerprint_raw, fingerprint_len);
@@ -1463,6 +1472,10 @@ _omemo_fingerprint(ec_public_key* identity, gboolean formatted)
static unsigned char*
_omemo_fingerprint_decode(const char* const fingerprint, size_t* len)
{
if (!fingerprint) {
*len = 0;
return NULL;
}
unsigned char* output = malloc(strlen(fingerprint) / 2 + 1);
if (!output) {
*len = 0;
@@ -1529,6 +1542,11 @@ omemo_trust(const char* const jid, const char* const fingerprint_formatted)
};
unsigned char* fingerprint_raw = _omemo_fingerprint_decode(fingerprint_formatted, &len);
if (!fingerprint_raw) {
log_error("[OMEMO] omemo_trust: failed to decode fingerprint for %s", jid);
cons_show_error("Failed to trust device: could not decode fingerprint.");
return;
}
unsigned char djb_type[] = { '\x05' };
signal_buffer* buffer = signal_buffer_create(djb_type, 1);
buffer = signal_buffer_append(buffer, fingerprint_raw, len);
@@ -1546,8 +1564,10 @@ omemo_untrust(const char* const jid, const char* const fingerprint_formatted)
{
size_t len;
unsigned char* identity = _omemo_fingerprint_decode(fingerprint_formatted, &len);
if (!identity)
if (!identity) {
log_error("[OMEMO] omemo_untrust: failed to decode fingerprint for %s", jid);
return;
}
GHashTableIter iter;
gpointer key, value;

View File

@@ -278,7 +278,8 @@ otr_on_message_recv(const char* const barejid, const char* const resource, const
if (strstr(message, OTRL_MESSAGE_TAG_V2) && strstr(message, OTRL_MESSAGE_TAG_V1)) {
tag_length = 32;
}
memmove(whitespace_base, whitespace_base + tag_length, tag_length);
// Move the rest of the message (with NUL) over the tag.
memmove(whitespace_base, whitespace_base + tag_length, strlen(whitespace_base + tag_length) + 1);
char* otr_query_message = otr_start_query();
cons_show("OTR Whitespace pattern detected. Attempting to start OTR session…");
free(message_send_chat_otr(barejid, otr_query_message, FALSE, NULL));

View File

@@ -138,7 +138,7 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
* so we can be sure there's runtime left after executing
* the last command.
*/
min_runtime += waittime;
min_runtime += (unsigned int)waittime;
} else {
log_error("%s", err_msg);
g_free(err_msg);

View File

@@ -1703,6 +1703,11 @@ cons_notify_setting(void)
else
cons_show("Subscription requests (/notify sub) : OFF");
if (prefs_get_boolean(PREF_AUTOPING_WARNING))
cons_show("Autoping warning (/autoping warning): ON");
else
cons_show("Autoping warning (/autoping warning): OFF");
gint remind_period = prefs_get_notify_remind();
if (remind_period == 0) {
cons_show("Reminder period (/notify remind) : OFF");
@@ -2003,21 +2008,26 @@ cons_autoping_setting(void)
{
gint autoping_interval = prefs_get_autoping();
if (autoping_interval == 0) {
cons_show("Autoping interval (/autoping) : OFF");
cons_show("Autoping interval (/autoping) : OFF");
} else if (autoping_interval == 1) {
cons_show("Autoping interval (/autoping) : 1 second");
cons_show("Autoping interval (/autoping) : 1 second");
} else {
cons_show("Autoping interval (/autoping) : %d seconds", autoping_interval);
cons_show("Autoping interval (/autoping) : %d seconds", autoping_interval);
}
gint autoping_timeout = prefs_get_autoping_timeout();
if (autoping_timeout == 0) {
cons_show("Autoping timeout (/autoping) : OFF");
cons_show("Autoping timeout (/autoping) : OFF");
} else if (autoping_timeout == 1) {
cons_show("Autoping timeout (/autoping) : 1 second");
cons_show("Autoping timeout (/autoping) : 1 second");
} else {
cons_show("Autoping timeout (/autoping) : %d seconds", autoping_timeout);
cons_show("Autoping timeout (/autoping) : %d seconds", autoping_timeout);
}
if (prefs_get_boolean(PREF_AUTOPING_WARNING))
cons_show("Autoping warning (/autoping warning): ON");
else
cons_show("Autoping warning (/autoping warning): OFF");
}
void

View File

@@ -99,6 +99,7 @@ static void _disco_items_result_handler(xmpp_stanza_t* const stanza);
static void _last_activity_get_handler(xmpp_stanza_t* const stanza);
static void _version_get_handler(xmpp_stanza_t* const stanza);
static void _ping_get_handler(xmpp_stanza_t* const stanza);
static void _disco_autoping_warning_message(GHashTable* features);
static int _version_result_id_handler(xmpp_stanza_t* const stanza, void* const userdata);
static int _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdata);
@@ -2429,6 +2430,11 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con
}
child = xmpp_stanza_get_next(child);
}
// Prevent repetitions by avoiding checks of disco items (from connection_set_disco_items)
if (from && g_ascii_strcasecmp(from, connection_get_domain()) == 0) {
_disco_autoping_warning_message(features);
}
}
connection_features_received(from);
@@ -2436,6 +2442,21 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con
return 0;
}
static void
_disco_autoping_warning_message(GHashTable* features)
{
gboolean server_supports_ping = g_hash_table_contains(features, "urn:xmpp:ping");
gboolean user_prefers_warning = prefs_get_boolean(PREF_AUTOPING_WARNING);
gboolean is_autoping_enabled = prefs_get_autoping() != 0;
if (!is_autoping_enabled && server_supports_ping && user_prefers_warning) {
cons_show("This server supports XEP-0199: XMPP Ping (better keepalive detection),\n"
"but autoping feature is currently disabled in settings.\n"
"Consider enabling it (e.g., `/autoping set 30`) for improved connection stability.\n"
"Use `/autoping warning off` to disable this message.");
}
}
static int
_http_upload_response_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
{

View File

@@ -117,7 +117,7 @@ jid_is_valid(const gchar* const str)
// Localpart validation
if (at) {
size_t local_len = at - str;
size_t local_len = g_diff_to_gsize(at, str);
if (local_len == 0 || local_len > JID_MAX_PART_LEN) {
return FALSE;
}
@@ -135,7 +135,7 @@ jid_is_valid(const gchar* const str)
// Resourcepart validation if present
if (slash) {
domain_len = slash - domain_start;
domain_len = g_diff_to_gsize(slash, domain_start);
size_t resource_len = strlen(slash + 1);
if (resource_len > JID_MAX_PART_LEN) {
return FALSE;

View File

@@ -65,6 +65,7 @@ static void _handle_pubsub(xmpp_stanza_t* const stanza, xmpp_stanza_t* const eve
static gboolean _handle_form(xmpp_stanza_t* const stanza);
static gboolean _handle_jingle_message(xmpp_stanza_t* const stanza);
static gboolean _should_ignore_based_on_silence(xmpp_stanza_t* const stanza);
static gboolean _stanza_id_by_trusted(const char* by);
#ifdef HAVE_OMEMO
static void _receive_omemo(xmpp_stanza_t* const stanza, ProfMessage* message);
#endif
@@ -1088,7 +1089,7 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
if (stanzaidst) {
const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
if (by && (g_strcmp0(by, from_jid->barejid) == 0)) {
if (by && g_strcmp0(by, from_jid->barejid) == 0 && _stanza_id_by_trusted(by)) {
stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
if (stanzaid) {
message->stanzaid = strdup(stanzaid);
@@ -1397,7 +1398,7 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c
xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
if (stanzaidst) {
const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
if (by && equals_our_barejid(by)) {
if (by && equals_our_barejid(by) && _stanza_id_by_trusted(by)) {
stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
if (stanzaid) {
message->stanzaid = strdup(stanzaid);
@@ -1735,3 +1736,22 @@ _should_ignore_based_on_silence(xmpp_stanza_t* const stanza)
}
return FALSE;
}
// XEP-0359 §6 disco-gate; falls back to domain since that's what disco is run against.
static gboolean
_stanza_id_by_trusted(const char* by)
{
if (!by) {
return FALSE;
}
GHashTable* features = connection_get_features(by);
if (features && g_hash_table_contains(features, STANZA_NS_STABLE_ID)) {
return TRUE;
}
const char* at = strchr(by, '@');
if (!at) {
return FALSE;
}
features = connection_get_features(at + 1);
return features && g_hash_table_contains(features, STANZA_NS_STABLE_ID);
}

View File

@@ -707,7 +707,7 @@ muc_autocomplete(ProfWin* window, const char* const input, gboolean previous)
} else {
search_str = last_space + 1;
if (!chat_room->autocomplete_prefix) {
chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
chat_room->autocomplete_prefix = g_strndup(input, g_diff_to_gsize(search_str, input));
}
}

View File

@@ -621,15 +621,20 @@ _available_handler(xmpp_stanza_t* const stanza)
}
if (g_strcmp0(xmpp_presence->jid->barejid, my_jid->barejid) == 0) {
connection_add_available_resource(resource);
// Copy what we read before connection_add_available_resource() takes ownership.
Resource* resource_for_roster = resource_copy(resource);
auto_gchar gchar* resource_name = g_strdup(resource->name);
auto_gchar gchar* resource_status = resource->status ? g_strdup(resource->status) : NULL;
int resource_priority = resource->priority;
resource_presence_t resource_presence = resource->presence;
connection_add_available_resource(resource);
sv_ev_contact_online(xmpp_presence->jid->barejid, resource_for_roster, xmpp_presence->last_activity, pgpsig);
const char* account_name = session_get_account_name();
int max_sessions = accounts_get_max_sessions(account_name);
if (max_sessions > 0) {
auto_gchar gchar* cur_resource = accounts_get_resource(account_name);
int res_count = connection_count_available_resources();
if (res_count > max_sessions && g_strcmp0(cur_resource, resource->name)) {
if (res_count > max_sessions && g_strcmp0(cur_resource, resource_name)) {
ProfWin* console = wins_get_console();
ProfWin* current_window = wins_get_current();
auto_gchar gchar* message = g_strdup_printf("Max sessions alarm! (%d/%d devices in use)", res_count, max_sessions);
@@ -639,14 +644,14 @@ _available_handler(xmpp_stanza_t* const stanza)
}
notify(message, 10000, "Security alert");
const char* resource_presence = string_from_resource_presence(resource->presence);
win_print(console, THEME_DEFAULT, "|", "New device info: \n %s (%d), %s", resource->name, resource->priority, resource_presence);
const char* resource_presence_str = string_from_resource_presence(resource_presence);
win_print(console, THEME_DEFAULT, "|", "New device info: \n %s (%d), %s", resource_name, resource_priority, resource_presence_str);
if (resource->status) {
win_append(console, THEME_DEFAULT, ", \"%s\"", resource->status);
if (resource_status) {
win_append(console, THEME_DEFAULT, ", \"%s\"", resource_status);
}
win_appendln(console, THEME_DEFAULT, "");
auto_jid Jid* jidp = jid_create_from_bare_and_resource(my_jid->barejid, resource->name);
auto_jid Jid* jidp = jid_create_from_bare_and_resource(my_jid->barejid, resource_name);
EntityCapabilities* caps = caps_lookup(jidp->fulljid);
if (caps) {

View File

@@ -150,6 +150,14 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(autoping_sends_ping_after_interval),
PROF_FUNC_TEST(autoping_server_not_supporting_ping),
/* Autoping availability warning - negative cases wait ~3s */
PROF_FUNC_TEST(autoping_warning_shown_when_disabled),
PROF_FUNC_TEST(autoping_warning_not_shown_when_server_unsupported),
PROF_FUNC_TEST(autoping_warning_not_shown_when_autoping_enabled),
PROF_FUNC_TEST(autoping_warning_not_shown_when_user_disabled),
PROF_FUNC_TEST(autoping_warning_not_shown_for_user_jid),
PROF_FUNC_TEST(autoping_warning_not_shown_for_subdomain_service),
/* Service Discovery - XEP-0030 */
PROF_FUNC_TEST(disco_info_shows_identity),
PROF_FUNC_TEST(disco_info_shows_features),
@@ -253,6 +261,10 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(message_receive_console),
PROF_FUNC_TEST(message_receive_chatwin),
/* XEP-0359 disco gate for stanza-id trust */
PROF_FUNC_TEST(stanza_id_dedup_fires_when_server_announces_sid0),
PROF_FUNC_TEST(stanza_id_not_trusted_when_server_does_not_announce_sid0),
#ifdef HAVE_SQLITE
/* MUC (groupchat) database — export/import of type="muc" messages */
PROF_FUNC_TEST(muc_export_sqlite_to_flatfile),

View File

@@ -119,10 +119,9 @@ _create_dir(const char* name)
gboolean
_mkdir_recursive(const char* dir)
{
int i;
gboolean result = TRUE;
for (i = 1; i <= strlen(dir); i++) {
for (size_t i = 1; i <= strlen(dir); i++) {
if (dir[i] == '/' || dir[i] == '\0') {
gchar* next_dir = g_strndup(dir, i);
result = _create_dir(next_dir);

View File

@@ -112,3 +112,144 @@ autoping_server_not_supporting_ping(void** state)
// Should show error about ping not being supported
assert_true(prof_output_regex("Server ping not supported"));
}
/*
* Autoping availability warning.
*
* The warning fires from the on-connect disco handler, so the three inputs
* (server ping support, autoping interval, warning preference) must all be
* set before prof_connect().
*/
/* Stable substring of the warning text emitted by iq.c. */
#define AUTOPING_WARNING_MATCH "This server supports XEP-0199"
static void
_stub_server_disco(gboolean with_ping)
{
if (with_ping) {
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='Stabber'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
} else {
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='Stabber'/>"
"</query>"
"</iq>"
);
}
}
void
autoping_warning_shown_when_disabled(void** state)
{
// All conditions met: server supports ping, autoping off, warning on (default).
_stub_server_disco(TRUE);
prof_input("/autoping set 0");
assert_true(prof_output_exact("Autoping disabled."));
prof_connect();
assert_true(prof_output_regex(AUTOPING_WARNING_MATCH));
}
void
autoping_warning_not_shown_when_server_unsupported(void** state)
{
// Server does not advertise urn:xmpp:ping -> no warning even with autoping off.
_stub_server_disco(FALSE);
prof_input("/autoping set 0");
assert_true(prof_output_exact("Autoping disabled."));
prof_connect();
prof_timeout(NEGATIVE_ASSERT_TIMEOUT);
assert_false(prof_output_regex(AUTOPING_WARNING_MATCH));
}
void
autoping_warning_not_shown_when_autoping_enabled(void** state)
{
// Warning is suppressed while autoping is enabled.
_stub_server_disco(TRUE);
prof_input("/autoping set 30");
assert_true(prof_output_exact("Autoping interval set to 30 seconds."));
prof_connect();
prof_timeout(NEGATIVE_ASSERT_TIMEOUT);
assert_false(prof_output_regex(AUTOPING_WARNING_MATCH));
}
void
autoping_warning_not_shown_when_user_disabled(void** state)
{
// User opted out -> no warning.
_stub_server_disco(TRUE);
prof_input("/autoping set 0");
assert_true(prof_output_exact("Autoping disabled."));
prof_input("/autoping warning off");
assert_true(prof_output_exact("Autoping availability warning disabled."));
prof_connect();
prof_timeout(NEGATIVE_ASSERT_TIMEOUT);
assert_false(prof_output_regex(AUTOPING_WARNING_MATCH));
}
void
autoping_warning_not_shown_for_user_jid(void** state)
{
// disco#info from a user JID (with '@') should not trigger the warning,
// only server responses (no '@') should.
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result' from='buddy1@localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='person' type='chat' name='Buddy1'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
prof_input("/autoping set 0");
assert_true(prof_output_exact("Autoping disabled."));
prof_connect();
prof_timeout(NEGATIVE_ASSERT_TIMEOUT);
assert_false(prof_output_regex(AUTOPING_WARNING_MATCH));
}
void
autoping_warning_not_shown_for_subdomain_service(void** state)
{
// disco#info from a subdomain service should not trigger the warning, only the server's own domain should
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result' from='conf.localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='conference' type='text' name='Conference Service'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
prof_input("/autoping set 0");
assert_true(prof_output_exact("Autoping disabled."));
prof_connect();
prof_timeout(NEGATIVE_ASSERT_TIMEOUT);
assert_false(prof_output_regex(AUTOPING_WARNING_MATCH));
}

View File

@@ -4,3 +4,9 @@ void autoping_timeout_set(void** state);
void autoping_timeout_zero_disables(void** state);
void autoping_sends_ping_after_interval(void** state);
void autoping_server_not_supporting_ping(void** state);
void autoping_warning_shown_when_disabled(void** state);
void autoping_warning_not_shown_when_server_unsupported(void** state);
void autoping_warning_not_shown_when_autoping_enabled(void** state);
void autoping_warning_not_shown_when_user_disabled(void** state);
void autoping_warning_not_shown_for_user_jid(void** state);
void autoping_warning_not_shown_for_subdomain_service(void** state);

View File

@@ -56,3 +56,70 @@ message_receive_chatwin(void **state)
assert_true(prof_output_regex("someuser@chatserv.org/laptop: .+How are you?"));
}
// XEP-0359 disco gate: server announces urn:xmpp:sid:0 -> stanza-id trusted -> replay flagged as duplicate.
void
stanza_id_dedup_fires_when_server_announces_sid0(void **state)
{
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq type='result' to='stabber@localhost/profanity' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='TestServer'/>"
"<feature var='urn:xmpp:sid:0'/>"
"</query>"
"</iq>"
);
prof_connect();
stbbr_send(
"<message id='m-trust-1' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>first</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-42'/>"
"</message>"
);
assert_true(prof_output_exact("<< chat message: someuser@chatserv.org/laptop (win 2)"));
stbbr_send(
"<message id='m-trust-2' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>replay</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-42'/>"
"</message>"
);
assert_true(prof_output_exact("Got a message with duplicate (server-generated) stanza-id from someuser@chatserv.org/laptop."));
}
// XEP-0359 disco gate: server does NOT announce urn:xmpp:sid:0 -> stanza-id untrusted -> no replay error.
void
stanza_id_not_trusted_when_server_does_not_announce_sid0(void **state)
{
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq type='result' to='stabber@localhost/profanity' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='TestServer'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
prof_connect();
stbbr_send(
"<message id='m-untrust-1' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>first</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-77'/>"
"</message>"
);
assert_true(prof_output_exact("<< chat message: someuser@chatserv.org/laptop (win 2)"));
stbbr_send(
"<message id='m-untrust-2' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>replay</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-77'/>"
"</message>"
);
prof_timeout(2);
assert_false(prof_output_exact("Got a message with duplicate (server-generated) stanza-id"));
prof_timeout_reset();
}

View File

@@ -1,3 +1,5 @@
void message_send(void **state);
void message_receive_console(void **state);
void message_receive_chatwin(void **state);
void stanza_id_dedup_fires_when_server_announces_sid0(void **state);
void stanza_id_not_trusted_when_server_does_not_announce_sid0(void **state);