Detect the terminal background (via COLORFGBG or an OSC 11 query) before
GUI init and, on the first start, automatically apply the built-in
"light" theme when a light terminal is detected.
The function gui_term_theme_is_light returns an int (1 if light, 0 otherwise,
0 being the safe value when detection is unsure).
Add a small core-theme-builtin.c module containing the core
contribution to the "light" theme: 33 overrides for
"weechat.bar.{status,title}.color_*" and "weechat.color.*" tuned for
light-background terminals.
theme_builtin_init() builds a hashtable from the static entry table and
calls theme_register("light", overrides), then frees the temporary
hashtable. It is called once from weechat_init right after theme_init.
Calling it twice is a no-op (the registry merges identical keys).
Default option values are NOT changed. Existing configs render exactly
as before; users opt in with "/theme apply light".
Add TEST(CoreTheme, BuiltinInit) covering:
- the "light" theme is absent before theme_builtin_init();
- it is present after, with >= 30 overrides;
- three spot-checked values match the source table;
- calling theme_builtin_init() a second time does not change the
override count.
Plugins contribute their own "light" overrides via weechat_theme_register
in subsequent commits.
Add a single new entry point to the plugin API:
struct t_theme *weechat_theme_register (const char *name,
struct t_hashtable *overrides);
Plugins call this at init time to contribute their per-theme color (or
other themable) overrides for a built-in theme like "dark". The
overrides hashtable maps full option names ("irc.color.input_nick") to
their string values; the caller retains ownership and may free it
right after the call. Repeated calls with the same theme name merge
into the existing registry entry, so each plugin can declare its own
contributions independently of core and of other plugins.
Wiring:
- struct t_theme forward-declared in weechat-plugin.h alongside the
other opaque types.
- theme_register function pointer added to t_weechat_plugin.
- weechat_theme_register convenience macro added.
- plugin.c initializes the pointer to core's theme_register.
- WEECHAT_PLUGIN_API_VERSION bumped to 20260704-01.
This commit is plumbing only: the underlying theme_register function
already has unit-test coverage in tests/unit/core/test-core-theme.cpp
(TEST(CoreTheme, Register)), so no new tests are added here.
Implement /theme apply <name> for themes currently in the in-memory
registry. The file-shadowing branch (read a .theme file from
${weechat_config_dir}/themes/ when no built-in matches) is added in
the next commit together with the parser.
Apply algorithm (theme_apply in core-theme.c):
- Look up the theme in the registry; abort with an error if unknown.
- If weechat.look.theme_backup is on and the target name does not
begin with "backup-", write a full snapshot of every themable
option to ${weechat_config_dir}/themes/backup-<timestamp>.theme
via theme_make_backup; abort the apply if the backup cannot be
written, so the user can always undo.
- Iterate the theme's overrides with theme_applying=1 so the
per-option config_change_color skips its gui refresh; for each
entry look up the option, refuse it if missing or non-themable
(warning to core buffer), otherwise call config_file_option_set.
- Perform a single gui_color_init_weechat + gui_window_ask_refresh
at the end.
- Persist the active label in weechat.look.theme and send signal
"theme_applied" with the name as data.
Add the new option weechat.look.theme_backup (boolean, default on)
which controls the backup-or-abort behavior described above.
Wire the new /theme apply subcommand into core-command.c with the
existing /theme registration; update help text accordingly.
Add the /theme command with two read-only subcommands for now:
- /theme (or /theme list): list registered themes; the active theme
(matching weechat.look.theme) is marked with "->".
- /theme info <name>: show name, description, creation date, WeeChat
version and override count of a theme.
Both subcommands only consider themes present in the in-memory
registry (registered via core/plugins/scripts). User theme files on
disk are not yet handled: the file parser and transient file reads
land in a later commit together with /theme apply.
Add a new string option "weechat.look.theme" holding the name of the
last theme applied via the upcoming /theme command. It is set
automatically by /theme apply and persisted on disk for /theme info to
display after restart; it is NOT re-applied at startup (the user's
saved color values win to avoid clobbering manual post-apply tweaks).
Amend config_change_color so it skips the gui_color_init_weechat ()
and gui_window_ask_refresh (1) calls when theme_applying is set.
/theme apply will set this flag while iterating overrides so the N
individual option changes do not trigger N redundant screen refreshes;
the apply path then performs a single refresh at the end.
Extend the "t:" filter so the special value "themable" matches every
option whose new themable flag is set, regardless of type (color,
string, integer, boolean, enum). This makes the flag interactively
discoverable in the fset buffer and is the natural way to inspect the
surface area that an upcoming /theme command will be allowed to touch.
The themable flag of an option is now mirrored on struct t_fset_option,
exposed via hdata ("themable", integer) and infolist ("themable",
integer), and printed in the log.
Add an "int themable" field on struct t_config_option. The flag is set
automatically for every CONFIG_OPTION_TYPE_COLOR option, and may be set
explicitly on any other type by suffixing the type argument with
"|themable" in the call to config_file_new_option (e.g. "string|themable"
for a string option whose value contains "${color:...}" references).
Opt in the relevant string options in core and in the buflist, fset, irc,
relay plugins.
The flag is exposed via hdata, infolist, and print_log so scripts and
/debug can read it. This is the foundation for an upcoming /theme
command that will only be allowed to modify themable options.
On modern 32-bit platforms with a 64-bit time_t, the value returned by SvIV can
be 32-bit (its width depends on how Perl was built), whereas time_t is 64-bit.
Read the date with SvNV instead: a double represents all real timestamps
exactly, so the conversion to time_t no longer depends on the size of the Perl
integer type.
A relay client could send data with no end-of-line (an unterminated method
or header line) and dribble its payload, making WeeChat accumulate it in the
partial message buffer that grew without limit, until all memory was
exhausted. This path is reachable before authentication during websocket
initialization with the "weechat" and "irc" protocols.
The accumulated partial message is now bounded by
RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH: once the limit is reached, the extra
data is ignored.
A relay client could announce a huge websocket frame (or HTTP body via
"Content-Length") and dribble its payload, making WeeChat accumulate it
in a buffer that grew without limit, until all memory was exhausted. The
websocket frame path is reachable before authentication with the
"weechat" and "irc" protocols.
The announced websocket frame length and HTTP "Content-Length" are now
bounded by WEBSOCKET_FRAME_MAX_LENGTH and RELAY_HTTP_BODY_MAX_LENGTH: an
oversized websocket frame closes the connection, and an oversized body is
rejected.
A malicious or compromised IRC server could send data with no end-of-line
(or a flood of "005" messages), making WeeChat accumulate it in a buffer
that grew without limit, until all memory was exhausted.
The unterminated received message and the accumulated "005" (ISUPPORT)
data are now bounded by IRC_SERVER_RECV_MSG_MAX_LENGTH and
IRC_SERVER_ISUPPORT_MAX_LENGTH: extra data is ignored once the limit is
reached.
weecrypto_totp_validate compared the generated and client-supplied OTPs
with strcmp and broke out of the time-window loop on the first match.
Both choices leaked information via response timing: strcmp leaked the
expected OTP digit-by-digit (shrinking the brute-force search from
~10^digits to a handful of guesses within the 30-second window), and
the early break leaked which window offset matched.
Compare in constant time with string_memcmp_constant_time and always
iterate the full window, OR-ing the result into otp_ok without an
early exit.
This affects both relay protocols (which call totp_validate via the
public info hook) and any other caller of the info hook.
The relay authentication used non-constant-time comparisons (strcasecmp,
strcmp) to verify password hashes and plaintext passwords, allowing an
attacker to derive the expected hash byte-by-byte from response timing
and then authenticate without knowing the password.
- SHA/PBKDF2 hex hash comparisons: normalize the client-supplied hash to
uppercase and compare in constant time over the fixed expected length.
- Plaintext password comparison: HMAC-SHA256 both passwords with a fresh
per-call random key and compare the fixed-size MACs in constant time,
hiding both per-byte timing and the password length.
Add string_memcmp_constant_time helper in core, exposed via the plugin
API. Bump WEECHAT_PLUGIN_API_VERSION accordingly.
An authenticated relay client using the permessage-deflate websocket
extension could send a small compressed frame that decompresses to an
unbounded amount of data, exhausting all memory and crashing WeeChat.
The output buffer in relay_websocket_inflate is now capped to
WEBSOCKET_INFLATE_MAX_SIZE: frames decompressing beyond this limit are
rejected and the connection is closed.
Since v4.8.0 and commit d0298b4738, toggling
filters with `/filter toggle` displays a message on core buffer.
This is OK when running the command manually, but not when pressing the key
alt+"=".