mirror of
https://github.com/irssi/irssi.git
synced 2024-12-04 14:46:39 -05:00
a7f5540cba
PARAM_FLAG_NOQUOTES flag for cmd_get_params() git-svn-id: http://svn.irssi.org/repos/irssi/trunk@290 dbcabf3a-b0e7-0310-adc4-f8d773084564
537 lines
12 KiB
C
537 lines
12 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "module.h"
|
|
#include "modules.h"
|
|
#include "signals.h"
|
|
#include "commands.h"
|
|
#include "misc.h"
|
|
#include "server.h"
|
|
#include "server-redirect.h"
|
|
#include "special-vars.h"
|
|
|
|
#include "lib-config/iconfig.h"
|
|
#include "settings.h"
|
|
|
|
#define alias_find(alias) \
|
|
iconfig_get_str("aliases", alias, NULL)
|
|
|
|
GSList *commands;
|
|
char *current_command;
|
|
|
|
static GSList *cmdget_funcs;
|
|
static int signal_default_command;
|
|
|
|
void command_bind_to(int pos, const char *cmd, const char *category, SIGNAL_FUNC func)
|
|
{
|
|
COMMAND_REC *rec;
|
|
char *str;
|
|
|
|
g_return_if_fail(cmd != 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);
|
|
|
|
if (func != NULL) {
|
|
str = g_strconcat("command ", cmd, NULL);
|
|
signal_add_to(MODULE_NAME, pos, str, func);
|
|
g_free(str);
|
|
}
|
|
|
|
signal_emit("commandlist new", 1, rec);
|
|
}
|
|
|
|
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_free(rec->cmd);
|
|
g_free(rec);
|
|
}
|
|
|
|
void command_unbind(const char *cmd, SIGNAL_FUNC func)
|
|
{
|
|
GSList *tmp;
|
|
char *str;
|
|
|
|
g_return_if_fail(cmd != NULL);
|
|
|
|
for (tmp = commands; tmp != NULL; tmp = tmp->next) {
|
|
COMMAND_REC *rec = tmp->data;
|
|
|
|
if (g_strcasecmp(rec->cmd, cmd) == 0) {
|
|
command_free(rec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (func != NULL) {
|
|
str = g_strconcat("command ", cmd, NULL);
|
|
signal_remove(str, func);
|
|
g_free(str);
|
|
}
|
|
}
|
|
|
|
void command_runsub(const char *cmd, const char *data, void *p1, void *p2)
|
|
{
|
|
char *subcmd, *defcmd, *args;
|
|
|
|
g_return_if_fail(data != NULL);
|
|
|
|
/* get command.. */
|
|
subcmd = g_strdup_printf("command %s %s", cmd, data);
|
|
args = strchr(subcmd+9 + strlen(cmd), ' ');
|
|
if (args != NULL) *args++ = '\0'; else args = "";
|
|
while (*args == ' ') args++;
|
|
|
|
g_strdown(subcmd);
|
|
if (!signal_emit(subcmd, 3, args, p1, p2)) {
|
|
defcmd = g_strdup_printf("default command %s", cmd);
|
|
if (!signal_emit(defcmd, 3, data, p1, p2))
|
|
signal_emit("unknown command", 3, strchr(subcmd, ' ')+1, p1, p2);
|
|
g_free(defcmd);
|
|
}
|
|
g_free(subcmd);
|
|
}
|
|
|
|
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) {
|
|
if (**data == '\\' && (*data)[1] != '\0')
|
|
g_memmove(*data, (*data)+1, strlen(*data));
|
|
(*data)++;
|
|
}
|
|
|
|
if (**data != '\0') *(*data)++ = '\0';
|
|
|
|
return pos;
|
|
}
|
|
|
|
static char *get_opt_args(char **data)
|
|
{
|
|
/* -cmd1 -cmd2 -cmd3 ... */
|
|
char *p, *ret;
|
|
int stopnext;
|
|
|
|
g_return_val_if_fail(data != NULL, NULL);
|
|
g_return_val_if_fail(*data != NULL, NULL);
|
|
|
|
stopnext = FALSE;
|
|
ret = NULL;
|
|
for (p = *data;;) {
|
|
if (*p != '-' || stopnext) {
|
|
if (p == *data) return "";
|
|
|
|
ret = *data;
|
|
*data = p;
|
|
|
|
while (isspace(p[-1]) && p > ret) p--;
|
|
if (*p != '\0') *p = '\0';
|
|
return ret;
|
|
}
|
|
|
|
if (p[1] == '-') {
|
|
/* -- argument means end of arguments even if
|
|
next word starts with - */
|
|
stopnext = TRUE;
|
|
}
|
|
|
|
while (!isspace(*p) && *p != '\0') p++;
|
|
while (isspace(*p)) p++;
|
|
}
|
|
}
|
|
|
|
static void cmd_params_pack(char ***subargs, char *end, char *start, char *newstart)
|
|
{
|
|
char ***tmp;
|
|
char *data;
|
|
int bufsize, datalen, len;
|
|
|
|
bufsize = (int) (end-newstart)+1;
|
|
|
|
data = g_malloc(bufsize); datalen = 0;
|
|
for (tmp = subargs; *tmp != NULL; tmp++) {
|
|
if (**tmp < start || **tmp > end)
|
|
continue;
|
|
|
|
len = strlen(**tmp)+1;
|
|
if (datalen+len > bufsize)
|
|
g_error("cmd_params_pack() : buffer overflow!");
|
|
|
|
memcpy(data+datalen, **tmp, len);
|
|
**tmp = newstart+datalen;
|
|
datalen += len;
|
|
}
|
|
|
|
g_memmove(newstart, data, datalen);
|
|
g_free(data);
|
|
}
|
|
|
|
int arg_find(char **array, const char *item)
|
|
{
|
|
char **tmp;
|
|
int index;
|
|
|
|
g_return_val_if_fail(array != NULL, 0);
|
|
g_return_val_if_fail(item != NULL, 0);
|
|
|
|
index = 0;
|
|
for (tmp = array; *tmp != NULL; tmp++, index++) {
|
|
if (g_strcasecmp(*tmp + (**tmp == '@'), item) == 0)
|
|
return index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int get_multi_args(char **data, int checkonly, va_list va)
|
|
{
|
|
/* -cmd1 arg1 -cmd2 "argument two" -cmd3 */
|
|
GString *returnargs;
|
|
char **args, **arglist, *arg, *origdata;
|
|
char **nextarg, ***subargs;
|
|
int eat, pos;
|
|
|
|
eat = 0;
|
|
args = (char **) va_arg(va, char **);
|
|
g_return_val_if_fail(args != NULL && *args != NULL && **args != '\0', 0);
|
|
|
|
arglist = g_strsplit(*args, " ", -1);
|
|
eat = strarray_length(arglist);
|
|
|
|
subargs = g_new(char **, eat+1);
|
|
for (pos = 0; pos < eat; pos++) {
|
|
subargs[pos] = (char **) va_arg(va, char **);
|
|
if (subargs[pos] == NULL) {
|
|
g_free(subargs);
|
|
g_warning("get_multi_args() : subargument == NULL");
|
|
return eat;
|
|
}
|
|
*subargs[pos] = "";
|
|
}
|
|
subargs[eat] = NULL;
|
|
|
|
origdata = *data;
|
|
returnargs = g_string_new(NULL);
|
|
nextarg = NULL;
|
|
for (;;) {
|
|
if (**data == '-') {
|
|
(*data)++;
|
|
if (**data == '-') {
|
|
/* -- argument means end of arguments even
|
|
if next word starts with - */
|
|
(*data)++;
|
|
while (isspace(**data)) (*data)++;
|
|
break;
|
|
}
|
|
|
|
arg = cmd_get_param(data);
|
|
g_string_sprintfa(returnargs, "-%s ", arg);
|
|
|
|
/* check if this argument can have parameter */
|
|
pos = arg_find(arglist, arg);
|
|
nextarg = pos == -1 ? NULL : subargs[pos];
|
|
|
|
while (isspace(**data)) (*data)++;
|
|
continue;
|
|
}
|
|
|
|
if (nextarg == NULL)
|
|
break;
|
|
|
|
if (*arglist[pos] == '@' && !isdigit(**data))
|
|
break; /* expected a numeric argument */
|
|
|
|
/* save the sub-argument to `nextarg' */
|
|
arg = cmd_get_quoted_param(data);
|
|
*nextarg = arg; nextarg = NULL;
|
|
|
|
while (isspace(**data)) (*data)++;
|
|
}
|
|
|
|
if (!checkonly) {
|
|
/* ok, this is a bit stupid. this will pack the arguments in
|
|
`data' like "-arg1 subarg -arg2 sub2\0" ->
|
|
"-arg1 -arg2\0subarg\0sub2\0" this is because it's easier
|
|
to free only _one_ string instead of two (`args') when
|
|
using PARAM_FLAG_MULTIARGS. */
|
|
if (returnargs->len == 0)
|
|
*args = "";
|
|
else {
|
|
cmd_params_pack(subargs, **data == '\0' ? *data : (*data)-1,
|
|
origdata, origdata+returnargs->len);
|
|
|
|
g_string_truncate(returnargs, returnargs->len-1);
|
|
strcpy(origdata, returnargs->str);
|
|
*args = origdata;
|
|
}
|
|
}
|
|
|
|
g_string_free(returnargs, TRUE);
|
|
g_strfreev(arglist);
|
|
g_free(subargs);
|
|
|
|
return eat;
|
|
}
|
|
|
|
char *cmd_get_callfuncs(const char *data, int *count, va_list *args)
|
|
{
|
|
CMD_GET_FUNC func;
|
|
GSList *tmp;
|
|
char *ret, *old;
|
|
|
|
ret = g_strdup(data);
|
|
for (tmp = cmdget_funcs; tmp != NULL; tmp = tmp->next) {
|
|
func = (CMD_GET_FUNC) tmp->data;
|
|
|
|
old = ret;
|
|
ret = func(ret, count, args);
|
|
g_free(old);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *cmd_get_params(const char *data, int count, ...)
|
|
{
|
|
char **str, *arg, *datad, *old;
|
|
va_list args;
|
|
int cnt, eat, len;
|
|
|
|
g_return_val_if_fail(data != NULL, NULL);
|
|
|
|
va_start(args, count);
|
|
|
|
/* get the length of the arguments in string */
|
|
if ((count & (PARAM_FLAG_MULTIARGS|PARAM_FLAG_OPTARGS)) == 0)
|
|
len = 0;
|
|
else {
|
|
old = datad = g_strdup(data);
|
|
if (count & PARAM_FLAG_MULTIARGS)
|
|
get_multi_args(&datad, TRUE, args);
|
|
else
|
|
get_opt_args(&datad);
|
|
len = (int) (datad-old);
|
|
g_free(old);
|
|
}
|
|
|
|
/* send the text to custom functions to handle - skip arguments */
|
|
old = datad = cmd_get_callfuncs(data+len, &count, &args);
|
|
|
|
if (len > 0) {
|
|
/* put the arguments + the new data to one string */
|
|
datad = g_malloc(len+1 + strlen(old)+1);
|
|
memcpy(datad, data, len);
|
|
datad[len] = ' ';
|
|
memcpy(datad+len+1, old, strlen(old)+1);
|
|
g_free(old);
|
|
|
|
old = datad;
|
|
}
|
|
|
|
/* and now handle the string */
|
|
cnt = PARAM_WITHOUT_FLAGS(count);
|
|
while (cnt-- > 0) {
|
|
if (count & PARAM_FLAG_MULTIARGS) {
|
|
eat = get_multi_args(&datad, FALSE, args)+1;
|
|
count &= ~PARAM_FLAG_MULTIARGS;
|
|
|
|
cnt -= eat-1;
|
|
while (eat-- > 0)
|
|
str = (char **) va_arg(args, char **);
|
|
continue;
|
|
} else if (count & PARAM_FLAG_OPTARGS) {
|
|
arg = get_opt_args(&datad);
|
|
count &= ~PARAM_FLAG_OPTARGS;
|
|
} else if (cnt == 0 && count & PARAM_FLAG_GETREST) {
|
|
/* get rest */
|
|
arg = datad;
|
|
} 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);
|
|
|
|
return old;
|
|
}
|
|
|
|
void cmd_get_add_func(CMD_GET_FUNC func)
|
|
{
|
|
cmdget_funcs = g_slist_prepend(cmdget_funcs, (void *) func);
|
|
}
|
|
|
|
void cmd_get_remove_func(CMD_GET_FUNC func)
|
|
{
|
|
cmdget_funcs = g_slist_prepend(cmdget_funcs, (void *) func);
|
|
}
|
|
|
|
static void parse_outgoing(const char *line, SERVER_REC *server, void *item)
|
|
{
|
|
const char *cmdchars, *alias;
|
|
char *cmd, *str, *args, *oldcmd;
|
|
int use_alias = TRUE;
|
|
|
|
g_return_if_fail(line != NULL);
|
|
|
|
if (*line == '\0') {
|
|
/* empty line, forget it. */
|
|
signal_stop();
|
|
return;
|
|
}
|
|
|
|
cmdchars = settings_get_str("cmdchars");
|
|
if (strchr(cmdchars, *line) == NULL)
|
|
return; /* handle only /commands here */
|
|
line++;
|
|
if (*line == ' ') {
|
|
/* "/ text" = same as sending "text" to active channel. */
|
|
return;
|
|
}
|
|
|
|
/* //command ignores aliases */
|
|
if (strchr(cmdchars, *line) != NULL) {
|
|
line++;
|
|
use_alias = FALSE;
|
|
}
|
|
|
|
cmd = str = g_strconcat("command ", line, NULL);
|
|
args = strchr(cmd+8, ' ');
|
|
if (args != NULL) *args++ = '\0'; else args = "";
|
|
|
|
/* check if there's an alias for command */
|
|
alias = use_alias ? alias_find(cmd+8) : NULL;
|
|
if (alias != NULL)
|
|
eval_special_string(alias, args, server, item);
|
|
else {
|
|
if (server != NULL)
|
|
server_redirect_default((SERVER_REC *) server, cmd);
|
|
|
|
g_strdown(cmd);
|
|
oldcmd = current_command;
|
|
current_command = cmd+8;
|
|
if (!signal_emit(cmd, 3, args, server, item))
|
|
signal_emit_id(signal_default_command, 3, line, server, item);
|
|
current_command = oldcmd;
|
|
}
|
|
|
|
g_free(str);
|
|
}
|
|
|
|
static void cmd_eval(const char *data, SERVER_REC *server, void *item)
|
|
{
|
|
g_return_if_fail(data != NULL);
|
|
|
|
eval_special_string(data, "", server, item);
|
|
}
|
|
|
|
static void cmd_cd(const char *data)
|
|
{
|
|
char *str;
|
|
|
|
g_return_if_fail(data != NULL);
|
|
if (*data == '\0') return;
|
|
|
|
str = convert_home(data);
|
|
chdir(str);
|
|
g_free(str);
|
|
}
|
|
|
|
static void cmd_rehash(const char *data)
|
|
{
|
|
char *fname;
|
|
|
|
fname = *data != '\0' ? g_strdup(data) :
|
|
g_strdup_printf("%s/.irssi/config", g_get_home_dir());
|
|
settings_reread(fname);
|
|
g_free(fname);
|
|
}
|
|
|
|
static void cmd_save(const char *data)
|
|
{
|
|
settings_save(*data != '\0' ? data : NULL);
|
|
}
|
|
|
|
void commands_init(void)
|
|
{
|
|
commands = NULL;
|
|
cmdget_funcs = NULL;
|
|
current_command = NULL;
|
|
|
|
signal_default_command = module_get_uniq_id_str("signals", "default command");
|
|
|
|
settings_add_str("misc", "cmdchars", "/");
|
|
signal_add("send command", (SIGNAL_FUNC) parse_outgoing);
|
|
|
|
command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
|
|
command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
|
|
command_bind("rehash", NULL, (SIGNAL_FUNC) cmd_rehash);
|
|
command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
|
|
}
|
|
|
|
void commands_deinit(void)
|
|
{
|
|
g_free_not_null(current_command);
|
|
g_slist_free(cmdget_funcs);
|
|
|
|
signal_remove("send command", (SIGNAL_FUNC) parse_outgoing);
|
|
|
|
command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
|
|
command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
|
|
command_unbind("rehash", (SIGNAL_FUNC) cmd_rehash);
|
|
command_unbind("save", (SIGNAL_FUNC) cmd_save);
|
|
}
|