1 /*
2 src/filemanager - tests for command_has_unquoted_metacharacters()
3
4 Copyright (C) 2026
5 Free Software Foundation, Inc.
6
7 Written by:
8 Marek Libra <marek.libra@gmail.com>, 2026
9
10 This file is part of the Midnight Commander.
11
12 The Midnight Commander is free software: you can redistribute it
13 and/or modify it under the terms of the GNU General Public License as
14 published by the Free Software Foundation, either version 3 of the License,
15 or (at your option) any later version.
16
17 The Midnight Commander is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26 #define TEST_SUITE_NAME "/src/filemanager"
27
28 #include "tests/mctest.h"
29
30 /* --------------------------------------------------------------------------------------------- */
31
32 MC_TESTABLE gboolean command_has_unquoted_metacharacters (const char *cmd);
33
34 /* --------------------------------------------------------------------------------------------- */
35
36 /* @DataSource("test_metacharacters_ds") */
37 static const struct test_metacharacters_ds
38 {
39 const char *input_value;
40 gboolean expected_result;
41 } test_metacharacters_ds[] = {
42 /* simple commands - no metacharacters */
43 { "cd", FALSE },
44 { "cd /tmp", FALSE },
45 { "cd ..", FALSE },
46 { "cd ~/projects", FALSE },
47 { "cd $HOME", FALSE },
48 { "cd -", FALSE },
49 { "cd /path/to/dir", FALSE },
50 { "cd foo bar", FALSE },
51
52 /* semicolons */
53 { "cd ..; make", TRUE },
54 { "cd ..; make; cd -", TRUE },
55 { "cd /tmp; ls", TRUE },
56 { "cd ..;ls", TRUE },
57 { "cd /tmp;ls", TRUE },
58
59 /* && */
60 { "cd .. && make", TRUE },
61 { "cd /tmp && ls && pwd", TRUE },
62 { "cd ..&&ls", TRUE },
63
64 /* || */
65 { "cd .. || echo fail", TRUE },
66 { "cd ..||ls", TRUE },
67
68 /* pipe */
69 { "cd foo | bar", TRUE },
70 { "cd ..|ls", TRUE },
71
72 /* background / single & */
73 { "cd &", TRUE },
74 { "cd a&b", TRUE },
75
76 /* quoted paths - metacharacters inside quotes are literal */
77 { "cd 'foo bar'", FALSE },
78 { "cd \"foo bar\"", FALSE },
79 { "cd 'foo;bar'", FALSE },
80 { "cd 'foo|bar'", FALSE },
81 { "cd 'foo&bar'", FALSE },
82 { "cd \"foo;bar\"", FALSE },
83 { "cd \"foo|bar\"", FALSE },
84 { "cd \"foo&bar\"", FALSE },
85
86 /* quoted path followed by a real separator */
87 { "cd 'foo'; ls", TRUE },
88
89 /* backslash-escaped metacharacters are literal */
90 { "cd foo\\ bar", FALSE },
91 { "cd foo\\;bar", FALSE },
92 { "cd foo\\|bar", FALSE },
93 { "cd foo\\&bar", FALSE },
94 { "cd path\\;with\\;semicolons", FALSE },
95
96 /* escaped metacharacter plus a real separator */
97 { "cd foo\\;bar; make", TRUE },
98
99 /* double-backslash before semicolon: \\; at runtime - first \ escapes second, the ; is real */
100 { "cd foo\\\\;ls", TRUE },
101 /* triple-backslash before semicolon: \\\; at runtime - \\ + \; (escaped semicolon) */
102 { "cd foo\\\\\\;ls", FALSE },
103
104 /* command substitution $(...) - internal cd cannot expand */
105 { "cd $(echo /tmp)", TRUE },
106 { "cd $(pwd)", TRUE },
107 { "cd $(echo foo;echo bar)", TRUE },
108
109 /* command substitution with backticks */
110 { "cd `echo /tmp`", TRUE },
111 { "cd `pwd`", TRUE },
112
113 /* single-quoted command substitution - literal, not metacharacters */
114 { "cd '$(echo /tmp)'", FALSE },
115 { "cd '`echo /tmp`'", FALSE },
116
117 /* double-quoted command substitution - shell still expands these */
118 { "cd \"$(echo /tmp)\"", TRUE },
119 { "cd \"`echo /tmp`\"", TRUE },
120
121 /* escaped $ and backtick */
122 { "cd \\$(echo /tmp)", FALSE },
123 { "cd \\`echo /tmp\\`", FALSE },
124
125 /* empty and whitespace-only strings */
126 { "", FALSE },
127 { " ", FALSE },
128
129 /* parameter expansion - not command substitution */
130 { "cd ${HOME}", FALSE },
131
132 /* arithmetic expansion - triggers $( detection */
133 { "cd $((1+2))", TRUE },
134
135 /* unterminated quotes - no metachar, shell would error anyway */
136 { "cd 'foo", FALSE },
137 { "cd \"foo", FALSE },
138 };
139
140 /* @Test(dataSource = "test_metacharacters_ds") */
141 START_PARAMETRIZED_TEST (test_metacharacters, test_metacharacters_ds)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
142 {
143 gboolean actual_result;
144
145 actual_result = command_has_unquoted_metacharacters (data->input_value);
146
147 ck_assert_int_eq (actual_result, data->expected_result);
148 }
149 END_PARAMETRIZED_TEST
150
151 /* --------------------------------------------------------------------------------------------- */
152
153 int
154 main (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
155 {
156 TCase *tc_core;
157
158 tc_core = tcase_create ("Core");
159
160 /* Add new tests here: *************** */
161 mctest_add_parameterized_test (tc_core, test_metacharacters, test_metacharacters_ds);
162 /* *********************************** */
163
164 return mctest_run_all (tc_core);
165 }
166
167 /* --------------------------------------------------------------------------------------------- */