Coverage Report

Created: 2026-09-01 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/irssi/src/core/special-vars.c
Line
Count
Source
1
/*
2
 special-vars.c : irssi
3
4
    Copyright (C) 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/expandos.h>
23
#include <irssi/src/core/misc.h>
24
#include <irssi/src/core/refstrings.h>
25
#include <irssi/src/core/servers.h>
26
#include <irssi/src/core/settings.h>
27
#include <irssi/src/core/signals.h>
28
#include <irssi/src/core/special-vars.h>
29
#include <irssi/src/core/utf8.h>
30
31
#define isvarchar(c) \
32
894k
        (i_isalnum(c) || (c) == '_')
33
34
#define isarg(c) \
35
61.3M
  (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-')
36
37
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* fuzzer should not exhaust memory here */
38
8.53M
#define ALIGN_MAX 512
39
#else
40
#define ALIGN_MAX 222488
41
#endif
42
43
static SPECIAL_HISTORY_FUNC history_func = NULL;
44
static GSList *special_collector;
45
static GSList *special_cache;
46
47
static char *get_argument(char **cmd, char **arglist)
48
23.3M
{
49
23.3M
  GString *str;
50
23.3M
  char *ret;
51
23.3M
  int max, arg, argcount;
52
53
23.3M
  arg = 0;
54
23.3M
  max = -1;
55
56
23.3M
  argcount = arglist == NULL ? 0 : g_strv_length(arglist);
57
58
23.3M
  if (**cmd == '*') {
59
    /* get all arguments */
60
22.1M
  } else if (**cmd == '~') {
61
    /* get last argument */
62
56.8k
    arg = max = argcount-1;
63
22.0M
  } else {
64
22.0M
    if (i_isdigit(**cmd)) {
65
      /* first argument */
66
18.3M
      arg = max = (**cmd)-'0';
67
18.3M
      (*cmd)++;
68
18.3M
    }
69
70
22.0M
    if (**cmd == '-') {
71
      /* get more than one argument */
72
4.66M
      (*cmd)++;
73
4.66M
      if (!i_isdigit(**cmd))
74
4.37M
        max = -1; /* get all the rest */
75
290k
      else {
76
290k
        max = (**cmd)-'0';
77
290k
        (*cmd)++;
78
290k
      }
79
4.66M
    }
80
22.0M
    (*cmd)--;
81
22.0M
  }
82
83
23.3M
  str = g_string_new(NULL);
84
35.4M
  while (arg >= 0 && arg < argcount && (arg <= max || max == -1)) {
85
12.0M
    g_string_append(str, arglist[arg]);
86
12.0M
    g_string_append_c(str, ' ');
87
12.0M
    arg++;
88
12.0M
  }
89
23.3M
  if (str->len > 0) g_string_truncate(str, str->len-1);
90
91
23.3M
  ret = g_string_free_and_steal(str);
92
23.3M
  return ret;
93
23.3M
}
94
95
static char *get_long_variable_value(const char *key, SERVER_REC *server,
96
             void *item, int *free_ret)
97
18
{
98
18
  EXPANDO_FUNC func;
99
18
  const char *ret;
100
18
  SETTINGS_REC *rec;
101
102
18
  *free_ret = FALSE;
103
104
  /* expando? */
105
18
        func = expando_find_long(key);
106
18
  if (func != NULL) {
107
18
    current_expando = key;
108
18
    return func(server, item, free_ret);
109
18
  }
110
111
  /* internal setting? */
112
0
  rec = settings_get_record(key);
113
0
  if (rec != NULL) {
114
0
    *free_ret = TRUE;
115
0
    return settings_get_print(rec);
116
0
  }
117
118
  /* environment variable? */
119
0
  ret = g_getenv(key);
120
0
  if (ret != NULL)
121
0
    return (char *) ret;
122
123
0
  return NULL;
124
0
}
125
126
static gboolean cache_find(GSList **cache, const char *var, char **ret)
127
894k
{
128
894k
  GSList *tmp;
129
894k
  GSList *prev = NULL;
130
131
894k
  if (cache == NULL)
132
0
    return FALSE;
133
134
894k
  for (tmp = *cache; tmp;) {
135
0
    if (g_strcmp0(var, tmp->data) == 0) {
136
0
      *ret = tmp->next->data;
137
0
      if (prev != NULL)
138
0
        prev->next->next = tmp->next->next;
139
0
      else
140
0
        *cache = tmp->next->next;
141
142
0
      g_slist_free_1(tmp->next);
143
0
      g_slist_free_1(tmp);
144
0
      return TRUE;
145
0
    }
146
0
    prev = tmp;
147
0
    tmp = tmp->next->next;
148
0
  }
149
894k
  return FALSE;
150
894k
}
151
152
static gboolean cache_find_char(GSList **cache, char var, char **ret)
153
894k
{
154
894k
  char varn[] = { var, '\0' };
155
894k
  return cache_find(cache, varn, ret);
156
894k
}
157
158
static char *get_long_variable(char **cmd, SERVER_REC *server, void *item, int *free_ret,
159
                               int getname, GSList **collector, GSList **cache)
