From 4776c1f1ec1eb1656a29be73132f264fa931387e Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Tue, 19 May 2026 16:40:21 +0000 Subject: [PATCH 1/3] fix(ai): properly decode \uXXXX JSON escape sequences as UTF-8 Add helper functions to parse hex digits and encode UTF-8 characters. Update buffer allocation to account for UTF-8 expansion and implement full surrogate pair support for characters outside the BMP. Previously, \uXXXX sequences were passed through verbatim; they are now correctly decoded into proper UTF-8 strings. --- src/ai/ai_client.c | 119 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 4 deletions(-) diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index 3d6f41b8..4402975c 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -186,14 +186,65 @@ ai_json_escape(const gchar* str) return g_string_free(result, FALSE); } +/** + * Convert a 4-hex-digit sequence at @p to a 16-bit code point. + * Returns -1 on invalid hex. + */ +static int +_hex4_to_int(const gchar* p) +{ + int val = 0; + for (int i = 0; i < 4; i++) { + char c = p[i]; + val <<= 4; + if (c >= '0' && c <= '9') + val |= c - '0'; + else if (c >= 'a' && c <= 'f') + val |= c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val |= c - 'A' + 10; + else + return -1; + } + return val; +} + +/** + * Write a UTF-8 encoding of @cp to @w, advancing @w. + * Returns the updated pointer. + */ +static gchar* +_write_utf8(gchar* w, unsigned int cp) +{ + if (cp <= 0x7F) { + *w++ = (char)cp; + } else if (cp <= 0x7FF) { + *w++ = (char)(0xC0 | (cp >> 6)); + *w++ = (char)(0x80 | (cp & 0x3F)); + } else if (cp <= 0xFFFF) { + *w++ = (char)(0xE0 | (cp >> 12)); + *w++ = (char)(0x80 | ((cp >> 6) & 0x3F)); + *w++ = (char)(0x80 | (cp & 0x3F)); + } else if (cp <= 0x10FFFF) { + *w++ = (char)(0xF0 | (cp >> 18)); + *w++ = (char)(0x80 | ((cp >> 12) & 0x3F)); + *w++ = (char)(0x80 | ((cp >> 6) & 0x3F)); + *w++ = (char)(0x80 | (cp & 0x3F)); + } + /* Invalid code point: silently skip */ + return w; +} + /* Decode JSON string escapes in [start, end) into a freshly allocated buffer. - * Handles \" \\ \/ \n \t \r \b \f. \uXXXX sequences are passed through verbatim - * since the rest of the AI pipeline already deals in UTF-8 byte streams. */ + * Handles \" \\ \/ \n \t \r \b \f and \uXXXX (including surrogate pairs). */ static gchar* _json_unescape_substring(const gchar* start, const gchar* end) { - gchar* out = g_new0(gchar, (end - start) + 1); + /* Estimate upper bound: each \uXXXX becomes up to 6 UTF-8 bytes. */ + gsize max_len = (end - start) * 6 + 1; + gchar* out = g_new0(gchar, max_len); gchar* w = out; + for (const gchar* in = start; in < end;) { if (*in == '\\' && in + 1 < end) { switch (in[1]) { @@ -229,6 +280,64 @@ _json_unescape_substring(const gchar* start, const gchar* end) *w++ = '\f'; in += 2; break; + case 'u': + /* Handle \uXXXX or surrogate pair \uHHHH\uLLLL */ + if (in + 6 > end) { + /* Not enough characters — emit literal */ + *w++ = *in++; + break; + } + if (in[2] != '\\' || in[3] != 'u') { + /* Single \uXXXX */ + int cp = _hex4_to_int(in + 2); + if (cp < 0) { + /* Invalid hex — emit literal backslash */ + *w++ = *in++; + } else if (cp < 0xD800 || cp > 0xDFFF) { + /* Not a surrogate — encode directly */ + w = _write_utf8(w, (unsigned int)cp); + in += 6; + } else { + /* High or low surrogate — expect pair */ + unsigned int high = (unsigned int)cp; + if ((high >= 0xD800 && high <= 0xDBFF) && in + 12 <= end && in[6] == '\\' && in[7] == 'u') { + /* High surrogate followed by \u */ + int low = _hex4_to_int(in + 8); + if (low >= 0xDC00 && low <= 0xDFFF) { + /* Valid surrogate pair */ + unsigned int cp2 = (0x10000 + ((high & 0x3FF) << 10)) | (low & 0x3FF); + w = _write_utf8(w, cp2); + in += 12; + } else { + /* Invalid low surrogate — emit replacement */ + w = _write_utf8(w, 0xFFFD); + in += 6; + } + } else { + /* Orphan high surrogate or lone low surrogate */ + w = _write_utf8(w, 0xFFFD); + in += 6; + } + } + } else { + /* \uHHHH\uLLLL — surrogate pair */ + int high = _hex4_to_int(in + 2); + if (high < 0) { + *w++ = *in++; + break; + } + int low = _hex4_to_int(in + 8); + if ((high >= 0xD800 && high <= 0xDBFF) && (low >= 0xDC00 && low <= 0xDFFF)) { + unsigned int cp2 = (0x10000 + ((unsigned int)(high & 0x3FF) << 10)) | (unsigned int)(low & 0x3FF); + w = _write_utf8(w, cp2); + in += 12; + } else { + /* Invalid pair — emit replacement for high */ + w = _write_utf8(w, 0xFFFD); + in += 6; + } + } + break; default: *w++ = *in++; break; @@ -238,7 +347,9 @@ _json_unescape_substring(const gchar* start, const gchar* end) } } *w = '\0'; - return out; + + /* Realloc to exact size */ + return g_realloc(out, w - out + 1); } /* Locate "field":"...value..." in json and return the unescaped value. From 53cdf488b61e2c49a9f13dee89e727dbc92d800e Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Tue, 19 May 2026 16:42:22 +0000 Subject: [PATCH 2/3] fix(ai): resolve UAF and require provider argument instead of defaults This enforces explicit provider specification. Automatic fallback to preferences or "openai" is removed. Users must now pass the provider name explicitly. default provider functionality was improperly removed and implemented (some parts still remain intact), leading to UAF. --- src/command/cmd_funcs.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 2a49415e..c575de6a 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -10932,21 +10932,14 @@ cmd_ai_start(ProfWin* window, const char* const command, gchar** args) if (g_strv_length(args) >= 2) { provider_name = args[1]; } - if (g_strv_length(args) >= 3) { - model = args[2]; + + if (!provider_name) { + cons_show_error("Please specify provider name."); + return TRUE; } - // Resolve defaults - auto_gchar gchar* owned_provider_name = NULL; - if (!provider_name) { - // Use default provider from preferences - auto_gchar gchar* default_provider = prefs_get_string(PREF_AI_PROVIDER); - if (default_provider && strlen(default_provider) > 0) { - provider_name = default_provider; - } - } - if (!provider_name) { - provider_name = "openai"; // Fallback + if (g_strv_length(args) >= 3) { + model = args[2]; } AIProvider* provider = ai_get_provider(provider_name); From 3b673150b40cd15f3112f262ef41166fae1efac6 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Wed, 20 May 2026 13:35:51 +0300 Subject: [PATCH 3/3] chore(chatlog): disable background message file logging (stage 1) First stage of removing the chatlog subsystem that writes plain-text per-day message logs to $XDG_DATA_HOME/profanity/chatlogs/. These files were never read back by Profanity (history replay reads from the database via log_database_get_previous_chat), so the feature only ever wrote to disk for users to inspect with external tools. This change reduces chatlog.c to no-op stubs, preserving all public signatures so the ~20 call sites in event/, otr/ and profanity.c continue to compile and link unchanged. Subsequent stages will remove the call sites, the related preferences (PREF_CHLOG, PREF_GRLOG and the per-encryption PREF_*_LOG) and the now-dead commands (/logging, /otr log, /pgp log, /omemo log, /ox log). Database-based history (PREF_DBLOG, /history) and the debug log (log.c, /log) are unaffected. --- src/chatlog.c | 405 +++----------------------------------------------- 1 file changed, 22 insertions(+), 383 deletions(-) diff --git a/src/chatlog.c b/src/chatlog.c index c0cd5d80..cafd0ed6 100644 --- a/src/chatlog.c +++ b/src/chatlog.c @@ -1,5 +1,5 @@ /* - * log.c + * chatlog.c * vim: expandtab:ts=4:sts=4:sw=4 * * Copyright (C) 2012 - 2019 James Booth @@ -36,442 +36,81 @@ #include "config.h" -#include -#include -#include -#include -#include -#include -#include - -#include "glib.h" -#include "glib/gstdio.h" - -#include "log.h" #include "chatlog.h" -#include "common.h" -#include "config/files.h" -#include "config/preferences.h" -#include "xmpp/xmpp.h" -#include "xmpp/muc.h" -static GHashTable* logs; -static GHashTable* groupchat_logs; - -struct dated_chat_log -{ - gchar* filename; - GDateTime* date; -}; - -static gboolean _log_roll_needed(struct dated_chat_log* dated_log); -static struct dated_chat_log* _create_chatlog(const char* const other, const char* const login); -static struct dated_chat_log* _create_groupchat_log(const char* const room, const char* const login); -static void _free_chat_log(struct dated_chat_log* dated_log); -static gboolean _key_equals(void* key1, void* key2); -static void _chat_log_chat(const char* const login, const char* const other, const gchar* const msg, - chat_log_direction_t direction, GDateTime* timestamp, const char* const resourcepart); -static void _groupchat_log_chat(const gchar* const login, const gchar* const room, const gchar* const nick, - const gchar* const msg); - -void -_chatlog_close(void) -{ - g_hash_table_destroy(logs); - g_hash_table_destroy(groupchat_logs); -} +/* + * Background message-logging to per-day plain-text files under + * $XDG_DATA_HOME/profanity/chatlogs/ is disabled. These files were never + * read back by the application — history replay reads from the database + * (log_database_get_previous_chat) — so this subsystem only ever wrote to + * disk for users to read with external tools. + * + * All public entry points are kept as no-ops to avoid touching the ~20 + * call sites in event/, otr/ and profanity.c until the API itself is + * removed. + */ void chatlog_init(void) { - log_info("Initialising chat logs"); - - prof_add_shutdown_routine(_chatlog_close); - - logs = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, free, - (GDestroyNotify)_free_chat_log); - groupchat_logs = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, free, - (GDestroyNotify)_free_chat_log); } void chat_log_msg_out(const char* const barejid, const char* const msg, const char* const resource) { - if (prefs_get_boolean(PREF_CHLOG)) { - _chat_log_chat(connection_get_barejid(), barejid, msg, PROF_OUT_LOG, NULL, resource); - } } void chat_log_otr_msg_out(const char* const barejid, const char* const msg, const char* const resource) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_otr_log = prefs_get_string(PREF_OTR_LOG); - if (strcmp(pref_otr_log, "on") == 0) { - _chat_log_chat(mybarejid, barejid, msg, PROF_OUT_LOG, NULL, resource); - } else if (strcmp(pref_otr_log, "redact") == 0) { - _chat_log_chat(mybarejid, barejid, "[redacted]", PROF_OUT_LOG, NULL, resource); - } - } } void chat_log_pgp_msg_out(const char* const barejid, const char* const msg, const char* const resource) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_pgp_log = prefs_get_string(PREF_PGP_LOG); - if (strcmp(pref_pgp_log, "on") == 0) { - _chat_log_chat(mybarejid, barejid, msg, PROF_OUT_LOG, NULL, resource); - } else if (strcmp(pref_pgp_log, "redact") == 0) { - _chat_log_chat(mybarejid, barejid, "[redacted]", PROF_OUT_LOG, NULL, resource); - } - } } void chat_log_omemo_msg_out(const char* const barejid, const char* const msg, const char* const resource) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_omemo_log = prefs_get_string(PREF_OMEMO_LOG); - if (strcmp(pref_omemo_log, "on") == 0) { - _chat_log_chat(mybarejid, barejid, msg, PROF_OUT_LOG, NULL, resource); - } else if (strcmp(pref_omemo_log, "redact") == 0) { - _chat_log_chat(mybarejid, barejid, "[redacted]", PROF_OUT_LOG, NULL, resource); - } - } -} - -void -chat_log_otr_msg_in(ProfMessage* message) -{ - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_otr_log = prefs_get_string(PREF_OTR_LOG); - if (message->enc == PROF_MSG_ENC_NONE || (strcmp(pref_otr_log, "on") == 0)) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, NULL); - } - } else if (strcmp(pref_otr_log, "redact") == 0) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, NULL); - } - } - } -} - -void -chat_log_pgp_msg_in(ProfMessage* message) -{ - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_pgp_log = prefs_get_string(PREF_PGP_LOG); - if (strcmp(pref_pgp_log, "on") == 0) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, NULL); - } - } else if (strcmp(pref_pgp_log, "redact") == 0) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, NULL); - } - } - } -} - -void -chat_log_omemo_msg_in(ProfMessage* message) -{ - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_omemo_log = prefs_get_string(PREF_OMEMO_LOG); - if (strcmp(pref_omemo_log, "on") == 0) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, NULL); - } - } else if (strcmp(pref_omemo_log, "redact") == 0) { - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, "[redacted]", PROF_IN_LOG, message->timestamp, NULL); - } - } - } } void chat_log_msg_in(ProfMessage* message) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - - if (message->type == PROF_MSG_TYPE_MUCPM) { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, message->from_jid->resourcepart); - } else { - _chat_log_chat(mybarejid, message->from_jid->barejid, message->plain, PROF_IN_LOG, message->timestamp, NULL); - } - } } -static void -_chat_log_chat(const char* const login, const char* const other, const char* msg, - chat_log_direction_t direction, GDateTime* timestamp, const char* const resourcepart) +void +chat_log_otr_msg_in(ProfMessage* message) { - auto_gchar gchar* pref_dblog = prefs_get_string(PREF_DBLOG); - if (g_strcmp0(pref_dblog, "redact") == 0) { - msg = "[REDACTED]"; - } - char* other_name; - GString* other_str = NULL; +} - if (resourcepart) { - other_str = g_string_new(other); - g_string_append(other_str, "_"); - g_string_append(other_str, resourcepart); +void +chat_log_pgp_msg_in(ProfMessage* message) +{ +} - other_name = other_str->str; - } else { - other_name = (char*)other; - } - - struct dated_chat_log* dated_log = g_hash_table_lookup(logs, other_name); - - // no log for user - if (dated_log == NULL) { - dated_log = _create_chatlog(other_name, login); - g_hash_table_insert(logs, strdup(other_name), dated_log); - - // log entry exists but file removed - } else if (!g_file_test(dated_log->filename, G_FILE_TEST_EXISTS)) { - dated_log = _create_chatlog(other_name, login); - g_hash_table_replace(logs, strdup(other_name), dated_log); - - // log file needs rolling - } else if (_log_roll_needed(dated_log)) { - dated_log = _create_chatlog(other_name, login); - g_hash_table_replace(logs, strdup(other_name), dated_log); - } - - if (resourcepart) { - g_string_free(other_str, TRUE); - } - - if (timestamp == NULL) { - timestamp = g_date_time_new_now_local(); - } else { - g_date_time_ref(timestamp); - } - - auto_gchar gchar* date_fmt = g_date_time_format_iso8601(timestamp); - FILE* chatlogp = fopen(dated_log->filename, "a"); - g_chmod(dated_log->filename, S_IRUSR | S_IWUSR); - if (chatlogp) { - if (direction == PROF_IN_LOG) { - if (strncmp(msg, "/me ", 4) == 0) { - if (resourcepart) { - fprintf(chatlogp, "%s - *%s %s\n", date_fmt, resourcepart, msg + 4); - } else { - fprintf(chatlogp, "%s - *%s %s\n", date_fmt, other, msg + 4); - } - } else { - if (resourcepart) { - fprintf(chatlogp, "%s - %s: %s\n", date_fmt, resourcepart, msg); - } else { - fprintf(chatlogp, "%s - %s: %s\n", date_fmt, other, msg); - } - } - } else { - if (strncmp(msg, "/me ", 4) == 0) { - fprintf(chatlogp, "%s - *me %s\n", date_fmt, msg + 4); - } else { - fprintf(chatlogp, "%s - me: %s\n", date_fmt, msg); - } - } - fflush(chatlogp); - int result = fclose(chatlogp); - if (result == EOF) { - log_error("Error closing file %s, errno = %d", dated_log->filename, errno); - } - } - - g_date_time_unref(timestamp); +void +chat_log_omemo_msg_in(ProfMessage* message) +{ } void groupchat_log_msg_out(const gchar* const room, const gchar* const msg) { - if (prefs_get_boolean(PREF_GRLOG)) { - const char* const mynick = muc_nick(room); - _groupchat_log_chat(connection_get_barejid(), room, mynick, msg); - } } void groupchat_log_msg_in(const gchar* const room, const gchar* const nick, const gchar* const msg) { - if (prefs_get_boolean(PREF_GRLOG)) { - _groupchat_log_chat(connection_get_barejid(), room, nick, msg); - } } void groupchat_log_omemo_msg_out(const gchar* const room, const gchar* const msg) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_omemo_log = prefs_get_string(PREF_OMEMO_LOG); - const char* const mynick = muc_nick(room); - - if (strcmp(pref_omemo_log, "on") == 0) { - _groupchat_log_chat(mybarejid, room, mynick, msg); - } else if (strcmp(pref_omemo_log, "redact") == 0) { - _groupchat_log_chat(mybarejid, room, mynick, "[redacted]"); - } - } } void groupchat_log_omemo_msg_in(const gchar* const room, const gchar* const nick, const gchar* const msg) { - if (prefs_get_boolean(PREF_CHLOG)) { - const char* mybarejid = connection_get_barejid(); - auto_gchar gchar* pref_omemo_log = prefs_get_string(PREF_OMEMO_LOG); - - if (strcmp(pref_omemo_log, "on") == 0) { - _groupchat_log_chat(mybarejid, room, nick, msg); - } else if (strcmp(pref_omemo_log, "redact") == 0) { - _groupchat_log_chat(mybarejid, room, nick, "[redacted]"); - } - } -} - -void -_groupchat_log_chat(const gchar* const login, const gchar* const room, const gchar* const nick, - const gchar* const msg) -{ - struct dated_chat_log* dated_log = g_hash_table_lookup(groupchat_logs, room); - - // no log for room - if (dated_log == NULL) { - dated_log = _create_groupchat_log(room, login); - g_hash_table_insert(groupchat_logs, strdup(room), dated_log); - - // log exists but needs rolling - } else if (_log_roll_needed(dated_log)) { - dated_log = _create_groupchat_log(room, login); - g_hash_table_replace(logs, strdup(room), dated_log); - } - - GDateTime* dt_tmp = g_date_time_new_now_local(); - - auto_gchar gchar* date_fmt = g_date_time_format_iso8601(dt_tmp); - - FILE* grpchatlogp = fopen(dated_log->filename, "a"); - g_chmod(dated_log->filename, S_IRUSR | S_IWUSR); - if (grpchatlogp) { - if (strncmp(msg, "/me ", 4) == 0) { - fprintf(grpchatlogp, "%s - *%s %s\n", date_fmt, nick, msg + 4); - } else { - fprintf(grpchatlogp, "%s - %s: %s\n", date_fmt, nick, msg); - } - - fflush(grpchatlogp); - int result = fclose(grpchatlogp); - if (result == EOF) { - log_error("Error closing file %s, errno = %d", dated_log->filename, errno); - } - } - - g_date_time_unref(dt_tmp); -} - -static char* -_get_log_filename(const char* const other, const char* const login, GDateTime* dt, gboolean is_room) -{ - auto_gchar gchar* chatlogs_dir = files_file_in_account_data_path(DIR_CHATLOGS, login, is_room ? "rooms" : NULL); - auto_gchar gchar* logfile_name = g_date_time_format(dt, "%Y_%m_%d.log"); - auto_char char* other_ = str_replace(other, "@", "_at_"); - auto_gchar gchar* logs_path = g_strdup_printf("%s/%s", chatlogs_dir, other_); - gchar* logfile_path = NULL; - - if (create_dir(logs_path)) { - logfile_path = g_strdup_printf("%s/%s", logs_path, logfile_name); - } - - return logfile_path; -} - -static struct dated_chat_log* -_create_chatlog(const char* const other, const char* const login) -{ - GDateTime* now = g_date_time_new_now_local(); - auto_char char* filename = _get_log_filename(other, login, now, FALSE); - - struct dated_chat_log* new_log = malloc(sizeof(struct dated_chat_log)); - new_log->filename = strdup(filename); - new_log->date = now; - - return new_log; -} - -static struct dated_chat_log* -_create_groupchat_log(const char* const room, const char* const login) -{ - GDateTime* now = g_date_time_new_now_local(); - auto_char char* filename = _get_log_filename(room, login, now, TRUE); - - struct dated_chat_log* new_log = malloc(sizeof(struct dated_chat_log)); - new_log->filename = strdup(filename); - new_log->date = now; - - return new_log; -} - -static gboolean -_log_roll_needed(struct dated_chat_log* dated_log) -{ - gboolean result = FALSE; - GDateTime* now = g_date_time_new_now_local(); - if (g_date_time_get_day_of_year(dated_log->date) != g_date_time_get_day_of_year(now)) { - result = TRUE; - } - g_date_time_unref(now); - - return result; -} - -static void -_free_chat_log(struct dated_chat_log* dated_log) -{ - if (dated_log) { - if (dated_log->filename) { - g_free(dated_log->filename); - dated_log->filename = NULL; - } - if (dated_log->date) { - g_date_time_unref(dated_log->date); - dated_log->date = NULL; - } - free(dated_log); - } -} - -static gboolean -_key_equals(void* key1, void* key2) -{ - gchar* str1 = (gchar*)key1; - gchar* str2 = (gchar*)key2; - - return (g_strcmp0(str1, str2) == 0); }