diff --git a/src/database_export.c b/src/database_export.c index 1e0b45d5..7e21e368 100644 --- a/src/database_export.c +++ b/src/database_export.c @@ -90,18 +90,21 @@ _close_temp_sqlite(int opened) // ========================================================================= // Returns a g_strdup'd key — callers free with g_free (gchar* convention). +// +// stanza_id is mixed into the hash but is NOT used as the sole key, since +// some clients (older Pidgin, Adium) reuse incremental ids per session and +// servers echo them back unchanged. Trusting stanza_id alone silently drops +// legitimate distinct messages on collision. By hashing id + timestamp + +// from_jid + body together we preserve id as a disambiguator without making +// it a single point of failure: true exact duplicates still dedup, but two +// messages that share an id with different content (or different timestamps) +// stay distinct. static gchar* _make_dedup_key(const char* stanza_id, const char* timestamp, const char* from_jid, const char* body) { - if (stanza_id && stanza_id[0] != '\0') - return g_strdup(stanza_id); - - // Fallback for messages without stanza-id: SHA-256 over the FULL body, - // not the first 256 bytes. The previous prefix-only hash collided on - // legitimate near-identical templates (signatures, code blocks, paste- - // bombs) — second occurrence was silently dropped on import. Hashing - // whole body is microseconds of extra work per row. GChecksum* sum = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(sum, (const guchar*)(stanza_id ? stanza_id : ""), -1); + g_checksum_update(sum, (const guchar*)"|", 1); g_checksum_update(sum, (const guchar*)(timestamp ? timestamp : ""), -1); g_checksum_update(sum, (const guchar*)"|", 1); g_checksum_update(sum, (const guchar*)(from_jid ? from_jid : ""), -1);