160
18
{
161
18
  char *start, *var, *ret;
162
163
  /* get variable name */
164
18
  start = *cmd;
165
126
  while (isvarchar((*cmd)[1])) (*cmd)++;
166
167
18
  var = g_strndup(start, (int) (*cmd-start)+1);
168
18
  if (getname) {
169
0
    *free_ret = TRUE;
170
0
                return var;
171
0
  }
172
18
  if (cache_find(cache, var, &ret)) {
173
0
    g_free(var);
174
0
    return ret;
175
0
  }
176
18
  ret = get_long_variable_value(var, server, item, free_ret);
177
18
  if (collector != NULL) {
178
0
    *collector = g_slist_prepend(*collector, g_strdup(ret));
179
0
    *collector = g_slist_prepend(*collector, i_refstr_intern(var));
180
0
  }
181
18
  g_free(var);
182
18
  return ret;
183
18
}
184
185
/* return the value of the variable found from `cmd'.
186
   if 'getname' is TRUE, return the name of the variable instead it's value */
187
static char *get_variable(char **cmd, SERVER_REC *server, void *item, char **arglist, int *free_ret,
188
                          int *arg_used, int getname, GSList **collector, GSList **cache)
189
24.2M
{
190
24.2M
  EXPANDO_FUNC func;
191
192
24.2M
  if (isarg(**cmd)) {
193
    /* argument */
194
23.3M
    *free_ret = TRUE;
195
23.3M
    if (arg_used != NULL) *arg_used = TRUE;
196
23.3M
    return getname ? g_strdup_printf("%c", **cmd) :
197
23.3M
      get_argument(cmd, arglist);
198
23.3M
  }
199
200
894k
  if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
201
    /* long variable name.. */
202
18
    return get_long_variable(cmd, server, item, free_ret, getname, collector, cache);
203
18
  }
204
205
  /* single character variable. */
206
894k
  if (getname) {
207
0
    *free_ret = TRUE;
208
0
                return g_strdup_printf("%c", **cmd);
209
0
  }
210
894k
  *free_ret = FALSE;
211
894k
  {
212
894k
    char *ret;
213
894k
    if (cache_find_char(cache, **cmd, &ret)) {
214
0
      return ret;
215
0
    }
216
894k
  }
217
894k
  func = expando_find_char(**cmd);
218
894k
  if (func == NULL)
219
0
    return NULL;
220
894k
  else {
221
894k
    char str[2];
222
894k
    char *ret;
223
224
894k
    str[0] = **cmd; str[1] = '\0';
225
894k
    current_expando = str;
226
894k
    ret = func(server, item, free_ret);
227
894k
    if (**cmd != 'Z' && collector != NULL) {
228
0
      *collector = g_slist_prepend(*collector, g_strdup(ret));
229
0
      *collector = g_slist_prepend(*collector, i_refstr_intern(str));
230
0
    }
231
894k
    return ret;
232
894k
  }
