fix(ui,db): harden NULL handling, fix CWE-134, optimize iterations

security(CWE-134): fix format string injections + add CI check
fix(ui): subwindow lifecycle, newwin/newpad guards, fallback timestamps
fix(db): sqlite cleanup on failures, sqlite3_close_v2
fix(xmpp): queued_messages loop, barejid leak
perf(core): g_hash_table_iter_init instead of g_hash_table_get_keys
refactor(ui): CLAMP macro in _check_subwin_width
test: XEP-0012 and XEP-0045 functional tests

Author: jabber.developer2
Closes #58, #85
This commit is contained in:
2026-02-06 19:27:40 +01:00
parent f8826b7c79
commit 467222d0ca
36 changed files with 656 additions and 238 deletions

View File

@@ -75,12 +75,22 @@ 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)
{
return cols <= 1 ? 1 : CLAMP(width, 1, cols - 1);
}
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 +98,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 +157,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 +189,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 +248,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 +268,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 +299,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 +317,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 +369,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 +577,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 +949,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 +2082,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 +2289,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