Files
cproof/src/ui/titlebar.c
jabber.developer2 9feff00ead refactor: clean narrowing conversions and harden unsigned arithmetic
Cleanup of the conversion-safety warnings exposed by enabling
-Wconversion / -Wsign-compare in the previous commit, plus guard
clauses at the few places where unsigned arithmetic could actually
misbehave.

Build:
- configure.ac drops -Wno-error=conversion and
  -Wno-error=float-conversion. Only -Wno-error=sign-conversion and
  -Wno-error=sign-compare remain, gating the ~230 sign warnings
  inherited from upstream that will be cleaned up in follow-ups.

Type / conversion fixes (no behaviour change):
- Length-like locals in command/cmd_ac.c, command/cmd_funcs.c,
  pgp/gpg.c, tools/autocomplete.c, tools/parser.c and ui/mucwin.c
  switched from int to size_t / glong (matching strlen /
  g_utf8_strlen return type) so we no longer need an (int) cast and
  loop counters / array sizes stay in their natural unsigned domain.
- g_timer_elapsed / GTimeSpan -> int casts in session.c, iq.c,
  core.c, server_events.c, window.c.
- _win_print_wrapped: indent parameter and local curx/maxx switched
  from size_t to int to match _win_indent / getcurx / getmaxx.
- Port casts (int -> unsigned short) at the libstrophe boundary in
  connection.c and session.c, each preceded by
  g_assert(port >= 0 && port <= UINT16_MAX) so the truncation is
  documented at the call-site.
- curl_off_t / fread size_t results cast at usage in http_upload.c,
  http_download.c, omemo/crypto.c.
- strtoul results cast to uint32_t in xmpp/omemo.c and omemo/omemo.c
  where device/prekey IDs are genuinely 32-bit.
- config/color.c: fg/bg/palette indices switched to `short`
  end-to-end (find_col, color_hash, find_closest_col,
  _color_pair_cache_get, cache.pairs), so the ncurses init_pair
  boundary needs at most one (short)i cast for the cache index. Also
  TODO-noted: init_extended_pair is needed for >15-bit palettes.
- xmpp/avatar.c: float arithmetic explicitly casts its int operands.
- tests/functionaltests/proftest.c: read() result handling uses
  size_t for the accumulator, _read_output returns ssize_t, and the
  buffer-shift check happens before space subtraction so the
  expression cannot underflow.

Real-risk guard clauses (the part that actually fixes bugs):
- src/ui/statusbar.c _tabs_width: `end > opened_tabs - 1` rewritten
  as `end < opened_tabs` so opened_tabs == 0 no longer underflows.
- src/ui/statusbar.c _status_bar_draw_extended_tabs: the mirror
  comparison rewritten as `end >= opened_tabs`.
- src/ui/statusbar.c status_bar_draw: replaced
  `MAX(0, getmaxx - (int)_tabs_width)` with an explicit precheck
  before subtraction.
- src/omemo/omemo.c prekey selection: prekey_index is now uint32_t
  and randomized into an unsigned buffer, so modulo with prekeys_len
  cannot yield a negative index for g_list_nth_data.
- src/omemo/crypto.c omemo_decrypt_func: PKCS#5/PKCS#7 unpadding
  reads `plaintext[plaintext_len - 1]`, which would underflow on a
  malformed empty ciphertext and read past the heap buffer. Reject
  plaintext_len == 0 before the padding peek and validate the
  padding byte against the buffer length before the unpad loop.
  Initialise plaintext = NULL so the early `goto out` cannot free
  uninitialised memory.
- src/ui/inputwin.c (4 mbrlen sites) and src/ui/window.c
  _win_print_wrapped: mbrlen() returns 0 for the null wide
  character. The existing checks rejected (size_t)-1 / -2 but
  treated 0 as a valid step, so the surrounding loops would either
  advance by SIZE_MAX (i += ch_len - 1) or spin in place
  (word_pos += 0 forever). Add `|| ch_len == 0` to each guard;
  inside the spell-check word-emission loop also fall back to a
  one-byte advance.
- Defensive `len > 0 ? len - 1 : 0` prechecks at the strlen-based
  g_strndup / loop sites in ui/console.c, plugins/c_api.c and
  plugins/python_plugins.c.
2026-04-28 10:24:52 +03:00

651 lines
17 KiB
C

