merge: sync upstream profanity-im/profanity
All checks were successful
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 34s
CI Code / Code Coverage (push) Successful in 2m36s
CI Code / Linux (debian) (push) Successful in 4m41s
CI Code / Linux (ubuntu) (push) Successful in 4m52s
CI Code / Linux (arch) (push) Successful in 5m40s

Sync with upstream profanity-im/profanity.

Major upstream changes incorporated:

Memory management
  - Replace malloc+memset with g_new0 throughout codebase
  - Adopt auto_gchar / auto_gcharv / auto_gerror / auto_jid cleanup macros
  - Replace free() with g_free() for GAlloc'd memory

Editor rewrite
  - Remove pthread-based async editor; use GChildWatch callback API
  - New launch_editor(initial_content, callback, user_data) interface
  - Proper signal handling (SIGINT, SIGTSTP, SIGPIPE reset in child)
  - ui_suspend()/ui_resume() integration for TTY management

OMEMO improvements
  - Dual backend support: libsignal-protocol-c and libomemo-c
  - Proper pre-key removal after use (XEP-0384 compliance)
  - Automatic pre-key regeneration when store drops below threshold
  - New functions: omemo_is_device_active(), omemo_is_jid_trusted()
  - omemo_get_jid_untrusted_fingerprints() for better error messages
  - Fingerprint notifications on new device identity discovery
  - Deterministic pre-key ID generation tracking max_pre_key_id
  - omemo_trust_changed() UI updates on trust state changes

