Fri, 14 Mar 2025 15:25:49 -0500
Fix the parameter order of an error in Ibis.Client.parse_mode_string
Testing Done:
Called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/3906/
/* * Ibis - IRCv3 Library * Copyright (C) 2022-2024 Ibis Developers <devel@pidgin.im> * * Ibis is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This library 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 2 of the License, or * (at your option) any later version. * * This library 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 this library; if not, see <https://www.gnu.org/licenses/>. */ #include "ibisfeatures.h" #include "ibisconstants.h" #include "ibisstring.h" enum { SIG_CHANGED, N_SIGNALS, }; static guint signals[N_SIGNALS] = {0, }; struct _IbisFeatures { GObject parent; GHashTable *features; }; /****************************************************************************** * Helpers *****************************************************************************/ static void ibis_features_free_gvalue(gpointer data) { GValue* value = data; g_value_unset(value); g_free(value); } static void ibis_features_set_uint(IbisFeatures *features, const char *feature, guint uvalue) { GQuark quark = g_quark_from_string(feature); GValue *existing_value = NULL; GValue *value = NULL; existing_value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(existing_value != NULL && g_value_get_uint(existing_value) == uvalue) { return; } value = g_new0(GValue, 1); g_value_init(value, G_TYPE_UINT); g_value_set_uint(value, uvalue); g_hash_table_insert(features->features, GINT_TO_POINTER(quark), value); g_signal_emit(features, signals[SIG_CHANGED], quark, feature); } static void ibis_features_set_string(IbisFeatures *features, const char *feature, const char *svalue) { GQuark quark = g_quark_from_string(feature); GValue *existing_value = NULL; GValue *value = NULL; existing_value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(existing_value != NULL && ibis_str_equal(g_value_get_string(existing_value), svalue)) { return; } value = g_new0(GValue, 1); g_value_init(value, G_TYPE_STRING); g_value_set_string(value, svalue); g_hash_table_insert(features->features, GINT_TO_POINTER(quark), value); g_signal_emit(features, signals[SIG_CHANGED], quark, feature); } static void ibis_features_set_char(IbisFeatures *features, const char *feature, char cvalue) { GQuark quark = g_quark_from_string(feature); GValue *existing_value = NULL; GValue *value = NULL; existing_value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(existing_value != NULL && g_value_get_schar(existing_value) == cvalue) { return; } value = g_new0(GValue, 1); g_value_init(value, G_TYPE_CHAR); g_value_set_schar(value, cvalue); g_hash_table_insert(features->features, GINT_TO_POINTER(quark), value); g_signal_emit(features, signals[SIG_CHANGED], quark, feature); } static void ibis_features_set_boolean(IbisFeatures *features, const char *feature, gboolean bvalue) { GQuark quark = g_quark_from_string(feature); GValue *existing_value = NULL; GValue *value = NULL; existing_value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(existing_value != NULL && g_value_get_boolean(existing_value) == bvalue) { return; } value = g_new0(GValue, 1); g_value_init(value, G_TYPE_BOOLEAN); g_value_set_boolean(value, bvalue); g_hash_table_insert(features->features, GINT_TO_POINTER(quark), value); g_signal_emit(features, signals[SIG_CHANGED], quark, feature); } /** * ibis_features_parse_prefix: (skip) * @features: The instance. * @value: The value of the PREFIX feature. * * Parses the value of the PREFIX feature and adds the synthetic MODES and * PREFIXES features. */ static void ibis_features_parse_prefix(IbisFeatures *features, const char *prefix) { GRegex *regex = NULL; GMatchInfo *info = NULL; gboolean matches = FALSE; regex = g_regex_new("^\\((?P<modes>.+)\\)(?P<prefixes>.+)$", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, NULL); matches = g_regex_match(regex, prefix, G_REGEX_MATCH_DEFAULT, &info); if(matches) { char *modes = NULL; char *prefixes = NULL; modes = g_match_info_fetch_named(info, "modes"); prefixes = g_match_info_fetch_named(info, "prefixes"); if(strlen(modes) == strlen(prefixes)) { ibis_features_set_string(features, IBIS_FEATURE_PREFIX_MODES, modes); ibis_features_set_string(features, IBIS_FEATURE_PREFIX_PREFIXES, prefixes); } else { g_warning("format of prefix '%s' is invalid", prefix); } g_free(modes); g_free(prefixes); } g_clear_pointer(&info, g_match_info_unref); g_clear_pointer(®ex, g_regex_unref); } static void ibis_features_set_feature(IbisFeatures *features, const char *feature, const char *value) { g_return_if_fail(IBIS_IS_FEATURES(features)); if(ibis_str_equal(feature, IBIS_FEATURE_AWAYLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_AWAYLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_CASEMAPPING)) { ibis_features_set_string(features, IBIS_FEATURE_CASEMAPPING, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_CHANLIMIT)) { ibis_features_set_string(features, IBIS_FEATURE_CHANLIMIT, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_CHANMODES)) { ibis_features_set_string(features, IBIS_FEATURE_CHANMODES, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_CHANNELLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_CHANNELLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_CHANTYPES)) { ibis_features_set_string(features, IBIS_FEATURE_CHANTYPES, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_CLIENTTAGDENY)) { ibis_features_set_string(features, IBIS_FEATURE_CLIENTTAGDENY, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_ELIST)) { ibis_features_set_string(features, IBIS_FEATURE_ELIST, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_EXCEPTS)) { if(strlen(value) == 1) { ibis_features_set_char(features, IBIS_FEATURE_EXCEPTS, value[0]); } else { ibis_features_set_char(features, IBIS_FEATURE_EXCEPTS, 'e'); } } else if(ibis_str_equal(feature, IBIS_FEATURE_EXTBAN)) { ibis_features_set_string(features, IBIS_FEATURE_EXTBAN, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_HOSTLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_HOSTLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_INVEX)) { if(strlen(value) == 1) { ibis_features_set_char(features, IBIS_FEATURE_INVEX, value[0]); } else { ibis_features_set_char(features, IBIS_FEATURE_INVEX, 'I'); } } else if(ibis_str_equal(feature, IBIS_FEATURE_KICKLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_KICKLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_MAXLIST)) { ibis_features_set_string(features, IBIS_FEATURE_MAXLIST, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_MAXTARGETS)) { ibis_features_set_uint(features, IBIS_FEATURE_MAXTARGETS, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_MODES)) { guint uvalue; if(strlen(value) > 0) { uvalue = ibis_str_to_uint(value); } else { uvalue = G_MAXUINT32; } ibis_features_set_uint(features, IBIS_FEATURE_MODES, uvalue); } else if(ibis_str_equal(feature, IBIS_FEATURE_NETWORK)) { ibis_features_set_string(features, IBIS_FEATURE_NETWORK, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_NICKLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_NICKLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_PREFIX)) { /* Parse the prefix first in case someone connects to the changed * signal for the prefix but really wants the modes or prefixes. */ ibis_features_parse_prefix(features, value); ibis_features_set_string(features, IBIS_FEATURE_PREFIX, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_SAFELIST)) { ibis_features_set_boolean(features, IBIS_FEATURE_SAFELIST, TRUE); } else if(ibis_str_equal(feature, IBIS_FEATURE_SILENCE)) { ibis_features_set_uint(features, IBIS_FEATURE_SILENCE, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_STATUSMSG)) { ibis_features_set_string(features, IBIS_FEATURE_STATUSMSG, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_TARGMAX)) { ibis_features_set_string(features, IBIS_FEATURE_TARGMAX, value); } else if(ibis_str_equal(feature, IBIS_FEATURE_TOPICLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_TOPICLEN, ibis_str_to_uint(value)); } else if(ibis_str_equal(feature, IBIS_FEATURE_USERLEN)) { ibis_features_set_uint(features, IBIS_FEATURE_USERLEN, ibis_str_to_uint(value)); } else { GRegex *regex = NULL; /* We're assuming we only get positive integers when receiving numeric * values. */ regex = g_regex_new("^[0-9]+$", 0, 0, NULL); if(g_regex_match(regex, value, 0, NULL)) { ibis_features_set_uint(features, feature, ibis_str_to_uint(value)); } else if(!ibis_str_is_empty(value)) { ibis_features_set_string(features, feature, value); } else { ibis_features_set_boolean(features, feature, TRUE); } g_regex_unref(regex); } } /****************************************************************************** * GObject Implementation *****************************************************************************/ G_DEFINE_FINAL_TYPE(IbisFeatures, ibis_features, G_TYPE_OBJECT) static void ibis_features_finalize(GObject *obj) { IbisFeatures *features = IBIS_FEATURES(obj); g_hash_table_destroy(features->features); G_OBJECT_CLASS(ibis_features_parent_class)->finalize(obj); } static void ibis_features_init(IbisFeatures *features) { features->features = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, ibis_features_free_gvalue); ibis_features_clear(features); } static void ibis_features_class_init(IbisFeaturesClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = ibis_features_finalize; /** * IbisFeatures::changed: * @features: the instance * @feature: the name of the feature that changed * * Emitted when a feature changes. * * This signal supports *details* so you can connect to it with something * like `changed::chantypes` so your callback is only called if the * `chantypes` feature changed. * * Note: This will also be called when a feature is removed. * * Since: 0.4 */ signals[SIG_CHANGED] = g_signal_new_class_handler( "changed", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } /****************************************************************************** * Public API *****************************************************************************/ void ibis_features_clear(IbisFeatures *features) { GHashTableIter iter; gpointer key = NULL; const char *default_prefix = "(ov)@+"; g_return_if_fail(IBIS_IS_FEATURES(features)); g_hash_table_iter_init(&iter, features->features); while(g_hash_table_iter_next(&iter, &key, NULL)) { GQuark detail = GPOINTER_TO_INT(key); g_hash_table_iter_remove(&iter); g_signal_emit(features, signals[SIG_CHANGED], detail, g_quark_to_string(detail)); } /* Set the default values specified at * https://modern.ircdocs.horse/#rplisupport-parameters */ ibis_features_set_string(features, IBIS_FEATURE_CHANTYPES, "#"); ibis_features_set_string(features, IBIS_FEATURE_PREFIX, default_prefix); ibis_features_parse_prefix(features, default_prefix); } gboolean ibis_features_client_tag_denied(IbisFeatures *features, const char *tag) { const char *clienttagdenied = NULL; gboolean denied = FALSE; GStrv denied_tags = NULL; guint n_denied_tags = 0; const char *queried_tag = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), FALSE); g_return_val_if_fail(!ibis_str_is_empty(tag), FALSE); clienttagdenied = ibis_features_get_string(features, IBIS_FEATURE_CLIENTTAGDENY); /* An empty or missing CLIENTTAGDENY matches the default case and indicates * that all client-only tags are allowed. */ if(ibis_str_is_empty(clienttagdenied)) { return FALSE; } /* An asterisk * (0x2A) indicates that all client-only tags are blocked. */ if(ibis_str_equal(clienttagdenied, "*")) { return TRUE; } /* The CLIENTTAGDENY token value is a comma , (0x2C) separated list of blocked * client-only tags. The client-only prefix (+) is omitted when a tag appears * in this list. */ if(tag[0] == '+') { queried_tag = tag + 1; } else { queried_tag = tag; } denied_tags = g_strsplit(clienttagdenied, ",", 0); n_denied_tags = g_strv_length(denied_tags); if(n_denied_tags == 1) { denied = ibis_str_equal(denied_tags[0], queried_tag); g_strfreev(denied_tags); return denied; } if(ibis_str_equal(denied_tags[0], "*")) { /* If the first denied tag is '*', subsequent tags are expected to start with * a hyphen to exempt particular tags from the catch-all. */ denied = TRUE; for(guint i = 1; i < n_denied_tags; i++) { const char *allowed_tag = NULL; if(denied_tags[i][0] != '-') { continue; } allowed_tag = denied_tags[i] + 1; if(ibis_str_equal(allowed_tag, queried_tag)) { denied = FALSE; break; } } } else { denied = FALSE; for(guint i = 0; i < n_denied_tags; i++) { if(ibis_str_equal(denied_tags[i], queried_tag)) { denied = TRUE; break; } } } g_strfreev(denied_tags); return denied; } IbisFeatures * ibis_features_new(void) { return g_object_new(IBIS_TYPE_FEATURES, NULL); } gboolean ibis_features_parse(IbisFeatures *features, IbisMessage *message) { GStrv params = NULL; guint param_count = 0; guint i; GRegex *regex = NULL; GMatchInfo *info = NULL; gboolean matches = FALSE; const char *command = NULL; command = ibis_message_get_command(message); if(g_strcmp0(command, IBIS_RPL_ISUPPORT) != 0) { return FALSE; } params = ibis_message_get_params(message); param_count = g_strv_length(params); regex = g_regex_new("(?<key>[A-Z0-9-]+)(?:=(?<value>\\S+))?", 0, 0, NULL); for(i = 0; i < param_count; ++i) { matches = g_regex_match_full(regex, params[i], -1, 0, 0, &info, NULL); if (matches && g_match_info_matches(info)) { char *key = NULL; char *value = NULL; key = g_match_info_fetch_named(info, "key"); value = g_match_info_fetch_named(info, "value"); ibis_features_set_feature(features, key, value); g_free(key); g_free(value); } g_match_info_unref(info); } g_regex_unref(regex); return TRUE; } guint ibis_features_get_awaylen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_AWAYLEN); } const char * ibis_features_get_casemapping(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_CASEMAPPING);; } const char * ibis_features_get_chanlimit(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_CHANLIMIT); } const char * ibis_features_get_chanmodes(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_CHANMODES); } const char * ibis_features_get_chantypes(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_CHANTYPES); } guint ibis_features_get_channellen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_CHANNELLEN); } const char * ibis_features_get_elist(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_ELIST); } char ibis_features_get_excepts(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_char(features, IBIS_FEATURE_EXCEPTS); } const char * ibis_features_get_extban(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_EXTBAN); } guint ibis_features_get_hostlen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_HOSTLEN); } char ibis_features_get_invex(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_char(features, IBIS_FEATURE_INVEX); } guint ibis_features_get_kicklen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_KICKLEN); } const char * ibis_features_get_maxlist(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_MAXLIST); } guint ibis_features_get_maxtargets(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_MAXTARGETS); } guint ibis_features_get_modes(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_MODES); } const char * ibis_features_get_network(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_NETWORK); } guint ibis_features_get_nicklen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_NICKLEN); } const char * ibis_features_get_prefix(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_PREFIX); } char ibis_features_get_prefix_for_mode(IbisFeatures *features, char mode) { const char *modes = NULL; const char *prefixes = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), '\0'); modes = ibis_features_get_string(features, IBIS_FEATURE_PREFIX_MODES); if(modes == NULL) { return '\0'; } prefixes = ibis_features_get_string(features, IBIS_FEATURE_PREFIX_PREFIXES); if(prefixes == NULL) { return '\0'; } if(strlen(modes) != strlen(prefixes)) { return '\0'; } for(int i = 0; modes[i] != '\0'; i++) { if(modes[i] == mode) { return prefixes[i]; } } return '\0'; } const char * ibis_features_get_prefix_modes(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_PREFIX_MODES); } const char * ibis_features_get_prefix_prefixes(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_PREFIX_PREFIXES); } gboolean ibis_features_get_safelist(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), FALSE); return ibis_features_get_boolean(features, IBIS_FEATURE_SAFELIST); } guint ibis_features_get_silence(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_SILENCE); } const char * ibis_features_get_statusmsg(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_STATUSMSG); } const char * ibis_features_get_targmax(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); return ibis_features_get_string(features, IBIS_FEATURE_TARGMAX); } guint ibis_features_get_topiclen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_TOPICLEN); } guint ibis_features_get_userlen(IbisFeatures *features) { g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); return ibis_features_get_uint(features, IBIS_FEATURE_USERLEN); } guint ibis_features_get_uint(IbisFeatures *features, const char *parameter) { GQuark quark = 0; GValue *value = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); quark = g_quark_from_string(parameter); value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(value == NULL || !G_VALUE_HOLDS_UINT(value)) { return 0; } return g_value_get_uint(value); } const char * ibis_features_get_string(IbisFeatures *features, const char *parameter) { GQuark quark = 0; GValue *value = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), NULL); quark = g_quark_from_string(parameter); value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(value == NULL || !G_VALUE_HOLDS_STRING(value)) { return NULL; } return g_value_get_string(value); } gboolean ibis_features_get_boolean(IbisFeatures *features, const char *parameter) { GQuark quark = 0; GValue *value = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), FALSE); quark = g_quark_from_string(parameter); value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(value == NULL || !G_VALUE_HOLDS_BOOLEAN(value)) { return FALSE; } return g_value_get_boolean(value); } char ibis_features_get_char(IbisFeatures *features, const char *parameter) { GQuark quark = 0; GValue *value = NULL; g_return_val_if_fail(IBIS_IS_FEATURES(features), 0); quark = g_quark_from_string(parameter); value = g_hash_table_lookup(features->features, GINT_TO_POINTER(quark)); if(value == NULL || !G_VALUE_HOLDS_CHAR(value)) { return 0; } return g_value_get_schar(value); }