0899c9fa3c725d5fdd76de9536ca2a8506387314
[crypto.git] / apps / irssi / src / core / commands.c
1 /*
2  commands.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "misc.h"
25 #include "special-vars.h"
26 #include "window-item-def.h"
27
28 #include "servers.h"
29 #include "channels.h"
30
31 #include "lib-config/iconfig.h"
32 #include "settings.h"
33
34 GSList *commands;
35 char *current_command;
36
37 static int signal_default_command;
38
39 static GSList *alias_runstack;
40
41 COMMAND_REC *command_find(const char *cmd)
42 {
43         GSList *tmp;
44
45         g_return_val_if_fail(cmd != NULL, NULL);
46
47         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
48                 COMMAND_REC *rec = tmp->data;
49
50                 if (g_strcasecmp(rec->cmd, cmd) == 0)
51                         return rec;
52         }
53
54         return NULL;
55 }
56
57 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
58                                                const char *module)
59 {
60         GSList *tmp;
61
62         g_return_val_if_fail(rec != NULL, NULL);
63         g_return_val_if_fail(module != NULL, NULL);
64
65         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
66                 COMMAND_MODULE_REC *rec = tmp->data;
67
68                 if (g_strcasecmp(rec->name, module) == 0)
69                         return rec;
70         }
71
72         return NULL;
73 }
74
75 static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec,
76                                                     SIGNAL_FUNC func)
77 {
78         GSList *tmp;
79
80         g_return_val_if_fail(rec != NULL, NULL);
81         g_return_val_if_fail(func != NULL, NULL);
82
83         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
84                 COMMAND_MODULE_REC *rec = tmp->data;
85
86                 if (g_slist_find(rec->signals, (void *) func) != NULL)
87                         return rec;
88         }
89
90         return NULL;
91 }
92
93 int command_have_sub(const char *command)
94 {
95         GSList *tmp;
96         int len;
97
98         g_return_val_if_fail(command != NULL, FALSE);
99
100         /* find "command "s */
101         len = strlen(command);
102         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
103                 COMMAND_REC *rec = tmp->data;
104
105                 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
106                     rec->cmd[len] == ' ')
107                         return TRUE;
108         }
109
110         return FALSE;
111 }
112
113 static COMMAND_MODULE_REC *
114 command_module_get(COMMAND_REC *rec, const char *module, int protocol)
115 {
116         COMMAND_MODULE_REC *modrec;
117
118         g_return_val_if_fail(rec != NULL, NULL);
119
120         modrec = command_module_find(rec, module);
121         if (modrec == NULL) {
122                 modrec = g_new0(COMMAND_MODULE_REC, 1);
123                 modrec->name = g_strdup(module);
124                 modrec->protocol = -1;
125                 rec->modules = g_slist_append(rec->modules, modrec);
126         }
127
128         if (protocol != -1)
129                 modrec->protocol = protocol;
130
131         return modrec;
132 }
133
134 void command_bind_to(const char *module, int pos, const char *cmd,
135                      int protocol, const char *category, SIGNAL_FUNC func)
136 {
137         COMMAND_REC *rec;
138         COMMAND_MODULE_REC *modrec;
139         char *str;
140
141         g_return_if_fail(module != NULL);
142         g_return_if_fail(cmd != NULL);
143
144         rec = command_find(cmd);
145         if (rec == NULL) {
146                 rec = g_new0(COMMAND_REC, 1);
147                 rec->cmd = g_strdup(cmd);
148                 rec->category = category == NULL ? NULL : g_strdup(category);
149                 commands = g_slist_append(commands, rec);
150         }
151         modrec = command_module_get(rec, module, protocol);
152
153         modrec->signals = g_slist_append(modrec->signals, (void *) func);
154
155         if (func != NULL) {
156                 str = g_strconcat("command ", cmd, NULL);
157                 signal_add_to(module, pos, str, func);
158                 g_free(str);
159         }
160
161         signal_emit("commandlist new", 1, rec);
162 }
163
164 static void command_free(COMMAND_REC *rec)
165 {
166         commands = g_slist_remove(commands, rec);
167         signal_emit("commandlist remove", 1, rec);
168
169         g_free_not_null(rec->category);
170         g_strfreev(rec->options);
171         g_free(rec->cmd);
172         g_free(rec);
173 }
174
175 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
176 {
177         rec->modules = g_slist_remove(rec->modules, modrec);
178
179         g_slist_free(modrec->signals);
180         g_free(modrec->name);
181         g_free_not_null(modrec->options);
182         g_free(modrec);
183 }
184
185 static void command_module_destroy(COMMAND_REC *rec,
186                                    COMMAND_MODULE_REC *modrec)
187 {
188         GSList *tmp, *freelist;
189
190         command_module_free(modrec, rec);
191
192         /* command_set_options() might have added module declaration of it's
193            own without any signals .. check if they're the only ones left
194            and if so, destroy them. */
195         freelist = NULL;
196         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
197                 COMMAND_MODULE_REC *rec = tmp->data;
198
199                 if (rec->signals == NULL)
200                         freelist = g_slist_append(freelist, rec);
201                 else {
202                         g_slist_free(freelist);
203                         freelist = NULL;
204                         break;
205                 }
206         }
207
208         g_slist_foreach(freelist, (GFunc) command_module_free, rec);
209         g_slist_free(freelist);
210
211         if (rec->modules == NULL)
212                 command_free(rec);
213 }
214
215 void command_unbind(const char *cmd, SIGNAL_FUNC func)
216 {
217         COMMAND_REC *rec;
218         COMMAND_MODULE_REC *modrec;
219         char *str;
220
221         g_return_if_fail(cmd != NULL);
222         g_return_if_fail(func != NULL);
223
224         rec = command_find(cmd);
225         if (rec != NULL) {
226                 modrec = command_module_find_func(rec, func);
227                 g_return_if_fail(modrec != NULL);
228
229                 modrec->signals =
230                         g_slist_remove(modrec->signals, (void *) func);
231                 if (modrec->signals == NULL)
232                         command_module_destroy(rec, modrec);
233         }
234
235         str = g_strconcat("command ", cmd, NULL);
236         signal_remove(str, func);
237         g_free(str);
238 }
239
240 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
241    match is found */
242 static const char *command_expand(char *cmd)
243 {
244         GSList *tmp;
245         const char *match;
246         int len, multiple;
247
248         g_return_val_if_fail(cmd != NULL, NULL);
249
250         multiple = FALSE;
251         match = NULL;
252         len = strlen(cmd);
253         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
254                 COMMAND_REC *rec = tmp->data;
255
256                 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
257                     strchr(rec->cmd+len, ' ') == NULL) {
258                         if (rec->cmd[len] == '\0') {
259                                 /* full match */
260                                 return rec->cmd;
261                         }
262
263                         if (match != NULL) {
264                                 /* multiple matches, we still need to check
265                                    if there's some command left that is a
266                                    full match.. */
267                                 multiple = TRUE;
268                         }
269
270                         /* check that this is the only match */
271                         match = rec->cmd;
272                 }
273         }
274
275         if (multiple) {
276                 signal_emit("error command", 2,
277                             GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
278                 return NULL;
279         }
280
281         return match != NULL ? match : cmd;
282 }
283
284 void command_runsub(const char *cmd, const char *data,
285                     void *server, void *item)
286 {
287         const char *newcmd;
288         char *orig, *subcmd, *defcmd, *args;
289
290         g_return_if_fail(data != NULL);
291
292         while (*data == ' ') data++;
293
294         if (*data == '\0') {
295                 /* no subcommand given - list the subcommands */
296                 signal_emit("list subcommands", 2, cmd);
297                 return;
298         }
299
300         /* get command.. */
301         orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
302         args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
303         if (args != NULL) *args++ = '\0'; else args = "";
304         while (*args == ' ') args++;
305
306         /* check if this command can be expanded */
307         newcmd = command_expand(subcmd+8);
308         if (newcmd == NULL) {
309                 /* ambiguous command */
310                 g_free(orig);
311                 return;
312         }
313
314         subcmd = g_strconcat("command ", newcmd, NULL);
315
316         g_strdown(subcmd);
317         if (!signal_emit(subcmd, 3, args, server, item)) {
318                 defcmd = g_strdup_printf("default command %s", cmd);
319                 if (!signal_emit(defcmd, 3, data, server, item)) {
320                         signal_emit("error command", 2,
321                                     GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
322                 }
323                 g_free(defcmd);
324         }
325
326         g_free(subcmd);
327         g_free(orig);
328 }
329
330 static GSList *optlist_find(GSList *optlist, const char *option)
331 {
332         while (optlist != NULL) {
333                 char *name = optlist->data;
334                 if (iscmdtype(*name)) name++;
335
336                 if (g_strcasecmp(name, option) == 0)
337                         return optlist;
338
339                 optlist = optlist->next;
340         }
341
342         return NULL;
343 }
344
345 int command_have_option(const char *cmd, const char *option)
346 {
347         COMMAND_REC *rec;
348         char **tmp;
349
350         g_return_val_if_fail(cmd != NULL, FALSE);
351         g_return_val_if_fail(option != NULL, FALSE);
352
353         rec = command_find(cmd);
354         g_return_val_if_fail(rec != NULL, FALSE);
355
356         if (rec->options == NULL)
357                 return FALSE;
358
359         for (tmp = rec->options; *tmp != NULL; tmp++) {
360                 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
361
362                 if (g_strcasecmp(name, option) == 0)
363                         return TRUE;
364         }
365
366         return FALSE;
367 }
368
369 static void command_calc_options(COMMAND_REC *rec, const char *options)
370 {
371         char **optlist, **tmp, *name, *str;
372         GSList *list, *oldopt;
373
374         optlist = g_strsplit(options, " ", -1);
375
376         if (rec->options == NULL) {
377                 /* first call - use specified args directly */
378                 rec->options = optlist;
379                 return;
380         }
381
382         /* save old options to linked list */
383         list = NULL;
384         for (tmp = rec->options; *tmp != NULL; tmp++)
385                 list = g_slist_append(list, g_strdup(*tmp));
386         g_strfreev(rec->options);
387
388         /* merge the options */
389         for (tmp = optlist; *tmp != NULL; tmp++) {
390                 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
391
392                 oldopt = optlist_find(list, name);
393                 if (oldopt != NULL) {
394                         /* already specified - overwrite old defination */
395                         g_free(oldopt->data);
396                         oldopt->data = g_strdup(*tmp);
397                 } else {
398                         /* new option, append to list */
399                         list = g_slist_append(list, g_strdup(*tmp));
400                 }
401         }
402         g_strfreev(optlist);
403
404         /* linked list -> string[] */
405         str = gslist_to_string(list, " ");
406         rec->options = g_strsplit(str, " ", -1);
407         g_free(str);
408
409         g_slist_foreach(list, (GFunc) g_free, NULL);
410         g_slist_free(list);
411 }
412
413 /* recalculate options to command from options in all modules */
414 static void command_update_options(COMMAND_REC *rec)
415 {
416         GSList *tmp;
417
418         g_strfreev(rec->options);
419         rec->options = NULL;
420
421         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
422                 COMMAND_MODULE_REC *modrec = tmp->data;
423
424                 if (modrec->options != NULL)
425                         command_calc_options(rec, modrec->options);
426         }
427 }
428
429 void command_set_options_module(const char *module,
430                                 const char *cmd, const char *options)
431 {
432         COMMAND_REC *rec;
433         COMMAND_MODULE_REC *modrec;
434         int reload;
435
436         g_return_if_fail(module != NULL);
437         g_return_if_fail(cmd != NULL);
438         g_return_if_fail(options != NULL);
439
440         rec = command_find(cmd);
441         g_return_if_fail(rec != NULL);
442         modrec = command_module_get(rec, module, -1);
443
444         reload = modrec->options != NULL;
445         if (reload) {
446                 /* options already set for the module ..
447                    we need to recalculate everything */
448                 g_free(modrec->options);
449         }
450
451         modrec->options = g_strdup(options);
452
453         if (reload)
454                 command_update_options(rec);
455         else
456                 command_calc_options(rec, options);
457 }
458
459 char *cmd_get_param(char **data)
460 {
461         char *pos;
462
463         g_return_val_if_fail(data != NULL, NULL);
464         g_return_val_if_fail(*data != NULL, NULL);
465
466         while (**data == ' ') (*data)++;
467         pos = *data;
468
469         while (**data != '\0' && **data != ' ') (*data)++;
470         if (**data == ' ') *(*data)++ = '\0';
471
472         return pos;
473 }
474
475 static char *cmd_get_quoted_param(char **data)
476 {
477         char *pos, quote;
478
479         g_return_val_if_fail(data != NULL, NULL);
480         g_return_val_if_fail(*data != NULL, NULL);
481
482         while (**data == ' ') (*data)++;
483         if (**data != '\'' && **data != '"')
484                 return cmd_get_param(data);
485
486         quote = **data; (*data)++;
487
488         pos = *data;
489         while (**data != '\0' && **data != quote) {
490                 if (**data == '\\' && (*data)[1] != '\0')
491                         g_memmove(*data, (*data)+1, strlen(*data));
492                 (*data)++;
493         }
494
495         if (**data != '\0') *(*data)++ = '\0';
496
497         return pos;
498 }
499
500 /* Find specified option from list of options - the `option' might be
501    shortened version of the full command. Returns index where the
502    option was found, -1 if not found or -2 if there was multiple matches. */
503 static int option_find(char **array, const char *option)
504 {
505         char **tmp;
506         int index, found, len, multiple;
507
508         g_return_val_if_fail(array != NULL, -1);
509         g_return_val_if_fail(option != NULL, -1);
510
511         len = strlen(option);
512
513         found = -1; index = 0; multiple = FALSE;
514         for (tmp = array; *tmp != NULL; tmp++, index++) {
515                 const char *text = *tmp + iscmdtype(**tmp);
516
517                 if (g_strncasecmp(text, option, len) == 0) {
518                         if (text[len] == '\0') {
519                                 /* full match */
520                                 return index;
521                         }
522
523                         if (found != -1) {
524                                 /* multiple matches - we still need to check
525                                    if there's a full match left.. */
526                                 multiple = TRUE;
527                         }
528
529                         /* partial match, check that it's the only one */
530                         found = index;
531                 }
532         }
533
534         if (multiple)
535                 return -2;
536
537         return found;
538 }
539
540 static int get_cmd_options(char **data, int ignore_unknown,
541                            const char *cmd, GHashTable *options)
542 {
543         COMMAND_REC *rec;
544         char *option, *arg, **optlist;
545         int pos;
546
547         /* get option definations */
548         rec = cmd == NULL ? NULL : command_find(cmd);
549         optlist = rec == NULL ? NULL : rec->options;
550
551         option = NULL; pos = -1;
552         for (;;) {
553                 if (**data == '-') {
554                         if (option != NULL && *optlist[pos] == '+') {
555                                 /* required argument missing! */
556                                 *data = optlist[pos] + 1;
557                                 return CMDERR_OPTION_ARG_MISSING;
558                         }
559
560                         (*data)++;
561                         if (**data == '-' && i_isspace((*data)[1])) {
562                                 /* -- option means end of options even
563                                    if next word starts with - */
564                                 (*data)++;
565                                 while (i_isspace(**data)) (*data)++;
566                                 break;
567                         }
568
569                         if (**data == '\0')
570                                 option = "-";
571                         else if (!i_isspace(**data))
572                                 option = cmd_get_param(data);
573                         else {
574                                 option = "-";
575                                 (*data)++;
576                         }
577
578                         /* check if this option can have argument */
579                         pos = optlist == NULL ? -1 :
580                                 option_find(optlist, option);
581
582                         if (pos == -1 && optlist != NULL &&
583                             is_numeric(option, '\0')) {
584                                 /* check if we want -<number> option */
585                                 pos = option_find(optlist, "#");
586                                 if (pos != -1) {
587                                         g_hash_table_insert(options, "#",
588                                                             option);
589                                         pos = -3;
590                                 }
591                         }
592
593                         if (pos == -1 && !ignore_unknown) {
594                                 /* unknown option! */
595                                 *data = option;
596                                 return CMDERR_OPTION_UNKNOWN;
597                         }
598                         if (pos == -2 && !ignore_unknown) {
599                                 /* multiple matches */
600                                 *data = option;
601                                 return CMDERR_OPTION_AMBIGUOUS;
602                         }
603                         if (pos >= 0) {
604                                 /* if we used a shortcut of parameter, put
605                                    the whole parameter name in options table */
606                                 option = optlist[pos] +
607                                         iscmdtype(*optlist[pos]);
608                         }
609                         if (options != NULL && pos != -3)
610                                 g_hash_table_insert(options, option, "");
611
612                         if (pos < 0 || !iscmdtype(*optlist[pos]) ||
613                             *optlist[pos] == '!')
614                                 option = NULL;
615
616                         while (i_isspace(**data)) (*data)++;
617                         continue;
618                 }
619
620                 if (option == NULL)
621                         break;
622
623                 if (*optlist[pos] == '@' && !i_isdigit(**data))
624                         break; /* expected a numeric argument */
625
626                 /* save the argument */
627                 arg = cmd_get_quoted_param(data);
628                 if (options != NULL) {
629                         g_hash_table_remove(options, option);
630                         g_hash_table_insert(options, option, arg);
631                 }
632                 option = NULL;
633
634                 while (i_isspace(**data)) (*data)++;
635         }
636
637         return 0;
638 }
639
640 typedef struct {
641         char *data;
642         GHashTable *options;
643 } CMD_TEMP_REC;
644
645 static char *get_optional_channel(WI_ITEM_REC *active_item, char **data,
646                                   int require_name)
647 {
648         CHANNEL_REC *chanrec;
649         char *tmp, *origtmp, *channel, *ret;
650
651         if (active_item == NULL) {
652                 /* no active channel in window, channel required */
653                 return cmd_get_param(data);
654         }
655
656         origtmp = tmp = g_strdup(*data);
657         channel = cmd_get_param(&tmp);
658
659         if (strcmp(channel, "*") == 0 && !require_name) {
660                 /* "*" means active channel */
661                 cmd_get_param(data);
662                 ret = active_item->name;
663         } else if (!server_ischannel(active_item->server, channel)) {
664                 /* we don't have channel parameter - use active channel */
665                 ret = active_item->name;
666         } else {
667                 /* Find the channel first and use it's name if found.
668                    This allows automatic !channel -> !XXXXXchannel replaces. */
669                 channel = cmd_get_param(data);
670
671                 chanrec = channel_find(active_item->server, channel);
672                 ret = chanrec == NULL ? channel : chanrec->name;
673         }
674
675         g_free(origtmp);
676         return ret;
677 }
678
679 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
680 {
681         WI_ITEM_REC *item;
682         CMD_TEMP_REC *rec;
683         GHashTable **opthash;
684         char **str, *arg, *datad;
685         va_list args;
686         int cnt, error, ignore_unknown, require_name;
687
688         g_return_val_if_fail(data != NULL, FALSE);
689
690         va_start(args, count);
691
692         rec = g_new0(CMD_TEMP_REC, 1);
693         rec->data = g_strdup(data);
694         *free_me = rec;
695
696         datad = rec->data;
697         error = FALSE;
698
699         item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
700                 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
701
702         if (count & PARAM_FLAG_OPTIONS) {
703                 arg = (char *) va_arg(args, char *);
704                 opthash = (GHashTable **) va_arg(args, GHashTable **);
705
706                 rec->options = *opthash =
707                         g_hash_table_new((GHashFunc) g_istr_hash,
708                                          (GCompareFunc) g_istr_equal);
709
710                 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
711                 error = get_cmd_options(&datad, ignore_unknown,
712                                         arg, *opthash);
713         }
714
715         if (!error) {
716                 /* and now handle the string */
717                 cnt = PARAM_WITHOUT_FLAGS(count);
718                 if (count & PARAM_FLAG_OPTCHAN) {
719                         /* optional channel as first parameter */
720                         require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
721                                 PARAM_FLAG_OPTCHAN_NAME;
722                         arg = get_optional_channel(item, &datad, require_name);
723
724                         str = (char **) va_arg(args, char **);
725                         if (str != NULL) *str = arg;
726                         cnt--;
727                 }
728
729                 while (cnt-- > 0) {
730                         if (cnt == 0 && count & PARAM_FLAG_GETREST) {
731                                 /* get rest */
732                                 arg = datad;
733                         } else {
734                                 arg = (count & PARAM_FLAG_NOQUOTES) ?
735                                         cmd_get_param(&datad) :
736                                         cmd_get_quoted_param(&datad);
737                         }
738
739                         str = (char **) va_arg(args, char **);
740                         if (str != NULL) *str = arg;
741                 }
742         }
743         va_end(args);
744
745         if (error) {
746                 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
747                 signal_stop();
748
749                 cmd_params_free(rec);
750                 *free_me = NULL;
751         }
752
753         return !error;
754 }
755
756 void cmd_params_free(void *free_me)
757 {
758         CMD_TEMP_REC *rec = free_me;
759
760         if (rec->options != NULL) g_hash_table_destroy(rec->options);
761         g_free(rec->data);
762         g_free(rec);
763 }
764
765 static void command_module_unbind_all(COMMAND_REC *rec,
766                                       COMMAND_MODULE_REC *modrec)
767 {
768         GSList *tmp, *next;
769
770         for (tmp = modrec->signals; tmp != NULL; tmp = next) {
771                 next = tmp->next;
772
773                 command_unbind(rec->cmd, (SIGNAL_FUNC) tmp->data);
774         }
775
776         if (g_slist_find(commands, rec) != NULL) {
777                 /* this module might have removed some options
778                    from command, update them. */
779                 command_update_options(rec);
780         }
781 }
782
783 void commands_remove_module(const char *module)
784 {
785         GSList *tmp, *next, *modlist;
786
787         g_return_if_fail(module != NULL);
788
789         for (tmp = commands; tmp != NULL; tmp = next) {
790                 COMMAND_REC *rec = tmp->data;
791
792                 next = tmp->next;
793                 modlist = gslist_find_string(rec->modules, module);
794                 if (modlist != NULL)
795                         command_module_unbind_all(rec, modlist->data);
796         }
797 }
798
799 static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
800 {
801         GSList *tmp;
802
803         for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
804                 COMMAND_MODULE_REC *rec = tmp->data;
805
806                 if (rec->protocol == -1) {
807                         /* at least one module accepts the command
808                            without specific protocol */
809                         return 1;
810                 }
811
812                 if (server != NULL && rec->protocol == server->chat_type) {
813                         /* matching protocol found */
814                         return 1;
815                 }
816         }
817
818         return 0;
819 }
820
821 #define alias_runstack_push(alias) \
822         alias_runstack = g_slist_append(alias_runstack, alias)
823
824 #define alias_runstack_pop(alias) \
825         alias_runstack = g_slist_remove(alias_runstack, alias)
826
827 #define alias_runstack_find(alias) \
828         (gslist_find_icase_string(alias_runstack, alias) != NULL)
829
830 static void parse_command(const char *command, int expand_aliases,
831                           SERVER_REC *server, void *item)
832 {
833         COMMAND_REC *rec;
834         const char *alias, *newcmd;
835         char *cmd, *orig, *args, *oldcmd;
836
837         g_return_if_fail(command != NULL);
838
839         cmd = orig = g_strconcat("command ", command, NULL);
840         args = strchr(cmd+8, ' ');
841         if (args != NULL) *args++ = '\0'; else args = "";
842
843         /* check if there's an alias for command. Don't allow
844            recursive aliases */
845         alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
846                 alias_find(cmd+8);
847         if (alias != NULL) {
848                 alias_runstack_push(cmd+8);
849                 eval_special_string(alias, args, server, item);
850                 alias_runstack_pop(cmd+8);
851                 g_free(orig);
852                 return;
853         }
854
855         /* check if this command can be expanded */
856         newcmd = command_expand(cmd+8);
857         if (newcmd == NULL) {
858                 /* ambiguous command */
859                 g_free(orig);
860                 return;
861         }
862
863         rec = command_find(newcmd);
864         if (rec != NULL && !cmd_protocol_match(rec, server)) {
865                 g_free(orig);
866
867                 signal_emit("error command", 2,
868                             GINT_TO_POINTER(server == NULL ?
869                                             CMDERR_NOT_CONNECTED :
870                                             CMDERR_ILLEGAL_PROTO));
871                 return;
872         }
873
874         cmd = g_strconcat("command ", newcmd, NULL);
875         g_strdown(cmd);
876
877         oldcmd = current_command;
878         current_command = cmd+8;
879         if (server != NULL) server_ref(server);
880         if (!signal_emit(cmd, 3, args, server, item)) {
881                 signal_emit_id(signal_default_command, 3,
882                                command, server, item);
883         }
884         if (server != NULL) {
885                 if (server->connection_lost)
886                         server_disconnect(server);
887                 server_unref(server);
888         }
889         current_command = oldcmd;
890
891         g_free(cmd);
892         g_free(orig);
893 }
894
895 static void event_command(const char *line, SERVER_REC *server, void *item)
896 {
897         char *cmdchar;
898         int expand_aliases = TRUE;
899
900         g_return_if_fail(line != NULL);
901
902         if (*line == '\0') {
903                 /* empty line, forget it. */
904                 signal_stop();
905                 return;
906         }
907
908         cmdchar = strchr(settings_get_str("cmdchars"), *line);
909         if (cmdchar != NULL && line[1] == ' ') {
910                 /* "/ text" = same as sending "text" to active channel. */
911                 line += 2;
912                 cmdchar = NULL;
913         }
914         if (cmdchar == NULL) {
915                 /* non-command - let someone else handle this */
916                 signal_emit("send text", 3, line, server, item);
917                 return;
918         }
919
920         /* same cmdchar twice ignores aliases ignores aliases */
921         line++;
922         if (*line == *cmdchar) {
923                 line++;
924                 expand_aliases = FALSE;
925         }
926
927         /* ^command hides the output - we'll do this at fe-common but
928            we have to skip the ^ char here.. */
929         if (*line == '^') line++;
930
931         parse_command(line, expand_aliases, server, item);
932 }
933
934 /* SYNTAX: EVAL <command(s)> */
935 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
936 {
937         g_return_if_fail(data != NULL);
938
939         eval_special_string(data, "", server, item);
940 }
941
942 /* SYNTAX: CD <directory> */
943 static void cmd_cd(const char *data)
944 {
945         char *str;
946
947         g_return_if_fail(data != NULL);
948         if (*data == '\0') return;
949
950         str = convert_home(data);
951         chdir(str);
952         g_free(str);
953 }
954
955 void commands_init(void)
956 {
957         commands = NULL;
958         current_command = NULL;
959         alias_runstack = NULL;
960
961         signal_default_command = signal_get_uniq_id("default command");
962
963         settings_add_str("misc", "cmdchars", "/");
964         signal_add("send command", (SIGNAL_FUNC) event_command);
965
966         command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
967         command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
968 }
969
970 void commands_deinit(void)
971 {
972         g_free_not_null(current_command);
973
974         signal_remove("send command", (SIGNAL_FUNC) event_command);
975
976         command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
977         command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
978 }