233
894k
}
234
235
static char *get_history(char **cmd, void *item, int *free_ret)
236
0
{
237
0
  char *start, *text, *ret;
238
239
  /* get variable name */
240
0
  start = ++(*cmd);
241
0
  while (**cmd != '\0' && **cmd != '!') (*cmd)++;
242
243
0
  if (history_func == NULL)
244
0
    ret = NULL;
245
0
  else {
246
0
    text = g_strndup(start, (int) (*cmd-start));
247
0
    ret = history_func(text, item, free_ret);
248
0
    g_free(text);
249
0
  }
250
251
0
  if (**cmd == '\0') (*cmd)--;
252
0
  return ret;
253
0
}
254
255
static char *get_special_value(char **cmd, SERVER_REC *server, void *item, char **arglist,
256
                               int *free_ret, int *arg_used, int flags, GSList **collector,
257
                               GSList **cache)
258
38.9M
{
259
38.9M
  char command, *value, *p;
260
38.9M
  int len;
261
262
38.9M
  if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
263
14.7M
    *free_ret = TRUE;
264
14.7M
    return g_strdup_printf("$%c", **cmd);
265
14.7M
  }
266
267
24.2M
  if (**cmd == '!') {
268
    /* find text from command history */
269
0
    if (flags & PARSE_FLAG_GETNAME)
270
0
      return "!";
271
272
0
    return get_history(cmd, item, free_ret);
273
0
  }
274
275
24.2M
  command = 0;
276
24.2M
  if (**cmd == '#' || **cmd == '@') {
277
0
                command = **cmd;
278
0
    if ((*cmd)[1] != '\0')
279
0
      (*cmd)++;
280
0
    else {
281
      /* default to $* */
282
0
      char *temp_cmd = "*";
283
284
0
      if (flags & PARSE_FLAG_GETNAME)
285
0
                                return "*";
286
287
0
      *free_ret = TRUE;
288
0
      return get_argument(&temp_cmd, arglist);
289
0
    }
290
0
  }
291
292
24.2M
  value = get_variable(cmd, server, item, arglist, free_ret, arg_used,
293
24.2M
                       flags & PARSE_FLAG_GETNAME, collector, cache);
294
295
24.2M
  if (flags & PARSE_FLAG_GETNAME)
296
0
    return value;
297
298
24.2M
  if (command == '#') {
299
    /* number of words */
300
0
    if (value == NULL || *value == '\0') {
301
0
      if (value != NULL && *free_ret) {
302
0
        g_free(value);
303
0
        *free_ret = FALSE;
304
0
      }
305
0
      return "0";
306
0
    }
307
308
0
    len = 1;
309
0
    for (p = value; *p != '\0'; p++) {
310
0
      if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
311
0
        len++;
312
0
    }
313
0
                if (*free_ret) g_free(value);
314
315
0
    *free_ret = TRUE;
316
0
    return g_strdup_printf("%d", len);
317
0
  }
318
319
24.2M
  if (command == '@') {
320
    /* number of characters */
321
0
    if (value == NULL) return "0";
322
323
0
    len = strlen(value);
324
0
                if (*free_ret) g_free(value);
325
326
0
    *free_ret = TRUE;
327
0
    return g_strdup_printf("%d", len);
328
0
  }
329
330
24.2M
  return value;
331
24.2M
}
332
333
/* get alignment arguments (inside the []) */
334
static int get_alignment_args(char **data, int *align, int *flags, char *pad)
335
8.63M
{
336
8.63M
  char *str;
337
8.63M
  char *endptr;
338
8.63M
  guint align_;
339
340
8.63M
  *align = 0;
341
8.63M
  *flags = ALIGN_CUT|ALIGN_PAD;
342
8.63M
        *pad = ' ';
343
344
  /* '!' = don't cut, '-' = right padding */
345
8.63M
  str = *data;
346
23.4M
  while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
347
14.8M
    if (*str == '!')
348
122k
      *flags &= ~ALIGN_CUT;
349
14.6M
    else if (*str == '-')
350
566k
      *flags |= ALIGN_RIGHT;
351
14.1M
    else if (*str == '.')
352
4.61k
                         *flags &= ~ALIGN_PAD;
353
14.8M
    str++;
354
14.8M
  }
355
8.63M
  if (!i_isdigit(*str))
356
51.3k
    return FALSE; /* expecting number */
357
358
  /* get the alignment size */
359
8.58M
  if (!parse_uint(str, &endptr, 10, &align_)) {
360
52.6k
    return FALSE;
361
52.6k
  }
362
  /* alignment larger than supported */
