Coverage Report

Created: 2026-01-21 07:20

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