1 /* strings.c -- misc string manipulation function */
2
3 /*
4 * This file is part of CliFM
5 *
6 * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7 * All rights reserved.
8
9 * CliFM is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * CliFM is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301, USA.
23 */
24
25 #include "helpers.h"
26
27 #ifdef __HAIKU__
28 #include <stdint.h>
29 #endif
30 #include <glob.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <wchar.h>
36 #if !defined(__HAIKU__) && !defined(__OpenBSD__)
37 #include <wordexp.h>
38 #endif
39
40 #include "aux.h"
41 #include "checks.h"
42 #include "exec.h"
43 #include "navigation.h"
44 #include "readline.h"
45
46 #define CMD_LEN_MAX (PATH_MAX + ((NAME_MAX + 1) << 1))
47 char len_buf[CMD_LEN_MAX] __attribute__((aligned));
48
49 /* states: S_N: normal, S_I: comparing integral part, S_F: comparing
50 fractionnal parts, S_Z: idem but with leading Zeroes only */
51 #define S_N 0x0
52 #define S_I 0x3
53 #define S_F 0x6
54 #define S_Z 0x9
55
56 /* result_type: VCMP: return diff; VLEN: compare using len_diff/diff */
57 #define VCMP 2
58 #define VLEN 3
59
60 #define MAX_STR_SZ 4096
61
62
63 /* Just a strlen that sets a read limit in case of non-null terminated
64 * string */
65 size_t
xstrnlen(const char * restrict s)66 xstrnlen(const char *restrict s)
67 {
68 return (size_t)((char *)memchr(s, '\0', MAX_STR_SZ) - s);
69 }
70
71 /* Taken from NNN's source code: very clever. Copy SRC into DST
72 * and return the string size all at once */
73 size_t
xstrsncpy(char * restrict dst,const char * restrict src,size_t n)74 xstrsncpy(char *restrict dst, const char *restrict src, size_t n)
75 {
76 n++;
77 char *end = memccpy(dst, src, '\0', n);
78 if (!end) {
79 dst[n - 1] = '\0';
80 end = dst + n;
81 }
82
83 return (size_t)(end - dst - 1);
84 }
85
86 /* strverscmp() is a GNU extension, and as such not available on some systems
87 * This function is a modified version of the GLIBC and uClibc strverscmp()
88 * taken from here:
89 * https://elixir.bootlin.com/uclibc-ng/latest/source/libc/string/strverscmp.c
90 */
91
92 /* Compare S1 and S2 as strings holding indices/version numbers,
93 returning less than, equal to or greater than zero if S1 is less than,
94 equal to or greater than S2 (for more info, see the texinfo doc).
95 */
96 int
xstrverscmp(const char * s1,const char * s2)97 xstrverscmp(const char *s1, const char *s2)
98 {
99 if ((*s1 & 0xc0) == 0xc0 || (*s2 & 0xc0) == 0xc0)
100 return strcoll(s1, s2);
101
102 const unsigned char *p1 = (const unsigned char *)s1;
103 const unsigned char *p2 = (const unsigned char *)s2;
104
105 /* Symbol(s) 0 [1-9] others
106 Transition (10) 0 (01) d (00) x */
107 static const uint8_t next_state[] = {
108 /* state x d 0 */
109 /* S_N */ S_N, S_I, S_Z,
110 /* S_I */ S_N, S_I, S_I,
111 /* S_F */ S_N, S_F, S_F,
112 /* S_Z */ S_N, S_F, S_Z
113 };
114
115 static const int8_t result_type[] __attribute__ ((aligned)) = {
116 /* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */
117 /* S_N */ VCMP, VCMP, VCMP, VCMP, VLEN, VCMP, VCMP, VCMP, VCMP,
118 /* S_I */ VCMP, -1, -1, 1, VLEN, VLEN, 1, VLEN, VLEN,
119 /* S_F */ VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP,
120 /* S_Z */ VCMP, 1, 1, -1, VCMP, VCMP, -1, VCMP, VCMP
121 };
122
123 unsigned char c1, c2;
124 int state, diff;
125
126 if (p1 == p2)
127 return 0;
128
129 if (!case_sensitive) {
130 c1 = TOUPPER(*p1);
131 ++p1;
132 c2 = TOUPPER(*p2);
133 ++p2;
134 } else {
135 c1 = *p1++;
136 c2 = *p2++;
137 }
138
139 /* Hint: '0' is a digit too. */
140 state = S_N + ((c1 == '0') + (_ISDIGIT(c1) != 0));
141
142 while ((diff = c1 - c2) == 0) {
143 if (c1 == '\0')
144 return diff;
145
146 state = next_state[state];
147 if (!case_sensitive) {
148 c1 = TOUPPER(*p1);
149 ++p1;
150 c2 = TOUPPER(*p2);
151 ++p2;
152 } else {
153 c1 = *p1++;
154 c2 = *p2++;
155 }
156 state += (c1 == '0') + (_ISDIGIT(c1) != 0);
157 }
158
159 state = result_type[state * 3 + (((c2 == '0') + (_ISDIGIT(c2) != 0)))];
160
161 switch (state) {
162 case VCMP: return diff;
163 case VLEN:
164 while (_ISDIGIT(*p1++))
165 if (!_ISDIGIT(*p2++))
166 return 1;
167
168 return _ISDIGIT(*p2) ? -1 : diff;
169
170 default: return state;
171 }
172 }
173
174 /* A strlen implementation able to handle wide chars */
175 size_t
wc_xstrlen(const char * restrict str)176 wc_xstrlen(const char *restrict str)
177 {
178 size_t len, _len;
179 wchar_t *const wbuf = (wchar_t *)len_buf;
180
181 /* Convert multi-byte to wide char */
182 _len = mbstowcs(wbuf, str, NAME_MAX);
183 int p = wcswidth(wbuf, _len);
184 if (p != -1)
185 len = (size_t)p;
186 else
187 len = 0;
188
189 return len;
190 }
191
192 /* Truncate an UTF-8 string at width N. Returns the difference beetween
193 * N and the point at which STR was actually trimmed (this difference
194 * should be added to STR as spaces to equate N and get a correct length)
195 * Since a wide char could take two o more columns to be draw, and since
196 * you might want to trim the name in the middle of a wide char, this
197 * function won't store the last wide char to avoid taking more columns
198 * than N. In this case, the programmer should take care of filling the
199 * empty spaces (usually no more than one) herself */
200 int
u8truncstr(char * restrict str,size_t n)201 u8truncstr(char *restrict str, size_t n)
202 {
203 int len = 0;
204 wchar_t buf[PATH_MAX] = {0};
205 if (mbstowcs(buf, str, PATH_MAX) == (size_t)-1)
206 return 0;
207
208 int i = 0;
209 for (; buf[i]; i++) {
210 int l = wcwidth(buf[i]);
211 if (len + l > (int)n) {
212 buf[i] = L'\0';
213 break;
214 }
215 len += l;
216 }
217
218 wcscpy((wchar_t *)str, buf);
219 return (int)n - len;
220 }
221
222 /* Returns the index of the first appearance of c in str, if any, and
223 * -1 if c was not found or if no str. NOTE: Same thing as strchr(),
224 * except that returns an index, not a pointer */
225 int
strcntchr(const char * str,const char c)226 strcntchr(const char *str, const char c)
227 {
228 if (!str)
229 return -1;
230
231 register int i = 0;
232
233 while (*str) {
234 if (*str == c)
235 return i;
236 i++;
237 str++;
238 }
239
240 return -1;
241 }
242
243 /* Returns the index of the last appearance of c in str, if any, and
244 * -1 if c was not found or if no str */
245 int
strcntchrlst(const char * str,const char c)246 strcntchrlst(const char *str, const char c)
247 {
248 if (!str)
249 return -1;
250
251 register int i = 0;
252
253 int p = -1;
254 while (*str) {
255 if (*str == c)
256 p = i;
257 i++;
258 str++;
259 }
260
261 return p;
262 }
263
264 /* Returns the string after the last appearance of a given char, or
265 * NULL if no match */
266 char *
straftlst(char * str,const char c)267 straftlst(char *str, const char c)
268 {
269 if (!str || !*str || !c)
270 return (char *)NULL;
271
272 char *p = str, *q = (char *)NULL;
273
274 while (*p) {
275 if (*p == c)
276 q = p;
277 p++;
278 }
279
280 if (!q || !*(q + 1))
281 return (char *)NULL;
282
283 char *buf = (char *)malloc(strlen(q + 1) + 1);
284
285 if (!buf)
286 return (char *)NULL;
287
288 strcpy(buf, q + 1);
289 return buf;
290 }
291
292 /* Get substring in STR before the last appearance of C. Returns
293 * substring if C is found and NULL if not (or if C was the first
294 * char in STR). */
295 char *
strbfrlst(char * str,const char c)296 strbfrlst(char *str, const char c)
297 {
298 if (!str || !*str || !c)
299 return (char *)NULL;
300
301 char *p = str, *q = (char *)NULL;
302 while (*p) {
303 if (*p == c)
304 q = p;
305 p++;
306 }
307
308 if (!q || q == str)
309 return (char *)NULL;
310
311 *q = '\0';
312
313 char *buf = (char *)malloc((size_t)(q - str + 1));
314 if (!buf) {
315 *q = c;
316 return (char *)NULL;
317 }
318
319 strcpy(buf, str);
320 *q = c;
321 return buf;
322 }
323
324 /* Returns the string between first ocurrence of A and the first
325 * ocurrence of B in STR, or NULL if: there is nothing between A and
326 * B, or A and/or B are not found */
327 char *
strbtw(char * str,const char a,const char b)328 strbtw(char *str, const char a, const char b)
329 {
330 if (!str || !*str || !a || !b)
331 return (char *)NULL;
332
333 char *p = str, *pa = (char *)NULL, *pb = (char *)NULL;
334 while (*p) {
335 if (!pa) {
336 if (*p == a)
337 pa = p;
338 } else if (*p == b) {
339 pb = p;
340 break;
341 }
342 p++;
343 }
344
345 if (!pb)
346 return (char *)NULL;
347
348 *pb = '\0';
349
350 char *buf = (char *)malloc((size_t)(pb - pa));
351
352 if (!buf) {
353 *pb = b;
354 return (char *)NULL;
355 }
356
357 strcpy(buf, pa + 1);
358 *pb = b;
359 return buf;
360 }
361
362 /* Replace the first occurrence of NEEDLE in HAYSTACK by REP */
363 char *
replace_substr(char * haystack,char * needle,char * rep)364 replace_substr(char *haystack, char *needle, char *rep)
365 {
366 if (!haystack || !*haystack || !needle || !*needle || !rep)
367 return (char *)NULL;
368
369 char *ret = strstr(haystack, needle);
370 if (!ret)
371 return (char *)NULL;
372
373 char *needle_end = ret + strlen(needle);
374 *ret = '\0';
375
376 if (*needle_end) {
377 size_t rem_len = strlen(needle_end);
378 char *rem = (char *)xnmalloc(rem_len + 1, sizeof(char));
379 strcpy(rem, needle_end);
380
381 char *new_str = (char *)xnmalloc(strlen(haystack) + strlen(rep)
382 + rem_len + 1, sizeof(char));
383 strcpy(new_str, haystack);
384 strcat(new_str, rep);
385 strcat(new_str, rem);
386 free(rem);
387 return new_str;
388 }
389
390 char *new_str = (char *)xnmalloc(strlen(haystack) + strlen(rep)
391 + 1, sizeof(char));
392 strcpy(new_str, haystack);
393 strcat(new_str, rep);
394 return new_str;
395 }
396
397 /* Generate a random string of LEN bytes using characters from CHARSET */
398 char *
gen_rand_str(size_t len)399 gen_rand_str(size_t len)
400 {
401 char charset[] = "0123456789#%-_"
402 "abcdefghijklmnopqrstuvwxyz"
403 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
404
405 srand((unsigned int)time(NULL));
406
407 char *str = (char *)malloc((len + 1) * sizeof(char));
408 char *p = str;
409
410 if (!p) {
411 fprintf(stderr, "Error allocating %zu bytes\n", len);
412 return (char *)NULL;
413 }
414
415 while (len--) {
416 int i = rand() % (int)(sizeof(charset) - 1);
417 *p++ = charset[i];
418 }
419
420 *p = '\0';
421 return str;
422 }
423
424 /* Removes end of line char and quotes (single and double) from STR.
425 * Returns a pointer to the modified STR if the result is non-blank
426 * or NULL */
427 char *
remove_quotes(char * str)428 remove_quotes(char *str)
429 {
430 if (!str || !*str)
431 return (char *)NULL;
432
433 char *p = str;
434 size_t len = strlen(p);
435
436 if (len > 0 && p[len - 1] == '\n') {
437 p[len - 1] = '\0';
438 len--;
439 }
440
441 if (len > 0 && (p[len - 1] == '\'' || p[len - 1] == '"'))
442 p[len - 1] = '\0';
443
444 if (*p == '\'' || *p == '"')
445 p++;
446
447 if (!*p)
448 return (char *)NULL;
449
450 char *q = p;
451 int blank = 1;
452
453 while (*q) {
454 if (*q != ' ' && *q != '\n' && *q != '\t') {
455 blank = 0;
456 break;
457 }
458 q++;
459 }
460
461 if (!blank)
462 return p;
463 return (char *)NULL;
464 }
465
466 /* This function takes a string as argument and split it into substrings
467 * taking tab, new line char, and space as word delimiters, except when
468 * they are preceded by a quote char (single or double quotes) or in
469 * case of command substitution ($(cmd) or `cmd`), in which case
470 * eveything after the corresponding closing char is taken as one single
471 * string. It also escapes spaecial chars. It returns an array of
472 * splitted strings (without leading and terminating spaces) or NULL if
473 * str is NULL or if no substring was found, i.e., if str contains
474 * only spaces. */
475 char **
split_str(const char * str)476 split_str(const char *str)
477 {
478 if (!str)
479 return (char **)NULL;
480
481 size_t buf_len = 0, words = 0, str_len = 0;
482 char *buf = (char *)NULL;
483 buf = (char *)xnmalloc(1, sizeof(char));
484 int quote = 0, close = 0;
485 char **substr = (char **)NULL;
486
487 while (*str) {
488 switch (*str) {
489 /* Command substitution */
490 case '$': /* fallthrough */
491 case '`':
492 /* Define the closing char: If "$(" then ')', else '`' */
493 if (*str == '$') {
494 /* If escaped, it has no special meaning */
495 if ((str_len && *(str - 1) == '\\') || *(str + 1) != '(') {
496 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
497 buf[buf_len++] = *str;
498 break;
499 } else {
500 close = ')';
501 }
502 } else {
503 /* If escaped, it has no special meaning */
504 if (str_len && *(str - 1) == '\\') {
505 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
506 buf[buf_len++] = *str;
507 break;
508 } else {
509 /* If '`' advance one char. Otherwise the while
510 * below will stop at first char, which is not
511 * what we want */
512 close = *str;
513 str++;
514 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
515 buf[buf_len++] = '`';
516 }
517 }
518
519 /* Copy everything until null byte or closing char */
520 while (*str && *str != close) {
521 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
522 buf[buf_len++] = *(str++);
523 }
524
525 /* If the while loop stopped with a null byte, there was
526 * no ending close (either ')' or '`')*/
527 if (!*str) {
528 fprintf(stderr, _("%s: Missing '%c'\n"), PROGRAM_NAME,
529 close);
530
531 free(buf);
532 buf = (char *)NULL;
533 int i = (int)words;
534
535 while (--i >= 0)
536 free(substr[i]);
537 free(substr);
538
539 return (char **)NULL;
540 }
541
542 /* Copy the closing char and add an space: this function
543 * takes space as word breaking char, so that everything
544 * in the buffer will be copied as one single word */
545 buf = (char *)xrealloc(buf, (buf_len + 2) * sizeof(char *));
546 buf[buf_len++] = *str;
547 buf[buf_len] = ' ';
548
549 break;
550
551 case '\'': /* fallthrough */
552 case '"':
553 /* If the quote is escaped, it has no special meaning */
554 if (str_len && *(str - 1) == '\\') {
555 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
556 buf[buf_len++] = *str;
557 break;
558 }
559
560 /* If not escaped, move on to the next char */
561 quote = *str;
562 str++;
563
564 /* Copy into the buffer whatever is after the first quote
565 * up to the last quote or NULL */
566 while (*str && *str != quote) {
567 /* If char has special meaning, escape it */
568 if (is_quote_char(*str)) {
569 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
570 buf[buf_len++] = '\\';
571 }
572
573 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
574 buf[buf_len++] = *(str++);
575 }
576
577 /* The above while breaks with NULL or quote, so that if
578 * *str is a null byte there was not ending quote */
579 if (!*str) {
580 fprintf(stderr, _("%s: Missing '%c'\n"), PROGRAM_NAME, quote);
581 /* Free the current buffer and whatever was already
582 * allocated */
583 free(buf);
584 buf = (char *)NULL;
585 int i = (int)words;
586
587 while (--i >= 0)
588 free(substr[i]);
589 free(substr);
590 return (char **)NULL;
591 }
592 break;
593
594 /* TAB, new line char, and space are taken as word breaking
595 * characters */
596 case '\t':
597 case '\n':
598 case ' ':
599 /* If escaped, just copy it into the buffer */
600 if (str_len && *(str - 1) == '\\') {
601 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
602 buf[buf_len++] = *str;
603 } else {
604 /* If not escaped, break the string */
605 /* Add a terminating null byte to the buffer, and, if
606 * not empty, dump the buffer into the substrings
607 * array */
608 buf[buf_len] = '\0';
609
610 if (buf_len > 0) {
611 substr = (char **)xrealloc(substr, (words + 1) * sizeof(char *));
612 substr[words] = savestring(buf, buf_len);
613 words++;
614 }
615
616 /* Clear te buffer to get a new string */
617 memset(buf, '\0', buf_len);
618 buf_len = 0;
619 }
620 break;
621
622 /* If neither a quote nor a breaking word char nor command
623 * substitution, just dump it into the buffer */
624 default:
625 buf = (char *)xrealloc(buf, (buf_len + 1) * sizeof(char *));
626 buf[buf_len++] = *str;
627 break;
628 }
629
630 str++;
631 str_len++;
632 }
633
634 /* The while loop stops when the null byte is reached, so that the
635 * last substring is not printed, but still stored in the buffer.
636 * Therefore, we need to add it, if not empty, to our subtrings
637 * array */
638 buf[buf_len] = '\0';
639
640 if (buf_len > 0) {
641 if (!words)
642 substr = (char **)xcalloc(words + 1, sizeof(char *));
643 else
644 substr = (char **)xrealloc(substr, (words + 1) * sizeof(char *));
645
646 substr[words] = savestring(buf, buf_len);
647 words++;
648 }
649
650 free(buf);
651 buf = (char *)NULL;
652
653 if (words) {
654 /* Add a final null string to the array */
655 substr = (char **)xrealloc(substr, (words + 1) * sizeof(char *));
656 substr[words] = (char *)NULL;
657
658 args_n = words - 1;
659 return substr;
660 } else {
661 args_n = 0; /* Just in case, but I think it's not needed */
662 return (char **)NULL;
663 }
664 }
665
666 /* Return 1 if STR contains only numbers of a range of numbers, and zero
667 * if not */
668 static int
check_fused_param(const char * str)669 check_fused_param(const char *str)
670 {
671 char *p = (char *)str;
672 size_t c = 0, i = 0;
673 int ok = 1;
674
675 while (*p) {
676 if (i && *p == '-' && *(p - 1) >= '0' && *(p - 1) <= '9'
677 && *(p + 1) >= '1' && *(p + 1) <= '9') {
678 c++;
679 } else if (*p == ' ') {
680 break;
681 } else if (*p < '0' || *p > '9') {
682 ok = 0;
683 break;
684 }
685 p++;
686 i++;
687 }
688
689 if (ok && c <= 1)
690 return 1;
691 return 0;
692 }
693
694 /* Check CMD against a list of internal commands taking ELN's or numbers
695 * as parameters. Used by split_fusedcmd() */
696 int
is_internal_f(const char * restrict cmd)697 is_internal_f(const char *restrict cmd)
698 {
699 const char *int_cmds[] = {
700 "ac", "ad",
701 "bb", "bleach",
702 "bm", "bookmarks",
703 "br", "bulk",
704 "c", "cp",
705 "cd",
706 "d", "dup",
707 "exp",
708 "l", "ln", "le",
709 "m", "mv",
710 "md", "mkdir",
711 "mf",
712 "n", "new",
713 "o", "open", "ow",
714 "p", "pp", "pr", "prop",
715 "paste",
716 "pin",
717 "r", "rm",
718 "s", "sel",
719 "st", "sort",
720 "t", "tr", "trash",
721 "te",
722 "unlink",
723 "ws",
724 NULL};
725
726 int i = (int)(sizeof(int_cmds) / sizeof(char *)) - 1;
727
728 while (--i >= 0) {
729 if (*cmd == *int_cmds[i] && strcmp(cmd, int_cmds[i]) == 0)
730 return 1;
731 }
732
733 return 0;
734 }
735
736 static char *
split_fusedcmd(char * str)737 split_fusedcmd(char *str)
738 {
739 if (!str || !*str || *str == ';' || *str == ':' || *str == '\\')
740 return (char *)NULL;
741
742 char *space = strchr(str, ' ');
743 char *slash = strchr(str, '/');
744
745 if (!space && slash) /* If "/some/path/" */
746 return (char *)NULL;
747
748 if (space && slash && slash < space) /* If "/some/string something" */
749 return (char *)NULL;
750
751 /* The buffer size is the double of STR, just in case each subtr
752 * needs to be splitted */
753 char *buf = (char *)xnmalloc(((strlen(str) * 2) + 2), sizeof(char));
754
755 size_t c = 0;
756 char *p = str, *pp = str, *b = buf;
757 size_t words = 1;
758 while (*p) {
759 switch(*p) {
760 case ' ': /* We only allow splitting for first command word */
761 if (c && *(p - 1) != ' ' && *(p - 1) != '|'
762 && *(p - 1) != '&' && *(p - 1) != ';')
763 words++;
764 if (*(p + 1))
765 pp = p + 1;
766 break;
767 case '&': // fallthrough
768 case '|': // fallthrough
769 case ';':
770 words = 1;
771 if (*(p + 1))
772 pp = p + 1;
773 break;
774 default: break;
775 }
776 if (words == 1 && c && *p >= '0' && *p <= '9'
777 && (*(p - 1) < '0' || *(p - 1) > '9')) {
778 if (check_fused_param(p)) {
779 char t = *p;
780 *p = '\0';
781 if (is_internal_f(pp))
782 *(b++) = ' ';
783 *p = t;
784 }
785 }
786 *(b++) = *(p++);
787 c++;
788 }
789
790 *b = '\0';
791
792 /* Readjust the buffer size */
793 size_t len = strlen(buf);
794 buf = (char *)xrealloc(buf, (len + 1) * sizeof(char));
795 return buf;
796 }
797
798 static int
check_shell_functions(char * str)799 check_shell_functions(char *str)
800 {
801 if (!str || !*str)
802 return 0;
803
804 if (!int_vars) {
805 char *s = strchr(str, ' ');
806 char *e = strchr(str, '=');
807 if (!s && e)
808 return 1;
809 if (s && e && e < s)
810 return 1;
811 }
812
813 /* char **b = (char **)NULL;
814
815 switch(shell) {
816 case SHELL_NONE: return 0;
817 case SHELL_BASH: b = bash_builtins; break;
818 case SHELL_DASH: b = dash_builtins; break;
819 case SHELL_KSH: b = ksh_builtins; break;
820 case SHELL_TCSH: b = tcsh_builtins; break;
821 case SHELL_ZSH: b = zsh_builtins; break;
822 default: return 0;
823 } */
824
825 char *funcs[] = {
826 "for ", "for(",
827 "do ", "do(",
828 "while ", "while(",
829 "until ", "until(",
830 "if ", "if(",
831 "[ ", "[[ ", "test ",
832 "case ", "case(",
833 "echo ", "printf ",
834 "declare ",
835 "(( ",
836 "set ",
837 "source ", ". ",
838 NULL
839 };
840
841 size_t i;
842 for (i = 0; funcs[i]; i++) {
843 size_t f_len = strlen(funcs[i]);
844 if (*str == *funcs[i] && strncmp(str, funcs[i], f_len) == 0)
845 return 1;
846 }
847
848 return 0;
849 }
850
851 /*
852 * This function is one of the keys of CliFM. It will perform a series of
853 * actions:
854 * 1) Take the string stored by readline and get its substrings without
855 * spaces.
856 * 2) In case of user defined variable (var=value), it will pass the
857 * whole string to exec_cmd(), which will take care of storing the
858 * variable;
859 * 3) If the input string begins with ';' or ':' the whole string is
860 * send to exec_cmd(), where it will be directly executed by the system
861 * shell (via launch_execle()) to prevent all of the expansions made
862 * here.
863 * 4) The following expansions (especific to CLiFM) are performed here:
864 * ELN's, "sel" keyword, ranges of numbers (ELN's), pinned dir and
865 * bookmark names, and, for internal commands only, tilde, braces,
866 * wildcards, command and paramenter substitution, and regex expansion
867 * are performed here as well.
868 * These expansions are the most import part of this function.
869 */
870
871 /* NOTE: Though file names could consist of everything except of slash
872 * and null characters, POSIX.1 recommends restricting file names to
873 * consist of the following characters: letters (a-z, A-Z), numbers
874 * (0-9), period (.), dash (-), and underscore ( _ ).
875
876 * NOTE 2: There is no any need to pass anything to this function, since
877 * the input string I need here is already in the readline buffer. So,
878 * instead of taking the buffer from a function parameter (str) I could
879 * simply use rl_line_buffer. However, since I use this function to
880 * parse other strings, like history lines, I need to keep the str
881 * argument */
882
883 /* This shit is HUGE! Almost 1000 LOC and a lot of indentation! */
884 char **
parse_input_str(char * str)885 parse_input_str(char *str)
886 {
887 register size_t i = 0;
888 int fusedcmd_ok = 0;
889
890 /** ###################### */
891 /* Before splitting 'CMDNUM' into 'CMD NUM', make sure CMDNUM is not
892 * a cmd in PATH (for example, md5sum) */
893 if (digit_found(str) && !is_bin_cmd(str)) {
894 char *p = split_fusedcmd(str);
895 if (p) {
896 fusedcmd_ok = 1;
897 str = p;
898 p = (char *)NULL;
899 }
900 }
901 /** ###################### */
902
903 /* ########################################
904 * # 0) CHECK FOR SPECIAL FUNCTIONS #
905 * ########################################*/
906
907 int chaining = 0, cond_cmd = 0, send_shell = 0;
908
909 /* ###########################
910 * # 0.a) RUN AS EXTERNAL #
911 * ###########################*/
912
913 /* If invoking a command via ';' or ':' set the send_shell flag to
914 * true and send the whole string to exec_cmd(), in which case no
915 * expansion is made: the command is send to the system shell as
916 * is. */
917 if (*str == ';' || *str == ':')
918 send_shell = 1;
919 else if (check_shell_functions(str))
920 send_shell = 1;
921
922 if (!send_shell) {
923 for (i = 0; str[i]; i++) {
924
925 /* ##################################
926 * # 0.b) CONDITIONAL EXECUTION #
927 * ##################################*/
928
929 /* Check for chained commands (cmd1;cmd2) */
930 if (!chaining && str[i] == ';' && i > 0 && str[i - 1] != '\\')
931 chaining = 1;
932
933 /* Check for conditional execution (cmd1 && cmd 2)*/
934 if (!cond_cmd && str[i] == '&' && i > 0 && str[i - 1] != '\\'
935 && str[i + 1] && str[i + 1] == '&')
936 cond_cmd = 1;
937
938 /* ##################################
939 * # 0.c) USER DEFINED VARIABLE #
940 * ##################################*/
941
942 /* If user defined variable send the whole string to
943 * exec_cmd(), which will take care of storing the
944 * variable. */
945 if (!(flags & IS_USRVAR_DEF) && str[i] == '=' && i > 0
946 && str[i - 1] != '\\' && str[0] != '=') {
947 /* Remove leading spaces. This: ' a="test"' should be
948 * taken as a valid variable declaration */
949 char *p = str;
950 while (*p == ' ' || *p == '\t')
951 p++;
952
953 /* If first non-space is a number, it's not a variable
954 * name */
955 if (!_ISDIGIT(*p)) {
956 int space_found = 0;
957 /* If there are no spaces before '=', take it as a
958 * variable. This check is done in order to avoid
959 * taking as a variable things like:
960 * 'ls -color=auto' */
961 while (*p != '=') {
962 if (*(p++) == ' ')
963 space_found = 1;
964 }
965
966 if (!space_found)
967 flags |= IS_USRVAR_DEF;
968 }
969
970 p = (char *)NULL;
971 }
972 }
973 }
974
975 /* If chained commands, check each of them. If at least one of them
976 * is internal, take care of the job (the system shell does not know
977 * our internal commands and therefore cannot execute them); else,
978 * if no internal command is found, let it to the system shell */
979 if (chaining || cond_cmd) {
980 /* User defined variables are always internal, so that there is
981 * no need to check whatever else is in the command string */
982 if (flags & IS_USRVAR_DEF) {
983 exec_chained_cmds(str);
984 if (fusedcmd_ok)
985 free(str);
986 return (char **)NULL;
987 }
988
989 register size_t j = 0;
990 size_t str_len = strlen(str), len = 0, internal_ok = 0;
991 char *buf = (char *)NULL;
992
993 /* Get each word (cmd) in STR */
994 buf = (char *)xcalloc(str_len + 1, sizeof(char));
995 for (j = 0; j < str_len; j++) {
996 while (str[j] && str[j] != ' ' && str[j] != ';' && str[j] != '&') {
997 buf[len++] = str[j++];
998 }
999
1000 if (strcmp(buf, "&&") != 0) {
1001 if (is_internal_c(buf)) {
1002 internal_ok = 1;
1003 break;
1004 }
1005 }
1006
1007 memset(buf, '\0', len);
1008 len = 0;
1009 }
1010
1011 free(buf);
1012 buf = (char *)NULL;
1013
1014 if (internal_ok) {
1015 exec_chained_cmds(str);
1016 if (fusedcmd_ok)
1017 free(str);
1018 return (char **)NULL;
1019 }
1020 }
1021
1022 if (flags & IS_USRVAR_DEF || send_shell) {
1023 /* Remove leading spaces, again */
1024 char *p = str;
1025 while (*p == ' ' || *p == '\t')
1026 p++;
1027
1028 args_n = 0;
1029
1030 char **cmd = (char **)NULL;
1031 cmd = (char **)xnmalloc(2, sizeof(char *));
1032 cmd[0] = savestring(p, strlen(p));
1033 cmd[1] = (char *)NULL;
1034
1035 p = (char *)NULL;
1036
1037 if (fusedcmd_ok)
1038 free(str);
1039
1040 return cmd;
1041 /* If ";cmd" or ":cmd" the whole input line will be send to
1042 * exec_cmd() and will be executed by the system shell via
1043 * execle(). Since we don't run split_str() here, dequoting
1044 * and deescaping is performed directly by the system shell */
1045 }
1046
1047 /* ################################################
1048 * # 1) SPLIT INPUT STRING INTO SUBSTRINGS #
1049 * ################################################ */
1050
1051 /* split_str() returns an array of strings without leading,
1052 * terminating and double spaces. */
1053 char **substr = split_str(str);
1054
1055 /** ###################### */
1056 if (fusedcmd_ok) /* Just in case split_fusedcmd returned NULL */
1057 free(str);
1058 /** ###################### */
1059
1060 /* NOTE: isspace() not only checks for space, but also for new line,
1061 * carriage return, vertical and horizontal TAB. Be careful when
1062 * replacing this function. */
1063
1064 if (!substr)
1065 return (char **)NULL;
1066
1067 /* Handle background/foreground process */
1068 bg_proc = 0;
1069
1070 if (*substr[args_n] == '&' && !*(substr[args_n] + 1)) {
1071 bg_proc = 1;
1072 free(substr[args_n]);
1073 substr[args_n--] = (char *)NULL;
1074 } else {
1075 size_t len = strlen(substr[args_n]);
1076 if (substr[args_n][len - 1] == '&' && !substr[args_n][len]) {
1077 substr[args_n][len - 1] = '\0';
1078 bg_proc = 1;
1079 }
1080 }
1081
1082 /* ######################
1083 * # TRASH AS RM #
1084 * ###################### */
1085 #ifndef _NO_TRASH
1086 if (tr_as_rm && substr[0] && *substr[0] == 'r' && !substr[0][1]) {
1087 substr[0] = (char *)xrealloc(substr[0], 3 * sizeof(char));
1088 *substr[0] = 't';
1089 substr[0][1] = 'r';
1090 substr[0][2] = '\0';
1091 }
1092 #endif
1093 /* ##############################
1094 * # 2) BUILTIN EXPANSIONS #
1095 * ##############################
1096
1097 * Ranges, sel, ELN, pinned dirs, bookmarks, and internal variables.
1098 * These expansions are specific to CliFM. To be able to use them
1099 * even with external commands, they must be expanded here, before
1100 * sending the input string, in case the command is external, to
1101 * the system shell */
1102
1103 is_sel = 0, sel_is_last = 0;
1104
1105 size_t int_array_max = 10, ranges_ok = 0;
1106 int *range_array = (int *)xnmalloc(int_array_max, sizeof(int));
1107
1108 for (i = 0; i <= args_n; i++) {
1109 if (!substr[i])
1110 continue;
1111
1112 register size_t j = 0;
1113 /* Replace . and .. by absolute paths */
1114 if (*substr[i] == '.' && (!substr[i][1] || (substr[i][1] == '.'
1115 && !substr[i][2]))) {
1116 char *tmp = (char *)NULL;
1117 tmp = realpath(substr[i], NULL);
1118 substr[i] = (char *)xrealloc(substr[i], (strlen(tmp) + 1)
1119 * sizeof(char));
1120 strcpy(substr[i], tmp);
1121 free(tmp);
1122 }
1123
1124 /* ######################################
1125 * # 2.a) FASTBACK EXPANSION #
1126 * ###################################### */
1127
1128 if (*substr[i] == '.' && substr[i][1] == '.' && substr[i][2] == '.') {
1129 char *tmp = fastback(substr[i]);
1130 if (tmp) {
1131 substr[i] = (char *)xrealloc(substr[i], (strlen(tmp) + 1)
1132 * sizeof(char));
1133 strcpy(substr[i], tmp);
1134 free(tmp);
1135 }
1136 }
1137
1138 /* ######################################
1139 * # 2.b) PINNED DIR EXPANSION #
1140 * ###################################### */
1141
1142 if (*substr[i] == ',' && !substr[i][1] && pinned_dir) {
1143 substr[i] = (char *)xrealloc(substr[i], (strlen(pinned_dir) + 1)
1144 * sizeof(char));
1145 strcpy(substr[i], pinned_dir);
1146 }
1147
1148 /* ######################################
1149 * # 2.c) BOOKMARKS EXPANSION #
1150 * ###################################### */
1151
1152 /* Expand bookmark names into paths */
1153 if (expand_bookmarks) {
1154 int bm_exp = 0;
1155
1156 for (j = 0; j < bm_n; j++) {
1157 if (bookmarks[j].name && *substr[i] == *bookmarks[j].name
1158 && strcmp(substr[i], bookmarks[j].name) == 0) {
1159
1160 /* Do not expand bookmark names that conflicts
1161 * with a file name in CWD */
1162 int conflict = 0, k = (int)files;
1163 while (--k >= 0) {
1164 if (*bookmarks[j].name == *file_info[k].name
1165 && strcmp(bookmarks[j].name, file_info[k].name) == 0) {
1166 conflict = 1;
1167 break;
1168 }
1169 }
1170
1171 if (!conflict && bookmarks[j].path) {
1172 substr[i] = (char *)xrealloc(substr[i],
1173 (strlen(bookmarks[j].path) + 1) * sizeof(char));
1174 strcpy(substr[i], bookmarks[j].path);
1175
1176 bm_exp = 1;
1177
1178 break;
1179 }
1180 }
1181 }
1182
1183 /* Do not perform further checks on the expanded bookmark */
1184 if (bm_exp)
1185 continue;
1186 }
1187
1188 /* ############################################# */
1189
1190 size_t substr_len = strlen(substr[i]);
1191
1192 /* Check for ranges */
1193 for (j = 0; substr[i][j]; j++) {
1194 /* If some alphabetic char, besides '-', is found in the
1195 * string, we have no range */
1196 if (substr[i][j] != '-' && !_ISDIGIT(substr[i][j]))
1197 break;
1198
1199 /* If a range is found, store its index */
1200 if (j > 0 && j < substr_len && substr[i][j] == '-' &&
1201 _ISDIGIT(substr[i][j - 1]) && _ISDIGIT(substr[i][j + 1]))
1202 if (ranges_ok < int_array_max)
1203 range_array[ranges_ok++] = (int)i;
1204 }
1205
1206 /* Expand 'sel' only as an argument, not as command */
1207 if (i > 0 && *substr[i] == 's' && strcmp(substr[i], "sel") == 0)
1208 is_sel = (short)i;
1209 }
1210
1211 /* ####################################
1212 * # 2.d) RANGES EXPANSION #
1213 * ####################################*/
1214
1215 /* Expand expressions like "1-3" to "1 2 3" if all the numbers in
1216 * the range correspond to an ELN */
1217
1218 if (ranges_ok) {
1219 size_t old_ranges_n = 0;
1220 register size_t r = 0;
1221
1222 for (r = 0; r < ranges_ok; r++) {
1223 size_t ranges_n = 0;
1224 int *ranges = expand_range(substr[range_array[r] +
1225 (int)old_ranges_n], 1);
1226 if (ranges) {
1227 register size_t j = 0;
1228
1229 for (ranges_n = 0; ranges[ranges_n]; ranges_n++);
1230
1231 char **ranges_cmd = (char **)NULL;
1232 ranges_cmd = (char **)xcalloc(args_n + ranges_n + 2,
1233 sizeof(char *));
1234
1235 for (i = 0; i < (size_t)range_array[r] + old_ranges_n; i++)
1236 ranges_cmd[j++] = savestring(substr[i], strlen(substr[i]));
1237
1238 for (i = 0; i < ranges_n; i++) {
1239 ranges_cmd[j] = (char *)xcalloc((size_t)DIGINUM(ranges[i])
1240 + 1, sizeof(int));
1241 sprintf(ranges_cmd[j++], "%d", ranges[i]);
1242 }
1243
1244 for (i = (size_t)range_array[r] + old_ranges_n + 1;
1245 i <= args_n; i++) {
1246 ranges_cmd[j++] = savestring(substr[i],
1247 strlen(substr[i]));
1248 }
1249
1250 ranges_cmd[j] = NULL;
1251 free(ranges);
1252
1253 for (i = 0; i <= args_n; i++)
1254 free(substr[i]);
1255
1256 substr = (char **)xrealloc(substr, (args_n + ranges_n + 2)
1257 * sizeof(char *));
1258
1259 for (i = 0; i < j; i++) {
1260 substr[i] = savestring(ranges_cmd[i], strlen(ranges_cmd[i]));
1261 free(ranges_cmd[i]);
1262 }
1263
1264 free(ranges_cmd);
1265 args_n = j - 1;
1266 }
1267
1268 old_ranges_n += (ranges_n - 1);
1269 }
1270 }
1271
1272 free(range_array);
1273
1274 /* ##########################
1275 * # 2.e) SEL EXPANSION #
1276 * ##########################*/
1277
1278 /* if (is_sel && *substr[0] != '/') { */
1279 if (is_sel) {
1280 if ((size_t)is_sel == args_n)
1281 sel_is_last = 1;
1282
1283 if (sel_n) {
1284 register size_t j = 0;
1285 char **sel_array = (char **)NULL;
1286 sel_array = (char **)xnmalloc(args_n + sel_n + 2, sizeof(char *));
1287
1288 for (i = 0; i < (size_t)is_sel; i++) {
1289 if (!substr[i])
1290 continue;
1291 sel_array[j++] = savestring(substr[i], strlen(substr[i]));
1292 }
1293
1294 for (i = 0; i < sel_n; i++) {
1295 /* Escape selected file names and copy them into tmp
1296 * array */
1297 char *esc_str = escape_str(sel_elements[i]);
1298 if (esc_str) {
1299 sel_array[j++] = savestring(esc_str, strlen(esc_str));
1300 free(esc_str);
1301 esc_str = (char *)NULL;
1302 } else {
1303 fprintf(stderr, _("%s: %s: Error quoting file name\n"),
1304 PROGRAM_NAME, sel_elements[j]);
1305 /* Free elements selected thus far and and all the
1306 * input substrings */
1307 register size_t k = 0;
1308 for (k = 0; k < j; k++)
1309 free(sel_array[k]);
1310 free(sel_array);
1311
1312 for (k = 0; k <= args_n; k++)
1313 free(substr[k]);
1314 free(substr);
1315
1316 return (char **)NULL;
1317 }
1318 }
1319
1320 for (i = (size_t)is_sel + 1; i <= args_n; i++)
1321 sel_array[j++] = savestring(substr[i], strlen(substr[i]));
1322
1323 for (i = 0; i <= args_n; i++)
1324 free(substr[i]);
1325
1326 substr = (char **)xrealloc(substr, (args_n + sel_n + 2)
1327 * sizeof(char *));
1328
1329 for (i = 0; i < j; i++) {
1330 substr[i] = savestring(sel_array[i], strlen(sel_array[i]));
1331 free(sel_array[i]);
1332 }
1333
1334 free(sel_array);
1335 substr[i] = (char *)NULL;
1336 args_n = j - 1;
1337 }
1338
1339 else {
1340 /* 'sel' is an argument, but there are no selected files. */
1341 fprintf(stderr, _("%c%s: There are no selected files%c"),
1342 kb_shortcut ? '\n' : '\0', PROGRAM_NAME,
1343 kb_shortcut ? '\0' : '\n');
1344
1345 register size_t j = 0;
1346 for (j = 0; j <= args_n; j++)
1347 free(substr[j]);
1348 free(substr);
1349
1350 return (char **)NULL;
1351 }
1352 }
1353
1354 int stdin_dir_ok = 0;
1355 if (stdin_tmp_dir && strcmp(ws[cur_ws].path, stdin_tmp_dir) == 0)
1356 stdin_dir_ok = 1;
1357
1358 for (i = 0; i <= args_n; i++) {
1359 if (!substr[i])
1360 continue;
1361
1362 /* ##########################
1363 * # 2.f) ELN EXPANSION #
1364 * ##########################*/
1365
1366 /* If autocd is set to false, i must be bigger than zero because
1367 * the first string in comm_array, the command name, should NOT
1368 * be expanded, but only arguments. Otherwise, if the expanded
1369 * ELN happens to be a program name as well, this program will
1370 * be executed, and this, for sure, is to be avoided */
1371
1372 /* The 'sort', 'mf', 'ws', and 'jo' commands take digits as
1373 * arguments. So, do not expand ELN's in these cases */
1374 if (substr[0] && strcmp(substr[0], "mf") != 0
1375 && strcmp(substr[0], "st") != 0 && strcmp(substr[0], "ws") != 0
1376 && strcmp(substr[0], "sort") != 0 && strcmp(substr[0], "jo") != 0) {
1377
1378 if (is_number(substr[i])) {
1379 /* Expand first word only if autocd is set to true */
1380 if ((i == 0 && !autocd && !auto_open) || !substr[i])
1381 continue;
1382
1383 int num = atoi(substr[i]);
1384 /* Expand numbers only if there is a corresponding ELN */
1385
1386 /* Do not expand ELN if there is a file named as the
1387 * ELN */
1388 if (eln_as_file_n) {
1389 int conflict = 0;
1390 if (eln_as_file_n > 1) {
1391 size_t j;
1392
1393 for (j = 0; j < eln_as_file_n; j++) {
1394 if (atoi(file_info[eln_as_file[j]].name) == num) {
1395 conflict = num;
1396 /* One conflicting file name is enough */
1397 break;
1398 }
1399 }
1400 } else {
1401 if (atoi(file_info[eln_as_file[0]].name) == num)
1402 conflict = num;
1403 }
1404
1405 if (conflict) {
1406 size_t j;
1407
1408 for (j = 0; j <= args_n; j++)
1409 free(substr[j]);
1410 free(substr);
1411
1412 fprintf(stderr, _("%s: %d: ELN-filename "
1413 "conflict. Bypass internal expansions "
1414 "to fix this issue: ';CMD "
1415 "FILENAME'\n"), PROGRAM_NAME, conflict);
1416 return (char **)NULL;
1417 }
1418 }
1419
1420 if (num > 0 && num <= (int)files) {
1421 /* Replace the ELN by the corresponding escaped
1422 * file name */
1423 int j = num - 1;
1424 char *esc_str = escape_str(file_info[j].name);
1425
1426 if (esc_str) {
1427 if (file_info[j].dir &&
1428 file_info[j].name[file_info[j].len - 1] != '/') {
1429 substr[i] = (char *)xrealloc(substr[i],
1430 (strlen(esc_str) + 2) * sizeof(char));
1431 sprintf(substr[i], "%s/", esc_str);
1432 } else {
1433 substr[i] = (char *)xrealloc(substr[i],
1434 (strlen(esc_str) + 1) * sizeof(char));
1435 strcpy(substr[i], esc_str);
1436 }
1437 free(esc_str);
1438 esc_str = (char *)NULL;
1439 } else {
1440 fprintf(stderr, _("%s: %s: Error quoting "
1441 "file name\n"),
1442 PROGRAM_NAME, file_info[num - 1].name);
1443 /* Free whatever was allocated thus far */
1444
1445 for (j = 0; j <= (int)args_n; j++)
1446 free(substr[j]);
1447 free(substr);
1448 return (char **)NULL;
1449 }
1450 }
1451 }
1452 }
1453
1454 /* #############################################
1455 * # 2.g) USER DEFINED VARIABLES EXPANSION #
1456 * #############################################*/
1457
1458 if (int_vars) {
1459 if (substr[i][0] == '$' && substr[i][1] != '(' && substr[i][1] != '{') {
1460 char *var_name = strchr(substr[i], '$');
1461 if (var_name && *(++var_name)) {
1462 int j = (int)usrvar_n;
1463 while (--j >= 0) {
1464 if (*var_name == *usr_var[j].name
1465 && strcmp(var_name, usr_var[j].name) == 0) {
1466 substr[i] = (char *)xrealloc(substr[i],
1467 (strlen(usr_var[j].value) + 1) * sizeof(char));
1468 strcpy(substr[i], usr_var[j].value);
1469 break;
1470 }
1471 }
1472 } else {
1473 fprintf(stderr, _("%s: %s: Error getting variable name\n"),
1474 PROGRAM_NAME, substr[i]);
1475 size_t j;
1476 for (j = 0; j <= args_n; j++)
1477 free(substr[j]);
1478 free(substr);
1479 return (char **)NULL;
1480 }
1481 }
1482 }
1483
1484 /* ###############################
1485 * # ENVIRONEMNT VARIABLES #
1486 * ###############################*/
1487
1488 if (*substr[i] == '$') {
1489 char *p = getenv(substr[i] + 1);
1490 if (p) {
1491 substr[i] = (char *)xrealloc(substr[i], (strlen(p) + 1) * sizeof(char));
1492 strcpy(substr[i], p);
1493 }
1494 }
1495
1496 /* We are in STDIN_TMP_DIR: Expand symlinks to target */
1497 if (stdin_dir_ok) {
1498 char *real_path = realpath(substr[i], NULL);
1499 if (real_path) {
1500 substr[i] = (char *)xrealloc(substr[i],
1501 (strlen(real_path) + 1) * sizeof(char));
1502 strcpy(substr[i], real_path);
1503 free(real_path);
1504 }
1505 }
1506 }
1507
1508 /* #### 3) NULL TERMINATE THE INPUT STRING ARRAY #### */
1509 substr = (char **)xrealloc(substr, sizeof(char *) * (args_n + 2));
1510 substr[args_n + 1] = (char *)NULL;
1511
1512 if (!is_internal(substr[0]))
1513 return substr;
1514
1515 /* #############################################################
1516 * # ONLY FOR INTERNAL COMMANDS #
1517 * #############################################################*/
1518
1519 /* Some functions of CliFM are purely internal, that is, they are not
1520 * wrappers of a shell command and do not call the system shell at all.
1521 * For this reason, some expansions normally made by the system shell
1522 * must be made here (in the lobby [got it?]) in order to be able to
1523 * understand these expansions at all. */
1524
1525 /* ###############################################
1526 * # 3) WILDCARD, BRACE, AND TILDE EXPANSION #
1527 * ############################################### */
1528
1529 int *glob_array = (int *)xnmalloc(int_array_max, sizeof(int));
1530 size_t glob_n = 0;
1531 #if !defined(__HAIKU__) && !defined(__OpenBSD__)
1532 int *word_array = (int *)xnmalloc(int_array_max, sizeof(int));
1533 size_t word_n = 0;
1534 #endif
1535
1536 for (i = 0; substr[i]; i++) {
1537 /* Do not perform any of the expansions below for selected
1538 * elements: they are full path file names that, as such, do not
1539 * need any expansion */
1540 if (is_sel) { /* is_sel is true only for the current input and if
1541 there was some "sel" keyword in it */
1542 /* Strings between is_sel and sel_n are selected file names */
1543 if (i >= (size_t)is_sel && i <= sel_n)
1544 continue;
1545 }
1546
1547 /* Ignore the first string of the search function: it will be
1548 * expanded by the search function itself */
1549 if (substr[0][0] == '/' && i == 0)
1550 continue;
1551
1552 /* Tilde expansion is made by glob() */
1553 if (*substr[i] == '~') {
1554 if (glob_n < int_array_max)
1555 glob_array[glob_n++] = (int)i;
1556 }
1557
1558 register size_t j = 0;
1559 for (j = 0; substr[i][j]; j++) {
1560 /* Brace and wildcard expansion is made by glob()
1561 * as well */
1562 if ((substr[i][j] == '*' || substr[i][j] == '?'
1563 || substr[i][j] == '[' || substr[i][j] == '{')
1564 && substr[i][j + 1] != ' ') {
1565 /* Strings containing these characters are taken as
1566 * wildacard patterns and are expanded by the glob
1567 * function. See man (7) glob */
1568 if (glob_n < int_array_max)
1569 glob_array[glob_n++] = (int)i;
1570 }
1571
1572 #if !defined(__HAIKU__) && !defined(__OpenBSD__)
1573 /* Command substitution is made by wordexp() */
1574 if (substr[i][j] == '$' && (substr[i][j + 1] == '('
1575 || substr[i][j + 1] == '{')) {
1576 if (word_n < int_array_max)
1577 word_array[word_n++] = (int)i;
1578 }
1579
1580 if (substr[i][j] == '`' && substr[i][j + 1] != ' ') {
1581 if (word_n < int_array_max)
1582 word_array[word_n++] = (int)i;
1583 }
1584 #endif /* __HAIKU__ */
1585 }
1586 }
1587
1588 /* Do not expand if command is deselect, sel or untrash, just to
1589 * allow the use of "*" for desel and untrash ("ds *" and "u *")
1590 * and to let the sel function handle patterns itself */
1591 if (glob_n && strcmp(substr[0], "s") != 0 && strcmp(substr[0], "sel") != 0
1592 && strcmp(substr[0], "ds") != 0 && strcmp(substr[0], "desel") != 0
1593 && strcmp(substr[0], "u") != 0 && strcmp(substr[0], "undel") != 0
1594 && strcmp(substr[0], "untrash") != 0) {
1595 /* 1) Expand glob
1596 2) Create a new array, say comm_array_glob, large enough to store
1597 the expanded glob and the remaining (non-glob) arguments
1598 (args_n+gl_pathc)
1599 3) Copy into this array everything before the glob
1600 (i=0;i<glob_char;i++)
1601 4) Copy the expanded elements (if none, copy the original element,
1602 comm_array[glob_char])
1603 5) Copy the remaining elements (i=glob_char+1;i<=args_n;i++)
1604 6) Free the old comm_array and fill it with comm_array_glob
1605 */
1606 size_t old_pathc = 0;
1607 /* glob_array stores the index of the globbed strings. However,
1608 * once the first expansion is done, the index of the next globbed
1609 * string has changed. To recover the next globbed string, and
1610 * more precisely, its index, we only need to add the amount of
1611 * files matched by the previous instances of glob(). Example:
1612 * if original indexes were 2 and 4, once 2 is expanded 4 stores
1613 * now some of the files expanded in 2. But if we add to 4 the
1614 * amount of files expanded in 2 (gl_pathc), we get now the
1615 * original globbed string pointed by 4.
1616 */
1617 register size_t g = 0;
1618 for (g = 0; g < (size_t)glob_n; g++) {
1619 glob_t globbuf;
1620
1621 if (glob(substr[glob_array[g] + (int)old_pathc],
1622 GLOB_BRACE | GLOB_TILDE, NULL, &globbuf) != EXIT_SUCCESS) {
1623 globfree(&globbuf);
1624 continue;
1625 }
1626
1627 if (globbuf.gl_pathc) {
1628 register size_t j = 0;
1629 char **glob_cmd = (char **)NULL;
1630 glob_cmd = (char **)xcalloc(args_n + globbuf.gl_pathc + 1,
1631 sizeof(char *));
1632
1633 for (i = 0; i < ((size_t)glob_array[g] + old_pathc); i++)
1634 glob_cmd[j++] = savestring(substr[i], strlen(substr[i]));
1635
1636 for (i = 0; i < globbuf.gl_pathc; i++) {
1637 /* Do not match "." or ".." */
1638 if (strcmp(globbuf.gl_pathv[i], ".") == 0
1639 || strcmp(globbuf.gl_pathv[i], "..") == 0)
1640 continue;
1641
1642 /* Escape the globbed file name and copy it */
1643 char *esc_str = escape_str(globbuf.gl_pathv[i]);
1644 if (esc_str) {
1645 glob_cmd[j++] = savestring(esc_str, strlen(esc_str));
1646 free(esc_str);
1647 } else {
1648 fprintf(stderr, _("%s: %s: Error quoting "
1649 "file name\n"), PROGRAM_NAME, globbuf.gl_pathv[i]);
1650 register size_t k = 0;
1651 for (k = 0; k < j; k++)
1652 free(glob_cmd[k]);
1653 free(glob_cmd);
1654 glob_cmd = (char **)NULL;
1655
1656 for (k = 0; k <= args_n; k++)
1657 free(substr[k]);
1658 free(substr);
1659 globfree(&globbuf);
1660 return (char **)NULL;
1661 }
1662 }
1663
1664 for (i = (size_t)glob_array[g] + old_pathc + 1;
1665 i <= args_n; i++)
1666 glob_cmd[j++] = savestring(substr[i], strlen(substr[i]));
1667
1668 glob_cmd[j] = (char *)NULL;
1669
1670 for (i = 0; i <= args_n; i++)
1671 free(substr[i]);
1672 free(substr);
1673
1674 substr = glob_cmd;
1675 glob_cmd = (char **)NULL;
1676 args_n = j - 1;
1677 }
1678
1679 old_pathc += (globbuf.gl_pathc - 1);
1680 globfree(&globbuf);
1681 }
1682 }
1683
1684 free(glob_array);
1685
1686 /* #############################################
1687 * # 4) COMMAND & PARAMETER SUBSTITUTION #
1688 * ############################################# */
1689 #if !defined(__HAIKU__) && !defined(__OpenBSD__)
1690 if (word_n) {
1691 size_t old_pathc = 0;
1692 register size_t w = 0;
1693 for (w = 0; w < (size_t)word_n; w++) {
1694 wordexp_t wordbuf;
1695 if (wordexp(substr[word_array[w] + (int)old_pathc],
1696 &wordbuf, 0) != EXIT_SUCCESS) {
1697 wordfree(&wordbuf);
1698 continue;
1699 }
1700
1701 if (wordbuf.we_wordc) {
1702 register size_t j = 0;
1703 char **word_cmd = (char **)NULL;
1704
1705 word_cmd = (char **)xcalloc(args_n + wordbuf.we_wordc + 1,
1706 sizeof(char *));
1707
1708 for (i = 0; i < ((size_t)word_array[w] + old_pathc); i++)
1709 word_cmd[j++] = savestring(substr[i], strlen(substr[i]));
1710
1711 for (i = 0; i < wordbuf.we_wordc; i++) {
1712 /* Escape the globbed file name and copy it*/
1713 char *esc_str = escape_str(wordbuf.we_wordv[i]);
1714 if (esc_str) {
1715 word_cmd[j++] = savestring(esc_str, strlen(esc_str));
1716 free(esc_str);
1717 } else {
1718 fprintf(stderr, _("%s: %s: Error quoting "
1719 "file name\n"), PROGRAM_NAME, wordbuf.we_wordv[i]);
1720
1721 register size_t k = 0;
1722 for (k = 0; k < j; k++)
1723 free(word_cmd[k]);
1724 free(word_cmd);
1725
1726 word_cmd = (char **)NULL;
1727
1728 for (k = 0; k <= args_n; k++)
1729 free(substr[k]);
1730 free(substr);
1731 return (char **)NULL;
1732 }
1733 }
1734
1735 for (i = (size_t)word_array[w] + old_pathc + 1;
1736 i <= args_n; i++)
1737 word_cmd[j++] = savestring(substr[i], strlen(substr[i]));
1738
1739 word_cmd[j] = (char *)NULL;
1740
1741 for (i = 0; i <= args_n; i++)
1742 free(substr[i]);
1743 free(substr);
1744 substr = word_cmd;
1745 word_cmd = (char **)NULL;
1746 args_n = j - 1;
1747 }
1748
1749 old_pathc += (wordbuf.we_wordc - 1);
1750 wordfree(&wordbuf);
1751 }
1752 }
1753
1754 free(word_array);
1755 #endif /* __HAIKU__ */
1756
1757 if (substr[0] && (*substr[0] == 'd' || *substr[0] == 'u')
1758 && (strcmp(substr[0], "desel") == 0 || strcmp(substr[0], "undel") == 0
1759 || strcmp(substr[0], "untrash") == 0)) {
1760 /* Null terminate the input string array (again) */
1761 substr = (char **)xrealloc(substr, (args_n + 2) * sizeof(char *));
1762 substr[args_n + 1] = (char *)NULL;
1763 return substr;
1764 }
1765
1766 /* #############################################
1767 * # 5) REGEX EXPANSION #
1768 * ############################################# */
1769
1770 if ((*substr[0] == 's' && (!substr[0][1] || strcmp(substr[0], "sel") == 0))
1771 || (*substr[0] == 'n' && (!substr[0][1] || strcmp(substr[0], "new") == 0)))
1772 return substr;
1773
1774 char **regex_files = (char **)xnmalloc(files + args_n + 2, sizeof(char *));
1775 size_t j, r_files = 0;
1776
1777 for (i = 0; substr[i]; i++) {
1778 if (r_files > (files + args_n))
1779 break;
1780
1781 /* Ignore the first string of the search function: it will be
1782 * expanded by the search function itself */
1783 if (*substr[0] == '/') {
1784 regex_files[r_files++] = substr[i];
1785 continue;
1786 }
1787
1788 /* At this point, all file names are escaped. But check_regex()
1789 * needs unescaped file names. So, let's deescape it */
1790 char *p = strchr(substr[i], '\\');
1791 char *dstr = (char *)NULL;
1792 if (p)
1793 dstr = dequote_str(substr[i], 0);
1794 int ret = check_regex(dstr ? dstr : substr[i]);
1795 free(dstr);
1796 if (ret != EXIT_SUCCESS) {
1797 regex_files[r_files++] = substr[i];
1798 continue;
1799 }
1800
1801 regex_t regex;
1802 if (regcomp(®ex, substr[i], REG_NOSUB | REG_EXTENDED) != EXIT_SUCCESS) {
1803 /* fprintf(stderr, "%s: %s: Invalid regular expression",
1804 PROGRAM_NAME, substr[i]); */
1805 regfree(®ex);
1806 regex_files[r_files++] = substr[i];
1807 continue;
1808 }
1809
1810 int reg_found = 0;
1811
1812 for (j = 0; j < files; j++) {
1813 if (regexec(®ex, file_info[j].name, 0, NULL, 0) == EXIT_SUCCESS) {
1814 regex_files[r_files++] = file_info[j].name;
1815 reg_found = 1;
1816 }
1817 }
1818
1819 if (!reg_found)
1820 regex_files[r_files++] = substr[i];
1821
1822 regfree(®ex);
1823 }
1824
1825 if (r_files) {
1826 regex_files[r_files] = (char *)NULL;
1827 char **tmp_files = (char **)xnmalloc(r_files + 2, sizeof(char *));
1828 size_t k = 0;
1829 for (j = 0; regex_files[j]; j++)
1830 tmp_files[k++] = savestring(regex_files[j], strlen(regex_files[j]));
1831 tmp_files[k] = (char *)NULL;
1832
1833 for (j = 0; j <= args_n; j++)
1834 free(substr[j]);
1835 free(substr);
1836
1837 substr = tmp_files;
1838 tmp_files = (char **)NULL;
1839 args_n = k - 1;
1840 free(tmp_files);
1841 }
1842
1843 free(regex_files);
1844 substr = (char **)xrealloc(substr, (args_n + 2) * sizeof(char *));
1845 substr[args_n + 1] = (char *)NULL;
1846
1847 return substr;
1848 }
1849
1850 /* Reduce "$HOME" to tilde ("~"). The new_path variable is always either
1851 * "$HOME" or "$HOME/file", that's why there's no need to check for
1852 * "/file" */
1853 char *
home_tilde(const char * new_path)1854 home_tilde(const char *new_path)
1855 {
1856 if (!home_ok || !new_path || !*new_path || !user.home)
1857 return (char *)NULL;
1858
1859 char *path_tilde = (char *)NULL;
1860
1861 /* If path == HOME */
1862 if (new_path[1] == user.home[1] && strcmp(new_path, user.home) == 0) {
1863 path_tilde = (char *)xnmalloc(2, sizeof(char));
1864 path_tilde[0] = '~';
1865 path_tilde[1] = '\0';
1866 } else if (new_path[1] == user.home[1]
1867 && strncmp(new_path, user.home, user.home_len) == 0) {
1868 /* If path == HOME/file */
1869 path_tilde = (char *)xnmalloc(strlen(new_path + user.home_len + 1) + 3,
1870 sizeof(char));
1871 sprintf(path_tilde, "~/%s", new_path + user.home_len + 1);
1872 } else {
1873 path_tilde = (char *)xnmalloc(strlen(new_path) + 1, sizeof(char));
1874 strcpy(path_tilde, new_path);
1875 }
1876
1877 return path_tilde;
1878 }
1879
1880 /* Expand a range of numbers given by str. It will expand the range
1881 * provided that both extremes are numbers, bigger than zero, equal or
1882 * smaller than the amount of files currently listed on the screen, and
1883 * the second (right) extreme is bigger than the first (left). Returns
1884 * an array of int's with the expanded range or NULL if one of the
1885 * above conditions is not met */
1886 int *
expand_range(char * str,int listdir)1887 expand_range(char *str, int listdir)
1888 {
1889 if (!str || !*str)
1890 return (int *)NULL;
1891
1892 char *p = strchr(str, '-');
1893 if (!p || p == str || *(p - 1) < '0' || *(p - 1) > '9'
1894 || !*(p + 1) || *(p + 1) < '0' || *(p + 1) > '9')
1895 return (int *)NULL;
1896
1897 *p = '\0';
1898 int ret = is_number(str);
1899 *p = '-';
1900 if (!ret)
1901 return (int *)NULL;
1902
1903 int afirst = atoi(str);
1904
1905 if (!is_number(++p))
1906 return (int *)NULL;
1907
1908 int asecond = atoi(p);
1909
1910 if (listdir) {
1911 if (afirst <= 0 || afirst > (int)files || asecond <= 0
1912 || asecond > (int)files || afirst >= asecond)
1913 return (int *)NULL;
1914 } else if (afirst >= asecond) {
1915 return (int *)NULL;
1916 }
1917
1918 int *buf = (int *)NULL;
1919 buf = (int *)xcalloc((size_t)(asecond - afirst) + 2, sizeof(int));
1920
1921 size_t i, j = 0;
1922 for (i = (size_t)afirst; i <= (size_t)asecond; i++)
1923 buf[j++] = (int)i;
1924
1925 return buf;
1926 }
1927
1928 /* used a lot.
1929 * creates a copy of a string */
1930 char *
savestring(const char * restrict str,size_t size)1931 savestring(const char *restrict str, size_t size)
1932 {
1933 if (!str)
1934 return (char *)NULL;
1935
1936 char *ptr = (char *)NULL;
1937 ptr = (char *)malloc((size + 1) * sizeof(char));
1938
1939 if (!ptr)
1940 return (char *)NULL;
1941 strcpy(ptr, str);
1942
1943 return ptr;
1944 }
1945
1946 /* Take a string and returns the same string escaped. If nothing to be
1947 * escaped, the original string is returned */
1948 char *
escape_str(const char * str)1949 escape_str(const char *str)
1950 {
1951 if (!str)
1952 return (char *)NULL;
1953
1954 size_t len = 0;
1955 char *buf = (char *)NULL;
1956
1957 buf = (char *)xnmalloc(strlen(str) * 2 + 1, sizeof(char));
1958
1959 while (*str) {
1960 if (is_quote_char(*str))
1961 buf[len++] = '\\';
1962 buf[len++] = *(str++);
1963 }
1964
1965 buf[len] = '\0';
1966 return buf;
1967 }
1968
1969 /* Get all substrings from STR using IFS as substring separator, and,
1970 * if there is a range, expand it. Returns an array containing all
1971 * substrings in STR plus expandes ranges, or NULL if: STR is NULL or
1972 * empty, STR contains only IFS(s), or in case of memory allocation
1973 * error */
1974 char **
get_substr(char * str,const char ifs)1975 get_substr(char *str, const char ifs)
1976 {
1977 if (!str || *str == '\0')
1978 return (char **)NULL;
1979
1980 /* ############## SPLIT THE STRING #######################*/
1981
1982 char **substr = (char **)NULL;
1983 void *p = (char *)NULL;
1984 size_t str_len = strlen(str);
1985 size_t length = 0, substr_n = 0;
1986 char *buf = (char *)xnmalloc(str_len + 1, sizeof(char));
1987
1988 while (*str) {
1989 while (*str != ifs && *str != '\0' && length < (str_len + 1))
1990 buf[length++] = *(str++);
1991 if (length) {
1992 buf[length] = '\0';
1993 p = (char *)realloc(substr, (substr_n + 1) * sizeof(char *));
1994 if (!p) {
1995 /* Free whatever was allocated so far */
1996 size_t i;
1997 for (i = 0; i < substr_n; i++)
1998 free(substr[i]);
1999 free(substr);
2000 return (char **)NULL;
2001 }
2002 substr = (char **)p;
2003 p = (char *)malloc(length + 1);
2004
2005 if (!p) {
2006 size_t i;
2007 for (i = 0; i < substr_n; i++)
2008 free(substr[i]);
2009 free(substr);
2010 return (char **)NULL;
2011 }
2012
2013 substr[substr_n] = p;
2014 p = (char *)NULL;
2015 xstrsncpy(substr[substr_n++], buf, length);
2016 length = 0;
2017 } else {
2018 str++;
2019 }
2020 }
2021
2022 free(buf);
2023
2024 if (!substr_n)
2025 return (char **)NULL;
2026
2027 size_t i = 0, j = 0;
2028 p = (char *)realloc(substr, (substr_n + 1) * sizeof(char *));
2029 if (!p) {
2030 for (i = 0; i < substr_n; i++)
2031 free(substr[i]);
2032 free(substr);
2033 substr = (char **)NULL;
2034 return (char **)NULL;
2035 }
2036
2037 substr = (char **)p;
2038 p = (char *)NULL;
2039 substr[substr_n] = (char *)NULL;
2040
2041 /* ################### EXPAND RANGES ######################*/
2042
2043 int afirst = 0, asecond = 0;
2044
2045 for (i = 0; substr[i]; i++) {
2046 /* Check if substr is a valid range */
2047 int ranges_ok = 0;
2048 /* If range, get both extremes of it */
2049 for (j = 1; substr[i][j]; j++) {
2050 if (substr[i][j] == '-') {
2051 /* Get strings before and after the dash */
2052 char *q = strchr(substr[i], '-');
2053 if (!q || !*q || q == substr[i] || *(q - 1) < '1'
2054 || *(q - 1) > '9' || !*(q + 1) || *(q + 1) < '1'
2055 || *(q + 1) > '9')
2056 break;
2057
2058 *q = '\0';
2059 char *first = savestring(substr[i], strlen(substr[i]));
2060 *(q++) = '-';
2061
2062 if (!first)
2063 break;
2064
2065 char *second = savestring(q, strlen(q));
2066 if (!second) {
2067 free(first);
2068 break;
2069 }
2070
2071 /* Make sure we have a valid range */
2072 if (is_number(first) && is_number(second)) {
2073 afirst = atoi(first), asecond = atoi(second);
2074 if (asecond <= afirst) {
2075 free(first);
2076 free(second);
2077 break;
2078 }
2079
2080 ranges_ok = 1;
2081 free(first);
2082 free(second);
2083 } else {
2084 free(first);
2085 free(second);
2086 break;
2087 }
2088 }
2089 }
2090
2091 if (!ranges_ok)
2092 continue;
2093
2094 /* If a valid range */
2095 size_t k = 0, next = 0;
2096 char **rbuf = (char **)NULL;
2097 rbuf = (char **)xnmalloc((substr_n + (size_t)(asecond - afirst) + 1),
2098 sizeof(char *));
2099 /* Copy everything before the range expression
2100 * into the buffer */
2101 for (j = 0; j < i; j++)
2102 rbuf[k++] = savestring(substr[j], strlen(substr[j]));
2103
2104 /* Copy the expanded range into the buffer */
2105 for (j = (size_t)afirst; j <= (size_t)asecond; j++) {
2106 rbuf[k] = (char *)xnmalloc((size_t)DIGINUM((int)j) + 1, sizeof(char));
2107 sprintf(rbuf[k++], "%zu", j);
2108 }
2109
2110 /* Copy everything after the range expression into
2111 * the buffer, if anything */
2112 if (substr[i + 1]) {
2113 next = k;
2114 for (j = (i + 1); substr[j]; j++) {
2115 rbuf[k++] = savestring(substr[j], strlen(substr[j]));
2116 }
2117 } else { /* If there's nothing after last range, there's no next
2118 either */
2119 next = 0;
2120 }
2121
2122 /* Repopulate the original array with the expanded range and
2123 * remaining strings */
2124 substr_n = k;
2125 for (j = 0; substr[j]; j++)
2126 free(substr[j]);
2127
2128 substr = (char **)xrealloc(substr, (substr_n + 1) * sizeof(char *));
2129
2130 for (j = 0; j < substr_n; j++) {
2131 substr[j] = savestring(rbuf[j], strlen(rbuf[j]));
2132 free(rbuf[j]);
2133 }
2134 free(rbuf);
2135
2136 substr[j] = (char *)NULL;
2137
2138 /* Proceede only if there's something after the last range */
2139 if (next)
2140 i = next;
2141 else
2142 break;
2143 }
2144
2145 /* ############## REMOVE DUPLICATES ###############*/
2146
2147 char **dstr = (char **)NULL;
2148 size_t len = 0, d;
2149
2150 for (i = 0; i < substr_n; i++) {
2151 int duplicate = 0;
2152 for (d = (i + 1); d < substr_n; d++) {
2153 if (*substr[i] == *substr[d] && strcmp(substr[i], substr[d]) == 0) {
2154 duplicate = 1;
2155 break;
2156 }
2157 }
2158
2159 if (duplicate) {
2160 free(substr[i]);
2161 continue;
2162 }
2163
2164 dstr = (char **)xrealloc(dstr, (len + 1) * sizeof(char *));
2165 dstr[len++] = savestring(substr[i], strlen(substr[i]));
2166 free(substr[i]);
2167 }
2168
2169 free(substr);
2170 dstr = (char **)xrealloc(dstr, (len + 1) * sizeof(char *));
2171 dstr[len] = (char *)NULL;
2172 return dstr;
2173 }
2174
2175 /* This function simply deescapes whatever escaped chars it founds in
2176 * TEXT, so that readline can compare it to system file names when
2177 * completing paths. Returns a string containing text without escape
2178 * sequences */
2179 char *
dequote_str(char * text,int mt)2180 dequote_str(char *text, int mt)
2181 {
2182 UNUSED(mt);
2183 if (!text || !*text)
2184 return (char *)NULL;
2185
2186 /* At most, we need as many bytes as text (in case no escape sequence
2187 * is found)*/
2188 char *buf = (char *)NULL;
2189 buf = (char *)xnmalloc(strlen(text) + 1, sizeof(char));
2190 size_t len = 0;
2191
2192 while (*text) {
2193 switch (*text) {
2194 case '\\':
2195 buf[len++] = *(++text);
2196 break;
2197 default:
2198 buf[len++] = *text;
2199 break;
2200 }
2201 if (!*text)
2202 break;
2203 text++;
2204 }
2205
2206 buf[len] = '\0';
2207 return buf;
2208 }
2209