363
8.53M
  if (align_ > ALIGN_MAX) {
364
202k
    return FALSE;
365
202k
  }
366
8.32M
  str = endptr;
367
8.32M
  *align = align_;
368
369
  /* get the pad character */
370
724M
  while (*str != '\0' && *str != ']') {
371
716M
    *pad = *str;
372
716M
    str++;
373
716M
  }
374
375
8.32M
  if (*str++ != ']') return FALSE;
376
377
8.20M
  *data = str;
378
8.20M
  return TRUE;
379
8.32M
}
380
381
/* return the aligned text */
382
char *get_alignment(const char *text, int align, int flags, char pad)
383
8.20M
{
384
8.20M
  GString *str;
385
8.20M
  char *ret;
386
8.20M
  int policy;
387
8.20M
  unsigned int cut_bytes;
388
389
8.20M
  g_return_val_if_fail(text != NULL, NULL);
390
391
8.20M
  policy = string_policy(text);
392
393
8.20M
  str = g_string_new(text);
394
395
  /* cut */
396
8.20M
  if ((flags & ALIGN_CUT) && align > 0 && string_width(text, policy) > align) {
397
122k
    string_chars_for_width(text, policy, align, &cut_bytes);
398
122k
    g_string_truncate(str, cut_bytes);
399
122k
  }
400
401
  /* add pad characters */
402
8.20M
  if (flags & ALIGN_PAD) {
403
8.20M
    int pad_len = align - string_width(str->str, policy);
404
8.20M
    if (pad_len > 0) {
405
8.00M
      char *pad_full = g_strnfill(pad_len, pad);
406
8.00M
      if (flags & ALIGN_RIGHT)
407
523k
        g_string_prepend(str, pad_full);
408
7.48M
      else
409
7.48M
        g_string_append(str, pad_full);
410
8.00M
      g_free(pad_full);
411
8.00M
    }
412
8.20M
  }
413
414
8.20M
  ret = g_string_free_and_steal(str);
415
8.20M
  return ret;
416
8.20M
}
417
418
/* Parse and expand text after '$' character. return value has to be
419
   g_free()'d if `free_ret' is TRUE. */
420
char *parse_special(char **cmd, SERVER_REC *server, void *item,
421
        char **arglist, int *free_ret, int *arg_used, int flags)
422
39.3M
{
423
39.3M
  static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
424
39.3M
  char command, *value;
425
426
39.3M
  char align_pad = '\0';
427
39.3M
  int align = 0, align_flags = 0;
428
429
39.3M
  char *nest_value;
430
39.3M
  int brackets, nest_free;
431
432
39.3M
  *free_ret = FALSE;
433
39.3M
  if (**cmd == '\0')
434
412
    return NULL;
435
436
39.3M
  command = **cmd; (*cmd)++;
437
39.3M
  switch (command) {
438
8.63M
  case '[':
439
    /* alignment */
440
8.63M
    if (!get_alignment_args(cmd, &align, &align_flags,
441
8.63M
          &align_pad) || **cmd == '\0') {
442
434k
      (*cmd)--;
443
434k
      return NULL;
444
434k
    }
445
8.20M
    break;
446
30.7M
  default:
447
30.7M
    command = 0;
448
30.7M
    (*cmd)--;
449
39.3M
  }
450
451
38.9M
  nest_free = FALSE; nest_value = NULL;
452
#if 0 /* this code is disabled due to security issues until it is fixed */
453
  if (**cmd == '(' && (*cmd)[1] != '\0') {
454
    /* subvariable */
455
    int toplevel = nested_orig_cmd == NULL;
456
457
    if (toplevel) nested_orig_cmd = cmd;
458
    (*cmd)++;
459
    if (**cmd != '$') {
460
      /* ... */
461
      nest_value = *cmd;
462
    } else {
463
      (*cmd)++;
464
      nest_value = parse_special(cmd, server, item, arglist,
465
               &nest_free, arg_used,
466
               flags);
467
    }
468
469
    if (nest_value == NULL || *nest_value == '\0')
470
      return NULL;
471
472
    while ((*nested_orig_cmd)[1] != '\0') {
473
      (*nested_orig_cmd)++;
474
      if (**nested_orig_cmd == ')')
475
        break;
476
    }
477
    cmd = &nest_value;
478
479
                if (toplevel) nested_orig_cmd = NULL;
480
  }
481
#else
482
38.9M
  if (nested_orig_cmd) nested_orig_cmd = NULL;
483
38.9M
#endif
484
485
38.9M
  if (**cmd != '{')
486
35.5M
    brackets = FALSE;
487
3.41M
  else {
488
    /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
489
3.41M
    if ((*cmd)[1] == '\0')
490
1.74k
      return NULL;
491
3.41M
    (*cmd)++;
492
3.41M
    brackets = TRUE;
493
3.41M
  }
494
495
38.9M
  value = get_special_value(cmd, server, item, arglist, free_ret, arg_used, flags,
496
38.9M
                            special_collector != NULL ? special_collector->data : NULL,
497
38.9M
                            &special_cache);
498
38.9M
  if (**cmd == '\0')
499
38.9M
    g_error("parse_special() : buffer overflow!");
500
501
38.9M
  if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
502
0
    *arg_used = TRUE;
503
504
38.9M
  if (brackets) {
505
1.35G
    while (**cmd != '}' && (*cmd)[1] != '\0')
506
1.34G
      (*cmd)++;
507
3.41M
  }
508
509
38.9M
  if (nest_free) g_free(nest_value);
510
511
38.9M
  if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
512
    /* alignment */
513
8.20M
    char *p;
514
515
8.20M
    if (value == NULL) return "";
516
517
8.20M
    p = get_alignment(value, align, align_flags, align_pad);
518
8.20M
    if (*free_ret) g_free(value);
519
520
8.20M
    *free_ret = TRUE;
521
8.20M
    return p;
522
8.20M
  }
