Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <stdbool.h> | ||
2 | #include <stdlib.h> | ||
3 | #include <string.h> | ||
4 | #include "parse.h" | ||
5 | #include "util/ascii.h" | ||
6 | #include "util/debug.h" | ||
7 | #include "util/string.h" | ||
8 | #include "util/strtonum.h" | ||
9 | #include "util/unicode.h" | ||
10 | #include "util/xmalloc.h" | ||
11 | |||
12 | 1090 | static size_t parse_sq(const char *cmd, size_t len, String *buf) | |
13 | { | ||
14 | 1090 | const char *end = memchr(cmd, '\'', len); | |
15 |
2/2✓ Branch 0 taken 1089 times.
✓ Branch 1 taken 1 times.
|
1090 | size_t pos = end ? (size_t)(end - cmd) : len; |
16 | 1090 | string_append_buf(buf, cmd, pos); | |
17 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1089 times.
|
1090 | return pos + (end ? 1 : 0); |
18 | } | ||
19 | |||
20 | 6 | static size_t unicode_escape(const char *str, size_t count, String *buf) | |
21 | { | ||
22 | // Note: `u` doesn't need to be initialized here, but `gcc -Og` | ||
23 | // gives a spurious -Wmaybe-uninitialized warning if it's not | ||
24 | 6 | unsigned int u = 0; | |
25 | 6 | static_assert(sizeof(u) >= 4); | |
26 | 6 | size_t n = buf_parse_hex_uint(str, count, &u); | |
27 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
6 | if (likely(n > 0 && u_is_unicode(u))) { |
28 | 4 | string_append_codepoint(buf, u); | |
29 | } | ||
30 | 6 | return n; | |
31 | } | ||
32 | |||
33 | 44 | static size_t hex_escape(const char *str, size_t count, String *buf) | |
34 | { | ||
35 | 44 | unsigned int x = 0; | |
36 | 44 | size_t n = buf_parse_hex_uint(str, count, &x); | |
37 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 5 times.
|
44 | if (likely(n == 2)) { |
38 | 39 | string_append_byte(buf, x); | |
39 | } | ||
40 | 44 | return n; | |
41 | } | ||
42 | |||
43 | 613 | static size_t parse_dq(const char *cmd, size_t len, String *buf) | |
44 | { | ||
45 | 613 | size_t pos = 0; | |
46 |
2/2✓ Branch 0 taken 2536 times.
✓ Branch 1 taken 2 times.
|
2538 | while (pos < len) { |
47 | 2536 | unsigned char ch = cmd[pos++]; | |
48 |
2/2✓ Branch 0 taken 1925 times.
✓ Branch 1 taken 611 times.
|
2536 | if (ch == '"') { |
49 | break; | ||
50 | } | ||
51 |
2/2✓ Branch 0 taken 618 times.
✓ Branch 1 taken 1307 times.
|
1925 | if (ch == '\\' && pos < len) { |
52 | 618 | ch = cmd[pos++]; | |
53 |
13/13✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 334 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 114 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 44 times.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 101 times.
|
618 | switch (ch) { |
54 | 1 | case 'a': ch = '\a'; break; | |
55 | 1 | case 'b': ch = '\b'; break; | |
56 | 1 | case 'e': ch = '\033'; break; | |
57 | 3 | case 'f': ch = '\f'; break; | |
58 | 334 | case 'n': ch = '\n'; break; | |
59 | 9 | case 'r': ch = '\r'; break; | |
60 | 114 | case 't': ch = '\t'; break; | |
61 | 1 | case 'v': ch = '\v'; break; | |
62 | case '\\': | ||
63 | case '"': | ||
64 | break; | ||
65 | 44 | case 'x': | |
66 | 44 | pos += hex_escape(cmd + pos, MIN(2, len - pos), buf); | |
67 | 44 | continue; | |
68 | 4 | case 'u': | |
69 | 4 | pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf); | |
70 | 4 | continue; | |
71 | 2 | case 'U': | |
72 | 2 | pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf); | |
73 | 2 | continue; | |
74 | 3 | default: | |
75 | 3 | string_append_byte(buf, '\\'); | |
76 | 3 | break; | |
77 | } | ||
78 | } | ||
79 | 1875 | string_append_byte(buf, ch); | |
80 | } | ||
81 | |||
82 | 613 | return pos; | |
83 | } | ||
84 | |||
85 | 22 | static size_t parse_var(const CommandRunner *runner, const char *cmd, size_t len, String *buf) | |
86 | { | ||
87 |
3/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 3 times.
|
22 | if (len == 0 || !is_alpha_or_underscore(cmd[0])) { |
88 | return 0; | ||
89 | } | ||
90 | |||
91 | size_t n = 1; | ||
92 |
4/4✓ Branch 0 taken 116 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 105 times.
✓ Branch 3 taken 11 times.
|
124 | while (n < len && is_alnum_or_underscore(cmd[n])) { |
93 | 105 | n++; | |
94 | } | ||
95 | |||
96 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | if (runner->expand_variable) { |
97 | 19 | char *name = xstrcut(cmd, n); | |
98 | 19 | char *value = runner->expand_variable(runner->e, name); | |
99 | 19 | free(name); | |
100 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 9 times.
|
19 | if (value) { |
101 | 10 | string_append_cstring(buf, value); | |
102 | 10 | free(value); | |
103 | } | ||
104 | } | ||
105 | |||
106 | return n; | ||
107 | } | ||
108 | |||
109 | // Parse a single dterc(5) argument from `cmd`, stopping when an unquoted | ||
110 | // whitespace or semicolon character is found or when all `len` bytes have | ||
111 | // been processed without encountering such a character. Escape sequences | ||
112 | // and $variables are expanded during processing and the fully expanded | ||
113 | // result is returned as a malloc'd string. | ||
114 | 25945 | char *parse_command_arg(const CommandRunner *runner, const char *cmd, size_t len) | |
115 | { | ||
116 | 25945 | String buf; | |
117 | 25945 | size_t pos = 0; | |
118 | |||
119 |
7/8✓ Branch 0 taken 25912 times.
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 25240 times.
✓ Branch 3 taken 672 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 25237 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
25945 | if (runner->expand_tilde_slash && len >= 2 && cmd[0] == '~' && cmd[1] == '/') { |
120 | 3 | buf = string_new(len + runner->home_dir->length); | |
121 | 3 | string_append_strview(&buf, runner->home_dir); | |
122 | 3 | string_append_byte(&buf, '/'); | |
123 | 3 | pos += 2; | |
124 | } else { | ||
125 | 25942 | buf = string_new(len); | |
126 | } | ||
127 | |||
128 |
2/2✓ Branch 0 taken 142359 times.
✓ Branch 1 taken 25939 times.
|
168298 | while (pos < len) { |
129 | 142359 | char ch = cmd[pos++]; | |
130 |
6/6✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1090 times.
✓ Branch 2 taken 613 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 226 times.
✓ Branch 5 taken 140403 times.
|
142359 | switch (ch) { |
131 | 5 | case '\t': | |
132 | case '\n': | ||
133 | case '\r': | ||
134 | case ' ': | ||
135 | case ';': | ||
136 | 5 | goto end; | |
137 | 1090 | case '\'': | |
138 | 1090 | pos += parse_sq(cmd + pos, len - pos, &buf); | |
139 | 1090 | break; | |
140 | 613 | case '"': | |
141 | 613 | pos += parse_dq(cmd + pos, len - pos, &buf); | |
142 | 613 | break; | |
143 | 22 | case '$': | |
144 | 22 | pos += parse_var(runner, cmd + pos, len - pos, &buf); | |
145 | 22 | break; | |
146 | 226 | case '\\': | |
147 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 225 times.
|
226 | if (unlikely(pos == len)) { |
148 | 1 | goto end; | |
149 | } | ||
150 | 225 | ch = cmd[pos++]; | |
151 | // Fallthrough | ||
152 | 140628 | default: | |
153 | 140628 | string_append_byte(&buf, ch); | |
154 | 140628 | break; | |
155 | } | ||
156 | } | ||
157 | |||
158 | 25939 | end: | |
159 | 25945 | return string_steal_cstring(&buf); | |
160 | } | ||
161 | |||
162 | 25892 | size_t find_end(const char *cmd, size_t pos, CommandParseError *err) | |
163 | { | ||
164 | 168234 | while (1) { | |
165 |
5/5✓ Branch 0 taken 140448 times.
✓ Branch 1 taken 1079 times.
✓ Branch 2 taken 600 times.
✓ Branch 3 taken 226 times.
✓ Branch 4 taken 25881 times.
|
168234 | switch (cmd[pos++]) { |
166 | 18811 | case '\'': | |
167 | 36543 | while (1) { | |
168 |
2/2✓ Branch 0 taken 1075 times.
✓ Branch 1 taken 17736 times.
|
18811 | if (cmd[pos] == '\'') { |
169 | 1075 | pos++; | |
170 | 1075 | break; | |
171 | } | ||
172 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 17732 times.
|
17736 | if (unlikely(cmd[pos] == '\0')) { |
173 | 4 | *err = CMDERR_UNCLOSED_SQUOTE; | |
174 | 4 | return 0; | |
175 | } | ||
176 | 17732 | pos++; | |
177 | } | ||
178 | 1075 | break; | |
179 | 1192 | case '"': | |
180 | 2550 | while (1) { | |
181 |
2/2✓ Branch 0 taken 595 times.
✓ Branch 1 taken 1955 times.
|
2550 | if (cmd[pos] == '"') { |
182 | 595 | pos++; | |
183 | 595 | break; | |
184 | } | ||
185 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1951 times.
|
1955 | if (unlikely(cmd[pos] == '\0')) { |
186 | 4 | *err = CMDERR_UNCLOSED_DQUOTE; | |
187 | 4 | return 0; | |
188 | } | ||
189 |
2/2✓ Branch 0 taken 1358 times.
✓ Branch 1 taken 593 times.
|
1951 | if (cmd[pos++] == '\\') { |
190 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 592 times.
|
593 | if (unlikely(cmd[pos] == '\0')) { |
191 | 1 | *err = CMDERR_UNEXPECTED_EOF; | |
192 | 1 | return 0; | |
193 | } | ||
194 | 592 | pos++; | |
195 | } | ||
196 | } | ||
197 | 595 | break; | |
198 | 226 | case '\\': | |
199 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 224 times.
|
226 | if (unlikely(cmd[pos] == '\0')) { |
200 | 2 | *err = CMDERR_UNEXPECTED_EOF; | |
201 | 2 | return 0; | |
202 | } | ||
203 | 224 | pos++; | |
204 | 224 | break; | |
205 | 25881 | case '\0': | |
206 | case '\t': | ||
207 | case '\n': | ||
208 | case '\r': | ||
209 | case ' ': | ||
210 | case ';': | ||
211 | 25881 | *err = CMDERR_NONE; | |
212 | 25881 | return pos - 1; | |
213 | } | ||
214 | } | ||
215 | |||
216 | BUG("Unexpected break of outer loop"); | ||
217 | } | ||
218 | |||
219 | // Note: `array` must be freed, regardless of the return value | ||
220 | 9232 | CommandParseError parse_commands(const CommandRunner *runner, PointerArray *array, const char *cmd) | |
221 | { | ||
222 | 9232 | for (size_t pos = 0; true; ) { | |
223 |
2/2✓ Branch 0 taken 17778 times.
✓ Branch 1 taken 34935 times.
|
52713 | while (ascii_isspace(cmd[pos])) { |
224 | 17778 | pos++; | |
225 | } | ||
226 | |||
227 |
2/2✓ Branch 0 taken 25714 times.
✓ Branch 1 taken 9221 times.
|
34935 | if (cmd[pos] == '\0') { |
228 | break; | ||
229 | } | ||
230 | |||
231 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 25676 times.
|
25714 | if (cmd[pos] == ';') { |
232 | 38 | ptr_array_append(array, NULL); | |
233 | 38 | pos++; | |
234 | 38 | continue; | |
235 | } | ||
236 | |||
237 | 25676 | CommandParseError err; | |
238 | 25676 | size_t end = find_end(cmd, pos, &err); | |
239 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 25665 times.
|
25676 | if (err != CMDERR_NONE) { |
240 | 11 | return err; | |
241 | } | ||
242 | |||
243 | 25665 | ptr_array_append(array, parse_command_arg(runner, cmd + pos, end - pos)); | |
244 | 25665 | pos = end; | |
245 | } | ||
246 | |||
247 | 9221 | ptr_array_append(array, NULL); | |
248 | 9221 | return CMDERR_NONE; | |
249 | } | ||
250 | |||
251 | 6 | const char *command_parse_error_to_string(CommandParseError err) | |
252 | { | ||
253 | 6 | static const char error_strings[][16] = { | |
254 | [CMDERR_UNCLOSED_SQUOTE] = "unclosed '", | ||
255 | [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"", | ||
256 | [CMDERR_UNEXPECTED_EOF] = "unexpected EOF", | ||
257 | }; | ||
258 | |||
259 | 6 | BUG_ON(err <= CMDERR_NONE); | |
260 | 6 | BUG_ON(err >= ARRAYLEN(error_strings)); | |
261 | 6 | return error_strings[err]; | |
262 | } | ||
263 |