/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 | } |