523
524
30.7M
  return value;
525
38.9M
}
526
527
static void gstring_append_escaped(GString *str, const char *text, int flags)
528
7.52M
{
529
7.52M
  char esc[4], *escpos;
530
531
7.52M
  escpos = esc;
532
7.52M
  if (flags & PARSE_FLAG_ESCAPE_VARS)
533
0
    *escpos++ = '%';
534
7.52M
  if (flags & PARSE_FLAG_ESCAPE_THEME) {
535
0
    *escpos++ = '{';
536
0
    *escpos++ = '}';
537
0
  }
538
539
7.52M
  if (escpos == esc) {
540
7.52M
    g_string_append(str, text);
541
7.52M
    return;
542
7.52M
  }
543
544
0
  *escpos = '\0';
545
0
  while (*text != '\0') {
546
0
    for (escpos = esc; *escpos != '\0'; escpos++) {
547
0
      if (*text == *escpos) {
548
0
                          g_string_append_c(str, '%');
549
0
                          break;
550
0
                  }
551
0
    }
552
0
    g_string_append_c(str, *text);
553
0
    text++;
554
0
  }
555
0
}
556
557
/* parse the whole string. $ and \ chars are replaced */
558
char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
559
         const char *data, int *arg_used, int flags)
560
3.17M
{
561
3.17M
  char code, **arglist, *ret;
562
3.17M
  GString *str;
563
3.17M
  int need_free, chr;
564
565
3.17M
  g_return_val_if_fail(cmd != NULL, NULL);
566
3.17M
  g_return_val_if_fail(data != NULL, NULL);
567
568
  /* create the argument list */
569
3.17M
  arglist = g_strsplit(data, " ", -1);
570
571
3.17M
  if (arg_used != NULL) *arg_used = FALSE;
572
3.17M
  code = 0;
573
3.17M
  str = g_string_new(NULL);
574
78.5M
  while (*cmd != '\0') {
575
75.3M
    if (code == '\\') {
576
124k
      if (*cmd == ';')
577
124k
        g_string_append_c(str, ';');
578
109k
      else {
579
109k
        chr = expand_escape(&cmd);
580
109k
        g_string_append_c(str, chr != -1 ? chr : *cmd);
581
109k
      }
582
124k
      code = 0;
583
75.2M
    } else if (code == '$') {
584
7.68M
      char *ret;
585
586
7.68M
      ret = parse_special((char **) &cmd, server, item,
587
7.68M
              arglist, &need_free, arg_used,
588
7.68M
              flags);
589
7.68M
      if (ret != NULL) {
590
7.52M
                                gstring_append_escaped(str, ret, flags);
591
7.52M
        if (need_free) g_free(ret);
592
7.52M
      }
593
7.68M
      code = 0;
594
67.5M
    } else {
595
67.5M
      if (*cmd == '\\' || *cmd == '$')
596
7.82M
        code = *cmd;
597
59.7M
      else
598
67.5M
        g_string_append_c(str, *cmd);
599
67.5M
    }
600
601
75.3M
                cmd++;
602
75.3M
  }
603
3.17M
  g_strfreev(arglist);
604
605
3.17M
  ret = g_string_free_and_steal(str);
606
3.17M
  return ret;
607
3.17M
}
608
609
#define is_split_char(str, start) \
610
0
  ((str)[0] == ';' && ((start) == (str) || \
611
0
    ((str)[-1] != '\\' && (str)[-1] != '$')))
