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