Compare commits
6 Commits
fix/autopi
...
58c23c4089
| Author | SHA1 | Date | |
|---|---|---|---|
|
58c23c4089
|
|||
|
6f136bde88
|
|||
|
58dd89be40
|
|||
|
b2ce06923e
|
|||
|
9b292a6100
|
|||
|
a9c21ce487
|
@@ -168,6 +168,8 @@ unittest_sources = \
|
||||
tests/unittests/test_callbacks.c tests/unittests/test_callbacks.h \
|
||||
tests/unittests/test_plugins_disco.c tests/unittests/test_plugins_disco.h \
|
||||
tests/unittests/test_forced_encryption.c tests/unittests/test_forced_encryption.h \
|
||||
tests/unittests/test_helpers.c tests/unittests/test_helpers.h \
|
||||
tests/unittests/test_autoping.c tests/unittests/test_autoping.h \
|
||||
tests/unittests/unittests.c
|
||||
|
||||
functionaltest_sources = \
|
||||
@@ -185,6 +187,7 @@ functionaltest_sources = \
|
||||
tests/functionaltests/test_software.c tests/functionaltests/test_software.h \
|
||||
tests/functionaltests/test_muc.c tests/functionaltests/test_muc.h \
|
||||
tests/functionaltests/test_disconnect.c tests/functionaltests/test_disconnect.h \
|
||||
tests/functionaltests/test_lastactivity.c tests/functionaltests/test_lastactivity.h \
|
||||
tests/functionaltests/functionaltests.c
|
||||
|
||||
main_source = src/main.c
|
||||
|
||||
69
check-cwe134.sh
Executable file
69
check-cwe134.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
# check-cwe134.sh - Static analysis for CWE-134 format string vulnerabilities
|
||||
#
|
||||
# This script detects potentially unsafe usage of format string functions
|
||||
# where user-controlled data may be passed without "%s" wrapper.
|
||||
#
|
||||
# Usage: ./check-cwe134.sh [directory]
|
||||
|
||||
set -e
|
||||
|
||||
DIR="${1:-src}"
|
||||
|
||||
echo "=== CWE-134 Format String Vulnerability Check ==="
|
||||
echo "Scanning: $DIR"
|
||||
echo ""
|
||||
|
||||
# Functions that accept format strings
|
||||
FORMAT_FUNCS="cons_show|cons_debug|cons_show_error|log_info|log_error|log_warning|log_debug|win_println|win_print"
|
||||
|
||||
ERRORS=0
|
||||
|
||||
echo "Checking for unsafe format string usage..."
|
||||
echo ""
|
||||
|
||||
# Pattern 1: function call with single variable argument (no format string)
|
||||
# Example: cons_show(variable); - BAD
|
||||
# Example: cons_show("%s", variable); - OK
|
||||
# Matches: func(identifier) or func(identifier->member) or func(identifier[index])
|
||||
RESULTS=$(grep -rn --include="*.c" -P "($FORMAT_FUNCS)\s*\(\s*[a-zA-Z_][a-zA-Z0-9_]*(\s*->\s*\w+|\s*\[\s*\w+\s*\])?\s*\)\s*;" "$DIR" 2>/dev/null || true)
|
||||
|
||||
# Filter out function definitions, declarations, and safe api_* wrappers
|
||||
RESULTS=$(echo "$RESULTS" | grep -v "const char\|void \|^[^:]*:[0-9]*:[a-z_]*(\|api_cons_show\|api_log_" || true)
|
||||
|
||||
if [ -n "$RESULTS" ]; then
|
||||
echo "❌ POTENTIAL CWE-134 VULNERABILITIES FOUND:"
|
||||
echo ""
|
||||
echo "$RESULTS"
|
||||
echo ""
|
||||
ERRORS=$(echo "$RESULTS" | wc -l)
|
||||
else
|
||||
echo "✅ No obvious CWE-134 issues found."
|
||||
fi
|
||||
|
||||
# Additional check: GString->str passed directly (not as %s argument)
|
||||
echo ""
|
||||
echo "Checking for GString->str passed to format functions..."
|
||||
|
||||
GSTRING_RESULTS=$(grep -rn --include="*.c" -P "($FORMAT_FUNCS)\s*\([^)]*->str\s*\)" "$DIR" 2>/dev/null | grep -v '"%s"' || true)
|
||||
|
||||
if [ -n "$GSTRING_RESULTS" ]; then
|
||||
echo "⚠️ GString->str passed without \"%s\" (review manually):"
|
||||
echo ""
|
||||
echo "$GSTRING_RESULTS"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Summary ==="
|
||||
echo "Critical issues: $ERRORS"
|
||||
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Fix by adding \"%s\" format specifier:"
|
||||
echo " BAD: cons_show(variable);"
|
||||
echo " GOOD: cons_show(\"%s\", variable);"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -168,6 +168,10 @@ num_cores()
|
||||
# Run test failure detection verification first
|
||||
verify_test_failure_detection
|
||||
|
||||
# Run CWE-134 format string vulnerability check
|
||||
echo "=== Running CWE-134 security check ==="
|
||||
./check-cwe134.sh || { echo "CWE-134 check failed!"; exit 1; }
|
||||
|
||||
# Parse arguments
|
||||
COVERAGE_ONLY=no
|
||||
for arg in "$@"; do
|
||||
|
||||
@@ -2825,16 +2825,15 @@ cmd_search_index_any(char* term)
|
||||
int terms_len = g_strv_length(processed_terms);
|
||||
|
||||
for (int i = 0; i < terms_len; i++) {
|
||||
GList* index_keys = g_hash_table_get_keys(search_index);
|
||||
GList* curr = index_keys;
|
||||
while (curr) {
|
||||
char* index_entry = g_hash_table_lookup(search_index, curr->data);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, search_index);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* index_entry = (char*)value;
|
||||
if (g_str_match_string(processed_terms[i], index_entry, FALSE)) {
|
||||
results = g_list_append(results, curr->data);
|
||||
results = g_list_append(results, key);
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(index_keys);
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -2848,13 +2847,14 @@ cmd_search_index_all(char* term)
|
||||
auto_gcharv gchar** terms = g_str_tokenize_and_fold(term, NULL, NULL);
|
||||
int terms_len = g_strv_length(terms);
|
||||
|
||||
GList* commands = g_hash_table_get_keys(search_index);
|
||||
GList* curr = commands;
|
||||
while (curr) {
|
||||
char* command = curr->data;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, search_index);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* command = (char*)key;
|
||||
char* command_index = (char*)value;
|
||||
int matches = 0;
|
||||
for (int i = 0; i < terms_len; i++) {
|
||||
char* command_index = g_hash_table_lookup(search_index, command);
|
||||
if (g_str_match_string(terms[i], command_index, FALSE)) {
|
||||
matches++;
|
||||
}
|
||||
@@ -2862,11 +2862,8 @@ cmd_search_index_all(char* term)
|
||||
if (matches == terms_len) {
|
||||
results = g_list_append(results, command);
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
g_list_free(commands);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -2989,7 +2986,18 @@ command_docgen(void)
|
||||
}
|
||||
|
||||
FILE* toc_fragment = fopen("toc_fragment.html", "w");
|
||||
if (!toc_fragment) {
|
||||
log_error("command_docgen(): unable to open toc_fragment.html for writing: %s", g_strerror(errno));
|
||||
g_list_free(cmds);
|
||||
return;
|
||||
}
|
||||
FILE* main_fragment = fopen("main_fragment.html", "w");
|
||||
if (!main_fragment) {
|
||||
log_error("command_docgen(): unable to open main_fragment.html for writing: %s", g_strerror(errno));
|
||||
fclose(toc_fragment);
|
||||
g_list_free(cmds);
|
||||
return;
|
||||
}
|
||||
|
||||
fputs("<ul><li><ul><li>\n", toc_fragment);
|
||||
fputs("<hr>\n", main_fragment);
|
||||
@@ -3094,6 +3102,11 @@ command_mangen(void)
|
||||
return;
|
||||
}
|
||||
FILE* manpage = fopen(filename, "w");
|
||||
if (!manpage) {
|
||||
log_error("command_mangen(): unable to open %s for writing: %s", filename, g_strerror(errno));
|
||||
curr = g_list_next(curr);
|
||||
continue;
|
||||
}
|
||||
|
||||
fprintf(manpage, "%s\n", header);
|
||||
fputs(".SH NAME\n", manpage);
|
||||
|
||||
@@ -184,7 +184,7 @@ _string_matches_one_of(const char* what, const char* is, bool is_can_be_null, co
|
||||
}
|
||||
va_end(ap);
|
||||
if (s > 0)
|
||||
cons_show(errmsg);
|
||||
cons_show("%s", errmsg);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -418,7 +418,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args)
|
||||
auto_char char* err_msg = NULL;
|
||||
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
||||
if (!res) {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_show("");
|
||||
port = 0;
|
||||
options_destroy(options);
|
||||
@@ -711,7 +711,7 @@ _account_set_port(char* account_name, char* port)
|
||||
auto_char char* err_msg = NULL;
|
||||
gboolean res = strtoi_range(port, &porti, 1, 65535, &err_msg);
|
||||
if (!res) {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_show("");
|
||||
} else {
|
||||
accounts_set_port(account_name, porti);
|
||||
@@ -903,7 +903,7 @@ _account_set_max_sessions(char* account_name, char* max_sessions_raw)
|
||||
auto_char char* err_msg = NULL;
|
||||
gboolean res = strtoi_range(max_sessions_raw, &max_sessions, 0, INT_MAX, &err_msg);
|
||||
if (!res) {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_show("");
|
||||
return TRUE;
|
||||
}
|
||||
@@ -924,7 +924,7 @@ _account_set_presence_priority(char* account_name, char* presence, char* priorit
|
||||
auto_char char* err_msg = NULL;
|
||||
gboolean res = strtoi_range(priority, &intval, -128, 127, &err_msg);
|
||||
if (!res) {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -1580,7 +1580,7 @@ _cmd_list_commands(GList* commands)
|
||||
while (curr) {
|
||||
gchar* cmd = curr->data;
|
||||
if (count == 5) {
|
||||
cons_show(cmds->str);
|
||||
cons_show("%s", cmds->str);
|
||||
g_string_free(cmds, TRUE);
|
||||
cmds = g_string_new("");
|
||||
count = 0;
|
||||
@@ -1589,7 +1589,7 @@ _cmd_list_commands(GList* commands)
|
||||
curr = g_list_next(curr);
|
||||
count++;
|
||||
}
|
||||
cons_show(cmds->str);
|
||||
cons_show("%s", cmds->str);
|
||||
g_string_free(cmds, TRUE);
|
||||
g_list_free(curr);
|
||||
|
||||
@@ -2407,7 +2407,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
||||
}
|
||||
return TRUE;
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -2467,7 +2467,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Roster contact indent set to: %d", intval);
|
||||
rosterwin_roster();
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2501,7 +2501,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Roster resource indent set to: %d", intval);
|
||||
rosterwin_roster();
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
}
|
||||
} else if (g_strcmp0(args[1], "join") == 0) {
|
||||
@@ -2527,7 +2527,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Roster presence indent set to: %d", intval);
|
||||
rosterwin_roster();
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -4371,7 +4371,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args)
|
||||
wins_resize_all();
|
||||
return TRUE;
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -4391,7 +4391,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args)
|
||||
|
||||
occupantswin_occupants_all();
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -4970,8 +4970,8 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args)
|
||||
alt_scheme = OMEMO_AESGCM_URL_SCHEME;
|
||||
alt_fragment = _add_omemo_stream(&fd, &fh, &err);
|
||||
if (err != NULL) {
|
||||
cons_show_error(err);
|
||||
win_println(window, THEME_ERROR, "-", err);
|
||||
cons_show_error("%s", err);
|
||||
win_println(window, THEME_ERROR, "-", "%s", err);
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
@@ -5836,7 +5836,7 @@ cmd_inpblock(ProfWin* window, const char* const command, gchar** args)
|
||||
prefs_set_inpblock(intval);
|
||||
inp_nonblocking(FALSE);
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -6063,7 +6063,7 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args)
|
||||
ui_resize();
|
||||
return TRUE;
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_bad_cmd_usage(command);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6094,7 +6094,7 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args)
|
||||
ui_resize();
|
||||
return TRUE;
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_bad_cmd_usage(command);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6254,7 +6254,7 @@ cmd_log(ProfWin* window, const char* const command, gchar** args)
|
||||
prefs_set_max_log_size(intval);
|
||||
cons_show("Log maximum size set to %d bytes", intval);
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -6304,7 +6304,7 @@ cmd_reconnect(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Reconnect interval set to %d seconds.", intval);
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_bad_cmd_usage(command);
|
||||
}
|
||||
|
||||
@@ -6330,7 +6330,7 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Autoping interval set to %d seconds.", intval);
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_bad_cmd_usage(command);
|
||||
}
|
||||
|
||||
@@ -6346,7 +6346,7 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Autoping timeout set to %d seconds.", intval);
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_bad_cmd_usage(command);
|
||||
}
|
||||
|
||||
@@ -6416,7 +6416,7 @@ cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Auto away time set to: %d minutes.", minutesval);
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -6439,7 +6439,7 @@ cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -6506,7 +6506,7 @@ cmd_priority(ProfWin* window, const char* const command, gchar** args)
|
||||
cl_ev_presence_send(last_presence, 0);
|
||||
cons_show("Priority set to %d.", intval);
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -6576,7 +6576,7 @@ cmd_tray(ProfWin* window, const char* const command, gchar** args)
|
||||
tray_set_timer(intval);
|
||||
}
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -7181,22 +7181,21 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args)
|
||||
}
|
||||
|
||||
cons_show("PGP keys:");
|
||||
GList* keylist = g_hash_table_get_keys(keys);
|
||||
GList* curr = keylist;
|
||||
while (curr) {
|
||||
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||
cons_show(" %s", key->name);
|
||||
cons_show(" ID : %s", key->id);
|
||||
auto_char char* format_fp = p_gpg_format_fp_str(key->fp);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, keys);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
||||
cons_show(" %s", pgp_key->name);
|
||||
cons_show(" ID : %s", pgp_key->id);
|
||||
auto_char char* format_fp = p_gpg_format_fp_str(pgp_key->fp);
|
||||
cons_show(" Fingerprint : %s", format_fp);
|
||||
if (key->secret) {
|
||||
if (pgp_key->secret) {
|
||||
cons_show(" Type : PUBLIC, PRIVATE");
|
||||
} else {
|
||||
cons_show(" Type : PUBLIC");
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(keylist);
|
||||
p_gpg_free_keys(keys);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -7237,25 +7236,24 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args)
|
||||
return TRUE;
|
||||
}
|
||||
GHashTable* pubkeys = p_gpg_pubkeys();
|
||||
GList* jids = g_hash_table_get_keys(pubkeys);
|
||||
if (!jids) {
|
||||
if (!pubkeys || g_hash_table_size(pubkeys) == 0) {
|
||||
cons_show("No contacts found with PGP public keys assigned.");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
cons_show("Assigned PGP public keys:");
|
||||
GList* curr = jids;
|
||||
while (curr) {
|
||||
char* jid = curr->data;
|
||||
ProfPGPPubKeyId* pubkeyid = g_hash_table_lookup(pubkeys, jid);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, pubkeys);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* jid = (char*)key;
|
||||
ProfPGPPubKeyId* pubkeyid = (ProfPGPPubKeyId*)value;
|
||||
if (pubkeyid->received) {
|
||||
cons_show(" %s: %s (received)", jid, pubkeyid->id);
|
||||
} else {
|
||||
cons_show(" %s: %s (stored)", jid, pubkeyid->id);
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(jids);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -7468,22 +7466,21 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
||||
}
|
||||
|
||||
cons_show("OpenPGP keys:");
|
||||
GList* keylist = g_hash_table_get_keys(keys);
|
||||
GList* curr = keylist;
|
||||
while (curr) {
|
||||
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||
cons_show(" %s", key->name);
|
||||
cons_show(" ID : %s", key->id);
|
||||
auto_char char* format_fp = p_gpg_format_fp_str(key->fp);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, keys);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
||||
cons_show(" %s", pgp_key->name);
|
||||
cons_show(" ID : %s", pgp_key->id);
|
||||
auto_char char* format_fp = p_gpg_format_fp_str(pgp_key->fp);
|
||||
cons_show(" Fingerprint : %s", format_fp);
|
||||
if (key->secret) {
|
||||
if (pgp_key->secret) {
|
||||
cons_show(" Type : PUBLIC, PRIVATE");
|
||||
} else {
|
||||
cons_show(" Type : PUBLIC");
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(keylist);
|
||||
p_gpg_free_keys(keys);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -7491,8 +7488,8 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
||||
else if (g_strcmp0(args[0], "contacts") == 0) {
|
||||
GHashTable* keys = ox_gpg_public_keys();
|
||||
cons_show("OpenPGP keys:");
|
||||
GList* keylist = g_hash_table_get_keys(keys);
|
||||
GList* curr = keylist;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
GSList* roster_list = NULL;
|
||||
jabber_conn_status_t conn_status = connection_get_status();
|
||||
@@ -7502,15 +7499,16 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
||||
roster_list = roster_get_contacts(ROSTER_ORD_NAME);
|
||||
}
|
||||
|
||||
while (curr) {
|
||||
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||
g_hash_table_iter_init(&iter, keys);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
||||
PContact contact = NULL;
|
||||
if (roster_list) {
|
||||
GSList* curr_c = roster_list;
|
||||
while (!contact && curr_c) {
|
||||
contact = curr_c->data;
|
||||
auto_gchar gchar* xmppuri = g_strdup_printf("xmpp:%s", p_contact_barejid(contact));
|
||||
if (g_strcmp0(key->name, xmppuri)) {
|
||||
if (g_strcmp0(pgp_key->name, xmppuri)) {
|
||||
contact = NULL;
|
||||
}
|
||||
curr_c = g_slist_next(curr_c);
|
||||
@@ -7518,11 +7516,10 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
cons_show("%s - %s", key->fp, key->name);
|
||||
cons_show("%s - %s", pgp_key->fp, pgp_key->name);
|
||||
} else {
|
||||
cons_show("%s - %s (not in roster)", key->fp, key->name);
|
||||
cons_show("%s - %s (not in roster)", pgp_key->fp, pgp_key->name);
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
} else if (g_strcmp0(args[0], "start") == 0) {
|
||||
@@ -9612,7 +9609,7 @@ cmd_register(ProfWin* window, const char* const command, gchar** args)
|
||||
auto_char char* err_msg = NULL;
|
||||
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
||||
if (!res) {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
cons_show("");
|
||||
port = 0;
|
||||
options_destroy(options);
|
||||
@@ -9682,7 +9679,7 @@ cmd_strophe(ProfWin* window, const char* const command, gchar** args)
|
||||
prefs_set_string(PREF_STROPHE_VERBOSITY, args[1]);
|
||||
return TRUE;
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
} else if (g_strcmp0(args[0], "sm") == 0) {
|
||||
if (g_strcmp0(args[1], "no-resend") == 0) {
|
||||
|
||||
@@ -143,7 +143,7 @@ auto_close_gfd(gint* fd)
|
||||
return;
|
||||
|
||||
if (close(*fd) == EOF)
|
||||
log_error(g_strerror(errno));
|
||||
log_error("%s", g_strerror(errno));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +158,7 @@ auto_close_FILE(FILE** fd)
|
||||
return;
|
||||
|
||||
if (fclose(*fd) == EOF)
|
||||
log_error(g_strerror(errno));
|
||||
log_error("%s", g_strerror(errno));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
||||
@@ -65,6 +65,36 @@ static gboolean _check_available_space_for_db_migration(char* path_to_db);
|
||||
|
||||
static const int latest_version = 2;
|
||||
|
||||
// Helper: close DB handle (if any), warn on busy, and shutdown SQLite
|
||||
static void
|
||||
_db_teardown(const char* ctx)
|
||||
{
|
||||
if (g_chatlog_database) {
|
||||
int rc = sqlite3_close_v2(g_chatlog_database);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_warning("sqlite3_close_v2 in %s returned %d; database may still have active statements.",
|
||||
ctx ? ctx : "db_teardown", rc);
|
||||
}
|
||||
g_chatlog_database = NULL;
|
||||
}
|
||||
sqlite3_shutdown();
|
||||
}
|
||||
|
||||
// Helper: prepare a statement and log a contextual error on failure
|
||||
static gboolean
|
||||
_db_prepare_ctx(const char* query, sqlite3_stmt** stmt, const char* ctx)
|
||||
{
|
||||
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("SQLite error in %s: (error code: %d) %s",
|
||||
ctx ? ctx : "sqlite3_prepare_v2",
|
||||
rc,
|
||||
sqlite3_errmsg(g_chatlog_database));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char*
|
||||
_db_strdup(const char* str)
|
||||
{
|
||||
@@ -98,17 +128,19 @@ log_database_init(ProfAccount* account)
|
||||
|
||||
auto_char char* filename = _get_db_filename(account);
|
||||
if (!filename) {
|
||||
sqlite3_shutdown();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ret = sqlite3_open(filename, &g_chatlog_database);
|
||||
if (ret != SQLITE_OK) {
|
||||
const char* err_msg = sqlite3_errmsg(g_chatlog_database);
|
||||
const char* err_msg = g_chatlog_database ? sqlite3_errmsg(g_chatlog_database) : "(no handle)";
|
||||
log_error("Error opening SQLite database: %s", err_msg);
|
||||
_db_teardown("log_database_init(open)");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char* err_msg;
|
||||
char* err_msg = NULL;
|
||||
|
||||
int db_version = _get_db_version();
|
||||
if (db_version == latest_version) {
|
||||
@@ -216,6 +248,7 @@ out:
|
||||
} else {
|
||||
log_error("Unknown SQLite error in log_database_init().");
|
||||
}
|
||||
_db_teardown("log_database_init(out)");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -223,11 +256,7 @@ void
|
||||
log_database_close(void)
|
||||
{
|
||||
log_debug("log_database_close() called");
|
||||
if (g_chatlog_database) {
|
||||
sqlite3_close(g_chatlog_database);
|
||||
sqlite3_shutdown();
|
||||
g_chatlog_database = NULL;
|
||||
}
|
||||
_db_teardown("log_database_close");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -281,8 +310,15 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
||||
{
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const Jid* myjid = connection_get_jid();
|
||||
if (!myjid->str)
|
||||
return NULL;
|
||||
// Always return a valid ProfMessage to avoid NULL dereferences in callers
|
||||
ProfMessage* msg = message_init();
|
||||
if (!myjid || !myjid->str) {
|
||||
// If caller requested the last message and we have no context, fall back to now
|
||||
if (is_last) {
|
||||
msg->timestamp = g_date_time_new_now_utc();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
const char* order = is_last ? "DESC" : "ASC";
|
||||
auto_sqlite char* query = sqlite3_mprintf("SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
|
||||
@@ -293,17 +329,19 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
||||
|
||||
if (!query) {
|
||||
log_error("Could not allocate memory for SQL query in log_database_get_limits_info()");
|
||||
return NULL;
|
||||
if (is_last) {
|
||||
msg->timestamp = g_date_time_new_now_utc();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Unknown SQLite error in log_database_get_last_info().");
|
||||
return NULL;
|
||||
if (!_db_prepare_ctx(query, &stmt, "log_database_get_limits_info()")) {
|
||||
if (is_last) {
|
||||
msg->timestamp = g_date_time_new_now_utc();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
ProfMessage* msg = message_init();
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
char* archive_id = (char*)sqlite3_column_text(stmt, 0);
|
||||
char* date = (char*)sqlite3_column_text(stmt, 1);
|
||||
@@ -313,6 +351,11 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// If nothing was found and caller expects the last message, provide a sane default
|
||||
if (!msg->timestamp && is_last) {
|
||||
msg->timestamp = g_date_time_new_now_utc();
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -357,9 +400,7 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const gchar*
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("SQLite error in log_database_get_previous_chat(): (error code: %d) %s", rc, sqlite3_errmsg(g_chatlog_database));
|
||||
if (!_db_prepare_ctx(query, &stmt, "log_database_get_previous_chat()")) {
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
|
||||
@@ -503,9 +544,7 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
|
||||
}
|
||||
|
||||
sqlite3_stmt* lmc_stmt = NULL;
|
||||
|
||||
if (SQLITE_OK != sqlite3_prepare_v2(g_chatlog_database, replace_check_query, -1, &lmc_stmt, NULL)) {
|
||||
log_error("SQLite error in _add_to_db() on selecting original message: %s", sqlite3_errmsg(g_chatlog_database));
|
||||
if (!_db_prepare_ctx(replace_check_query, &lmc_stmt, "_add_to_db(replace_check)")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -542,8 +581,7 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
|
||||
}
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
if (SQLITE_OK == sqlite3_prepare_v2(g_chatlog_database, duplicate_check_query, -1, &stmt, NULL)) {
|
||||
if (_db_prepare_ctx(duplicate_check_query, &stmt, "_add_to_db(duplicate_check)")) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
log_error("Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s; content: %s", message->id, message->stanzaid, from_jid->barejid, message->plain);
|
||||
cons_show_error("Got a message with duplicate (server-generated) stanza-id from %s.", from_jid->fulljid);
|
||||
@@ -599,8 +637,7 @@ _get_db_version(void)
|
||||
int current_version = -1;
|
||||
const char* query = "SELECT `version` FROM `DbVersion` LIMIT 1";
|
||||
sqlite3_stmt* statement;
|
||||
|
||||
if (sqlite3_prepare_v2(g_chatlog_database, query, -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (_db_prepare_ctx(query, &statement, "_get_db_version()")) {
|
||||
if (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
current_version = sqlite3_column_int(statement, 0);
|
||||
}
|
||||
|
||||
@@ -532,11 +532,13 @@ omemo_set_device_list(const char* const from, GList* device_list)
|
||||
for (device_id = device_list; device_id != NULL; device_id = device_id->next) {
|
||||
GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid->barejid);
|
||||
if (known_identities) {
|
||||
GList* fp = NULL;
|
||||
for (fp = g_hash_table_get_keys(known_identities); fp != NULL; fp = fp->next) {
|
||||
if (device_id->data == g_hash_table_lookup(known_identities, fp->data)) {
|
||||
cons_show("OMEMO: Adding firstusage trust for %s device %d - Fingerprint %s", jid->barejid, device_id->data, omemo_format_fingerprint(fp->data));
|
||||
omemo_trust(jid->barejid, omemo_format_fingerprint(fp->data));
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, known_identities);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
if (device_id->data == value) {
|
||||
cons_show("OMEMO: Adding firstusage trust for %s device %d - Fingerprint %s", jid->barejid, device_id->data, omemo_format_fingerprint(key));
|
||||
omemo_trust(jid->barejid, omemo_format_fingerprint(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,14 +345,13 @@ p_gpg_list_keys(void)
|
||||
|
||||
// TODO: move autocomplete in other place
|
||||
autocomplete_clear(key_ac);
|
||||
GList* ids = g_hash_table_get_keys(result);
|
||||
GList* curr = ids;
|
||||
while (curr) {
|
||||
ProfPGPKey* key = g_hash_table_lookup(result, curr->data);
|
||||
autocomplete_add(key_ac, key->id);
|
||||
curr = curr->next;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, result);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
||||
autocomplete_add(key_ac, pgp_key->id);
|
||||
}
|
||||
g_list_free(ids);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -141,18 +141,16 @@ autocompleters_complete(const char* const input, gboolean previous)
|
||||
while (curr_hash) {
|
||||
GHashTable* key_to_ac = curr_hash->data;
|
||||
|
||||
GList* keys = g_hash_table_get_keys(key_to_ac);
|
||||
GList* curr = keys;
|
||||
while (curr) {
|
||||
result = autocomplete_param_with_ac(input, curr->data, g_hash_table_lookup(key_to_ac, curr->data), TRUE, previous);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, key_to_ac);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
result = autocomplete_param_with_ac(input, key, value, TRUE, previous);
|
||||
if (result) {
|
||||
g_list_free(ac_hashes);
|
||||
g_list_free(keys);
|
||||
return result;
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(keys);
|
||||
|
||||
curr_hash = g_list_next(curr_hash);
|
||||
}
|
||||
@@ -162,22 +160,19 @@ autocompleters_complete(const char* const input, gboolean previous)
|
||||
curr_hash = filepath_hashes;
|
||||
while (curr_hash) {
|
||||
GHashTable* prefixes_hash = curr_hash->data;
|
||||
GList* prefixes = g_hash_table_get_keys(prefixes_hash);
|
||||
GList* curr_prefix = prefixes;
|
||||
while (curr_prefix) {
|
||||
char* prefix = curr_prefix->data;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, prefixes_hash);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* prefix = (char*)key;
|
||||
if (g_str_has_prefix(input, prefix)) {
|
||||
result = cmd_ac_complete_filepath(input, prefix, previous);
|
||||
if (result) {
|
||||
g_list_free(filepath_hashes);
|
||||
g_list_free(prefixes);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
curr_prefix = g_list_next(curr_prefix);
|
||||
}
|
||||
g_list_free(prefixes);
|
||||
|
||||
curr_hash = g_list_next(curr_hash);
|
||||
}
|
||||
|
||||
@@ -149,15 +149,14 @@ callbacks_remove(const char* const plugin_name)
|
||||
{
|
||||
GHashTable* command_hash = g_hash_table_lookup(p_commands, plugin_name);
|
||||
if (command_hash) {
|
||||
GList* commands = g_hash_table_get_keys(command_hash);
|
||||
GList* curr = commands;
|
||||
while (curr) {
|
||||
char* command = curr->data;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, command_hash);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* command = (char*)key;
|
||||
cmd_ac_remove(command);
|
||||
cmd_ac_remove_help(&command[1]);
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(commands);
|
||||
}
|
||||
|
||||
g_hash_table_remove(p_commands, plugin_name);
|
||||
@@ -165,13 +164,12 @@ callbacks_remove(const char* const plugin_name)
|
||||
|
||||
GHashTable* tag_to_win_cb_hash = g_hash_table_lookup(p_window_callbacks, plugin_name);
|
||||
if (tag_to_win_cb_hash) {
|
||||
GList* tags = g_hash_table_get_keys(tag_to_win_cb_hash);
|
||||
GList* curr = tags;
|
||||
while (curr) {
|
||||
wins_close_plugin(curr->data);
|
||||
curr = g_list_next(curr);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, tag_to_win_cb_hash);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
wins_close_plugin(key);
|
||||
}
|
||||
g_list_free(tags);
|
||||
}
|
||||
|
||||
g_hash_table_remove(p_window_callbacks, plugin_name);
|
||||
|
||||
@@ -107,10 +107,11 @@ disco_remove_features(const char* plugin_name)
|
||||
return;
|
||||
}
|
||||
|
||||
GList* plugin_feature_list = g_hash_table_get_keys(plugin_features_set);
|
||||
GList* curr = plugin_feature_list;
|
||||
while (curr) {
|
||||
char* feature = curr->data;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, plugin_features_set);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
char* feature = (char*)key;
|
||||
if (g_hash_table_contains(features, feature)) {
|
||||
void* refcountp = g_hash_table_lookup(features, feature);
|
||||
int refcount = GPOINTER_TO_INT(refcountp);
|
||||
@@ -121,10 +122,7 @@ disco_remove_features(const char* plugin_name)
|
||||
g_hash_table_replace(features, strdup(feature), GINT_TO_POINTER(refcount));
|
||||
}
|
||||
}
|
||||
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
g_list_free(plugin_feature_list);
|
||||
}
|
||||
|
||||
GList*
|
||||
|
||||
@@ -887,8 +887,8 @@ _python_undefined_error(ProfPlugin* plugin, char* hook, char* type)
|
||||
g_string_append(err_msg, hook);
|
||||
g_string_append(err_msg, "(): return value undefined, expected ");
|
||||
g_string_append(err_msg, type);
|
||||
log_error(err_msg->str);
|
||||
cons_show_error(err_msg->str);
|
||||
log_error("%s", err_msg->str);
|
||||
cons_show_error("%s", err_msg->str);
|
||||
g_string_free(err_msg, TRUE);
|
||||
}
|
||||
|
||||
@@ -901,8 +901,8 @@ _python_type_error(ProfPlugin* plugin, char* hook, char* type)
|
||||
g_string_append(err_msg, hook);
|
||||
g_string_append(err_msg, "(): incorrect return type, expected ");
|
||||
g_string_append(err_msg, type);
|
||||
log_error(err_msg->str);
|
||||
cons_show_error(err_msg->str);
|
||||
log_error("%s", err_msg->str);
|
||||
cons_show_error("%s", err_msg->str);
|
||||
g_string_free(err_msg, TRUE);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
|
||||
*/
|
||||
min_runtime += waittime;
|
||||
} else {
|
||||
log_error(err_msg);
|
||||
log_error("%s", err_msg);
|
||||
g_free(err_msg);
|
||||
commands = NULL;
|
||||
}
|
||||
@@ -245,7 +245,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
|
||||
if (prof_log_level == PROF_LEVEL_DEBUG) {
|
||||
ProfWin* console = wins_get_console();
|
||||
win_println(console, THEME_DEFAULT, "-", "Debug mode enabled! Logging to: ");
|
||||
win_println(console, THEME_DEFAULT, "-", get_log_file_location());
|
||||
win_println(console, THEME_DEFAULT, "-", "%s", get_log_file_location());
|
||||
}
|
||||
session_init();
|
||||
cmd_init();
|
||||
|
||||
@@ -311,7 +311,7 @@ http_file_put(void* userdata)
|
||||
}
|
||||
win_update_entry_message(upload->window, upload->put_url, err_msg);
|
||||
}
|
||||
cons_show_error(err_msg);
|
||||
cons_show_error("%s", err_msg);
|
||||
} else {
|
||||
if (!upload->cancel) {
|
||||
auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': 100%%", upload->filename);
|
||||
@@ -327,7 +327,7 @@ http_file_put(void* userdata)
|
||||
if (!fail_msg) {
|
||||
fail_msg = g_strdup(FALLBACK_MSG);
|
||||
}
|
||||
cons_show_error(fail_msg);
|
||||
cons_show_error("%s", fail_msg);
|
||||
} else {
|
||||
switch (upload->window->type) {
|
||||
case WIN_CHAT:
|
||||
|
||||
@@ -140,6 +140,10 @@ void
|
||||
buffer_remove_entry(ProfBuff buffer, int entry)
|
||||
{
|
||||
GSList* node = g_slist_nth(buffer->entries, entry);
|
||||
if (node == NULL) {
|
||||
// Index out of range; nothing to remove
|
||||
return;
|
||||
}
|
||||
ProfBuffEntry* e = node->data;
|
||||
buffer->lines -= e->_lines;
|
||||
_free_entry(e);
|
||||
@@ -168,6 +172,9 @@ ProfBuffEntry*
|
||||
buffer_get_entry(ProfBuff buffer, int entry)
|
||||
{
|
||||
GSList* node = g_slist_nth(buffer->entries, entry);
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return node->data;
|
||||
}
|
||||
|
||||
@@ -189,6 +196,7 @@ buffer_get_entry_by_id(ProfBuff buffer, const char* const id)
|
||||
static ProfBuffEntry*
|
||||
_create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos)
|
||||
{
|
||||
assert(time != NULL);
|
||||
ProfBuffEntry* e = malloc(sizeof(struct prof_buff_entry_t));
|
||||
e->show_char = STRDUP_OR_NULL(show_char);
|
||||
e->pad_indent = pad_indent;
|
||||
|
||||
@@ -592,8 +592,11 @@ chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* e
|
||||
{
|
||||
auto_gchar gchar* _end_time = NULL;
|
||||
if (!end_time && buffer_size(((ProfWin*)chatwin)->layout->buffer) > 0) {
|
||||
_end_time = g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0)->time);
|
||||
end_time = _end_time;
|
||||
ProfBuffEntry* first = buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0);
|
||||
if (first && first->time) {
|
||||
_end_time = g_date_time_format_iso8601(first->time);
|
||||
end_time = _end_time;
|
||||
}
|
||||
}
|
||||
|
||||
GSList* history = NULL;
|
||||
|
||||
@@ -152,7 +152,7 @@ cons_bad_cmd_usage(const char* const cmd)
|
||||
g_string_printf(msg, "Invalid usage, see '/help %s' for details.", &cmd[1]);
|
||||
|
||||
cons_show("");
|
||||
cons_show(msg->str);
|
||||
cons_show("%s", msg->str);
|
||||
|
||||
g_string_free(msg, TRUE);
|
||||
}
|
||||
@@ -773,7 +773,7 @@ cons_show_disco_info(const char* jid, GSList* identities, GSList* features)
|
||||
if (identity->category) {
|
||||
identity_str = g_string_append(identity_str, identity->category);
|
||||
}
|
||||
cons_show(identity_str->str);
|
||||
cons_show("%s", identity_str->str);
|
||||
g_string_free(identity_str, TRUE);
|
||||
identities = g_slist_next(identities);
|
||||
}
|
||||
@@ -938,7 +938,7 @@ cons_show_account_list(gchar** accounts)
|
||||
theme_item_t presence_colour = theme_main_presence_attrs(string_from_resource_presence(presence));
|
||||
win_println(console, presence_colour, "-", "%s", accounts[i]);
|
||||
} else {
|
||||
cons_show(accounts[i]);
|
||||
cons_show("%s", accounts[i]);
|
||||
}
|
||||
}
|
||||
cons_show("");
|
||||
@@ -1019,7 +1019,7 @@ cons_show_account(ProfAccount* account)
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
cons_show(manual->str);
|
||||
cons_show("%s", manual->str);
|
||||
g_string_free(manual, TRUE);
|
||||
}
|
||||
if (g_list_length(account->otr_opportunistic) > 0) {
|
||||
@@ -1032,7 +1032,7 @@ cons_show_account(ProfAccount* account)
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
cons_show(opportunistic->str);
|
||||
cons_show("%s", opportunistic->str);
|
||||
g_string_free(opportunistic, TRUE);
|
||||
}
|
||||
if (g_list_length(account->otr_always) > 0) {
|
||||
@@ -1045,7 +1045,7 @@ cons_show_account(ProfAccount* account)
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
cons_show(always->str);
|
||||
cons_show("%s", always->str);
|
||||
g_string_free(always, TRUE);
|
||||
}
|
||||
|
||||
@@ -2297,7 +2297,7 @@ cons_show_themes(GSList* themes)
|
||||
} else {
|
||||
cons_show("Available themes:");
|
||||
while (themes) {
|
||||
cons_show(themes->data);
|
||||
cons_show("%s", themes->data);
|
||||
themes = g_slist_next(themes);
|
||||
}
|
||||
}
|
||||
@@ -2315,7 +2315,7 @@ cons_show_scripts(GSList* scripts)
|
||||
} else {
|
||||
cons_show("Scripts:");
|
||||
while (scripts) {
|
||||
cons_show(scripts->data);
|
||||
cons_show("%s", scripts->data);
|
||||
scripts = g_slist_next(scripts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ ui_handle_error(const char* const err_msg)
|
||||
GString* msg = g_string_new("");
|
||||
g_string_printf(msg, "Error %s", err_msg);
|
||||
|
||||
cons_show_error(msg->str);
|
||||
cons_show_error("%s", msg->str);
|
||||
|
||||
g_string_free(msg, TRUE);
|
||||
}
|
||||
@@ -461,7 +461,7 @@ ui_invalid_command_usage(const char* const cmd, void (*setting_func)(void))
|
||||
(*setting_func)();
|
||||
} else {
|
||||
cons_show("");
|
||||
cons_show(msg->str);
|
||||
cons_show("%s", msg->str);
|
||||
ProfWin* current = wins_get_current();
|
||||
if (current->type == WIN_CHAT) {
|
||||
win_println(current, THEME_DEFAULT, "-", "%s", msg->str);
|
||||
|
||||
@@ -144,10 +144,13 @@ void
|
||||
create_input_window(void)
|
||||
{
|
||||
/* MB_CUR_MAX is evaluated at runtime depending on the current
|
||||
* locale, therefore we check that our own version is big enough
|
||||
* and bail out if it isn't.
|
||||
* locale; ensure our own compiled-in maximum is sufficient.
|
||||
* Fail gracefully instead of aborting in production.
|
||||
*/
|
||||
assert(MB_CUR_MAX <= PROF_MB_CUR_MAX);
|
||||
if (MB_CUR_MAX > PROF_MB_CUR_MAX) {
|
||||
cons_show_error("Your locale's MB_CUR_MAX (%zu) exceeds PROF_MB_CUR_MAX (%d); input window disabled.", (size_t)MB_CUR_MAX, PROF_MB_CUR_MAX);
|
||||
return;
|
||||
}
|
||||
#ifdef NCURSES_REENTRANT
|
||||
set_escdelay(25);
|
||||
#else
|
||||
@@ -163,6 +166,10 @@ create_input_window(void)
|
||||
rl_callback_handler_install(NULL, _inp_rl_linehandler);
|
||||
|
||||
inp_win = newpad(1, INP_WIN_MAX);
|
||||
if (!inp_win) {
|
||||
// Failed to allocate input pad; leave inp_win NULL and avoid further use
|
||||
return;
|
||||
}
|
||||
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
|
||||
keypad(inp_win, TRUE);
|
||||
wmove(inp_win, 0, 0);
|
||||
@@ -238,6 +245,9 @@ inp_readline(void)
|
||||
void
|
||||
inp_win_resize(void)
|
||||
{
|
||||
if (!inp_win) {
|
||||
return;
|
||||
}
|
||||
int col = getcurx(inp_win);
|
||||
int wcols = getmaxx(stdscr);
|
||||
|
||||
@@ -285,8 +295,10 @@ void
|
||||
inp_close(void)
|
||||
{
|
||||
rl_callback_handler_remove();
|
||||
delwin(inp_win);
|
||||
inp_win = NULL;
|
||||
if (inp_win) {
|
||||
delwin(inp_win);
|
||||
inp_win = NULL;
|
||||
}
|
||||
fclose(discard);
|
||||
discard = NULL;
|
||||
}
|
||||
@@ -294,6 +306,9 @@ inp_close(void)
|
||||
char*
|
||||
inp_get_line(void)
|
||||
{
|
||||
if (!inp_win) {
|
||||
return NULL;
|
||||
}
|
||||
werase(inp_win);
|
||||
wmove(inp_win, 0, 0);
|
||||
_inp_win_update_virtual();
|
||||
@@ -318,6 +333,9 @@ inp_set_line(const char* const new_line)
|
||||
char*
|
||||
inp_get_password(void)
|
||||
{
|
||||
if (!inp_win) {
|
||||
return NULL;
|
||||
}
|
||||
werase(inp_win);
|
||||
wmove(inp_win, 0, 0);
|
||||
_inp_win_update_virtual();
|
||||
|
||||
@@ -111,16 +111,23 @@ status_bar_init(void)
|
||||
|
||||
int row = screen_statusbar_row();
|
||||
int cols = getmaxx(stdscr);
|
||||
if (cols <= 0) {
|
||||
cols = 1;
|
||||
}
|
||||
statusbar_win = newwin(1, cols, row, 0);
|
||||
|
||||
status_bar_draw();
|
||||
if (statusbar_win) {
|
||||
status_bar_draw();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
status_bar_close(void)
|
||||
{
|
||||
delwin(statusbar_win);
|
||||
statusbar_win = NULL;
|
||||
if (statusbar_win) {
|
||||
delwin(statusbar_win);
|
||||
statusbar_win = NULL;
|
||||
}
|
||||
if (statusbar) {
|
||||
if (statusbar->time) {
|
||||
g_free(statusbar->time);
|
||||
@@ -145,7 +152,13 @@ status_bar_close(void)
|
||||
void
|
||||
status_bar_resize(void)
|
||||
{
|
||||
if (!statusbar_win) {
|
||||
return;
|
||||
}
|
||||
int cols = getmaxx(stdscr);
|
||||
if (cols <= 0) {
|
||||
cols = 1;
|
||||
}
|
||||
werase(statusbar_win);
|
||||
int row = screen_statusbar_row();
|
||||
wresize(statusbar_win, 1, cols);
|
||||
@@ -285,6 +298,9 @@ status_bar_clear_fulljid(void)
|
||||
void
|
||||
status_bar_draw(void)
|
||||
{
|
||||
if (!statusbar_win) {
|
||||
return;
|
||||
}
|
||||
werase(statusbar_win);
|
||||
wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
|
||||
|
||||
@@ -674,8 +690,13 @@ _display_name(StatusBarTab* tab)
|
||||
fullname = g_strconcat(mucwin_title, " conf", NULL);
|
||||
} else if (tab->window_type == WIN_PRIVATE) {
|
||||
auto_jid Jid* jid = jid_create(tab->identifier);
|
||||
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
|
||||
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
|
||||
if (jid) {
|
||||
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
|
||||
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
|
||||
} else {
|
||||
// Fallback: use identifier directly if JID parsing failed
|
||||
fullname = strdup(tab->identifier);
|
||||
}
|
||||
} else {
|
||||
fullname = strdup("window");
|
||||
}
|
||||
|
||||
@@ -73,9 +73,16 @@ void
|
||||
create_title_bar(void)
|
||||
{
|
||||
int cols = getmaxx(stdscr);
|
||||
if (cols <= 0) {
|
||||
cols = 1;
|
||||
}
|
||||
|
||||
int row = screen_titlebar_row();
|
||||
win = newwin(1, cols, row, 0);
|
||||
if (!win) {
|
||||
// Failed to create title bar window; skip initialization to avoid NULL deref
|
||||
return;
|
||||
}
|
||||
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
|
||||
title_bar_console();
|
||||
title_bar_set_presence(CONTACT_OFFLINE);
|
||||
@@ -88,13 +95,18 @@ create_title_bar(void)
|
||||
void
|
||||
free_title_bar(void)
|
||||
{
|
||||
delwin(win);
|
||||
win = NULL;
|
||||
if (win) {
|
||||
delwin(win);
|
||||
win = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
title_bar_update_virtual(void)
|
||||
{
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
ProfWin* window = wins_get_current();
|
||||
if (window->type != WIN_CONSOLE) {
|
||||
if (typing_elapsed) {
|
||||
@@ -114,7 +126,13 @@ title_bar_update_virtual(void)
|
||||
void
|
||||
title_bar_resize(void)
|
||||
{
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
int cols = getmaxx(stdscr);
|
||||
if (cols <= 0) {
|
||||
cols = 1;
|
||||
}
|
||||
|
||||
werase(win);
|
||||
|
||||
@@ -131,6 +149,9 @@ title_bar_resize(void)
|
||||
void
|
||||
title_bar_console(void)
|
||||
{
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
werase(win);
|
||||
if (typing_elapsed) {
|
||||
g_timer_destroy(typing_elapsed);
|
||||
@@ -192,6 +213,9 @@ title_bar_set_typing(gboolean is_typing)
|
||||
static void
|
||||
_title_bar_draw(void)
|
||||
{
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
int pos;
|
||||
int maxrightpos;
|
||||
ProfWin* current = wins_get_current();
|
||||
|
||||
@@ -75,12 +75,30 @@ static void _win_print_internal(ProfWin* window, const char* show_char, int pad_
|
||||
int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt);
|
||||
static void _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent);
|
||||
|
||||
// Helper: clamp a subwindow width to a sane range [1, cols-1] if possible
|
||||
static int
|
||||
_check_subwin_width(int cols, int width)
|
||||
{
|
||||
if (cols > 1) {
|
||||
if (width < 1)
|
||||
width = 1;
|
||||
if (width >= cols)
|
||||
width = cols - 1;
|
||||
} else {
|
||||
width = 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
int
|
||||
win_roster_cols(void)
|
||||
{
|
||||
int roster_win_percent = prefs_get_roster_size();
|
||||
int cols = getmaxx(stdscr);
|
||||
return CEILING((((double)cols) / 100) * roster_win_percent);
|
||||
int width = CEILING((((double)cols) / 100) * roster_win_percent);
|
||||
// Clamp to a sane range to avoid zero/full-width pads
|
||||
width = _check_subwin_width(cols, width);
|
||||
return width;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -88,7 +106,10 @@ win_occpuants_cols(void)
|
||||
{
|
||||
int occupants_win_percent = prefs_get_occupants_size();
|
||||
int cols = getmaxx(stdscr);
|
||||
return CEILING((((double)cols) / 100) * occupants_win_percent);
|
||||
int width = CEILING((((double)cols) / 100) * occupants_win_percent);
|
||||
// Clamp to a sane range to avoid zero/full-width pads
|
||||
width = _check_subwin_width(cols, width);
|
||||
return width;
|
||||
}
|
||||
|
||||
static ProfLayout*
|
||||
@@ -144,6 +165,7 @@ win_create_console(void)
|
||||
ProfWin*
|
||||
win_create_chat(const char* const barejid)
|
||||
{
|
||||
assert(barejid != NULL);
|
||||
ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
|
||||
new_win->window.type = WIN_CHAT;
|
||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||
@@ -175,6 +197,7 @@ win_create_chat(const char* const barejid)
|
||||
ProfWin*
|
||||
win_create_muc(const char* const roomjid)
|
||||
{
|
||||
assert(roomjid != NULL);
|
||||
ProfMucWin* new_win = malloc(sizeof(ProfMucWin));
|
||||
int cols = getmaxx(stdscr);
|
||||
|
||||
@@ -233,6 +256,8 @@ win_create_muc(const char* const roomjid)
|
||||
ProfWin*
|
||||
win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
|
||||
{
|
||||
assert(roomjid != NULL);
|
||||
assert(form != NULL);
|
||||
ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
|
||||
new_win->window.type = WIN_CONFIG;
|
||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||
@@ -251,6 +276,7 @@ win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback
|
||||
ProfWin*
|
||||
win_create_private(const char* const fulljid)
|
||||
{
|
||||
assert(fulljid != NULL);
|
||||
ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
|
||||
new_win->window.type = WIN_PRIVATE;
|
||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||
@@ -281,6 +307,8 @@ win_create_xmlconsole(void)
|
||||
ProfWin*
|
||||
win_create_plugin(const char* const plugin_name, const char* const tag)
|
||||
{
|
||||
assert(plugin_name != NULL);
|
||||
assert(tag != NULL);
|
||||
ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
|
||||
new_win->window.type = WIN_PLUGIN;
|
||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||
@@ -297,6 +325,7 @@ win_create_plugin(const char* const plugin_name, const char* const tag)
|
||||
ProfWin*
|
||||
win_create_vcard(vCard* vcard)
|
||||
{
|
||||
assert(vcard != NULL);
|
||||
ProfVcardWin* new_win = malloc(sizeof(ProfVcardWin));
|
||||
new_win->window.type = WIN_VCARD;
|
||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||
@@ -348,7 +377,7 @@ win_get_title(ProfWin* window)
|
||||
const ProfConfWin* confwin = (ProfConfWin*)window;
|
||||
assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
|
||||
auto_gchar gchar* mucwin_title = mucwin_generate_title(confwin->roomjid, PREF_TITLEBAR_MUC_TITLE);
|
||||
if (confwin->form->modified) {
|
||||
if (confwin->form && confwin->form->modified) {
|
||||
return g_strconcat(mucwin_title, " config *", NULL);
|
||||
}
|
||||
return g_strconcat(mucwin_title, " config", NULL);
|
||||
@@ -556,7 +585,25 @@ win_show_subwin(ProfWin* window)
|
||||
}
|
||||
|
||||
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
||||
// If a subwindow already exists (e.g. repeated call), destroy it to avoid leaks
|
||||
if (layout->subwin) {
|
||||
delwin(layout->subwin);
|
||||
layout->subwin = NULL;
|
||||
}
|
||||
|
||||
// Ensure minimum width to avoid creating a zero-width pad
|
||||
if (subwin_cols <= 0) {
|
||||
subwin_cols = 1;
|
||||
}
|
||||
|
||||
layout->subwin = newpad(PAD_SIZE, subwin_cols);
|
||||
if (layout->subwin == NULL) {
|
||||
// Failed to allocate subwindow; keep base window resized to full width
|
||||
log_error("Failed to create subwindow pad (cols=%d)", subwin_cols);
|
||||
wresize(layout->base.win, PAD_SIZE, cols);
|
||||
win_redraw(window);
|
||||
return;
|
||||
}
|
||||
wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
|
||||
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
|
||||
win_redraw(window);
|
||||
@@ -910,6 +957,11 @@ win_refresh_with_subwin(ProfWin* window)
|
||||
int row_end = screen_mainwin_row_end();
|
||||
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
||||
|
||||
// Safety: if subwindow is not active, nothing to refresh
|
||||
if (layout == NULL || layout->subwin == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window->type == WIN_MUC) {
|
||||
subwin_cols = win_occpuants_cols();
|
||||
} else if (window->type == WIN_CONSOLE) {
|
||||
@@ -2038,7 +2090,14 @@ win_print_loading_history(ProfWin* window)
|
||||
gboolean is_buffer_empty = buffer_size(window->layout->buffer) == 0;
|
||||
|
||||
if (!is_buffer_empty) {
|
||||
timestamp = buffer_get_entry(window->layout->buffer, 0)->time;
|
||||
ProfBuffEntry* first = buffer_get_entry(window->layout->buffer, 0);
|
||||
if (first && first->time) {
|
||||
timestamp = first->time;
|
||||
} else {
|
||||
// Fallback to current time if entry/time is unavailable
|
||||
timestamp = g_date_time_new_now_local();
|
||||
is_buffer_empty = TRUE; // ensure we unref fallback timestamp below
|
||||
}
|
||||
} else {
|
||||
timestamp = g_date_time_new_now_local();
|
||||
}
|
||||
@@ -2238,7 +2297,7 @@ void
|
||||
win_handle_command_exec_result_note(ProfWin* window, const char* const type, const char* const value)
|
||||
{
|
||||
assert(window != NULL);
|
||||
win_println(window, THEME_DEFAULT, "!", value);
|
||||
win_println(window, THEME_DEFAULT, "!", "%s", value);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -407,8 +407,10 @@ wins_get_by_string(const char* str)
|
||||
if (barejid) {
|
||||
ProfChatWin* chatwin = wins_get_chat(barejid);
|
||||
if (chatwin) {
|
||||
free(barejid);
|
||||
return (ProfWin*)chatwin;
|
||||
}
|
||||
free(barejid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,6 +605,9 @@ wins_new_xmlconsole(void)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_xmlconsole();
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
autocomplete_add(wins_ac, "xmlconsole");
|
||||
autocomplete_add(wins_close_ac, "xmlconsole");
|
||||
@@ -614,6 +619,9 @@ wins_new_chat(const char* const barejid)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_chat(barejid);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
|
||||
autocomplete_add(wins_ac, barejid);
|
||||
@@ -637,6 +645,9 @@ wins_new_muc(const char* const roomjid)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_muc(roomjid);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
autocomplete_add(wins_ac, roomjid);
|
||||
autocomplete_add(wins_close_ac, roomjid);
|
||||
@@ -651,6 +662,9 @@ wins_new_config(const char* const roomjid, DataForm* form, ProfConfWinCallback s
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
|
||||
return newwin;
|
||||
@@ -661,6 +675,9 @@ wins_new_private(const char* const fulljid)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_private(fulljid);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
autocomplete_add(wins_ac, fulljid);
|
||||
autocomplete_add(wins_close_ac, fulljid);
|
||||
@@ -675,6 +692,9 @@ wins_new_plugin(const char* const plugin_name, const char* const tag)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_plugin(plugin_name, tag);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
autocomplete_add(wins_ac, tag);
|
||||
autocomplete_add(wins_close_ac, tag);
|
||||
@@ -686,6 +706,9 @@ wins_new_vcard(vCard* vcard)
|
||||
{
|
||||
int result = _wins_get_next_available_num(keys);
|
||||
ProfWin* newwin = win_create_vcard(vcard);
|
||||
if (!newwin) {
|
||||
return NULL;
|
||||
}
|
||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||
|
||||
return newwin;
|
||||
|
||||
@@ -171,16 +171,15 @@ caps_get_features(void)
|
||||
{
|
||||
GList* result = NULL;
|
||||
|
||||
GList* features_as_list = g_hash_table_get_keys(prof_features);
|
||||
GList* curr = features_as_list;
|
||||
while (curr) {
|
||||
result = g_list_append(result, strdup(curr->data));
|
||||
curr = g_list_next(curr);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init(&iter, prof_features);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
result = g_list_append(result, strdup((char*)key));
|
||||
}
|
||||
g_list_free(features_as_list);
|
||||
|
||||
GList* plugin_features = plugins_get_disco_features();
|
||||
curr = plugin_features;
|
||||
GList* curr = plugin_features;
|
||||
while (curr) {
|
||||
result = g_list_append(result, strdup(curr->data));
|
||||
curr = g_list_next(curr);
|
||||
|
||||
@@ -148,7 +148,7 @@ connection_init(void)
|
||||
if (string_to_verbosity(v, &verbosity, &err_msg)) {
|
||||
xmpp_ctx_set_verbosity(conn.xmpp_ctx, verbosity);
|
||||
} else {
|
||||
cons_show(err_msg);
|
||||
cons_show("%s", err_msg);
|
||||
}
|
||||
conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
|
||||
|
||||
@@ -638,22 +638,18 @@ gboolean
|
||||
connection_supports(const char* const feature)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
GList* jids = g_hash_table_get_keys(conn.features_by_jid);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
GList* curr = jids;
|
||||
while (curr) {
|
||||
char* jid = curr->data;
|
||||
GHashTable* features = g_hash_table_lookup(conn.features_by_jid, jid);
|
||||
g_hash_table_iter_init(&iter, conn.features_by_jid);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
GHashTable* features = (GHashTable*)value;
|
||||
if (features && g_hash_table_lookup(features, feature)) {
|
||||
ret = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
g_list_free(jids);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -664,22 +660,17 @@ connection_jid_for_feature(const char* const feature)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GList* jids = g_hash_table_get_keys(conn.features_by_jid);
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
GList* curr = jids;
|
||||
while (curr) {
|
||||
char* jid = curr->data;
|
||||
GHashTable* features = g_hash_table_lookup(conn.features_by_jid, jid);
|
||||
g_hash_table_iter_init(&iter, conn.features_by_jid);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
GHashTable* features = (GHashTable*)value;
|
||||
if (features && g_hash_table_lookup(features, feature)) {
|
||||
g_list_free(jids);
|
||||
return jid;
|
||||
return (const char*)key;
|
||||
}
|
||||
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
g_list_free(jids);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1034,7 +1025,7 @@ _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status
|
||||
conn.sm_state = xmpp_conn_get_sm_state(conn.xmpp_conn);
|
||||
if (send_queue_len > 0 && prefs_get_boolean(PREF_STROPHE_SM_RESEND)) {
|
||||
conn.queued_messages = calloc(send_queue_len + 1, sizeof(*conn.queued_messages));
|
||||
for (int n = 0; n < send_queue_len && conn.queued_messages[n]; ++n) {
|
||||
for (int n = 0; n < send_queue_len; ++n) {
|
||||
conn.queued_messages[n] = xmpp_conn_send_queue_drop_element(conn.xmpp_conn, XMPP_QUEUE_OLDEST);
|
||||
}
|
||||
} else if (send_queue_len > 0) {
|
||||
@@ -1189,12 +1180,13 @@ connection_debug_print_features()
|
||||
continue;
|
||||
}
|
||||
|
||||
GList* feature_keys = g_hash_table_get_keys(features);
|
||||
for (GList* l = feature_keys; l != NULL; l = l->next) {
|
||||
const char* feature = (const char*)l->data;
|
||||
GHashTableIter feature_iter;
|
||||
gpointer feature_key, feature_value;
|
||||
g_hash_table_iter_init(&feature_iter, features);
|
||||
while (g_hash_table_iter_next(&feature_iter, &feature_key, &feature_value)) {
|
||||
const char* feature = (const char*)feature_key;
|
||||
log_debug("%s:\t%s", jid, feature);
|
||||
}
|
||||
g_list_free(feature_keys);
|
||||
}
|
||||
|
||||
log_debug("=== End of Features ===");
|
||||
|
||||
@@ -437,18 +437,7 @@ form_get_form_type_field(DataForm* form)
|
||||
gboolean
|
||||
form_tag_exists(DataForm* form, const char* const tag)
|
||||
{
|
||||
GList* tags = g_hash_table_get_keys(form->tag_to_var);
|
||||
GList* curr = tags;
|
||||
while (curr) {
|
||||
if (g_strcmp0(curr->data, tag) == 0) {
|
||||
g_list_free(tags);
|
||||
return TRUE;
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
g_list_free(tags);
|
||||
return FALSE;
|
||||
return g_hash_table_contains(form->tag_to_var, tag);
|
||||
}
|
||||
|
||||
form_field_type_t
|
||||
|
||||
@@ -868,7 +868,7 @@ _handle_error(xmpp_stanza_t* const stanza)
|
||||
g_string_append(log_msg, " error=");
|
||||
g_string_append(log_msg, err_msg);
|
||||
|
||||
log_info(log_msg->str);
|
||||
log_info("%s", log_msg->str);
|
||||
|
||||
g_string_free(log_msg, TRUE);
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ _ox_metadata_result(xmpp_stanza_t* const stanza, void* const userdata)
|
||||
|
||||
if (fingerprint) {
|
||||
if (strlen(fingerprint) == KEYID_LENGTH) {
|
||||
cons_show(fingerprint);
|
||||
cons_show("%s", fingerprint);
|
||||
} else {
|
||||
cons_show("OX: Wrong char size of public key");
|
||||
log_error("[OX] Wrong chat size of public key %s", fingerprint);
|
||||
|
||||
@@ -455,7 +455,7 @@ _presence_error_handler(xmpp_stanza_t* const stanza)
|
||||
g_string_append(log_msg, " error=");
|
||||
g_string_append(log_msg, err_msg);
|
||||
|
||||
log_info(log_msg->str);
|
||||
log_info("%s", log_msg->str);
|
||||
|
||||
g_string_free(log_msg, TRUE);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "test_software.h"
|
||||
#include "test_muc.h"
|
||||
#include "test_disconnect.h"
|
||||
#include "test_lastactivity.h"
|
||||
|
||||
/* Macro to wrap each test with setup/teardown functions */
|
||||
#define PROF_FUNC_TEST(test) cmocka_unit_test_setup_teardown(test, init_prof_test, close_prof_test)
|
||||
@@ -104,6 +105,10 @@ main(int argc, char* argv[])
|
||||
PROF_FUNC_TEST(display_software_version_result_when_from_domainpart),
|
||||
PROF_FUNC_TEST(show_message_in_chat_window_when_no_resource),
|
||||
PROF_FUNC_TEST(display_software_version_result_in_chat),
|
||||
|
||||
/* Last Activity - XEP-0012 */
|
||||
PROF_FUNC_TEST(responds_to_last_activity_request),
|
||||
PROF_FUNC_TEST(last_activity_request_to_contact),
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
@@ -188,6 +193,10 @@ main(int argc, char* argv[])
|
||||
PROF_FUNC_TEST(shows_first_message_in_console_when_window_not_focussed),
|
||||
PROF_FUNC_TEST(shows_no_message_in_console_when_window_not_focussed),
|
||||
|
||||
/* MUC moderation - XEP-0045 room admin */
|
||||
PROF_FUNC_TEST(sends_affiliation_list_request),
|
||||
PROF_FUNC_TEST(sends_kick_request),
|
||||
|
||||
/* Message Carbons - XEP-0280 (message sync across devices) */
|
||||
PROF_FUNC_TEST(send_enable_carbons),
|
||||
PROF_FUNC_TEST(connect_with_carbons_enabled),
|
||||
|
||||
63
tests/functionaltests/test_lastactivity.c
Normal file
63
tests/functionaltests/test_lastactivity.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* test_lastactivity.c
|
||||
* Functional tests for Last Activity (XEP-0012)
|
||||
*/
|
||||
#include <glib.h>
|
||||
#include "prof_cmocka.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stabber.h>
|
||||
|
||||
#include "proftest.h"
|
||||
|
||||
void
|
||||
responds_to_last_activity_request(void **state)
|
||||
{
|
||||
prof_connect();
|
||||
|
||||
// Send incoming last activity request
|
||||
stbbr_send(
|
||||
"<iq id='last1' type='get' to='stabber@localhost/profanity' from='buddy1@localhost/mobile'>"
|
||||
"<query xmlns='jabber:iq:last'/>"
|
||||
"</iq>"
|
||||
);
|
||||
|
||||
// Verify profanity responds with last activity info
|
||||
// The 'seconds' attribute indicates idle time
|
||||
assert_true(stbbr_received(
|
||||
"<iq id='last1' type='result' to='buddy1@localhost/mobile'>"
|
||||
"<query xmlns='jabber:iq:last' seconds='*'/>"
|
||||
"</iq>"
|
||||
));
|
||||
}
|
||||
|
||||
void
|
||||
last_activity_request_to_contact(void **state)
|
||||
{
|
||||
prof_connect();
|
||||
|
||||
stbbr_send(
|
||||
"<presence to='stabber@localhost' from='buddy1@localhost/mobile'>"
|
||||
"<priority>10</priority>"
|
||||
"<status>I'm here</status>"
|
||||
"</presence>"
|
||||
);
|
||||
assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\""));
|
||||
|
||||
// Register response for last activity query
|
||||
stbbr_for_query("jabber:iq:last",
|
||||
"<iq id='*' type='result' from='buddy1@localhost/mobile' to='stabber@localhost/profanity'>"
|
||||
"<query xmlns='jabber:iq:last' seconds='120'/>"
|
||||
"</iq>"
|
||||
);
|
||||
|
||||
prof_input("/lastactivity get buddy1@localhost/mobile");
|
||||
|
||||
// Verify the request was sent
|
||||
assert_true(stbbr_received(
|
||||
"<iq id='*' to='buddy1@localhost/mobile' type='get'>"
|
||||
"<query xmlns='jabber:iq:last'/>"
|
||||
"</iq>"
|
||||
));
|
||||
}
|
||||
7
tests/functionaltests/test_lastactivity.h
Normal file
7
tests/functionaltests/test_lastactivity.h
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* test_lastactivity.h
|
||||
* Header for Last Activity tests (XEP-0012)
|
||||
*/
|
||||
|
||||
void responds_to_last_activity_request(void **state);
|
||||
void last_activity_request_to_contact(void **state);
|
||||
@@ -393,3 +393,83 @@ shows_no_message_in_console_when_window_not_focussed(void **state)
|
||||
assert_false(prof_output_regex("testroom@conference\\.localhost \\(win 2\\)"));
|
||||
prof_timeout_reset();
|
||||
}
|
||||
|
||||
void
|
||||
sends_affiliation_list_request(void **state)
|
||||
{
|
||||
prof_connect();
|
||||
|
||||
stbbr_for_presence_to("testroom@conference.localhost/stabber",
|
||||
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
|
||||
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
|
||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
||||
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='owner'/>"
|
||||
"</x>"
|
||||
"<status code='110'/>"
|
||||
"</presence>"
|
||||
);
|
||||
|
||||
prof_input("/join testroom@conference.localhost");
|
||||
assert_true(prof_output_regex("-> You have joined the room as stabber, role: moderator, affiliation: owner"));
|
||||
|
||||
prof_input("/affiliation owner list");
|
||||
|
||||
assert_true(stbbr_received(
|
||||
"<iq id='*' to='testroom@conference.localhost' type='get'>"
|
||||
"<query xmlns='http://jabber.org/protocol/muc#admin'>"
|
||||
"<item affiliation='owner'/>"
|
||||
"</query>"
|
||||
"</iq>"
|
||||
));
|
||||
}
|
||||
|
||||
void
|
||||
sends_kick_request(void **state)
|
||||
{
|
||||
prof_connect();
|
||||
|
||||
// Enable MUC presence messages to see occupant join/leave
|
||||
prof_input("/presence room all");
|
||||
assert_true(prof_output_regex("All presence updates will appear"));
|
||||
|
||||
stbbr_for_presence_to("testroom@conference.localhost/stabber",
|
||||
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
|
||||
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
|
||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
||||
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='admin'/>"
|
||||
"</x>"
|
||||
"<status code='110'/>"
|
||||
"</presence>"
|
||||
);
|
||||
|
||||
prof_input("/join testroom@conference.localhost");
|
||||
assert_true(prof_output_regex("-> You have joined the room as stabber, role: moderator, affiliation: admin"));
|
||||
|
||||
// Simulate another user in the room
|
||||
stbbr_send(
|
||||
"<presence to='stabber@localhost/profanity' from='testroom@conference.localhost/baduser'>"
|
||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
||||
"<item role='participant' jid='baduser@localhost/phone' affiliation='none'/>"
|
||||
"</x>"
|
||||
"</presence>"
|
||||
);
|
||||
sleep(1);
|
||||
assert_true(prof_output_regex("baduser has joined"));
|
||||
|
||||
// Register success response for kick
|
||||
stbbr_for_query("http://jabber.org/protocol/muc#admin",
|
||||
"<iq id='*' type='result' from='testroom@conference.localhost'/>"
|
||||
);
|
||||
|
||||
prof_input("/kick baduser \"spamming\"");
|
||||
|
||||
assert_true(stbbr_received(
|
||||
"<iq id='*' to='testroom@conference.localhost' type='set'>"
|
||||
"<query xmlns='http://jabber.org/protocol/muc#admin'>"
|
||||
"<item nick='baduser' role='none'>"
|
||||
"<reason>spamming</reason>"
|
||||
"</item>"
|
||||
"</query>"
|
||||
"</iq>"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ void shows_me_message_from_self(void **state);
|
||||
void shows_all_messages_in_console_when_window_not_focussed(void **state);
|
||||
void shows_first_message_in_console_when_window_not_focussed(void **state);
|
||||
void shows_no_message_in_console_when_window_not_focussed(void **state);
|
||||
void sends_affiliation_list_request(void **state);
|
||||
void sends_kick_request(void **state);
|
||||
|
||||
441
tests/unittests/test_autoping.c
Normal file
441
tests/unittests/test_autoping.c
Normal file
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* test_autoping.c
|
||||
* Unit tests for autoping functionality (XEP-0199)
|
||||
*
|
||||
* Tests the autoping logic including:
|
||||
* - Timer state management
|
||||
* - Timeout checking
|
||||
* - Connection state validation
|
||||
*/
|
||||
|
||||
#include "prof_cmocka.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* Since autoping functions use static variables and are tightly coupled
|
||||
* to the connection and xmpp libraries, we test the logic patterns here
|
||||
* rather than calling the actual functions.
|
||||
*
|
||||
* These tests validate the correctness of the autoping algorithm:
|
||||
* - State machine for ping wait
|
||||
* - Timeout calculation
|
||||
* - Connection state checks
|
||||
*/
|
||||
|
||||
/* Connection status enum (isolated from xmpp.h to avoid conflicts) */
|
||||
typedef enum {
|
||||
CONN_DISCONNECTED,
|
||||
CONN_CONNECTED,
|
||||
CONN_CONNECTING
|
||||
} ConnStatus;
|
||||
|
||||
/* Test structure simulating autoping state */
|
||||
typedef struct {
|
||||
gboolean wait;
|
||||
GTimer* timer;
|
||||
int timeout_seconds;
|
||||
ConnStatus conn_status;
|
||||
} AutopingState;
|
||||
|
||||
static AutopingState*
|
||||
autoping_state_new(void)
|
||||
{
|
||||
AutopingState* state = calloc(1, sizeof(AutopingState));
|
||||
state->wait = FALSE;
|
||||
state->timer = NULL;
|
||||
state->timeout_seconds = 30;
|
||||
state->conn_status = CONN_DISCONNECTED;
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
autoping_state_free(AutopingState* state)
|
||||
{
|
||||
if (state) {
|
||||
if (state->timer) {
|
||||
g_timer_destroy(state->timer);
|
||||
}
|
||||
free(state);
|
||||
}
|
||||
}
|
||||
|
||||
/* Simulates iq_autoping_timer_cancel logic */
|
||||
static void
|
||||
autoping_cancel(AutopingState* state)
|
||||
{
|
||||
state->wait = FALSE;
|
||||
if (state->timer) {
|
||||
g_timer_destroy(state->timer);
|
||||
state->timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Simulates ping send logic */
|
||||
static gboolean
|
||||
autoping_send(AutopingState* state)
|
||||
{
|
||||
if (state->conn_status != CONN_CONNECTED) {
|
||||
return FALSE;
|
||||
}
|
||||
if (state->wait) {
|
||||
/* Already waiting for pong */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
state->wait = TRUE;
|
||||
if (state->timer) {
|
||||
g_timer_destroy(state->timer);
|
||||
}
|
||||
state->timer = g_timer_new();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Simulates iq_autoping_check logic */
|
||||
typedef enum {
|
||||
AUTOPING_OK,
|
||||
AUTOPING_NOT_CONNECTED,
|
||||
AUTOPING_NOT_WAITING,
|
||||
AUTOPING_NO_TIMER,
|
||||
AUTOPING_TIMEOUT
|
||||
} AutopingCheckResult;
|
||||
|
||||
static AutopingCheckResult
|
||||
autoping_check(AutopingState* state)
|
||||
{
|
||||
if (state->conn_status != CONN_CONNECTED) {
|
||||
return AUTOPING_NOT_CONNECTED;
|
||||
}
|
||||
if (state->wait == FALSE) {
|
||||
return AUTOPING_NOT_WAITING;
|
||||
}
|
||||
if (state->timer == NULL) {
|
||||
return AUTOPING_NO_TIMER;
|
||||
}
|
||||
|
||||
gdouble elapsed = g_timer_elapsed(state->timer, NULL);
|
||||
unsigned long seconds_elapsed = (unsigned long)(elapsed * 1.0);
|
||||
|
||||
if (state->timeout_seconds > 0 && seconds_elapsed >= (unsigned long)state->timeout_seconds) {
|
||||
return AUTOPING_TIMEOUT;
|
||||
}
|
||||
|
||||
return AUTOPING_OK;
|
||||
}
|
||||
|
||||
/* Simulates timer extend logic (reset timer on activity) */
|
||||
static void
|
||||
autoping_extend(AutopingState* state)
|
||||
{
|
||||
if (state->timer) {
|
||||
g_timer_start(state->timer);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_cancel resets state correctly
|
||||
*/
|
||||
void
|
||||
test_autoping_cancel_resets_state(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
/* Setup: send a ping */
|
||||
autoping_send(as);
|
||||
assert_true(as->wait);
|
||||
assert_non_null(as->timer);
|
||||
|
||||
/* Cancel should reset everything */
|
||||
autoping_cancel(as);
|
||||
assert_false(as->wait);
|
||||
assert_null(as->timer);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_send fails when disconnected
|
||||
*/
|
||||
void
|
||||
test_autoping_send_fails_when_disconnected(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_DISCONNECTED;
|
||||
|
||||
gboolean result = autoping_send(as);
|
||||
|
||||
assert_false(result);
|
||||
assert_false(as->wait);
|
||||
assert_null(as->timer);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_send succeeds when connected
|
||||
*/
|
||||
void
|
||||
test_autoping_send_succeeds_when_connected(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
gboolean result = autoping_send(as);
|
||||
|
||||
assert_true(result);
|
||||
assert_true(as->wait);
|
||||
assert_non_null(as->timer);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_send returns false if already waiting
|
||||
*/
|
||||
void
|
||||
test_autoping_send_fails_if_already_waiting(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
/* First send succeeds */
|
||||
gboolean result1 = autoping_send(as);
|
||||
assert_true(result1);
|
||||
|
||||
/* Second send fails - already waiting */
|
||||
gboolean result2 = autoping_send(as);
|
||||
assert_false(result2);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_check returns NOT_CONNECTED when disconnected
|
||||
*/
|
||||
void
|
||||
test_autoping_check_not_connected(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_DISCONNECTED;
|
||||
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
|
||||
assert_int_equal(result, AUTOPING_NOT_CONNECTED);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_check returns NOT_WAITING when not waiting for pong
|
||||
*/
|
||||
void
|
||||
test_autoping_check_not_waiting(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
as->wait = FALSE;
|
||||
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
|
||||
assert_int_equal(result, AUTOPING_NOT_WAITING);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_check returns NO_TIMER when timer is null
|
||||
*/
|
||||
void
|
||||
test_autoping_check_no_timer(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
as->wait = TRUE;
|
||||
as->timer = NULL;
|
||||
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
|
||||
assert_int_equal(result, AUTOPING_NO_TIMER);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_check returns OK when within timeout
|
||||
*/
|
||||
void
|
||||
test_autoping_check_ok_within_timeout(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
as->timeout_seconds = 30;
|
||||
|
||||
autoping_send(as);
|
||||
|
||||
/* Immediately after send, should be OK */
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
|
||||
assert_int_equal(result, AUTOPING_OK);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: autoping_check returns OK when timeout is 0 (disabled)
|
||||
*/
|
||||
void
|
||||
test_autoping_check_ok_when_timeout_disabled(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
as->timeout_seconds = 0; /* Disabled */
|
||||
|
||||
autoping_send(as);
|
||||
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
|
||||
assert_int_equal(result, AUTOPING_OK);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: timer extend resets the timer
|
||||
*/
|
||||
void
|
||||
test_autoping_timer_extend_resets_timer(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
autoping_send(as);
|
||||
|
||||
/* Wait a tiny bit */
|
||||
g_usleep(10000); /* 10ms */
|
||||
|
||||
gdouble elapsed_before = g_timer_elapsed(as->timer, NULL);
|
||||
|
||||
/* Extend should reset timer */
|
||||
autoping_extend(as);
|
||||
|
||||
gdouble elapsed_after = g_timer_elapsed(as->timer, NULL);
|
||||
|
||||
/* After extend, elapsed time should be very small */
|
||||
assert_true(elapsed_after < elapsed_before);
|
||||
assert_true(elapsed_after < 0.005); /* Less than 5ms */
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: timer extend does nothing when timer is null
|
||||
*/
|
||||
void
|
||||
test_autoping_timer_extend_noop_without_timer(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->timer = NULL;
|
||||
|
||||
/* Should not crash */
|
||||
autoping_extend(as);
|
||||
|
||||
assert_null(as->timer);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: pong received cancels wait state
|
||||
*/
|
||||
void
|
||||
test_autoping_pong_received_cancels_wait(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
/* Send ping */
|
||||
autoping_send(as);
|
||||
assert_true(as->wait);
|
||||
|
||||
/* Simulate pong received - calls cancel */
|
||||
autoping_cancel(as);
|
||||
|
||||
assert_false(as->wait);
|
||||
assert_null(as->timer);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: multiple pings can be sent after pong received
|
||||
*/
|
||||
void
|
||||
test_autoping_can_send_after_pong(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
|
||||
/* First ping */
|
||||
gboolean result1 = autoping_send(as);
|
||||
assert_true(result1);
|
||||
|
||||
/* Pong received */
|
||||
autoping_cancel(as);
|
||||
|
||||
/* Second ping should succeed */
|
||||
gboolean result2 = autoping_send(as);
|
||||
assert_true(result2);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: connection status changes affect behavior
|
||||
*/
|
||||
void
|
||||
test_autoping_respects_connection_state_changes(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
|
||||
/* Start disconnected */
|
||||
as->conn_status = CONN_DISCONNECTED;
|
||||
assert_false(autoping_send(as));
|
||||
|
||||
/* Connect */
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
assert_true(autoping_send(as));
|
||||
|
||||
/* Disconnect - check should handle gracefully */
|
||||
as->conn_status = CONN_DISCONNECTED;
|
||||
AutopingCheckResult result = autoping_check(as);
|
||||
assert_int_equal(result, AUTOPING_NOT_CONNECTED);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: timeout with very short timeout value
|
||||
*/
|
||||
void
|
||||
test_autoping_timeout_detection(void** state)
|
||||
{
|
||||
AutopingState* as = autoping_state_new();
|
||||
as->conn_status = CONN_CONNECTED;
|
||||
as->timeout_seconds = 1; /* 1 second timeout */
|
||||
|
||||
autoping_send(as);
|
||||
|
||||
/* Immediately should be OK */
|
||||
AutopingCheckResult result1 = autoping_check(as);
|
||||
assert_int_equal(result1, AUTOPING_OK);
|
||||
|
||||
/* Manipulate timer to simulate timeout */
|
||||
/* We can't easily wait 1 second in a unit test, so we test the logic */
|
||||
/* by checking that elapsed time is calculated correctly */
|
||||
gdouble elapsed = g_timer_elapsed(as->timer, NULL);
|
||||
assert_true(elapsed < 1.0);
|
||||
|
||||
autoping_state_free(as);
|
||||
}
|
||||
20
tests/unittests/test_autoping.h
Normal file
20
tests/unittests/test_autoping.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* test_autoping.h
|
||||
* Header for autoping unit tests
|
||||
*/
|
||||
|
||||
void test_autoping_cancel_resets_state(void** state);
|
||||
void test_autoping_send_fails_when_disconnected(void** state);
|
||||
void test_autoping_send_succeeds_when_connected(void** state);
|
||||
void test_autoping_send_fails_if_already_waiting(void** state);
|
||||
void test_autoping_check_not_connected(void** state);
|
||||
void test_autoping_check_not_waiting(void** state);
|
||||
void test_autoping_check_no_timer(void** state);
|
||||
void test_autoping_check_ok_within_timeout(void** state);
|
||||
void test_autoping_check_ok_when_timeout_disabled(void** state);
|
||||
void test_autoping_timer_extend_resets_timer(void** state);
|
||||
void test_autoping_timer_extend_noop_without_timer(void** state);
|
||||
void test_autoping_pong_received_cancels_wait(void** state);
|
||||
void test_autoping_can_send_after_pong(void** state);
|
||||
void test_autoping_respects_connection_state_changes(void** state);
|
||||
void test_autoping_timeout_detection(void** state);
|
||||
261
tests/unittests/test_helpers.c
Normal file
261
tests/unittests/test_helpers.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* test_helpers.c
|
||||
* Unit tests for helper functions added in refactoring
|
||||
*/
|
||||
|
||||
#include "prof_cmocka.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* Tests for _check_subwin_width logic (from src/ui/window.c)
|
||||
* Since _check_subwin_width is static, we test the logic here
|
||||
*/
|
||||
|
||||
static int
|
||||
check_subwin_width(int cols, int width)
|
||||
{
|
||||
if (cols > 1) {
|
||||
if (width < 1)
|
||||
width = 1;
|
||||
if (width >= cols)
|
||||
width = cols - 1;
|
||||
} else {
|
||||
width = 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/* Test: width is clamped to minimum 1 when cols > 1 */
|
||||
void
|
||||
test_subwin_width_clamps_to_min(void** state)
|
||||
{
|
||||
assert_int_equal(check_subwin_width(80, 0), 1);
|
||||
assert_int_equal(check_subwin_width(80, -5), 1);
|
||||
assert_int_equal(check_subwin_width(100, -100), 1);
|
||||
}
|
||||
|
||||
/* Test: width is clamped to cols-1 when >= cols */
|
||||
void
|
||||
test_subwin_width_clamps_to_max(void** state)
|
||||
{
|
||||
assert_int_equal(check_subwin_width(80, 80), 79);
|
||||
assert_int_equal(check_subwin_width(80, 100), 79);
|
||||
assert_int_equal(check_subwin_width(80, 1000), 79);
|
||||
}
|
||||
|
||||
/* Test: valid width passes through unchanged */
|
||||
void
|
||||
test_subwin_width_valid_unchanged(void** state)
|
||||
{
|
||||
assert_int_equal(check_subwin_width(80, 20), 20);
|
||||
assert_int_equal(check_subwin_width(80, 1), 1);
|
||||
assert_int_equal(check_subwin_width(80, 79), 79);
|
||||
assert_int_equal(check_subwin_width(100, 50), 50);
|
||||
}
|
||||
|
||||
/* Test: edge case when cols <= 1 */
|
||||
void
|
||||
test_subwin_width_small_cols(void** state)
|
||||
{
|
||||
assert_int_equal(check_subwin_width(1, 50), 1);
|
||||
assert_int_equal(check_subwin_width(0, 50), 1);
|
||||
assert_int_equal(check_subwin_width(-1, 50), 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for hash table iteration pattern
|
||||
* Verifies that g_hash_table_iter produces same results as get_keys+list
|
||||
*/
|
||||
|
||||
void
|
||||
test_hash_table_iter_finds_all_keys(void** state)
|
||||
{
|
||||
GHashTable* ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
|
||||
g_hash_table_insert(ht, g_strdup("key1"), g_strdup("value1"));
|
||||
g_hash_table_insert(ht, g_strdup("key2"), g_strdup("value2"));
|
||||
g_hash_table_insert(ht, g_strdup("key3"), g_strdup("value3"));
|
||||
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
int count = 0;
|
||||
gboolean found_key1 = FALSE, found_key2 = FALSE, found_key3 = FALSE;
|
||||
|
||||
g_hash_table_iter_init(&iter, ht);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
count++;
|
||||
if (strcmp((char*)key, "key1") == 0) found_key1 = TRUE;
|
||||
if (strcmp((char*)key, "key2") == 0) found_key2 = TRUE;
|
||||
if (strcmp((char*)key, "key3") == 0) found_key3 = TRUE;
|
||||
}
|
||||
|
||||
assert_int_equal(count, 3);
|
||||
assert_true(found_key1);
|
||||
assert_true(found_key2);
|
||||
assert_true(found_key3);
|
||||
|
||||
g_hash_table_destroy(ht);
|
||||
}
|
||||
|
||||
void
|
||||
test_hash_table_iter_empty_table(void** state)
|
||||
{
|
||||
GHashTable* ht = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
int count = 0;
|
||||
|
||||
g_hash_table_iter_init(&iter, ht);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
count++;
|
||||
}
|
||||
|
||||
assert_int_equal(count, 0);
|
||||
|
||||
g_hash_table_destroy(ht);
|
||||
}
|
||||
|
||||
void
|
||||
test_hash_table_iter_early_exit(void** state)
|
||||
{
|
||||
GHashTable* ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
|
||||
g_hash_table_insert(ht, g_strdup("target"), g_strdup("found"));
|
||||
g_hash_table_insert(ht, g_strdup("other1"), g_strdup("skip"));
|
||||
g_hash_table_insert(ht, g_strdup("other2"), g_strdup("skip"));
|
||||
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
const char* result = NULL;
|
||||
|
||||
g_hash_table_iter_init(&iter, ht);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
if (strcmp((char*)value, "found") == 0) {
|
||||
result = (const char*)key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_non_null(result);
|
||||
assert_string_equal(result, "target");
|
||||
|
||||
g_hash_table_destroy(ht);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for nested hash table lookup (like features_by_jid)
|
||||
*/
|
||||
void
|
||||
test_nested_hash_table_feature_lookup(void** state)
|
||||
{
|
||||
// Simulate conn.features_by_jid structure
|
||||
GHashTable* features_by_jid = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
g_free, (GDestroyNotify)g_hash_table_destroy);
|
||||
|
||||
// Add jid1 with features
|
||||
GHashTable* jid1_features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
g_hash_table_insert(jid1_features, g_strdup("feature_a"), GINT_TO_POINTER(1));
|
||||
g_hash_table_insert(jid1_features, g_strdup("feature_b"), GINT_TO_POINTER(1));
|
||||
g_hash_table_insert(features_by_jid, g_strdup("jid1@server"), jid1_features);
|
||||
|
||||
// Add jid2 with different features
|
||||
GHashTable* jid2_features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
g_hash_table_insert(jid2_features, g_strdup("feature_c"), GINT_TO_POINTER(1));
|
||||
g_hash_table_insert(features_by_jid, g_strdup("jid2@server"), jid2_features);
|
||||
|
||||
// Test: find feature_a (should be in jid1)
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
gboolean found_a = FALSE;
|
||||
|
||||
g_hash_table_iter_init(&iter, features_by_jid);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
GHashTable* features = (GHashTable*)value;
|
||||
if (features && g_hash_table_lookup(features, "feature_a")) {
|
||||
found_a = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_true(found_a);
|
||||
|
||||
// Test: find feature_c (should be in jid2)
|
||||
gboolean found_c = FALSE;
|
||||
g_hash_table_iter_init(&iter, features_by_jid);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
GHashTable* features = (GHashTable*)value;
|
||||
if (features && g_hash_table_lookup(features, "feature_c")) {
|
||||
found_c = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_true(found_c);
|
||||
|
||||
// Test: feature_x not found
|
||||
gboolean found_x = FALSE;
|
||||
g_hash_table_iter_init(&iter, features_by_jid);
|
||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||
GHashTable* features = (GHashTable*)value;
|
||||
if (features && g_hash_table_lookup(features, "feature_x")) {
|
||||
found_x = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_false(found_x);
|
||||
|
||||
g_hash_table_destroy(features_by_jid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for calloc array loop fix (connection.c queued_messages bug)
|
||||
* The bug was: for (n = 0; n < len && array[n]; ++n)
|
||||
* After calloc, array[n] is always NULL, so loop exits immediately
|
||||
*/
|
||||
void
|
||||
test_calloc_array_iteration_bug(void** state)
|
||||
{
|
||||
int len = 5;
|
||||
|
||||
// Simulate the buggy pattern
|
||||
char** array = calloc(len + 1, sizeof(char*));
|
||||
int buggy_count = 0;
|
||||
for (int n = 0; n < len && array[n]; ++n) {
|
||||
buggy_count++;
|
||||
}
|
||||
// Bug: loop never executes because calloc zeros everything
|
||||
assert_int_equal(buggy_count, 0);
|
||||
|
||||
// Correct pattern
|
||||
int correct_count = 0;
|
||||
for (int n = 0; n < len; ++n) {
|
||||
correct_count++;
|
||||
// In real code, we'd store values here: array[n] = get_value();
|
||||
}
|
||||
assert_int_equal(correct_count, 5);
|
||||
|
||||
free(array);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for format string safety
|
||||
* Verifies that using "%s" format prevents interpretation of % in strings
|
||||
*/
|
||||
void
|
||||
test_format_string_with_percent(void** state)
|
||||
{
|
||||
char buffer[256];
|
||||
const char* dangerous_input = "test %s %n %x string";
|
||||
|
||||
// Safe: using %s format
|
||||
int ret = snprintf(buffer, sizeof(buffer), "%s", dangerous_input);
|
||||
assert_true(ret > 0);
|
||||
assert_string_equal(buffer, dangerous_input);
|
||||
|
||||
// The string should be preserved exactly, including % characters
|
||||
assert_non_null(strstr(buffer, "%s"));
|
||||
assert_non_null(strstr(buffer, "%n"));
|
||||
assert_non_null(strstr(buffer, "%x"));
|
||||
}
|
||||
25
tests/unittests/test_helpers.h
Normal file
25
tests/unittests/test_helpers.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* test_helpers.h
|
||||
* Header for helper function tests
|
||||
*/
|
||||
|
||||
#ifndef TEST_HELPERS_H
|
||||
#define TEST_HELPERS_H
|
||||
|
||||
/* Subwindow width clamping tests */
|
||||
void test_subwin_width_clamps_to_min(void** state);
|
||||
void test_subwin_width_clamps_to_max(void** state);
|
||||
void test_subwin_width_valid_unchanged(void** state);
|
||||
void test_subwin_width_small_cols(void** state);
|
||||
|
||||
/* Hash table iteration tests */
|
||||
void test_hash_table_iter_finds_all_keys(void** state);
|
||||
void test_hash_table_iter_empty_table(void** state);
|
||||
void test_hash_table_iter_early_exit(void** state);
|
||||
void test_nested_hash_table_feature_lookup(void** state);
|
||||
|
||||
/* Bug fix verification tests */
|
||||
void test_calloc_array_iteration_bug(void** state);
|
||||
void test_format_string_with_percent(void** state);
|
||||
|
||||
#endif
|
||||
@@ -36,6 +36,8 @@
|
||||
#include "test_form.h"
|
||||
#include "test_callbacks.h"
|
||||
#include "test_plugins_disco.h"
|
||||
#include "test_helpers.h"
|
||||
#include "test_autoping.h"
|
||||
|
||||
#define muc_unit_test(f) cmocka_unit_test_setup_teardown(f, muc_before_test, muc_after_test)
|
||||
|
||||
@@ -656,6 +658,35 @@ main(int argc, char* argv[])
|
||||
cmocka_unit_test_setup_teardown(test_allow_unencrypted_message_confirms_on_second_attempt, load_preferences, close_preferences),
|
||||
cmocka_unit_test_setup_teardown(test_cmd_force_encryption_invalid_policy, load_preferences, close_preferences),
|
||||
cmocka_unit_test_setup_teardown(test_allow_unencrypted_message_invalid_mode, load_preferences, close_preferences),
|
||||
|
||||
// Helper function tests
|
||||
cmocka_unit_test(test_subwin_width_clamps_to_min),
|
||||
cmocka_unit_test(test_subwin_width_clamps_to_max),
|
||||
cmocka_unit_test(test_subwin_width_valid_unchanged),
|
||||
cmocka_unit_test(test_subwin_width_small_cols),
|
||||
cmocka_unit_test(test_hash_table_iter_finds_all_keys),
|
||||
cmocka_unit_test(test_hash_table_iter_empty_table),
|
||||
cmocka_unit_test(test_hash_table_iter_early_exit),
|
||||
cmocka_unit_test(test_nested_hash_table_feature_lookup),
|
||||
cmocka_unit_test(test_calloc_array_iteration_bug),
|
||||
cmocka_unit_test(test_format_string_with_percent),
|
||||
|
||||
// Autoping tests (XEP-0199)
|
||||
cmocka_unit_test(test_autoping_cancel_resets_state),
|
||||
cmocka_unit_test(test_autoping_send_fails_when_disconnected),
|
||||
cmocka_unit_test(test_autoping_send_succeeds_when_connected),
|
||||
cmocka_unit_test(test_autoping_send_fails_if_already_waiting),
|
||||
cmocka_unit_test(test_autoping_check_not_connected),
|
||||
cmocka_unit_test(test_autoping_check_not_waiting),
|
||||
cmocka_unit_test(test_autoping_check_no_timer),
|
||||
cmocka_unit_test(test_autoping_check_ok_within_timeout),
|
||||
cmocka_unit_test(test_autoping_check_ok_when_timeout_disabled),
|
||||
cmocka_unit_test(test_autoping_timer_extend_resets_timer),
|
||||
cmocka_unit_test(test_autoping_timer_extend_noop_without_timer),
|
||||
cmocka_unit_test(test_autoping_pong_received_cancels_wait),
|
||||
cmocka_unit_test(test_autoping_can_send_after_pong),
|
||||
cmocka_unit_test(test_autoping_respects_connection_state_changes),
|
||||
cmocka_unit_test(test_autoping_timeout_detection),
|
||||
};
|
||||
return cmocka_run_group_tests(all_tests, NULL, NULL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user