612
613
/* execute the commands in string - commands can be split with ';' */
614
void eval_special_string(const char *cmd, const char *data,
615
       SERVER_REC *server, void *item)
616
0
{
617
0
  const char *cmdchars;
618
0
  char *orig, *str, *start, *ret;
619
0
  int arg_used, arg_used_ever;
620
0
  GSList *commands;
621
622
0
  commands = NULL;
623
0
  arg_used_ever = FALSE;
624
0
  cmdchars = settings_get_str("cmdchars");
625
626
  /* get a list of all the commands to run */
627
0
  orig = start = str = g_strdup(cmd);
628
0
  do {
629
0
    if (is_split_char(str, start)) {
630
0
      *str++ = '\0';
631
0
                        while (*str == ' ') str++;
632
0
    } else if (*str != '\0') {
633
0
      str++;
634
0
      continue;
635
0
    }
636
637
0
    ret = parse_special_string(start, server, item,
638
0
             data, &arg_used, 0);
639
0
    if (*ret != '\0') {
640
0
      if (arg_used) arg_used_ever = TRUE;
641
642
0
      if (strchr(cmdchars, *ret) == NULL) {
643
        /* no command char - let's put it there.. */
644
0
        char *old = ret;
645
646
0
        ret = g_strdup_printf("%c%s", *cmdchars, old);
647
0
        g_free(old);
648
0
      }
649
0
      commands = g_slist_append(commands, ret);
650
0
    }
651
0
    start = str;
652
0
  } while (*start != '\0');
653
654
  /* run the command, if no arguments were ever used, append all of them
655
     after each command */
656
0
  while (commands != NULL) {
657
0
    ret = commands->data;
658
659
0
    if (!arg_used_ever && *data != '\0') {
660
0
      char *old = ret;
661
662
0
      ret = g_strconcat(old, " ", data, NULL);
663
0
      g_free(old);
664
0
    }
665
666
0
                if (server != NULL)
667
0
      server_ref(server);
668
0
    signal_emit("send command", 3, ret, server, item);
669
670
0
    if (server != NULL && !server_unref(server)) {
671
                        /* the server was destroyed */
672
0
      server = NULL;
673
0
                        item = NULL;
674
0
    }
675
676
    /* FIXME: window item would need reference counting as well,
677
       eg. "/EVAL win close;say hello" wouldn't work now.. */
678
679
0
    commands = g_slist_remove(commands, commands->data);
680
0
    g_free(ret);
681
0
  }
682
0
  g_free(orig);
683
0
}
684
685
void special_history_func_set(SPECIAL_HISTORY_FUNC func)
686
8
{
687
8
  history_func = func;
688
8
}
689
690
void special_push_collector(GSList **list)
691
0
{
692
0
  special_collector = g_slist_prepend(special_collector, list);
693
0
}
694
695
void special_pop_collector(void)
696
0
{
697
0
  special_collector = g_slist_delete_link(special_collector, special_collector);
698
0
}
699
700
void special_fill_cache(GSList *list)
701
0
{
702
0
  g_slist_free(special_cache);
703
0
  special_cache = g_slist_copy(list);
704
0
}
705
706
static void update_signals_hash(GHashTable **hash, int *signals)
707
0
{
708
0
  void *signal_id;
709
0
        int arg_type;
710
711
0
  if (*hash == NULL) {
712
0
    *hash = g_hash_table_new((GHashFunc) g_direct_hash,
713
0
           (GCompareFunc) g_direct_equal);
714
0
  }
715
716
0
  while (*signals != -1) {
717
0
                signal_id = GINT_TO_POINTER(*signals);
718
0
    arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id));
