diff --git a/src/database.c b/src/database.c index 45eeb4c4a..016b24f3b 100644 --- a/src/database.c +++ b/src/database.c @@ -36,6 +36,7 @@ #include "config.h" #include +#include #include #include #include @@ -58,6 +59,9 @@ static void _add_to_db(ProfMessage* message, char* type, const Jid* const from_j static char* _get_db_filename(ProfAccount* account); static prof_msg_type_t _get_message_type_type(const char* const type); static prof_enc_t _get_message_enc_type(const char* const encstr); +static int _get_db_version(void); +static gboolean _migrate_to_v2(void); +static gboolean _check_available_space_for_db_migration(char* path_to_db); #define auto_sqlite __attribute__((__cleanup__(auto_free_sqlite))) @@ -97,12 +101,16 @@ log_database_init(ProfAccount* account) } char* err_msg; + + // ChatLogs Table + // Contains visual representation of chat logs + // // id is the ID of DB the entry // from_jid is the senders jid // to_jid is the receivers jid // from_resource is the senders resource // to_jid is the receivers resource - // message is the message text + // message is the message text -- may be replaced by LMC (XEP-0308) // timestamp the timestamp like "2020/03/24 11:12:14" // type is there to distinguish: message (chat), MUC message (muc), muc pm (mucpm) // stanza_id is the ID in @@ -115,16 +123,39 @@ log_database_init(ProfAccount* account) goto out; } - query = "CREATE TABLE IF NOT EXISTS `DbVersion` ( `dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE)"; + gboolean new_installation = (_get_db_version() == -1); + + query = "CREATE TABLE IF NOT EXISTS `DbVersion` (`dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE)"; if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { goto out; } - query = "INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES('1')"; + query = "INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES ('1')"; if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { goto out; } + // Check && Update DB based on version + + int db_version = _get_db_version(); + if (db_version == -1) { + cons_show_error("DB Initialization Error: Unable to check DB version."); + goto out; + } + + if (db_version < 2) { + if (!new_installation) { + cons_show("Updating DB to V2. Please, wait..."); + } + if (!_check_available_space_for_db_migration(filename) || !_migrate_to_v2()) { + cons_show_error("DB Initialization Error: Unable to update DB to V2. Please, check error logs for details."); + goto out; + } + if (!new_installation) { + cons_show("DB update was successful."); + } + } + log_debug("Initialized SQLite database: %s", filename); return TRUE; @@ -258,16 +289,14 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s GDateTime* now = g_date_time_new_now_local(); auto_gchar gchar* end_date_fmt = end_time ? end_time : g_date_time_format_iso8601(now); auto_sqlite gchar* query = sqlite3_mprintf("SELECT * FROM (" - "SELECT COALESCE(B.`message`, A.`message`) AS message, " - "A.`timestamp`, A.`from_jid`, A.`type`, A.`encryption` FROM `ChatLogs` AS A " - "LEFT JOIN `ChatLogs` AS B ON (A.`stanza_id` = B.`replace_id` AND A.`from_jid` = B.`from_jid`) " - "WHERE A.`replace_id` = '' " - "AND ((A.`from_jid` = '%q' AND A.`to_jid` = '%q') OR (A.`from_jid` = '%q' AND A.`to_jid` = '%q')) " - "AND A.`timestamp` < '%q' " - "AND (%Q IS NULL OR A.`timestamp` > %Q) " - "ORDER BY A.`timestamp` %s LIMIT %d) " - "ORDER BY `timestamp` %s;", - contact_barejid, myjid->barejid, myjid->barejid, contact_barejid, end_date_fmt, start_time, start_time, sort1, MESSAGES_TO_RETRIEVE, sort2); + "SELECT message, `timestamp`, `from_jid`, `type`, `encryption` " + "FROM `ChatLogs` " + "WHERE `replaces_stanza_id` = '' " + "AND `timestamp` < '%q' " + "AND (%Q IS NULL OR `timestamp` > %Q) " + "ORDER BY `timestamp` %s LIMIT %d " + ") ORDER BY `timestamp` %s;", + end_date_fmt, start_time, start_time, sort1, MESSAGES_TO_RETRIEVE, sort2); g_date_time_unref(now); @@ -278,7 +307,7 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s 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_previous_chat()"); + log_error("SQLite error in log_database_get_previous_chat(): %s", sqlite3_errmsg(g_chatlog_database)); return NULL; } @@ -375,6 +404,8 @@ static void _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Jid* const to_jid) { auto_gchar gchar* pref_dblog = prefs_get_string(PREF_DBLOG); + auto_char char* original_message = NULL; + int original_message_id = -1; if (g_strcmp0(pref_dblog, "off") == 0) { return; @@ -407,9 +438,9 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji type = (char*)_get_message_type_str(message->type); } - // Check LMC validity (XEP-0308) + // Apply LMC and check its validity (XEP-0308) if (message->replace_id) { - auto_sqlite char* replace_check_query = sqlite3_mprintf("SELECT `from_jid` FROM `ChatLogs` WHERE `stanza_id` = '%q'", + auto_sqlite char* replace_check_query = sqlite3_mprintf("SELECT `id`, `from_jid`, `message`, `replaces_db_id` FROM `ChatLogs` WHERE `stanza_id` = '%q' ORDER BY `timestamp` DESC LIMIT 1", message->replace_id ? message->replace_id : ""); if (!replace_check_query) { @@ -419,25 +450,53 @@ _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)) { - if (sqlite3_step(lmc_stmt) == SQLITE_ROW) { - const char* from_jid_orig = (const char*)sqlite3_column_text(lmc_stmt, 0); + 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)); + return; + } + + if (sqlite3_step(lmc_stmt) == SQLITE_ROW) { + original_message_id = sqlite3_column_int(lmc_stmt, 0); + const char* from_jid_orig = (const char*)sqlite3_column_text(lmc_stmt, 1); + original_message = strdup((char*)sqlite3_column_text(lmc_stmt, 2)); + + // Handle non-XEP-compliant replacement messages (edit->edit->original) + int tmp = sqlite3_column_int(lmc_stmt, 3); + original_message_id = tmp ? tmp : original_message_id; + + if (g_strcmp0(from_jid_orig, from_jid->barejid) != 0) { + log_error("Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s. Message: %s", from_jid->barejid, from_jid_orig, message->replace_id, message->plain); + cons_show_error("%s sent a message correction with mismatched sender. See log for details.", from_jid->barejid); + sqlite3_finalize(lmc_stmt); + return; + } - if (g_strcmp0(from_jid_orig, from_jid->barejid) != 0) { - log_error("Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s. Message: %s", from_jid->barejid, from_jid_orig, message->replace_id, message->plain); - cons_show_error("%s sent message correction with mismatched sender. See log for details.", from_jid->barejid); - sqlite3_finalize(lmc_stmt); - return; - } + auto_sqlite char* original_replace_query = sqlite3_mprintf("UPDATE `ChatLogs` SET `message` = '%q' WHERE `id` = %d", + message->plain ? message->plain : "", + original_message_id); + if (!original_replace_query) { + log_error("Could not allocate memory for SQL replace old message query in log_database_add()"); + return; } - sqlite3_finalize(lmc_stmt); + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, original_replace_query, NULL, 0, &err_msg)) { + log_error("Unable to replace old message: %s", err_msg); + sqlite3_free(err_msg); + return; + } + + // to avoid putting the message content in DB twice, we clear the `message` field for LMC + if (message->plain) { + message->plain[0] = '\0'; + } + } else { + log_error("Got LMC message that does not have original message counterpart in the DB from %s", message->from_jid->fulljid); } + sqlite3_finalize(lmc_stmt); } // Check for duplicate messages - auto_sqlite char* duplicate_check_query = sqlite3_mprintf("SELECT 1 FROM `ChatLogs` WHERE (`archive_id` = '%q' AND `archive_id` != '') OR (`stanza_id` = '%q' AND `stanza_id` != '')", - message->stanzaid ? message->stanzaid : "", - message->id ? message->id : ""); + auto_sqlite char* duplicate_check_query = sqlite3_mprintf("SELECT 1 FROM `ChatLogs` WHERE (`archive_id` = '%q' AND `archive_id` != '')", + message->stanzaid ? message->stanzaid : ""); if (!duplicate_check_query) { log_error("Could not allocate memory for SQL duplicate query in log_database_add()"); @@ -455,12 +514,12 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji } if (duplicate_exists) { - log_warning("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); - return; + log_error("Duplicate server 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 ID from %s.", from_jid->fulljid); } // Insert the message - auto_sqlite char* query = sqlite3_mprintf("INSERT INTO `ChatLogs` (`from_jid`, `from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`, `stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) VALUES ('%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q')", + auto_sqlite char* query = sqlite3_mprintf("INSERT INTO `ChatLogs` (`from_jid`, `from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`, `stanza_id`, `archive_id`, `replaces_db_id`, `replaces_stanza_id`, `type`, `encryption`, `original_message`) VALUES ('%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q')", from_jid->barejid, from_jid->resourcepart ? from_jid->resourcepart : "", to_jid->barejid, @@ -469,9 +528,11 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji date_fmt ? date_fmt : "", message->id ? message->id : "", message->stanzaid ? message->stanzaid : "", + original_message_id == -1 ? "" : sqlite3_mprintf("%d", original_message_id), message->replace_id ? message->replace_id : "", type ? type : "", - enc ? enc : ""); + enc ? enc : "", + original_message ? original_message : ""); if (!query) { log_error("Could not allocate memory for SQL insert query in log_database_add()"); return; @@ -493,3 +554,159 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji } } } + +static int +_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 (sqlite3_step(statement) == SQLITE_ROW) { + current_version = sqlite3_column_int(statement, 0); + } + sqlite3_finalize(statement); + } + return current_version; +} + +/** + * @brief Migration to V2 that introduces new fields and reorganizes message storage. + * `replace_id` -> `replaces_stanza_id` stanza ID of the replaced message + * `replaces_db_id` db ID of the replaced message + * `original_message` original message before replacement + * `message` now is used to store visual message (replaced by LMC) + * + * @return gboolean with migration success status. + */ +static gboolean +_migrate_to_v2(void) +{ + char* err_msg; + + // Make V2 table + char* query = "CREATE TABLE `ChatLogsV2` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `from_jid` TEXT NOT NULL, `to_jid` TEXT NOT NULL, `from_resource` TEXT, `to_resource` TEXT, `message` TEXT, `timestamp` TEXT, `type` TEXT, `stanza_id` TEXT, `archive_id` TEXT, `encryption` TEXT, `marked_read` INTEGER, `original_message` TEXT, `replaces_db_id` INTEGER, `replaces_stanza_id` TEXT)"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to create V2 table."); + return FALSE; + } + + query = "CREATE INDEX ChatLogs_timestamp_IDX ON `ChatLogsV2` (`timestamp`)"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to create index for timestamp."); + goto cleanup; + } + + // Select all messages form V1 to V2 (and replace_id->replaces_stanza_id) + query = "INSERT INTO `ChatLogsV2` (`from_jid`, `to_jid`, `from_resource`, `to_resource`, `message`, `timestamp`, `type`, `stanza_id`, `archive_id`, `replaces_stanza_id`, `encryption`, `marked_read`) " + "SELECT `from_jid`, `to_jid`, `from_resource`, `to_resource`, `message`, `timestamp`, `type`, `stanza_id`, `archive_id`, `replace_id` AS `replaces_stanza_id`, `encryption`, `marked_read`" + "FROM `ChatLogs`;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to copy V2 table."); + goto cleanup; + } + + // Save original message and DB ID in replacement messages + // Note: sender check is required due to #1898 (24d0030) since previous messages might be affected. + query = "UPDATE `ChatLogsV2` AS A " + "SET `original_message` = B.`message`, " + "`replaces_db_id` = B.`id` " + "FROM `ChatLogs` AS B " + "WHERE A.`replaces_stanza_id` IS NOT NULL AND A.`replaces_stanza_id` != '' AND A.`replaces_stanza_id` = B.`stanza_id` " + "AND A.`from_jid` = B.`from_jid` AND A.`to_jid` = B.`to_jid`;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to add original messages to replacement messages in V2 table."); + goto cleanup; + } + + // Set original message to redacted version + // Note: sender check is required due to #1898 (24d0030) since previous messages might be affected. + query = "UPDATE `ChatLogsV2` AS A " + "SET `message` = COALESCE(B.`message`, A.`message`)" + "FROM `ChatLogsV2` AS B " + "WHERE B.`replaces_db_id` IS NOT NULL AND B.`replaces_db_id` != 0 AND A.`id` = B.`replaces_db_id` " + "AND A.`from_jid` = B.`from_jid` AND A.`to_jid` = B.`to_jid`;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to change messages in V2 table."); + goto cleanup; + } + + // Cleanup LMC as we only keep "original message" in them so we don't store the same data twice + query = "UPDATE `ChatLogsV2` " + "SET `message` = NULL " + "WHERE `replaces_stanza_id` != '';"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to change messages in V2 table."); + goto cleanup; + } + + // Rename original table to new name + query = "ALTER TABLE ChatLogs RENAME TO ChatLogs_bck;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to rename old table."); + goto cleanup; + } + + // Set V2 table as main + query = "ALTER TABLE ChatLogsV2 RENAME TO ChatLogs;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to rename new table."); + goto cleanup; + } + + // TODO: drop old table (_bck). Only after sufficient testing this part should be added + /* + query = "DROP TABLE ChatLogs_bck;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to delete old DB backup."); + return FALSE; + } + + */ + + // Set new DB version (V2) + query = "UPDATE `DbVersion` SET `version` = 2"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to update DB Version."); + return FALSE; // hard case, tbf. The DB is updated, but we can't update version, which will cause problems in the future. + } + + return TRUE; + +cleanup: + if (err_msg) { + log_error("SQLite error: %s", err_msg); + sqlite3_free(err_msg); + err_msg = NULL; + } else { + log_error("Unknown SQLite error."); + } + + query = "DROP TABLE `ChatLogsV2`;"; + if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { + log_error("[DB Migration] Unable to drop V2 table: %s", err_msg); + sqlite3_free(err_msg); // TODO: test this part, looks suspicious + } + + return FALSE; +} + +// Checks if there is more system storage space available than current DB takes + 40% (for indexing and other potential size increases) +static gboolean +_check_available_space_for_db_migration(char* path_to_db) +{ + struct stat file_stat; + struct statvfs fs_stat; + + if (statvfs(path_to_db, &fs_stat) == 0 && stat(path_to_db, &file_stat) == 0) { + unsigned long long file_size = file_stat.st_size / 1024; + unsigned long long available_space_kb = fs_stat.f_frsize * fs_stat.f_bavail / 1024; + log_debug("_check_available_space_for_db_migration(): Available space on disk: %llu KB; DB size: %llu KB", available_space_kb, file_size); + + return (available_space_kb >= (file_size + (file_size * 10 / 4))); + } else { + log_error("Error checking available space."); + return FALSE; + } +} diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c index 8f37ce131..b99c01744 100644 --- a/src/ui/chatwin.c +++ b/src/ui/chatwin.c @@ -451,8 +451,8 @@ chatwin_outgoing_msg(ProfChatWin* chatwin, const char* const message, char* id, win_print_outgoing((ProfWin*)chatwin, enc_char, id, replace_id, message); } - // save last id and message for LMC - if (id) { + // save last id and message for LMC in case if it's not LMC message + if (id && !replace_id) { _chatwin_set_last_message(chatwin, id, message); } } diff --git a/src/ui/window.c b/src/ui/window.c index 730859d24..3f047a8e0 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1324,19 +1324,23 @@ win_show_status_string(ProfWin* window, const char* const from, win_appendln(window, presence_colour, ""); } -static void +/** Corrects the visual representation of a message with prior check for sender validity. + * + * @returns TRUE if the message was successfully corrected and should not be printed, FALSE otherwise + */ +static gboolean _win_correct(ProfWin* window, const char* const message, const char* const id, const char* const replace_id, const char* const from_jid) { ProfBuffEntry* entry = buffer_get_entry_by_id(window->layout->buffer, replace_id); if (!entry) { - log_debug("Replace ID %s could not be found in buffer. Message: %s", replace_id, message); - return; + log_warning("Replace ID %s could not be found in buffer. Message: %s", replace_id, message); + return FALSE; } if (g_strcmp0(entry->from_jid, from_jid) != 0) { log_debug("Illicit LMC attempt from %s for message from %s with: %s", from_jid, entry->from_jid, message); cons_show("Illicit LMC attempt from %s for message from %s", from_jid, entry->from_jid); - return; + return TRUE; } /*TODO: set date? @@ -1358,12 +1362,10 @@ _win_correct(ProfWin* window, const char* const message, const char* const id, c } entry->message = strdup(message); - if (entry->id) { - free(entry->id); - } - entry->id = strdup(id); + // LMC requires original message ID, hence ID remains the same win_redraw(window); + return TRUE; } void @@ -1395,9 +1397,7 @@ win_print_incoming(ProfWin* window, const char* const display_name_from, ProfMes enc_char = strdup("-"); } - if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && message->replace_id) { - _win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->barejid); - } else { + if (!prefs_get_boolean(PREF_CORRECTION_ALLOW) || !message->replace_id || !_win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->barejid)) { // Prevent duplicate messages when current client is sending a message or if it's mam if (g_strcmp0(message->from_jid->fulljid, connection_get_fulljid()) != 0 && !message->is_mam) { _win_printf(window, enc_char, 0, message->timestamp, flags, THEME_TEXT_THEM, display_name_from, message->from_jid->barejid, message->id, "%s", message->plain); @@ -1423,9 +1423,7 @@ win_print_them(ProfWin* window, theme_item_t theme_item, const char* const show_ void win_println_incoming_muc_msg(ProfWin* window, char* show_char, int flags, const ProfMessage* const message) { - if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && message->replace_id) { - _win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->fulljid); - } else { + if (!prefs_get_boolean(PREF_CORRECTION_ALLOW) || !message->replace_id || !_win_correct(window, message->plain, message->id, message->replace_id, message->from_jid->fulljid)) { _win_printf(window, show_char, 0, message->timestamp, flags | NO_ME, THEME_TEXT_THEM, message->from_jid->resourcepart, message->from_jid->fulljid, message->id, "%s", message->plain); } @@ -1437,9 +1435,7 @@ win_print_outgoing_muc_msg(ProfWin* window, char* show_char, const char* const m { GDateTime* timestamp = g_date_time_new_now_local(); - if (prefs_get_boolean(PREF_CORRECTION_ALLOW) && replace_id) { - _win_correct(window, message, id, replace_id, me); - } else { + if (!prefs_get_boolean(PREF_CORRECTION_ALLOW) || !replace_id || !_win_correct(window, message, id, replace_id, me)) { _win_printf(window, show_char, 0, timestamp, 0, THEME_TEXT_ME, me, me, id, "%s", message); } @@ -1453,9 +1449,7 @@ win_print_outgoing(ProfWin* window, const char* show_char, const char* const id, GDateTime* timestamp = g_date_time_new_now_local(); const char* myjid = connection_get_fulljid(); - if (replace_id) { - _win_correct(window, message, id, replace_id, myjid); - } else { + if (replace_id || !_win_correct(window, message, id, replace_id, myjid)) { auto_gchar gchar* outgoing_str = prefs_get_string(PREF_OUTGOING_STAMP); _win_printf(window, show_char, 0, timestamp, 0, THEME_TEXT_ME, outgoing_str, myjid, id, "%s", message); } @@ -1614,10 +1608,15 @@ win_print_outgoing_with_receipt(ProfWin* window, const char* show_char, const ch receipt->received = FALSE; const char* myjid = connection_get_fulljid(); + gboolean corrected = FALSE; if (replace_id) { - _win_correct(window, message, id, replace_id, myjid); - free(receipt); // TODO: probably we should use this in _win_correct() - } else { + corrected = _win_correct(window, message, id, replace_id, myjid); + if (corrected) { + free(receipt); // TODO: probably we should use this in _win_correct() + } + } + + if (!corrected) { buffer_append(window->layout->buffer, show_char, 0, time, 0, THEME_TEXT_ME, from, myjid, message, receipt, id); _win_print_internal(window, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt); }