/*
* titlebar.c
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "config.h"
#include "common.h"
#include "config/theme.h"
#include "config/preferences.h"
#include "ui/ui.h"
#include "ui/titlebar.h"
#include "ui/inputwin.h"
#include "ui/window_list.h"
#include "ui/window.h"
#include "ui/screen.h"
#ifdef HAVE_OMEMO
#include "omemo/omemo.h"
#endif
#include "xmpp/roster_list.h"
#include "xmpp/chat_session.h"
static WINDOW* win;
static contact_presence_t current_presence;
static gboolean tls_secured;
static gboolean is_connected;
static gboolean typing;
static GTimer* typing_elapsed;
static void _title_bar_draw(void);
static void _show_self_presence(void);
static int _calc_self_presence(void);
static void _show_contact_presence(ProfChatWin* chatwin, int pos, int maxpos);
static void _show_privacy(ProfChatWin* chatwin);
static void _show_muc_privacy(ProfMucWin* mucwin);
static void _show_scrolled(ProfWin* current);
static void _show_attention(ProfWin* current, gboolean attention);
static void _show_trust_indicator(gboolean trusted, int bracket_attrs, int trusted_attrs, int untrusted_attrs);
static inline void
_wprintw_withattr(WINDOW* w, const char* text, int attrs)
{
wattron(w, attrs);
wprintw(w, "%s", text);
wattroff(w, attrs);
}
static void
_show_trust_indicator(gboolean trusted, int bracket_attrs, int trusted_attrs, int untrusted_attrs)
{
wprintw(win, " ");
_wprintw_withattr(win, "[", bracket_attrs);
_wprintw_withattr(win, trusted ? "trusted" : "untrusted", trusted ? trusted_attrs : untrusted_attrs);
_wprintw_withattr(win, "]", bracket_attrs);
}
void
create_title_bar(void)
{
int cols = getmaxx(stdscr);
if (cols <= 0) {
cols = 1;
}
int row = screen_titlebar_row();
win = newwin(1, cols, row, 0);
if (!win) {
// Failed to create title bar window; skip initialization to avoid NULL deref
return;
}
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
title_bar_console();
title_bar_set_presence(CONTACT_OFFLINE);
title_bar_set_tls(FALSE);
title_bar_set_connected(FALSE);
wnoutrefresh(win);
inp_put_back();
}
void
free_title_bar(void)
{
if (win) {
delwin(win);
win = NULL;
}
}
void
title_bar_update_virtual(void)
{
if (!win) {
return;
}
ProfWin* window = wins_get_current();
if (window->type != WIN_CONSOLE) {
if (typing_elapsed) {
gdouble seconds = g_timer_elapsed(typing_elapsed, NULL);
if (seconds >= 10) {
typing = FALSE;
g_timer_destroy(typing_elapsed);
typing_elapsed = NULL;
}
}
}
_title_bar_draw();
}
void
title_bar_resize(void)
{
if (!win) {
return;
}
int cols = getmaxx(stdscr);
if (cols <= 0) {
cols = 1;
}
werase(win);
int row = screen_titlebar_row();
wresize(win, 1, cols);
mvwin(win, row, 0);
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
_title_bar_draw();
}
void
title_bar_console(void)
{
if (!win) {
return;
}
werase(win);
if (typing_elapsed) {
g_timer_destroy(typing_elapsed);
}
typing_elapsed = NULL;
typing = FALSE;
_title_bar_draw();
}
void
title_bar_set_presence(contact_presence_t presence)
{
current_presence = presence;
_title_bar_draw();
}
void
title_bar_set_connected(gboolean connected)
{
is_connected = connected;
_title_bar_draw();
}
void
title_bar_set_tls(gboolean secured)
{
tls_secured = secured;
_title_bar_draw();
}
void
title_bar_switch(void)
{
if (typing_elapsed) {
g_timer_destroy(typing_elapsed);
typing_elapsed = NULL;
typing = FALSE;
}
_title_bar_draw();
}
void
title_bar_set_typing(gboolean is_typing)
{
if (is_typing) {
if (typing_elapsed) {
g_timer_start(typing_elapsed);
} else {
typing_elapsed = g_timer_new();
}
}
typing = is_typing;
_title_bar_draw();
}
static void
_title_bar_draw(void)
{
if (!win) {
return;
}
int pos;
int maxrightpos;
ProfWin* current = wins_get_current();
werase(win);
wmove(win, 0, 0);
for (int i = 0; i < 45; i++) {
waddch(win, ' ');
}
auto_gchar gchar* title = win_get_title(current);
mvwprintw(win, 0, 0, " %s", title);
pos = (int)strlen(title) + 1;
// presence is written from the right side
// calculate it first so we have a maxposition
maxrightpos = _calc_self_presence();
if (current && current->type == WIN_CHAT) {
ProfChatWin* chatwin = (ProfChatWin*)current;
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
_show_contact_presence(chatwin, pos, maxrightpos);
_show_privacy(chatwin);
_show_attention(current, chatwin->has_attention);
_show_scrolled(current);
if (typing) {
wprintw(win, " (typing...)");
}
} else if (current && current->type == WIN_MUC) {
ProfMucWin* mucwin = (ProfMucWin*)current;
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
_show_muc_privacy(mucwin);
_show_attention(current, mucwin->has_attention);
_show_scrolled(current);
}
_show_self_presence();
wnoutrefresh(win);
inp_put_back();
}
static void
_show_attention(ProfWin* current, gboolean attention)
{
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int text_attrs = theme_attrs(THEME_TITLE_TEXT);
if (attention) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, text_attrs);
wprintw(win, "ATTENTION");
wattroff(win, text_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
}
static void
_show_scrolled(ProfWin* current)
{
if (current && current->layout->paged == 1) {
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int scrolled_attrs = theme_attrs(THEME_TITLE_SCROLLED);
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, scrolled_attrs);
if (current->layout->unread_msg == 0) {
wprintw(win, "SCROLLED");
} else {
wprintw(win, "SCROLLED, NEW MESSAGES");
}
wattroff(win, scrolled_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
}
static int
_calc_self_presence(void)
{
int tls_start = 0;
switch (current_presence) {
case CONTACT_ONLINE:
tls_start = 15;
break;
case CONTACT_AWAY:
tls_start = 13;
break;
case CONTACT_DND:
tls_start = 12;
break;
case CONTACT_CHAT:
tls_start = 13;
break;
case CONTACT_XA:
tls_start = 11;
break;
case CONTACT_OFFLINE:
tls_start = 16;
break;
}
return tls_start - 1;
}
static void
_show_self_presence(void)
{
int presence_attrs = 0;
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int encrypted_attrs = theme_attrs(THEME_TITLE_ENCRYPTED);
int unencrypted_attrs = theme_attrs(THEME_TITLE_UNENCRYPTED);
int cols = getmaxx(stdscr);
int tls_start = 0;
switch (current_presence) {
case CONTACT_ONLINE:
presence_attrs = theme_attrs(THEME_TITLE_ONLINE);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 9, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 8, "online");
wattroff(win, presence_attrs);
tls_start = 15;
break;
case CONTACT_AWAY:
presence_attrs = theme_attrs(THEME_TITLE_AWAY);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 7, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 6, "away");
wattroff(win, presence_attrs);
tls_start = 13;
break;
case CONTACT_DND:
presence_attrs = theme_attrs(THEME_TITLE_DND);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 6, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 5, "dnd");
wattroff(win, presence_attrs);
tls_start = 12;
break;
case CONTACT_CHAT:
presence_attrs = theme_attrs(THEME_TITLE_CHAT);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 7, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 6, "chat");
wattroff(win, presence_attrs);
tls_start = 13;
break;
case CONTACT_XA:
presence_attrs = theme_attrs(THEME_TITLE_XA);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 5, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 4, "xa");
wattroff(win, presence_attrs);
tls_start = 11;
break;
case CONTACT_OFFLINE:
presence_attrs = theme_attrs(THEME_TITLE_OFFLINE);
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 10, '[');
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
mvwprintw(win, 0, cols - 9, "offline");
wattroff(win, presence_attrs);
tls_start = 16;
break;
}
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - 2, ']');
wattroff(win, bracket_attrs);
if (is_connected && prefs_get_boolean(PREF_TLS_SHOW)) {
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - tls_start, '[');
wattroff(win, bracket_attrs);
if (tls_secured) {
wattron(win, encrypted_attrs);
mvwprintw(win, 0, cols - (tls_start - 1), "TLS");
wattroff(win, encrypted_attrs);
} else {
wattron(win, unencrypted_attrs);
mvwprintw(win, 0, cols - (tls_start - 1), "TLS");
wattroff(win, unencrypted_attrs);
}
wattron(win, bracket_attrs);
mvwaddch(win, 0, cols - (tls_start - 4), ']');
wattroff(win, bracket_attrs);
}
}
static void
_show_muc_privacy(ProfMucWin* mucwin)
{
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int encrypted_attrs = theme_attrs(THEME_TITLE_ENCRYPTED);
#ifdef HAVE_OMEMO
int trusted_attrs = theme_attrs(THEME_TITLE_TRUSTED);
int untrusted_attrs = theme_attrs(THEME_TITLE_UNTRUSTED);
#endif
if (mucwin->is_omemo) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "OMEMO");
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
#ifdef HAVE_OMEMO
_show_trust_indicator(mucwin->omemo_trusted, bracket_attrs, trusted_attrs, untrusted_attrs);
#endif
return;
}
if (mucwin->enctext) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "%s", mucwin->enctext);
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
return;
}
}
static void
_show_privacy(ProfChatWin* chatwin)
{
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int encrypted_attrs = theme_attrs(THEME_TITLE_ENCRYPTED);
int unencrypted_attrs = theme_attrs(THEME_TITLE_UNENCRYPTED);
int trusted_attrs = theme_attrs(THEME_TITLE_TRUSTED);
int untrusted_attrs = theme_attrs(THEME_TITLE_UNTRUSTED);
if (chatwin->enctext) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "%s", chatwin->enctext);
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
return;
}
// XEP-0373: OpenPGP for XMPP
if (chatwin->is_ox) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "OX");
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
if (chatwin->is_otr) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "OTR");
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
_show_trust_indicator(chatwin->otr_is_trusted, bracket_attrs, trusted_attrs, untrusted_attrs);
return;
}
if (chatwin->pgp_send || chatwin->pgp_recv) {
GString* pgpmsg = g_string_new("PGP ");
if (chatwin->pgp_send && !chatwin->pgp_recv) {
g_string_append(pgpmsg, "send");
} else if (!chatwin->pgp_send && chatwin->pgp_recv) {
g_string_append(pgpmsg, "recv");
} else {
g_string_append(pgpmsg, "send/recv");
}
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "%s", pgpmsg->str);
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
g_string_free(pgpmsg, TRUE);
return;
}
if (chatwin->is_omemo) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "OMEMO");
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
#ifdef HAVE_OMEMO
_show_trust_indicator(chatwin->omemo_trusted, bracket_attrs, trusted_attrs, untrusted_attrs);
#endif
return;
}
if (prefs_get_boolean(PREF_ENC_WARN)) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, unencrypted_attrs);
wprintw(win, "unencrypted");
wattroff(win, unencrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
}
static void
_show_contact_presence(ProfChatWin* chatwin, int pos, int maxpos)
{
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
char* resource = NULL;
ChatSession* session = chat_session_get(chatwin->barejid);
if (chatwin->resource_override) {
resource = chatwin->resource_override;
} else if (session && session->resource) {
resource = session->resource;
}
if (resource && prefs_get_boolean(PREF_RESOURCE_TITLE)) {
int needed = (int)strlen(resource) + 1;
if (pos + needed < maxpos) {
wprintw(win, "/");
wprintw(win, "%s", resource);
}
}
if (prefs_get_boolean(PREF_PRESENCE)) {
theme_item_t presence_colour = THEME_TITLE_OFFLINE;
const char* presence = "offline";
jabber_conn_status_t conn_status = connection_get_status();
if (conn_status == JABBER_CONNECTED) {
PContact contact = roster_get_contact(chatwin->barejid);
if (contact) {
if (resource) {
Resource* resourcep = p_contact_get_resource(contact, resource);
if (resourcep) {
presence = string_from_resource_presence(resourcep->presence);
}
} else {
presence = p_contact_presence(contact);
}
}
}
presence_colour = THEME_TITLE_ONLINE;
if (g_strcmp0(presence, "offline") == 0) {
presence_colour = THEME_TITLE_OFFLINE;
} else if (g_strcmp0(presence, "away") == 0) {
presence_colour = THEME_TITLE_AWAY;
} else if (g_strcmp0(presence, "xa") == 0) {
presence_colour = THEME_TITLE_XA;
} else if (g_strcmp0(presence, "chat") == 0) {
presence_colour = THEME_TITLE_CHAT;
} else if (g_strcmp0(presence, "dnd") == 0) {
presence_colour = THEME_TITLE_DND;
}
int presence_attrs = theme_attrs(presence_colour);
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, presence_attrs);
wprintw(win, "%s", presence);
wattroff(win, presence_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
}