719
0
    if (arg_type != 0 && arg_type != signals[1]) {
720
      /* same signal is used for different purposes ..
721
         not sure if this should ever happen, but change
722
         the argument type to none so it will at least
723
         work. */
724
0
      arg_type = EXPANDO_ARG_NONE;
725
0
    }
726
727
0
    if (arg_type == 0) arg_type = signals[1];
728
0
    g_hash_table_insert(*hash, signal_id,
729
0
            GINT_TO_POINTER(arg_type));
730
0
    signals += 2;
731
0
  }
732
0
}
733
734
static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
735
0
{
736
0
  (*pos)[0] = GPOINTER_TO_INT(signal_id);
737
0
        (*pos)[1] = GPOINTER_TO_INT(arg_type);
738
0
        (*pos) += 2;
739
0
}
740
741
static int *get_signals_list(GHashTable *hash)
742
0
{
743
0
  int *signals, *pos;
744
745
0
  if (hash == NULL) {
746
    /* no expandos in text - never needs updating */
747
0
    return NULL;
748
0
  }
749
750
0
        pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
751
0
  g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
752
0
        *pos = -1;
753
754
0
  g_hash_table_destroy(hash);
755
0
        return signals;
756
757
0
}
758
759
0
#define TASK_BIND   1
760
0
#define TASK_UNBIND   2
761
0
#define TASK_GET_SIGNALS  3
762
763
static int *special_vars_signals_task(const char *text, int funccount,
764
              SIGNAL_FUNC *funcs, int task)
765
0
{
766
0
        GHashTable *signals;
767
0
  char *expando;
768
0
  int need_free, *expando_signals;
769
770
0
        signals = NULL;
771
0
  while (*text != '\0') {
772
0
    if (*text == '\\' && text[1] != '\0') {
773
                        /* escape */
774
0
      text += 2;
775
0
    } else if (*text == '$' && text[1] != '\0') {
776
                        /* expando */
777
0
      text++;
778
0
      expando = parse_special((char **) &text, NULL, NULL,
779
0
            NULL, &need_free, NULL,
780
0
            PARSE_FLAG_GETNAME);
781
0
      if (expando == NULL)
782
0
        continue;
783
784
0
      switch (task) {
785
0
      case TASK_BIND:
786
0
        expando_bind(expando, funccount, funcs);
787
0
        break;
788
0
      case TASK_UNBIND:
789
0
        expando_unbind(expando, funccount, funcs);
790
0
        break;
791
0
      case TASK_GET_SIGNALS:
792
0
        expando_signals = expando_get_signals(expando);
793
0
        if (expando_signals != NULL) {
794
0
          update_signals_hash(&signals,
795
0
                  expando_signals);
796
0
                                        g_free(expando_signals);
797
0
        }
798
0
        break;
799
0
      }
800
0
      if (need_free) g_free(expando);
801
0
    } else {
802
                        /* just a char */
803
0
      text++;
804
0
    }
805
0
  }
806
807
0
  if (task == TASK_GET_SIGNALS)
808
0
                return get_signals_list(signals);
809
810
0
        return NULL;
811
0
}
812
813
void special_vars_add_signals(const char *text,
814
            int funccount, SIGNAL_FUNC *funcs)
815
0
{
816
0
        special_vars_signals_task(text, funccount, funcs, TASK_BIND);
817
0
}
818
819
void special_vars_remove_signals(const char *text,
820
         int funccount, SIGNAL_FUNC *funcs)
821
0
{
822
0
        special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
823
0
}
824
825
int *special_vars_get_signals(const char *text)
826
0
{
827
0
  return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);
828
0
}
829
830
void special_vars_init(void)
831
8
{
832
8
  special_cache = NULL;
833
8
  special_collector = NULL;
834
8
}
835
836
void special_vars_deinit(void)
837
0
{
838
0
  g_slist_free(special_cache);
839
0
  g_slist_free(special_collector);
840
0
}