From 1aaa382d5e2bb00bd7cf6e4712ba4c6f7b07052d Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Wed, 6 May 2026 14:19:46 +0300 Subject: [PATCH] fix(verify): per-contact context in output, demote duplicate stanza-id to debug --- src/database_flatfile_verify.c | 18 +++++++++++---- src/database_sqlite.c | 42 +++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/database_flatfile_verify.c b/src/database_flatfile_verify.c index 2e37e83b..740f83a2 100644 --- a/src/database_flatfile_verify.c +++ b/src/database_flatfile_verify.c @@ -211,9 +211,13 @@ _first_pass(FILE* fp, const char* basename, if (pl->stanza_id && pl->stanza_id[0] != '\0') { if (g_hash_table_contains(seen_stanza_ids, pl->stanza_id)) { - *issues = g_slist_prepend(*issues, - _issue_new(INTEGRITY_WARNING, basename, lineno, - g_strdup_printf("Duplicate stanza-id \"%s\"", pl->stanza_id))); + // Older clients (Pidgin, Adium, even early Profanity) + // sometimes reuse client-generated stanza-ids across + // distinct messages, so a collision is not a real + // integrity defect — log it for diagnostics but don't + // surface it through /history verify. + log_debug("flatfile verify: duplicate stanza-id \"%s\" at %s:%d", + pl->stanza_id, basename, lineno); } else { g_hash_table_insert(seen_stanza_ids, g_strdup(pl->stanza_id), GINT_TO_POINTER(lineno)); } @@ -302,7 +306,13 @@ _verify_contact_dir(const char* cdir_path, GSList** issues) return; } - const char* basename = "history.log"; + // Reconstruct the contact JID from the directory name so issue reports + // identify which contact the problem belongs to (every contact has its + // own history.log, so the bare filename is ambiguous). + auto_gchar gchar* dir_name = g_path_get_basename(cdir_path); + auto_gcharv gchar** parts = g_strsplit(dir_name, "_at_", -1); + auto_gchar gchar* contact_jid = g_strjoinv("@", parts); + auto_gchar gchar* basename = g_strdup_printf("%s/history.log", contact_jid); _check_permissions(filepath, basename, issues); diff --git a/src/database_sqlite.c b/src/database_sqlite.c index 2061c1ee..446b14cb 100644 --- a/src/database_sqlite.c +++ b/src/database_sqlite.c @@ -466,23 +466,36 @@ _sqlite_verify_integrity(const gchar* const contact_barejid) sqlite3_finalize(stmt); } - // Check timestamp ordering for a specific contact or all + // Check timestamp ordering, restricted to consecutive rows belonging + // to the same conversation pair so adjacency does not span unrelated + // contacts. Adjacent rows by id from different conversations are + // legitimately out-of-order and should not be reported. const Jid* myjid = connection_get_jid(); if (myjid && myjid->barejid) { auto_sqlite char* query = NULL; if (contact_barejid) { query = sqlite3_mprintf( - "SELECT A.`id`, A.`timestamp`, B.`id`, B.`timestamp` FROM `ChatLogs` A " - "JOIN `ChatLogs` B ON B.`id` = A.`id` + 1 " + "SELECT A.`id`, A.`timestamp`, A.`from_jid`, A.`to_jid`, B.`id`, B.`timestamp` " + "FROM `ChatLogs` A " + "JOIN `ChatLogs` B ON B.`from_jid` = A.`from_jid` AND B.`to_jid` = A.`to_jid` " + " AND B.`id` > A.`id` " "WHERE A.`timestamp` > B.`timestamp` " "AND ((A.`from_jid` = %Q AND A.`to_jid` = %Q) OR (A.`from_jid` = %Q AND A.`to_jid` = %Q)) " + "AND NOT EXISTS (SELECT 1 FROM `ChatLogs` C " + " WHERE C.`from_jid` = A.`from_jid` AND C.`to_jid` = A.`to_jid` " + " AND C.`id` > A.`id` AND C.`id` < B.`id`) " "LIMIT 50", contact_barejid, myjid->barejid, myjid->barejid, contact_barejid); } else { query = sqlite3_mprintf( - "SELECT A.`id`, A.`timestamp`, B.`id`, B.`timestamp` FROM `ChatLogs` A " - "JOIN `ChatLogs` B ON B.`id` = A.`id` + 1 " + "SELECT A.`id`, A.`timestamp`, A.`from_jid`, A.`to_jid`, B.`id`, B.`timestamp` " + "FROM `ChatLogs` A " + "JOIN `ChatLogs` B ON B.`from_jid` = A.`from_jid` AND B.`to_jid` = A.`to_jid` " + " AND B.`id` > A.`id` " "WHERE A.`timestamp` > B.`timestamp` " + "AND NOT EXISTS (SELECT 1 FROM `ChatLogs` C " + " WHERE C.`from_jid` = A.`from_jid` AND C.`to_jid` = A.`to_jid` " + " AND C.`id` > A.`id` AND C.`id` < B.`id`) " "LIMIT 50"); } @@ -490,15 +503,19 @@ _sqlite_verify_integrity(const gchar* const contact_barejid) while (sqlite3_step(stmt) == SQLITE_ROW) { int id_a = sqlite3_column_int(stmt, 0); const char* ts_a = (const char*)sqlite3_column_text(stmt, 1); - int id_b = sqlite3_column_int(stmt, 2); - const char* ts_b = (const char*)sqlite3_column_text(stmt, 3); + const char* from_a = (const char*)sqlite3_column_text(stmt, 2); + const char* to_a = (const char*)sqlite3_column_text(stmt, 3); + int id_b = sqlite3_column_int(stmt, 4); + const char* ts_b = (const char*)sqlite3_column_text(stmt, 5); integrity_issue_t* issue = g_malloc0(sizeof(integrity_issue_t)); issue->level = INTEGRITY_WARNING; - issue->file = g_strdup("chatlog.db"); + issue->file = g_strdup_printf("%s↔%s/chatlog.db", + from_a ? from_a : "?", to_a ? to_a : "?"); issue->line = id_a; issue->message = g_strdup_printf("Timestamp out of order: row %d (%s) > row %d (%s)", - id_a, ts_a ? ts_a : "NULL", id_b, ts_b ? ts_b : "NULL"); + id_a, ts_a ? ts_a : "NULL", + id_b, ts_b ? ts_b : "NULL"); issues = g_slist_append(issues, issue); } sqlite3_finalize(stmt); @@ -506,7 +523,7 @@ _sqlite_verify_integrity(const gchar* const contact_barejid) // Check broken LMC references auto_sqlite char* lmc_query = sqlite3_mprintf( - "SELECT A.`id`, A.`replaces_db_id` FROM `ChatLogs` A " + "SELECT A.`id`, A.`replaces_db_id`, A.`from_jid`, A.`to_jid` FROM `ChatLogs` A " "WHERE A.`replaces_db_id` IS NOT NULL " "AND NOT EXISTS (SELECT 1 FROM `ChatLogs` B WHERE B.`id` = A.`replaces_db_id`) " "LIMIT 50"); @@ -515,10 +532,13 @@ _sqlite_verify_integrity(const gchar* const contact_barejid) while (sqlite3_step(stmt) == SQLITE_ROW) { int id = sqlite3_column_int(stmt, 0); int replaces_id = sqlite3_column_int(stmt, 1); + const char* from = (const char*)sqlite3_column_text(stmt, 2); + const char* to = (const char*)sqlite3_column_text(stmt, 3); integrity_issue_t* issue = g_malloc0(sizeof(integrity_issue_t)); issue->level = INTEGRITY_ERROR; - issue->file = g_strdup("chatlog.db"); + issue->file = g_strdup_printf("%s↔%s/chatlog.db", + from ? from : "?", to ? to : "?"); issue->line = id; issue->message = g_strdup_printf("Broken LMC reference: row %d references non-existent row %d", id, replaces_id);