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