JID validation
  - RFC 6122-compliant validation in jid_is_valid()
  - Character-level checks (RFC 6122 forbidden chars: & ' / : < > @)
  - Length limits: 1023 per component, 3071 total
  - New jid_is_valid_user_jid() for user vs. service JID distinction

Database
  - Schema migration v3: UNIQUE constraint on archive_id for deduplication
  - Triggers for corrected message tracking (replaces_db_id / replaced_by_db_id)
  - db_history_result_t return type, _truncate_datetime_suffix()

UI / console
  - win_warn_needed() / win_warn_sent() warning deduplication hash table
  - PAD_MIN_HEIGHT dynamic pad sizing with PAD_THRESHOLD auto-cleanup
  - Spellcheck integration in input field with Unicode word detection
  - cons_spellcheck_setting() for /settings ui output
  - /[command]? shortcut for command help

Account config
  - Account name sanitization for GKeyFile special chars ([ ] = # \n \r)
  - Replace popen() with g_spawn_sync() for eval_password
  - TLS policy: add "direct" option alongside legacy

Connection
  - Port validation with g_assert (0–65535)
  - SHA-256 certificate fingerprint support (XMPP_CERT_PUBKEY_FINGERPRINT_SHA256)
  - "direct" TLS policy alias for legacy SSL

Common utilities
  - str_xml_sanitize() for XML 1.0 illegal character removal
  - string_matches_one_of() with formatted error messages
  - valid_tls_policy_option() helper
  - prof_date_time_format_iso8601() utility
  - Improved strip_arg_quotes() with backslash unescaping
  - prof_occurrences() uses g_slist_prepend + reverse for performance

PGP / OX
  - Proper GPGME resource cleanup with goto-cleanup pattern
  - g_string_free(xmppuri) leak fix in _ox_key_lookup

CSV export
  - Use GString + g_file_set_contents instead of raw write() syscalls

Tests
  - Restructured into subdirectories: command/, config/, xmpp/, ui/, omemo/, otr/, pgp/
  - New test_cmd_ac.c for autocompleter unit tests
  - Updated stubs for new UI suspend/resume functions

License headers
  - Migrate to SPDX-3.0 identifiers (GPL-3.0-or-later WITH OpenSSL-exception)

────────────────────────────────────────────────────
cproof-specific preservations:

  - XEP-0308 LMC: replace_id ?: id logic in message/stanza/omemo
  - Force encryption: cmd_force_encryption, test_forced_encryption
  - CWE-134: format string protection (cons_show("%s", ...))
  - y_start_pos-based paging in window.c
  - db_history_result_t return type, _truncate_datetime_suffix()

Merge-time fixes:

  - common.c: format-security (-Werror) — cons_show(errmsg) → cons_show("%s", errmsg)
  - database.c: null-deref guard — !msg->timestamp → msg && !msg->timestamp
  - console.c: implicit size_t → int cast — (int)(maxlen + 1)
  - tlscerts.c: %d for size_t — %zu

Build system:
  - Kept autotools (Makefile.am, configure.ac); upstream uses Meson
  - Restored deleted files: bootstrap.sh, autogen.sh, ax_valgrind_check.m4, configure-debug
  - Updated Makefile.am test paths for subdirectory structure
  - Added test_cmd_ac, test_forced_encryption to test sources

Functional tests:
  - Use cproof version; upstream requires stbbr_for_xmlns from updated stabber
  - Not yet available in devs/stabber fork

Closes #64

Merge author: jabber.developer2
Commits authors:
 Michael Vetter <jubalh@iodoru.org>
& Steffen Jaeckel <s@jaeckel.eu>
This commit is contained in:
2026-05-26 17:48:14 +00:00
parent 3b673150b4
commit 72f4f186da
303 changed files with 10658 additions and 9857 deletions

View File

@@ -3,35 +3,9 @@
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
*
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#include "config.h"
@@ -71,6 +45,7 @@
static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _autoaway_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _autoconnect_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous);
@@ -136,6 +111,7 @@ static char* _lastactivity_autocomplete(ProfWin* window, const char* const input
static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _stamp_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _force_encryption_autocomplete(ProfWin* window, const char* const input, gboolean previous);
@@ -167,6 +143,7 @@ static Autocomplete autoconnect_ac;
static Autocomplete wintitle_ac;
static Autocomplete theme_ac;
static Autocomplete theme_load_ac;
static Autocomplete spellcheck_ac;
static Autocomplete account_ac;
static Autocomplete account_set_ac;
static Autocomplete account_clear_ac;
@@ -297,6 +274,8 @@ static Autocomplete mood_type_ac;
static Autocomplete strophe_ac;
static Autocomplete strophe_sm_ac;
static Autocomplete strophe_verbosity_ac;
static Autocomplete stamp_ac;
static Autocomplete stamp_unset_ac;
static Autocomplete adhoc_cmd_ac;
static Autocomplete lastactivity_ac;
static Autocomplete vcard_ac;
@@ -310,6 +289,8 @@ static Autocomplete vcard_address_type_ac;
static Autocomplete force_encryption_ac;
static Autocomplete force_encryption_policy_ac;
static char* last_filepath_input = NULL;
static Autocomplete* all_acs[] = {
&commands_ac,
&who_room_ac,
@@ -332,6 +313,7 @@ static Autocomplete* all_acs[] = {
&autoconnect_ac,
&wintitle_ac,
&theme_ac,
&spellcheck_ac,
&account_ac,
&account_set_ac,
&account_clear_ac,
@@ -453,6 +435,8 @@ static Autocomplete* all_acs[] = {
&strophe_ac,
&strophe_sm_ac,
&strophe_verbosity_ac,
&stamp_ac,
&stamp_unset_ac,
&adhoc_cmd_ac,
&lastactivity_ac,
&vcard_ac,
@@ -604,6 +588,11 @@ cmd_ac_init(void)
autocomplete_add(theme_ac, "colours");
autocomplete_add(theme_ac, "properties");
autocomplete_add(spellcheck_ac, "on");
autocomplete_add(spellcheck_ac, "off");
autocomplete_add(spellcheck_ac, "list");
autocomplete_add(spellcheck_ac, "lang");
autocomplete_add(disco_ac, "info");
autocomplete_add(disco_ac, "items");
@@ -875,8 +864,9 @@ cmd_ac_init(void)
autocomplete_add(tls_property_ac, "force");
autocomplete_add(tls_property_ac, "allow");
autocomplete_add(tls_property_ac, "trust");
autocomplete_add(tls_property_ac, "legacy");
autocomplete_add(tls_property_ac, "direct");
autocomplete_add(tls_property_ac, "disable");
autocomplete_add(tls_property_ac, "legacy");
autocomplete_add(auth_property_ac, "default");
autocomplete_add(auth_property_ac, "legacy");
@@ -1232,6 +1222,13 @@ cmd_ac_init(void)
autocomplete_add(strophe_verbosity_ac, "2");
autocomplete_add(strophe_verbosity_ac, "3");
autocomplete_add(stamp_ac, "outgoing");
autocomplete_add(stamp_ac, "incoming");
autocomplete_add(stamp_ac, "unset");
autocomplete_add(stamp_unset_ac, "outgoing");
autocomplete_add(stamp_unset_ac, "incoming");
autocomplete_add(mood_ac, "set");
autocomplete_add(mood_ac, "clear");
autocomplete_add(mood_ac, "on");
@@ -1461,6 +1458,7 @@ cmd_ac_init(void)
g_hash_table_insert(ac_funcs, "/status", _status_autocomplete);
g_hash_table_insert(ac_funcs, "/statusbar", _statusbar_autocomplete);
g_hash_table_insert(ac_funcs, "/strophe", _strophe_autocomplete);
g_hash_table_insert(ac_funcs, "/stamp", _stamp_autocomplete);
g_hash_table_insert(ac_funcs, "/sub", _sub_autocomplete);
g_hash_table_insert(ac_funcs, "/subject", _subject_autocomplete);
g_hash_table_insert(ac_funcs, "/theme", _theme_autocomplete);
@@ -1605,6 +1603,10 @@ cmd_ac_remove_form_fields(DataForm* form)
char*
cmd_ac_complete(ProfWin* window, const char* const input, gboolean previous)
{
if (!input) {
return NULL;
}
char* found = NULL;
// autocomplete command
if ((strncmp(input, "/", 1) == 0) && (!strchr(input, ' '))) {
@@ -1709,65 +1711,74 @@ cmd_ac_uninit(void)
autocomplete_free(plugins_unload_ac);
autocomplete_free(plugins_reload_ac);
autocomplete_free(script_show_ac);
g_free(last_filepath_input);
last_filepath_input = NULL;
g_hash_table_destroy(ac_funcs);
ac_funcs = NULL;
}
static void
_filepath_item_free(char** ptr)
_filepath_item_free(gchar** ptr)
{
char* item = *ptr;
free(item);
gchar* item = *ptr;
g_free(item);
}
char*
cmd_ac_complete_filepath(const char* const input, char* const startstr, gboolean previous)
{
unsigned int output_off = 0;
char* tmp = NULL;
// strip command
char* inpcp = (char*)input + strlen(startstr);
while (*inpcp == ' ') {
inpcp++;
char* inpcp_ptr = (char*)input + strlen(startstr);
while (*inpcp_ptr == ' ') {
inpcp_ptr++;
}
inpcp = strdup(inpcp);
// strip quotes
if (*inpcp == '"') {
tmp = strrchr(inpcp + 1, '"');
if (tmp) {
*tmp = '\0';
}
tmp = strdup(inpcp + 1);
free(inpcp);
inpcp = tmp;
tmp = NULL;
}
// expand ~ to $HOME
if (inpcp[0] == '~' && inpcp[1] == '/') {
char* home = getenv("HOME");
if (!home) {
free(inpcp);
return NULL;
}
tmp = g_strdup_printf("%s/%sfoo", home, inpcp + 2);
output_off = strlen(home) + 1;
} else {
tmp = g_strdup_printf("%sfoo", inpcp);
}
free(inpcp);
if (!tmp) {
auto_gchar gchar* inpcp = g_strdup(inpcp_ptr);
if (!inpcp) {
return NULL;
}
char* foofile = strdup(basename(tmp));
char* directory = strdup(dirname(tmp));
g_free(tmp);
// strip quotes
if (inpcp[0] == '"') {
char* last_quote = strrchr(inpcp + 1, '"');
if (last_quote) {
*last_quote = '\0';
}
gchar* unquoted = g_strdup(inpcp + 1);
g_free(inpcp);
inpcp = unquoted;
}
GArray* files = g_array_new(TRUE, FALSE, sizeof(char*));
auto_gchar gchar* expanded = get_expanded_path(inpcp);
if (!expanded) {
return NULL;
}
auto_gchar gchar* foofile = g_path_get_basename(expanded);
auto_gchar gchar* directory = g_path_get_dirname(expanded);
// If the input is already a known completion, don't update to allow cycling
if (last_filepath_input && (g_strcmp0(inpcp, last_filepath_input) == 0 || autocomplete_contains(filepath_ac, inpcp))) {
return autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE, previous);
}
// If the input ends with a slash, the basename will be "." or "/"
// In that case, we are looking for all files in that directory
gboolean find_all = FALSE;
size_t inpcp_len = strlen(inpcp);
if (inpcp_len == 0 || inpcp[inpcp_len - 1] == '/') {
find_all = TRUE;
}
char* last_slash = strrchr(inpcp, '/');
auto_gchar gchar* user_dir_part = NULL;
if (last_slash) {
user_dir_part = g_strndup(inpcp, last_slash - inpcp + 1);
} else {
user_dir_part = g_strdup("");
}
GArray* files = g_array_new(TRUE, FALSE, sizeof(gchar*));
g_array_set_clear_func(files, (GDestroyNotify)_filepath_item_free);
DIR* d = opendir(directory);
@@ -1775,47 +1786,48 @@ cmd_ac_complete_filepath(const char* const input, char* const startstr, gboolean
struct dirent* dir;
while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0) {
continue;
} else if (strcmp(dir->d_name, "..") == 0) {
continue;
} else if (*(dir->d_name) == '.' && *foofile != '.') {
// only show hidden files on explicit request
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) {
continue;
}
char* acstring = NULL;
if (output_off) {
tmp = g_strdup_printf("%s/%s", directory, dir->d_name);
if (tmp) {
acstring = g_strdup_printf("~/%s", tmp + output_off);
g_free(tmp);
}
} else if (strcmp(directory, "/") == 0) {
acstring = g_strdup_printf("/%s", dir->d_name);
} else {
acstring = g_strdup_printf("%s/%s", directory, dir->d_name);
}
if (!acstring) {
g_array_free(files, TRUE);
free(foofile);
free(directory);
return NULL;
// check if it matches prefix
if (!find_all && !g_str_has_prefix(dir->d_name, foofile)) {
continue;
}
g_array_append_val(files, acstring);
// only show hidden files on explicit request
if (dir->d_name[0] == '.' && (find_all || foofile[0] != '.')) {
continue;
}
gchar* acstring = g_strdup_printf("%s%s", user_dir_part, dir->d_name);
if (acstring) {
g_array_append_val(files, acstring);
}
}
closedir(d);
}
free(directory);
free(foofile);
autocomplete_update(filepath_ac, (char**)files->data);
g_array_free(files, TRUE);
g_free(last_filepath_input);
last_filepath_input = g_strdup(inpcp);
return autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE, previous);
}
static char*
_spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
result = autocomplete_param_with_ac(input, "/spellcheck", spellcheck_ac, TRUE, previous);
return result;
}
static char*
_cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous)
{
@@ -1828,7 +1840,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
"/vercheck", "/privileges", "/wrap",
"/carbons", "/slashguard", "/mam", "/silence" };
for (int i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
for (size_t i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
result = autocomplete_param_with_func(input, boolean_choices[i], prefs_autocomplete_boolean_choice, previous, NULL);
if (result) {
return result;
@@ -1841,7 +1853,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
}
gchar* history_jid_subcmds[] = { "/history verify", "/history export", "/history import" };
for (int i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) {
for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) {
result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL);
if (result) {
return result;
@@ -1853,6 +1865,11 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
return result;
}
result = _spellcheck_autocomplete(window, input, previous);
if (result) {
return result;
}
// autocomplete nickname in chat rooms
if (window->type == WIN_MUC) {
ProfMucWin* mucwin = (ProfMucWin*)window;
@@ -1863,7 +1880,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
// Remove quote character before and after names when doing autocomplete
char* unquoted = strip_arg_quotes(input);
for (int i = 0; i < ARRAY_SIZE(nick_choices); i++) {
for (size_t i = 0; i < ARRAY_SIZE(nick_choices); i++) {
result = autocomplete_param_with_ac(unquoted, nick_choices[i], nick_ac, TRUE, previous);
if (result) {
free(unquoted);
@@ -1878,7 +1895,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
gchar* contact_choices[] = { "/msg", "/info" };
// Remove quote character before and after names when doing autocomplete
char* unquoted = strip_arg_quotes(input);
for (int i = 0; i < ARRAY_SIZE(contact_choices); i++) {
for (size_t i = 0; i < ARRAY_SIZE(contact_choices); i++) {
result = autocomplete_param_with_func(unquoted, contact_choices[i], roster_contact_autocomplete, previous, NULL);
if (result) {
free(unquoted);
@@ -1893,7 +1910,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
free(unquoted);
gchar* resource_choices[] = { "/caps", "/ping" };
for (int i = 0; i < ARRAY_SIZE(resource_choices); i++) {
for (size_t i = 0; i < ARRAY_SIZE(resource_choices); i++) {
result = autocomplete_param_with_func(input, resource_choices[i], roster_fulljid_autocomplete, previous, NULL);
if (result) {
return result;
@@ -1902,7 +1919,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
}
gchar* invite_choices[] = { "/join" };
for (int i = 0; i < ARRAY_SIZE(invite_choices); i++) {
for (size_t i = 0; i < ARRAY_SIZE(invite_choices); i++) {
result = autocomplete_param_with_func(input, invite_choices[i], muc_invites_find, previous, NULL);
if (result) {
return result;
@@ -1922,16 +1939,16 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
{ "/inputwin", winpos_ac },
};
for (int i = 0; i < ARRAY_SIZE(ac_cmds); i++) {
for (size_t i = 0; i < ARRAY_SIZE(ac_cmds); i++) {
result = autocomplete_param_with_ac(input, ac_cmds[i].cmd, ac_cmds[i].completer, TRUE, previous);
if (result) {
return result;
}
}
int len = strlen(input);
size_t len = strlen(input);
char parsed[len + 1];
int i = 0;
size_t i = 0;
while (i < len) {
if (input[i] == ' ') {
break;
@@ -1942,7 +1959,8 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
}
parsed[i] = '\0';
char* (*ac_func)(ProfWin*, const char* const, gboolean) = g_hash_table_lookup(ac_funcs, parsed);
char* (*ac_func)(ProfWin*, const char* const, gboolean) = (char* (*)(ProfWin*, const char* const, gboolean))g_hash_table_lookup(ac_funcs, parsed);
if (ac_func) {
result = ac_func(window, input, previous);
if (result) {
@@ -2011,7 +2029,7 @@ _who_autocomplete(ProfWin* window, const char* const input, gboolean previous)
"/who chat", "/who away", "/who xa", "/who dnd", "/who available",
"/who unavailable" };
for (int i = 0; i < ARRAY_SIZE(group_commands); i++) {
for (size_t i = 0; i < ARRAY_SIZE(group_commands); i++) {
result = autocomplete_param_with_func(input, group_commands[i], roster_group_autocomplete, previous, NULL);
if (result) {
return result;
@@ -2333,7 +2351,7 @@ _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous
gchar* boolean_choices1[] = { "/notify room current", "/notify chat current", "/notify typing current",
"/notify room text", "/notify chat text", "/notify room offline" };
for (int i = 0; i < ARRAY_SIZE(boolean_choices1); i++) {
for (size_t i = 0; i < ARRAY_SIZE(boolean_choices1); i++) {
result = autocomplete_param_with_func(input, boolean_choices1[i], prefs_autocomplete_boolean_choice, previous, NULL);
if (result) {
return result;
@@ -2366,7 +2384,7 @@ _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous
}
gchar* boolean_choices2[] = { "/notify invite", "/notify sub", "/notify mention", "/notify trigger" };
for (int i = 0; i < ARRAY_SIZE(boolean_choices2); i++) {
for (size_t i = 0; i < ARRAY_SIZE(boolean_choices2); i++) {
result = autocomplete_param_with_func(input, boolean_choices2[i], prefs_autocomplete_boolean_choice, previous, NULL);
if (result) {
return result;
@@ -3812,7 +3830,7 @@ _account_autocomplete(ProfWin* window, const char* const input, gboolean previou
"/account disable", "/account rename", "/account clear", "/account remove",
"/account default set" };
for (int i = 0; i < ARRAY_SIZE(account_choice); i++) {
for (size_t i = 0; i < ARRAY_SIZE(account_choice); i++) {
found = autocomplete_param_with_func(input, account_choice[i], accounts_find_all, previous, NULL);
if (found) {
return found;
@@ -4324,6 +4342,20 @@ _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previou
return autocomplete_param_with_ac(input, "/strophe", strophe_ac, FALSE, previous);
}
static char*
_stamp_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
result = autocomplete_param_with_ac(input, "/stamp unset", stamp_unset_ac, TRUE, previous);
if (result) {
return result;
}
result = autocomplete_param_with_ac(input, "/stamp", stamp_ac, TRUE, previous);
return result;
}
static char*
_adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
@@ -4348,7 +4380,7 @@ _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous)
gboolean is_num = TRUE;
if (num_args >= 2) {
for (int i = 0; i < strlen(args[1]); i++) {
for (size_t i = 0; i < strlen(args[1]); i++) {
if (!isdigit((int)args[1][i])) {
is_num = FALSE;
break;

View File

@@ -4,33 +4,7 @@
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
*
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#ifndef COMMAND_CMD_AC_H

View File

@@ -3,35 +3,9 @@
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
*
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#include "config.h"
@@ -105,6 +79,9 @@ static gboolean _cmd_has_tag(Command* pcmd, const char* const tag);
* Command list
*/
#define CMD_TLS_DIRECT "Use direct TLS for the connection. It means TLS handshake is started right after TCP connection is established."
#define CMD_TLS_LEGACY "Alternative keyword for 'direct', which was created when one still thought that 'STARTTLS' is the future."
// clang-format off
static const struct cmd_t command_defs[] = {
{ CMD_PREAMBLE("/help",
@@ -147,7 +124,7 @@ static const struct cmd_t command_defs[] = {
CMD_TAG_CONNECTION)
CMD_SYN(
"/connect [<account>]",
"/connect <account> [server <server>] [port <port>] [tls force|allow|trust|legacy|disable] [auth default|legacy]",
"/connect <account> [server <server>] [port <port>] [tls force|allow|trust|direct|disable|legacy] [auth default|legacy]",
"/connect <server>")
CMD_DESC(
"Login to a chat service. "
@@ -162,8 +139,9 @@ static const struct cmd_t command_defs[] = {
{ "tls force", "Force TLS connection, and fail if one cannot be established, this is default behaviour." },
{ "tls allow", "Use TLS for the connection if it is available." },
{ "tls trust", "Force TLS connection and trust server's certificate." },
{ "tls legacy", "Use legacy TLS for the connection. It means server doesn't support STARTTLS and TLS is forced just after TCP connection is established." },
{ "tls direct", CMD_TLS_DIRECT },
{ "tls disable", "Disable TLS for the connection." },
{ "tls legacy", CMD_TLS_LEGACY },
{ "auth default", "Default authentication process." },
{ "auth legacy", "Allow legacy authentication." })
CMD_EXAMPLES(
@@ -404,15 +382,17 @@ static const struct cmd_t command_defs[] = {
CMD_DESC(
"Manage blocked users (XEP-0191), calling with no arguments shows the current list of blocked users. "
"To blog a certain user in a MUC use the following as jid: room@conference.example.org/spammy-user"
"It is also possible to block and report (XEP-0377) a user with the report-abuse and report-spam commands.")
"It is also possible to block and report (XEP-0377) a user with the report-abuse and report-spam commands. "
"For spam reporting, please include the content of the spam message as evidence for the service operator.")
CMD_ARGS(
{ "add [<jid>]", "Block the specified Jabber ID. If in a chat window and no jid is specified, the current recipient will be blocked." },
{ "remove <jid>", "Remove the specified Jabber ID from the blocked list." },
{ "report-abuse <jid> [<message>]", "Report the jid as abuse with an optional message to the service operator." },
{ "report-spam <jid> [<message>]", "Report the jid as spam with an optional message to the service operator." })
{ "report-abuse <jid> [<message>]", "Report a user for abuse with an optional description of the behavior." },
{ "report-spam <jid> [<message>]", "Report a user for spam, including the actual spam message as evidence." })
CMD_EXAMPLES(
"/blocked add hel@helheim.edda",
"/blocked report-spam hel@helheim.edda Very annoying guy",
"/blocked report-abuse hel@helheim.edda Harassing me in MUC",
"/blocked report-spam spambot@example.com \"You won a free prize!\"",
"/blocked add profanity@rooms.dismail.de/spammy-user")
},
@@ -1473,6 +1453,23 @@ static const struct cmd_t command_defs[] = {
{ "on|off", "Enable or disable splash logo." })
},
{ CMD_PREAMBLE("/spellcheck",
parse_args, 1, 2, &cons_spellcheck_setting)
CMD_MAINFUNC(cmd_spellcheck)
CMD_TAGS(
CMD_TAG_UI)
CMD_SYN(
"/spellcheck on|off",
"/spellcheck list",
"/spellcheck lang <locale>")
CMD_DESC(
"Enable or disable spellchecking, or set the language.")
CMD_ARGS(
{ "on|off", "Enable or disable spellchecking." },
{ "list", "List available dictionaries recognized by Enchant." },
{ "lang <locale>", "Set the spellcheck language (en_US)." })
},
{ CMD_PREAMBLE("/autoconnect",
parse_args, 1, 2, &cons_autoconnect_setting)
CMD_MAINFUNC(cmd_autoconnect)
@@ -2079,7 +2076,7 @@ static const struct cmd_t command_defs[] = {
"/account set <account> pgpkeyid <pgpkeyid>",
"/account set <account> startscript <script>",
"/account set <account> clientid \"<name> <version>\"",
"/account set <account> tls force|allow|trust|legacy|disable",
"/account set <account> tls force|allow|trust|direct|disable|legacy",
"/account set <account> auth default|legacy",
"/account set <account> theme <theme>",
"/account set <account> session_alarm <max_sessions>",
@@ -2125,8 +2122,9 @@ static const struct cmd_t command_defs[] = {
{ "set <account> tls force", "Force TLS connection, and fail if one cannot be established, this is default behaviour." },
{ "set <account> tls allow", "Use TLS for the connection if it is available." },
{ "set <account> tls trust", "Force TLS connection and trust server's certificate." },
{ "set <account> tls legacy", "Use legacy TLS for the connection. It means server doesn't support STARTTLS and TLS is forced just after TCP connection is established." },
{ "set <account> tls direct", CMD_TLS_DIRECT },
{ "set <account> tls disable", "Disable TLS for the connection." },
{ "set <account> tls legacy", CMD_TLS_LEGACY },
{ "set <account> auth default", "Use default authentication process." },
{ "set <account> auth legacy", "Allow legacy authentication." },
{ "set <account> theme <theme>", "Set the UI theme for the account." },
@@ -2153,6 +2151,7 @@ static const struct cmd_t command_defs[] = {
"/account set me status dnd",
"/account set me dnd -1",
"/account set me clientid \"Profanity 0.42 (Dev)\"",
"/account set me eval_password \"pass \\\"Test Accounts/my user\\\"\"",
"/account rename me chattyme",
"/account clear me pgpkeyid")
},
@@ -2336,13 +2335,18 @@ static const struct cmd_t command_defs[] = {
"/omemo clear_device_list",
"/omemo qrcode")
CMD_DESC(
"OMEMO commands to manage keys, and perform encryption during chat sessions.")
"OMEMO commands to manage keys, and perform encryption during chat sessions.\n"
"The title bar will show the OMEMO session status:\n"
"[OMEMO][trusted] - All active devices for the contact are trusted.\n"
"[OMEMO][untrusted] - One or more active devices for the contact are untrusted.\n")
CMD_ARGS(
{ "gen", "Generate OMEMO cryptographic materials for current account." },
{ "start [<contact>]", "Start an OMEMO session with contact, or current recipient if omitted." },
{ "end", "End the current OMEMO session." },
{ "log on|off", "Enable or disable plaintext logging of OMEMO encrypted messages." },
{ "log redact", "Log OMEMO encrypted messages, but replace the contents with [redacted]." },
{ "trust [<contact>] <fp>", "Trust a fingerprint for a contact, or current recipient if omitted. If all active devices are trusted, the title bar will show [trusted]. Otherwise, it will show [untrusted]." },
{ "untrust [<contact>] <fp>","Untrust a fingerprint for a contact, or current recipient if omitted." },
{ "fingerprint [<contact>]", "Show contact's fingerprints, or current recipient's if omitted." },
{ "char <char>", "Set the character to be displayed next to OMEMO encrypted messages." },
{ "trustmode manual", "Set the global OMEMO trust mode to manual, OMEMO keys has to be trusted manually." },
@@ -2361,6 +2365,15 @@ static const struct cmd_t command_defs[] = {
"/omemo char *")
},
{ CMD_PREAMBLE("/changes",
parse_args, 0, 0, NULL)
CMD_MAINFUNC(cmd_changes)
CMD_SYN(
"/changes")
CMD_DESC(
"Show changes from saved configuration file.")
},
{ CMD_PREAMBLE("/save",
parse_args, 0, 0, NULL)
CMD_MAINFUNC(cmd_save)
@@ -2419,7 +2432,7 @@ static const struct cmd_t command_defs[] = {
"/stamp unset outgoing|incoming")
CMD_DESC("Set chat window stamp. "
"The format of line in the chat window is: \"<timestamp> <encryption sign> <stamp> <message>\" "
"where <stamp> is \"me:\" for incoming messages or \"username@server/resource\" for outgoing messages. "
"where <stamp> is \"me:\" for outgoing messages or \"username@server/resource\" for incoming messages. "
"This command allows to change <stamp> value.")
CMD_ARGS({ "outgoing", "Set outgoing stamp" },
{ "incoming", "Set incoming stamp"},
@@ -2659,7 +2672,7 @@ static const struct cmd_t command_defs[] = {
CMD_TAGS(
CMD_TAG_CONNECTION)
CMD_SYN(
"/register <username> <server> [port <port>] [tls force|allow|trust|legacy|disable]")
"/register <username> <server> [port <port>] [tls force|allow|trust|direct|disable|legacy]")
CMD_DESC(
"Register an account on a server.")
CMD_ARGS(
@@ -2669,8 +2682,9 @@ static const struct cmd_t command_defs[] = {
{ "tls force", "Force TLS connection, and fail if one cannot be established. This is the default behavior." },
{ "tls allow", "Use TLS for the connection if it is available." },
{ "tls trust", "Force TLS connection and trust the server's certificate." },
{ "tls legacy", "Use legacy TLS for the connection. This forces TLS just after the TCP connection is established. Use when a server doesn't support STARTTLS." },
{ "tls disable", "Disable TLS for the connection." })
{ "tls direct", CMD_TLS_DIRECT },
{ "tls disable", "Disable TLS for the connection." },
{ "tls legacy", CMD_TLS_LEGACY })
CMD_EXAMPLES(
"/register odin valhalla.edda ",
"/register freyr vanaheimr.edda port 5678",
@@ -2878,7 +2892,7 @@ _cmd_index(const Command* cmd)
g_string_free(index_source, TRUE);
GString* index = g_string_new("");
for (int i = 0; i < g_strv_length(tokens); i++) {
for (guint i = 0; i < g_strv_length(tokens); i++) {
index = g_string_append(index, tokens[i]);
index = g_string_append(index, " ");
}
@@ -3055,16 +3069,11 @@ command_docgen(void)
cmds = g_list_insert_sorted(cmds, (gpointer)pcmd, (GCompareFunc)_cmp_command);
}
FILE* toc_fragment = fopen("toc_fragment.html", "w");
if (!toc_fragment) {
log_error("command_docgen(): unable to open toc_fragment.html for writing: %s", g_strerror(errno));
g_list_free(cmds);
return;
}
FILE* main_fragment = fopen("main_fragment.html", "w");
if (!main_fragment) {
log_error("command_docgen(): unable to open main_fragment.html for writing: %s", g_strerror(errno));
fclose(toc_fragment);
auto_FILE FILE* toc_fragment = fopen("toc_fragment.html", "w");
auto_FILE FILE* main_fragment = fopen("main_fragment.html", "w");
if (!toc_fragment || !main_fragment) {
log_error("command_docgen(): unable to open html files: %s", g_strerror(errno));
g_list_free(cmds);
return;
}
@@ -3135,8 +3144,6 @@ command_docgen(void)
fputs("</ul></ul>\n", toc_fragment);
fclose(toc_fragment);
fclose(main_fragment);
printf("\nProcessed %u commands.\n\n", g_list_length(cmds));
g_list_free(cmds);
}

View File

@@ -3,35 +3,9 @@
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
*
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#ifndef COMMAND_CMD_DEFS_H

File diff suppressed because it is too large Load Diff

View File

@@ -3,35 +3,9 @@
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
*
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#ifndef COMMAND_CMD_FUNCS_H
@@ -129,6 +103,7 @@ gboolean cmd_bookmark_ignore(ProfWin* window, const char* const command, gchar**
gboolean cmd_roster(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_software(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_splash(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_spellcheck(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_states(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_status_get(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_status_set(ProfWin* window, const char* const command, gchar** args);
@@ -242,6 +217,7 @@ gboolean cmd_omemo_policy(ProfWin* window, const char* const command, gchar** ar
gboolean cmd_omemo_clear_device_list(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_omemo_qrcode(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_changes(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_save(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_reload(ProfWin* window, const char* const command, gchar** args);