mirror of
https://github.com/ailin-nemui/irssi.git
synced 2025-04-25 20:41:08 -05:00
Most of these have been deprecated since forever (2.2), but they didn't raise warnings. Now they do, and the warnings are not the most verbose warnings you could ask for, but, they point in the right direction. This doesn't handle the GTimeVal deprecation warnings. Those seem trickier since they cover API, will look into those right after this.
1009 lines
24 KiB
C
1009 lines
24 KiB
C
/*
|
|
commands.c : irssi
|
|
|
|
Copyright (C) 1999-2000 Timo Sirainen
|
|
|
|
This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "module.h"
|
|
#include <irssi/src/core/signals.h>
|
|
#include <irssi/src/core/commands.h>
|
|
#include <irssi/src/core/misc.h>
|
|
#include <irssi/src/core/special-vars.h>
|
|
#include <irssi/src/core/window-item-def.h>
|
|
|
|
#include <irssi/src/core/servers.h>
|
|
#include <irssi/src/core/channels.h>
|
|
|
|
#include <irssi/src/lib-config/iconfig.h>
|
|
#include <irssi/src/core/settings.h>
|
|
|
|
GSList *commands;
|
|
char *current_command;
|
|
|
|
static int signal_default_command;
|
|
|
|
static GSList *alias_runstack;
|
|
|
|
COMMAND_REC *command_find(const char *cmd)
|
|
{
|
|
GSList *tmp;
|
|
|
|
g_return_val_if_fail(cmd != NULL, NULL);
|
|
|
|
for (tmp = commands; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_REC *rec = tmp->data;
|
|
|
|
if (g_ascii_strcasecmp(rec->cmd, cmd) == 0)
|
|
return rec;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
|
|
const char *module)
|
|
{
|
|
GSList *tmp;
|
|
|
|
g_return_val_if_fail(rec != NULL, NULL);
|
|
g_return_val_if_fail(module != NULL, NULL);
|
|
|
|
for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_MODULE_REC *rec = tmp->data;
|
|
|
|
if (g_ascii_strcasecmp(rec->name, module) == 0)
|
|
return rec;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static COMMAND_MODULE_REC *
|
|
command_module_find_and_remove(COMMAND_REC *rec, SIGNAL_FUNC func)
|
|
{
|
|
GSList *tmp, *tmp2;
|
|
|
|
g_return_val_if_fail(rec != NULL, NULL);
|
|
g_return_val_if_fail(func != NULL, NULL);
|
|
|
|
for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_MODULE_REC *rec = tmp->data;
|
|
|
|
for (tmp2 = rec->callbacks; tmp2 != NULL; tmp2 = tmp2->next) {
|
|
COMMAND_CALLBACK_REC *cb = tmp2->data;
|
|
|
|
if (cb->func == func) {
|
|
rec->callbacks =
|
|
g_slist_remove(rec->callbacks, cb);
|
|
g_free(cb);
|
|
return rec;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int command_have_sub(const char *command)
|
|
{
|
|
GSList *tmp;
|
|
int len;
|
|
|
|
g_return_val_if_fail(command != NULL, FALSE);
|
|
|
|
/* find "command "s */
|
|
len = strlen(command);
|
|
for (tmp = commands; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_REC *rec = tmp->data;
|
|
|
|
if (g_ascii_strncasecmp(rec->cmd, command, len) == 0 &&
|
|
rec->cmd[len] == ' ')
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static COMMAND_MODULE_REC *
|
|
command_module_get(COMMAND_REC *rec, const char *module, int protocol)
|
|
{
|
|
COMMAND_MODULE_REC *modrec;
|
|
|
|
g_return_val_if_fail(rec != NULL, NULL);
|
|
|
|
modrec = command_module_find(rec, module);
|
|
if (modrec == NULL) {
|
|
modrec = g_new0(COMMAND_MODULE_REC, 1);
|
|
modrec->name = g_strdup(module);
|
|
modrec->protocol = -1;
|
|
rec->modules = g_slist_append(rec->modules, modrec);
|
|
}
|
|
|
|
if (protocol != -1)
|
|
modrec->protocol = protocol;
|
|
|
|
return modrec;
|
|
}
|
|
|
|
void command_bind_full(const char *module, int priority, const char *cmd,
|
|
int protocol, const char *category, SIGNAL_FUNC func,
|
|
void *user_data)
|
|
{
|
|
COMMAND_REC *rec;
|
|
COMMAND_MODULE_REC *modrec;
|
|
COMMAND_CALLBACK_REC *cb;
|
|
char *str;
|
|
|
|
g_return_if_fail(module != NULL);
|
|
g_return_if_fail(cmd != NULL);
|
|
|
|
rec = command_find(cmd);
|
|
if (rec == NULL) {
|
|
rec = g_new0(COMMAND_REC, 1);
|
|
rec->cmd = g_strdup(cmd);
|
|
rec->category = category == NULL ? NULL : g_strdup(category);
|
|
commands = g_slist_append(commands, rec);
|
|
}
|
|
modrec = command_module_get(rec, module, protocol);
|
|
|
|
cb = g_new0(COMMAND_CALLBACK_REC, 1);
|
|
cb->func = func;
|
|
cb->user_data = user_data;
|
|
modrec->callbacks = g_slist_append(modrec->callbacks, cb);
|
|
|
|
if (func != NULL) {
|
|
str = g_strconcat("command ", cmd, NULL);
|
|
signal_add_full(module, priority, str, func, user_data);
|
|
g_free(str);
|
|
}
|
|
|
|
signal_emit("commandlist new", 1, rec);
|
|
}
|
|
|
|
static void command_free(COMMAND_REC *rec)
|
|
{
|
|
commands = g_slist_remove(commands, rec);
|
|
signal_emit("commandlist remove", 1, rec);
|
|
|
|
g_free_not_null(rec->category);
|
|
g_strfreev(rec->options);
|
|
g_free(rec->cmd);
|
|
g_free(rec);
|
|
}
|
|
|
|
static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
|
|
{
|
|
rec->modules = g_slist_remove(rec->modules, modrec);
|
|
|
|
g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
|
|
g_slist_free(modrec->callbacks);
|
|
g_free(modrec->name);
|
|
g_free_not_null(modrec->options);
|
|
g_free(modrec);
|
|
}
|
|
|
|
static void command_module_destroy(COMMAND_REC *rec,
|
|
COMMAND_MODULE_REC *modrec)
|
|
{
|
|
GSList *tmp, *freelist;
|
|
|
|
command_module_free(modrec, rec);
|
|
|
|
/* command_set_options() might have added module declaration of it's
|
|
own without any signals .. check if they're the only ones left
|
|
and if so, destroy them. */
|
|
freelist = NULL;
|
|
for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_MODULE_REC *rec = tmp->data;
|
|
|
|
if (rec->callbacks == NULL)
|
|
freelist = g_slist_append(freelist, rec);
|
|
else {
|
|
g_slist_free(freelist);
|
|
freelist = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_slist_foreach(freelist, (GFunc) command_module_free, rec);
|
|
g_slist_free(freelist);
|
|
|
|
if (rec->modules == NULL)
|
|
command_free(rec);
|
|
}
|
|
|
|
void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
|
|
{
|
|
COMMAND_REC *rec;
|
|
COMMAND_MODULE_REC *modrec;
|
|
char *str;
|
|
|
|
g_return_if_fail(cmd != NULL);
|
|
g_return_if_fail(func != NULL);
|
|
|
|
rec = command_find(cmd);
|
|
if (rec != NULL) {
|
|
modrec = command_module_find_and_remove(rec, func);
|
|
g_return_if_fail(modrec != NULL);
|
|
|
|
if (modrec->callbacks == NULL)
|
|
command_module_destroy(rec, modrec);
|
|
}
|
|
|
|
str = g_strconcat("command ", cmd, NULL);
|
|
signal_remove_data(str, func, user_data);
|
|
g_free(str);
|
|
}
|
|
|
|
/* Expand `cmd' - returns `cmd' if not found, NULL if more than one
|
|
match is found */
|
|
static const char *command_expand(char *cmd)
|
|
{
|
|
GSList *tmp;
|
|
const char *match;
|
|
int len, multiple;
|
|
|
|
g_return_val_if_fail(cmd != NULL, NULL);
|
|
|
|
multiple = FALSE;
|
|
match = NULL;
|
|
len = strlen(cmd);
|
|
for (tmp = commands; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_REC *rec = tmp->data;
|
|
|
|
if (g_ascii_strncasecmp(rec->cmd, cmd, len) == 0 &&
|
|
strchr(rec->cmd+len, ' ') == NULL) {
|
|
if (rec->cmd[len] == '\0') {
|
|
/* full match */
|
|
return rec->cmd;
|
|
}
|
|
|
|
if (match != NULL) {
|
|
/* multiple matches, we still need to check
|
|
if there's some command left that is a
|
|
full match.. */
|
|
multiple = TRUE;
|
|
}
|
|
|
|
/* check that this is the only match */
|
|
match = rec->cmd;
|
|
}
|
|
}
|
|
|
|
if (multiple) {
|
|
signal_emit("error command", 2,
|
|
GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
|
|
return NULL;
|
|
}
|
|
|
|
return match != NULL ? match : cmd;
|
|
}
|
|
|
|
void command_runsub(const char *cmd, const char *data,
|
|
void *server, void *item)
|
|
{
|
|
const char *newcmd;
|
|
char *orig, *subcmd, *defcmd, *args;
|
|
|
|
g_return_if_fail(data != NULL);
|
|
|
|
while (*data == ' ') data++;
|
|
|
|
if (*data == '\0') {
|
|
/* no subcommand given - list the subcommands */
|
|
signal_emit("list subcommands", 1, cmd);
|
|
return;
|
|
}
|
|
|
|
/* get command.. */
|
|
orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
|
|
args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
|
|
if (args != NULL) *args++ = '\0'; else args = "";
|
|
while (*args == ' ') args++;
|
|
|
|
/* check if this command can be expanded */
|
|
newcmd = command_expand(subcmd+8);
|
|
if (newcmd == NULL) {
|
|
/* ambiguous command */
|
|
g_free(orig);
|
|
return;
|
|
}
|
|
|
|
subcmd = g_strconcat("command ", newcmd, NULL);
|
|
|
|
ascii_strdown(subcmd);
|
|
if (!signal_emit(subcmd, 3, args, server, item)) {
|
|
defcmd = g_strdup_printf("default command %s", cmd);
|
|
if (!signal_emit(defcmd, 3, data, server, item)) {
|
|
signal_emit("error command", 2,
|
|
GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
|
|
}
|
|
g_free(defcmd);
|
|
}
|
|
|
|
g_free(subcmd);
|
|
g_free(orig);
|
|
}
|
|
|
|
static GSList *optlist_find(GSList *optlist, const char *option)
|
|
{
|
|
while (optlist != NULL) {
|
|
char *name = optlist->data;
|
|
if (iscmdtype(*name)) name++;
|
|
|
|
if (g_ascii_strcasecmp(name, option) == 0)
|
|
return optlist;
|
|
|
|
optlist = optlist->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int command_have_option(const char *cmd, const char *option)
|
|
{
|
|
COMMAND_REC *rec;
|
|
char **tmp;
|
|
|
|
g_return_val_if_fail(cmd != NULL, FALSE);
|
|
g_return_val_if_fail(option != NULL, FALSE);
|
|
|
|
rec = command_find(cmd);
|
|
g_return_val_if_fail(rec != NULL, FALSE);
|
|
|
|
if (rec->options == NULL)
|
|
return FALSE;
|
|
|
|
for (tmp = rec->options; *tmp != NULL; tmp++) {
|
|
char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
|
|
|
|
if (g_ascii_strcasecmp(name, option) == 0)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void command_calc_options(COMMAND_REC *rec, const char *options)
|
|
{
|
|
char **optlist, **tmp, *name, *str;
|
|
GSList *list, *oldopt;
|
|
|
|
optlist = g_strsplit(options, " ", -1);
|
|
|
|
if (rec->options == NULL) {
|
|
/* first call - use specified args directly */
|
|
rec->options = optlist;
|
|
return;
|
|
}
|
|
|
|
/* save old options to linked list */
|
|
list = NULL;
|
|
for (tmp = rec->options; *tmp != NULL; tmp++)
|
|
list = g_slist_append(list, g_strdup(*tmp));
|
|
g_strfreev(rec->options);
|
|
|
|
/* merge the options */
|
|
for (tmp = optlist; *tmp != NULL; tmp++) {
|
|
name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
|
|
|
|
oldopt = optlist_find(list, name);
|
|
if (oldopt != NULL) {
|
|
/* already specified - overwrite old definition */
|
|
g_free(oldopt->data);
|
|
oldopt->data = g_strdup(*tmp);
|
|
} else {
|
|
/* new option, append to list */
|
|
list = g_slist_append(list, g_strdup(*tmp));
|
|
}
|
|
}
|
|
g_strfreev(optlist);
|
|
|
|
/* linked list -> string[] */
|
|
str = gslist_to_string(list, " ");
|
|
rec->options = g_strsplit(str, " ", -1);
|
|
g_free(str);
|
|
|
|
g_slist_foreach(list, (GFunc) g_free, NULL);
|
|
g_slist_free(list);
|
|
}
|
|
|
|
/* recalculate options to command from options in all modules */
|
|
static void command_update_options(COMMAND_REC *rec)
|
|
{
|
|
GSList *tmp;
|
|
|
|
g_strfreev(rec->options);
|
|
rec->options = NULL;
|
|
|
|
for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_MODULE_REC *modrec = tmp->data;
|
|
|
|
if (modrec->options != NULL)
|
|
command_calc_options(rec, modrec->options);
|
|
}
|
|
}
|
|
|
|
void command_set_options_module(const char *module,
|
|
const char *cmd, const char *options)
|
|
{
|
|
COMMAND_REC *rec;
|
|
COMMAND_MODULE_REC *modrec;
|
|
int reload;
|
|
|
|
g_return_if_fail(module != NULL);
|
|
g_return_if_fail(cmd != NULL);
|
|
g_return_if_fail(options != NULL);
|
|
|
|
rec = command_find(cmd);
|
|
g_return_if_fail(rec != NULL);
|
|
modrec = command_module_get(rec, module, -1);
|
|
|
|
reload = modrec->options != NULL;
|
|
if (reload) {
|
|
/* options already set for the module ..
|
|
we need to recalculate everything */
|
|
g_free(modrec->options);
|
|
}
|
|
|
|
modrec->options = g_strdup(options);
|
|
|
|
if (reload)
|
|
command_update_options(rec);
|
|
else
|
|
command_calc_options(rec, options);
|
|
}
|
|
|
|
char *cmd_get_param(char **data)
|
|
{
|
|
char *pos;
|
|
|
|
g_return_val_if_fail(data != NULL, NULL);
|
|
g_return_val_if_fail(*data != NULL, NULL);
|
|
|
|
while (**data == ' ') (*data)++;
|
|
pos = *data;
|
|
|
|
while (**data != '\0' && **data != ' ') (*data)++;
|
|
if (**data == ' ') *(*data)++ = '\0';
|
|
|
|
return pos;
|
|
}
|
|
|
|
char *cmd_get_quoted_param(char **data)
|
|
{
|
|
char *pos, quote;
|
|
|
|
g_return_val_if_fail(data != NULL, NULL);
|
|
g_return_val_if_fail(*data != NULL, NULL);
|
|
|
|
while (**data == ' ') (*data)++;
|
|
if (**data != '\'' && **data != '"')
|
|
return cmd_get_param(data);
|
|
|
|
quote = **data; (*data)++;
|
|
|
|
pos = *data;
|
|
while (**data != '\0' && (**data != quote ||
|
|
((*data)[1] != ' ' && (*data)[1] != '\0'))) {
|
|
if (**data == '\\' && (*data)[1] != '\0')
|
|
memmove(*data, (*data)+1, strlen(*data));
|
|
(*data)++;
|
|
}
|
|
|
|
if (**data == quote) {
|
|
*(*data)++ = '\0';
|
|
if (**data == ' ')
|
|
(*data)++;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/* Find specified option from list of options - the `option' might be
|
|
shortened version of the full command. Returns index where the
|
|
option was found, -1 if not found or -2 if there was multiple matches. */
|
|
static int option_find(char **array, const char *option)
|
|
{
|
|
char **tmp;
|
|
int index, found, len, multiple;
|
|
|
|
g_return_val_if_fail(array != NULL, -1);
|
|
g_return_val_if_fail(option != NULL, -1);
|
|
|
|
len = strlen(option);
|
|
|
|
found = -1; index = 0; multiple = FALSE;
|
|
for (tmp = array; *tmp != NULL; tmp++, index++) {
|
|
const char *text = *tmp + iscmdtype(**tmp);
|
|
|
|
if (g_ascii_strncasecmp(text, option, len) == 0) {
|
|
if (text[len] == '\0') {
|
|
/* full match */
|
|
return index;
|
|
}
|
|
|
|
if (found != -1) {
|
|
/* multiple matches - we still need to check
|
|
if there's a full match left.. */
|
|
multiple = TRUE;
|
|
}
|
|
|
|
/* partial match, check that it's the only one */
|
|
found = index;
|
|
}
|
|
}
|
|
|
|
if (multiple)
|
|
return -2;
|
|
|
|
return found;
|
|
}
|
|
|
|
static int get_cmd_options(char **data, int ignore_unknown,
|
|
const char *cmd, GHashTable *options)
|
|
{
|
|
COMMAND_REC *rec;
|
|
char *option, *arg, **optlist;
|
|
int pos;
|
|
|
|
/* get option definitions */
|
|
rec = cmd == NULL ? NULL : command_find(cmd);
|
|
optlist = rec == NULL ? NULL : rec->options;
|
|
|
|
option = NULL; pos = -1;
|
|
for (;;) {
|
|
if (**data == '\0' || **data == '-') {
|
|
if (option != NULL && *optlist[pos] == '+') {
|
|
/* required argument missing! */
|
|
*data = optlist[pos] + 1;
|
|
return CMDERR_OPTION_ARG_MISSING;
|
|
}
|
|
}
|
|
if (**data == '-') {
|
|
(*data)++;
|
|
if (**data == '-' && (*data)[1] == ' ') {
|
|
/* -- option means end of options even
|
|
if next word starts with - */
|
|
(*data)++;
|
|
while (**data == ' ') (*data)++;
|
|
break;
|
|
}
|
|
|
|
if (**data == '\0')
|
|
option = "-";
|
|
else if (**data != ' ')
|
|
option = cmd_get_param(data);
|
|
else {
|
|
option = "-";
|
|
(*data)++;
|
|
}
|
|
|
|
/* check if this option can have argument */
|
|
pos = optlist == NULL ? -1 :
|
|
option_find(optlist, option);
|
|
|
|
if (pos == -1 && optlist != NULL &&
|
|
is_numeric(option, '\0')) {
|
|
/* check if we want -<number> option */
|
|
pos = option_find(optlist, "#");
|
|
if (pos != -1) {
|
|
g_hash_table_insert(options, "#",
|
|
option);
|
|
pos = -3;
|
|
}
|
|
}
|
|
|
|
if (pos == -1 && !ignore_unknown) {
|
|
/* unknown option! */
|
|
*data = option;
|
|
return CMDERR_OPTION_UNKNOWN;
|
|
}
|
|
if (pos == -2 && !ignore_unknown) {
|
|
/* multiple matches */
|
|
*data = option;
|
|
return CMDERR_OPTION_AMBIGUOUS;
|
|
}
|
|
if (pos >= 0) {
|
|
/* if we used a shortcut of parameter, put
|
|
the whole parameter name in options table */
|
|
option = optlist[pos] +
|
|
iscmdtype(*optlist[pos]);
|
|
}
|
|
if (options != NULL && pos != -3)
|
|
g_hash_table_insert(options, option, "");
|
|
|
|
if (pos < 0 || !iscmdtype(*optlist[pos]) ||
|
|
*optlist[pos] == '!')
|
|
option = NULL;
|
|
|
|
while (**data == ' ') (*data)++;
|
|
continue;
|
|
}
|
|
|
|
if (option == NULL)
|
|
break;
|
|
|
|
if (*optlist[pos] == '@' && !is_numeric(*data, ' '))
|
|
break; /* expected a numeric argument */
|
|
|
|
/* save the argument */
|
|
arg = cmd_get_quoted_param(data);
|
|
if (options != NULL) {
|
|
g_hash_table_remove(options, option);
|
|
g_hash_table_insert(options, option, arg);
|
|
}
|
|
option = NULL;
|
|
|
|
while (**data == ' ') (*data)++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
char *data;
|
|
GHashTable *options;
|
|
} CMD_TEMP_REC;
|
|
|
|
static const char *
|
|
get_optional_channel(WI_ITEM_REC *active_item, char **data, int require_name)
|
|
{
|
|
CHANNEL_REC *chanrec;
|
|
const char *ret;
|
|
char *tmp, *origtmp, *channel;
|
|
|
|
if (active_item == NULL || active_item->server == NULL) {
|
|
/* no active channel in window, channel required */
|
|
return cmd_get_param(data);
|
|
}
|
|
|
|
origtmp = tmp = g_strdup(*data);
|
|
channel = cmd_get_param(&tmp);
|
|
|
|
if (g_strcmp0(channel, "*") == 0 && IS_CHANNEL(active_item) &&
|
|
!require_name) {
|
|
/* "*" means active channel */
|
|
cmd_get_param(data);
|
|
ret = window_item_get_target(active_item);
|
|
} else if (IS_CHANNEL(active_item) &&
|
|
!server_ischannel(active_item->server, channel)) {
|
|
/* we don't have channel parameter - use active channel */
|
|
ret = window_item_get_target(active_item);
|
|
} else {
|
|
/* Find the channel first and use it's name if found.
|
|
This allows automatic !channel -> !XXXXXchannel replaces. */
|
|
channel = cmd_get_param(data);
|
|
|
|
chanrec = channel_find(active_item->server, channel);
|
|
ret = chanrec == NULL ? channel : chanrec->name;
|
|
}
|
|
|
|
g_free(origtmp);
|
|
return ret;
|
|
}
|
|
|
|
int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
|
|
{
|
|
WI_ITEM_REC *item;
|
|
CMD_TEMP_REC *rec;
|
|
GHashTable **opthash;
|
|
char **str, *arg, *datad;
|
|
va_list args;
|
|
int cnt, error, ignore_unknown, require_name;
|
|
|
|
g_return_val_if_fail(data != NULL, FALSE);
|
|
|
|
va_start(args, count);
|
|
|
|
rec = g_new0(CMD_TEMP_REC, 1);
|
|
rec->data = g_strdup(data);
|
|
*free_me = rec;
|
|
|
|
datad = rec->data;
|
|
error = FALSE;
|
|
|
|
item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
|
|
(WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
|
|
|
|
if (count & PARAM_FLAG_OPTIONS) {
|
|
arg = (char *) va_arg(args, char *);
|
|
opthash = (GHashTable **) va_arg(args, GHashTable **);
|
|
|
|
rec->options = *opthash =
|
|
g_hash_table_new((GHashFunc) g_istr_hash,
|
|
(GCompareFunc) g_istr_equal);
|
|
|
|
ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
|
|
error = get_cmd_options(&datad, ignore_unknown,
|
|
arg, *opthash);
|
|
}
|
|
|
|
if (!error) {
|
|
/* and now handle the string */
|
|
cnt = PARAM_WITHOUT_FLAGS(count);
|
|
if (count & PARAM_FLAG_OPTCHAN) {
|
|
/* optional channel as first parameter */
|
|
require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
|
|
PARAM_FLAG_OPTCHAN_NAME;
|
|
arg = (char *) get_optional_channel(item, &datad, require_name);
|
|
|
|
str = (char **) va_arg(args, char **);
|
|
if (str != NULL) *str = arg;
|
|
cnt--;
|
|
}
|
|
|
|
while (cnt-- > 0) {
|
|
if (cnt == 0 && count & PARAM_FLAG_GETREST) {
|
|
/* get rest */
|
|
arg = datad;
|
|
|
|
/* strip the trailing whitespace */
|
|
if (count & PARAM_FLAG_STRIP_TRAILING_WS) {
|
|
arg = g_strchomp(arg);
|
|
}
|
|
} else {
|
|
arg = (count & PARAM_FLAG_NOQUOTES) ?
|
|
cmd_get_param(&datad) :
|
|
cmd_get_quoted_param(&datad);
|
|
}
|
|
|
|
str = (char **) va_arg(args, char **);
|
|
if (str != NULL) *str = arg;
|
|
}
|
|
}
|
|
va_end(args);
|
|
|
|
if (error) {
|
|
signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
|
|
signal_stop();
|
|
|
|
cmd_params_free(rec);
|
|
*free_me = NULL;
|
|
}
|
|
|
|
return !error;
|
|
}
|
|
|
|
void cmd_params_free(void *free_me)
|
|
{
|
|
CMD_TEMP_REC *rec = free_me;
|
|
|
|
if (rec->options != NULL) g_hash_table_destroy(rec->options);
|
|
g_free(rec->data);
|
|
g_free(rec);
|
|
}
|
|
|
|
static void command_module_unbind_all(COMMAND_REC *rec,
|
|
COMMAND_MODULE_REC *modrec)
|
|
{
|
|
GSList *tmp, *next;
|
|
|
|
for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
|
|
COMMAND_CALLBACK_REC *cb = tmp->data;
|
|
next = tmp->next;
|
|
|
|
command_unbind_full(rec->cmd, cb->func, cb->user_data);
|
|
}
|
|
|
|
if (g_slist_find(commands, rec) != NULL) {
|
|
/* this module might have removed some options
|
|
from command, update them. */
|
|
command_update_options(rec);
|
|
}
|
|
}
|
|
|
|
void commands_remove_module(const char *module)
|
|
{
|
|
GSList *tmp, *next, *modlist;
|
|
|
|
g_return_if_fail(module != NULL);
|
|
|
|
for (tmp = commands; tmp != NULL; tmp = next) {
|
|
COMMAND_REC *rec = tmp->data;
|
|
|
|
next = tmp->next;
|
|
modlist = gslist_find_string(rec->modules, module);
|
|
if (modlist != NULL)
|
|
command_module_unbind_all(rec, modlist->data);
|
|
}
|
|
}
|
|
|
|
static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
|
|
{
|
|
GSList *tmp;
|
|
|
|
for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_MODULE_REC *rec = tmp->data;
|
|
|
|
if (rec->protocol == -1) {
|
|
/* at least one module accepts the command
|
|
without specific protocol */
|
|
return 1;
|
|
}
|
|
|
|
if (server != NULL && rec->protocol == server->chat_type) {
|
|
/* matching protocol found */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define alias_runstack_push(alias) \
|
|
alias_runstack = g_slist_append(alias_runstack, alias)
|
|
|
|
#define alias_runstack_pop(alias) \
|
|
alias_runstack = g_slist_remove(alias_runstack, alias)
|
|
|
|
#define alias_runstack_find(alias) \
|
|
(gslist_find_icase_string(alias_runstack, alias) != NULL)
|
|
|
|
static void parse_command(const char *command, int expand_aliases,
|
|
SERVER_REC *server, void *item)
|
|
{
|
|
COMMAND_REC *rec;
|
|
const char *alias, *newcmd;
|
|
char *cmd, *orig, *args, *oldcmd;
|
|
|
|
g_return_if_fail(command != NULL);
|
|
|
|
cmd = orig = g_strconcat("command ", command, NULL);
|
|
args = strchr(cmd+8, ' ');
|
|
if (args != NULL) *args++ = '\0'; else args = "";
|
|
|
|
/* check if there's an alias for command. Don't allow
|
|
recursive aliases */
|
|
alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
|
|
alias_find(cmd+8);
|
|
if (alias != NULL) {
|
|
alias_runstack_push(cmd+8);
|
|
eval_special_string(alias, args, server, item);
|
|
alias_runstack_pop(cmd+8);
|
|
g_free(orig);
|
|
return;
|
|
}
|
|
|
|
/* check if this command can be expanded */
|
|
newcmd = command_expand(cmd+8);
|
|
if (newcmd == NULL) {
|
|
/* ambiguous command */
|
|
g_free(orig);
|
|
return;
|
|
}
|
|
|
|
rec = command_find(newcmd);
|
|
if (rec != NULL && !cmd_protocol_match(rec, server)) {
|
|
g_free(orig);
|
|
|
|
signal_emit("error command", 1,
|
|
GINT_TO_POINTER(server == NULL ?
|
|
CMDERR_NOT_CONNECTED :
|
|
CMDERR_ILLEGAL_PROTO));
|
|
return;
|
|
}
|
|
|
|
cmd = g_strconcat("command ", newcmd, NULL);
|
|
ascii_strdown(cmd);
|
|
|
|
oldcmd = current_command;
|
|
current_command = cmd+8;
|
|
if (server != NULL) server_ref(server);
|
|
if (!signal_emit(cmd, 3, args, server, item)) {
|
|
signal_emit_id(signal_default_command, 3,
|
|
command, server, item);
|
|
}
|
|
if (server != NULL) {
|
|
if (server->connection_lost)
|
|
server_disconnect(server);
|
|
server_unref(server);
|
|
}
|
|
current_command = oldcmd;
|
|
|
|
g_free(cmd);
|
|
g_free(orig);
|
|
}
|
|
|
|
static void event_command(const char *line, SERVER_REC *server, void *item)
|
|
{
|
|
char *cmdchar;
|
|
int expand_aliases = TRUE;
|
|
|
|
g_return_if_fail(line != NULL);
|
|
|
|
cmdchar = *line == '\0' ? NULL :
|
|
strchr(settings_get_str("cmdchars"), *line);
|
|
if (cmdchar != NULL && line[1] == ' ') {
|
|
/* "/ text" = same as sending "text" to active channel. */
|
|
line += 2;
|
|
cmdchar = NULL;
|
|
}
|
|
if (cmdchar == NULL) {
|
|
/* non-command - let someone else handle this */
|
|
signal_emit("send text", 3, line, server, item);
|
|
return;
|
|
}
|
|
|
|
/* same cmdchar twice ignores aliases */
|
|
line++;
|
|
if (*line == *cmdchar) {
|
|
line++;
|
|
expand_aliases = FALSE;
|
|
}
|
|
|
|
/* ^command hides the output - we'll do this at fe-common but
|
|
we have to skip the ^ char here.. */
|
|
if (*line == '^') line++;
|
|
|
|
parse_command(line, expand_aliases, server, item);
|
|
}
|
|
|
|
static int eval_recursion_depth=0;
|
|
/* SYNTAX: EVAL <command(s)> */
|
|
static void cmd_eval(const char *data, SERVER_REC *server, void *item)
|
|
{
|
|
g_return_if_fail(data != NULL);
|
|
if (eval_recursion_depth > 100)
|
|
cmd_return_error(CMDERR_EVAL_MAX_RECURSE);
|
|
|
|
|
|
eval_recursion_depth++;
|
|
eval_special_string(data, "", server, item);
|
|
eval_recursion_depth--;
|
|
}
|
|
|
|
/* SYNTAX: CD <directory> */
|
|
static void cmd_cd(const char *data)
|
|
{
|
|
char *str;
|
|
|
|
g_return_if_fail(data != NULL);
|
|
if (*data == '\0') return;
|
|
|
|
str = convert_home(data);
|
|
if (chdir(str) != 0) {
|
|
g_warning("Failed to chdir(): %s", strerror(errno));
|
|
}
|
|
g_free(str);
|
|
}
|
|
|
|
void commands_init(void)
|
|
{
|
|
commands = NULL;
|
|
current_command = NULL;
|
|
alias_runstack = NULL;
|
|
|
|
signal_default_command = signal_get_uniq_id("default command");
|
|
|
|
settings_add_str("misc", "cmdchars", "/");
|
|
signal_add("send command", (SIGNAL_FUNC) event_command);
|
|
|
|
command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
|
|
command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
|
|
}
|
|
|
|
void commands_deinit(void)
|
|
{
|
|
g_free_not_null(current_command);
|
|
|
|
signal_remove("send command", (SIGNAL_FUNC) event_command);
|
|
|
|
command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
|
|
command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
|
|
}
|