Line data Source code
1 : #include <stdlib.h>
2 : #include <string.h>
3 : #include "parse.h"
4 : #include "util/ascii.h"
5 : #include "util/debug.h"
6 : #include "util/string.h"
7 : #include "util/strtonum.h"
8 : #include "util/unicode.h"
9 : #include "util/xmalloc.h"
10 :
11 397 : static size_t parse_sq(const char *cmd, size_t len, String *buf)
12 : {
13 397 : const char *end = memchr(cmd, '\'', len);
14 397 : size_t pos = end ? (size_t)(end - cmd) : len;
15 397 : string_append_buf(buf, cmd, pos);
16 397 : return pos + (end ? 1 : 0);
17 : }
18 :
19 6 : static size_t unicode_escape(const char *str, size_t count, String *buf)
20 : {
21 : // Note: `u` doesn't need to be initialized here, but `gcc -Og`
22 : // gives a spurious -Wmaybe-uninitialized warning if it's not
23 6 : unsigned int u = 0;
24 6 : static_assert(sizeof(u) >= 4);
25 6 : size_t n = buf_parse_hex_uint(str, count, &u);
26 6 : if (likely(n > 0 && u_is_unicode(u))) {
27 4 : string_append_codepoint(buf, u);
28 : }
29 6 : return n;
30 : }
31 :
32 44 : static size_t hex_escape(const char *str, size_t count, String *buf)
33 : {
34 44 : unsigned int x = 0;
35 44 : size_t n = buf_parse_hex_uint(str, count, &x);
36 44 : if (likely(n == 2)) {
37 39 : string_append_byte(buf, x);
38 : }
39 44 : return n;
40 : }
41 :
42 557 : static size_t parse_dq(const char *cmd, size_t len, String *buf)
43 : {
44 557 : size_t pos = 0;
45 1990 : while (pos < len) {
46 1988 : unsigned char ch = cmd[pos++];
47 1988 : if (ch == '"') {
48 : break;
49 : }
50 1433 : if (ch == '\\' && pos < len) {
51 553 : ch = cmd[pos++];
52 553 : switch (ch) {
53 1 : case 'a': ch = '\a'; break;
54 1 : case 'b': ch = '\b'; break;
55 1 : case 'e': ch = '\033'; break;
56 3 : case 'f': ch = '\f'; break;
57 297 : case 'n': ch = '\n'; break;
58 7 : case 'r': ch = '\r'; break;
59 90 : case 't': ch = '\t'; break;
60 1 : case 'v': ch = '\v'; break;
61 : case '\\':
62 : case '"':
63 : break;
64 44 : case 'x':
65 44 : pos += hex_escape(cmd + pos, MIN(2, len - pos), buf);
66 44 : continue;
67 4 : case 'u':
68 4 : pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf);
69 4 : continue;
70 2 : case 'U':
71 2 : pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf);
72 2 : continue;
73 3 : default:
74 3 : string_append_byte(buf, '\\');
75 3 : break;
76 : }
77 880 : }
78 1383 : string_append_byte(buf, ch);
79 : }
80 :
81 557 : return pos;
82 : }
83 :
84 21 : static size_t parse_var(const CommandRunner *runner, const char *cmd, size_t len, String *buf)
85 : {
86 21 : if (len == 0 || !is_alpha_or_underscore(cmd[0])) {
87 : return 0;
88 : }
89 :
90 : size_t n = 1;
91 121 : while (n < len && is_alnum_or_underscore(cmd[n])) {
92 103 : n++;
93 : }
94 :
95 18 : const CommandSet *cmds = runner->cmds;
96 18 : void *ud = runner->userdata;
97 18 : char *name = xstrcut(cmd, n);
98 18 : char *value;
99 18 : if (cmds->expand_variable && cmds->expand_variable(name, &value, ud)) {
100 9 : if (value) {
101 3 : string_append_cstring(buf, value);
102 3 : free(value);
103 : }
104 9 : } else if (cmds->expand_env_vars) {
105 9 : const char *val = getenv(name);
106 9 : if (val) {
107 8 : string_append_cstring(buf, val);
108 : }
109 : }
110 :
111 18 : free(name);
112 18 : return n;
113 : }
114 :
115 17377 : char *parse_command_arg(const CommandRunner *runner, const char *cmd, size_t len, bool tilde)
116 : {
117 17377 : String buf;
118 17377 : size_t pos = 0;
119 :
120 17377 : if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') {
121 3 : buf = string_new(len + runner->home_dir->length);
122 3 : string_append_strview(&buf, runner->home_dir);
123 3 : string_append_byte(&buf, '/');
124 3 : pos += 2;
125 : } else {
126 17374 : buf = string_new(len);
127 : }
128 :
129 116919 : while (pos < len) {
130 99548 : char ch = cmd[pos++];
131 99548 : switch (ch) {
132 5 : case '\t':
133 : case '\n':
134 : case '\r':
135 : case ' ':
136 : case ';':
137 5 : goto end;
138 397 : case '\'':
139 397 : pos += parse_sq(cmd + pos, len - pos, &buf);
140 397 : break;
141 557 : case '"':
142 557 : pos += parse_dq(cmd + pos, len - pos, &buf);
143 557 : break;
144 21 : case '$':
145 21 : pos += parse_var(runner, cmd + pos, len - pos, &buf);
146 21 : break;
147 159 : case '\\':
148 159 : if (unlikely(pos == len)) {
149 1 : goto end;
150 : }
151 158 : ch = cmd[pos++];
152 : // Fallthrough
153 98567 : default:
154 98567 : string_append_byte(&buf, ch);
155 98567 : break;
156 : }
157 : }
158 :
159 17371 : end:
160 17377 : return string_steal_cstring(&buf);
161 : }
162 :
163 17328 : size_t find_end(const char *cmd, size_t pos, CommandParseError *err)
164 : {
165 116871 : while (1) {
166 116871 : switch (cmd[pos++]) {
167 5574 : case '\'':
168 10760 : while (1) {
169 5574 : if (cmd[pos] == '\'') {
170 385 : pos++;
171 385 : break;
172 : }
173 5189 : if (unlikely(cmd[pos] == '\0')) {
174 3 : *err = CMDERR_UNCLOSED_SQUOTE;
175 3 : return 0;
176 : }
177 5186 : pos++;
178 : }
179 385 : break;
180 1069 : case '"':
181 1994 : while (1) {
182 1994 : if (cmd[pos] == '"') {
183 539 : pos++;
184 539 : break;
185 : }
186 1455 : if (unlikely(cmd[pos] == '\0')) {
187 2 : *err = CMDERR_UNCLOSED_DQUOTE;
188 2 : return 0;
189 : }
190 1453 : if (cmd[pos++] == '\\') {
191 528 : if (unlikely(cmd[pos] == '\0')) {
192 1 : *err = CMDERR_UNEXPECTED_EOF;
193 1 : return 0;
194 : }
195 527 : pos++;
196 : }
197 : }
198 539 : break;
199 158 : case '\\':
200 158 : if (unlikely(cmd[pos] == '\0')) {
201 1 : *err = CMDERR_UNEXPECTED_EOF;
202 1 : return 0;
203 : }
204 157 : pos++;
205 157 : break;
206 17321 : case '\0':
207 : case '\t':
208 : case '\n':
209 : case '\r':
210 : case ' ':
211 : case ';':
212 17321 : *err = CMDERR_NONE;
213 17321 : return pos - 1;
214 : }
215 : }
216 :
217 : BUG("Unexpected break of outer loop");
218 : }
219 :
220 : // Note: `array` must be freed, regardless of the return value
221 5932 : CommandParseError parse_commands(const CommandRunner *runner, PointerArray *array, const char *cmd)
222 : {
223 5932 : for (size_t pos = 0; true; ) {
224 35351 : while (ascii_isspace(cmd[pos])) {
225 12260 : pos++;
226 : }
227 :
228 23091 : if (cmd[pos] == '\0') {
229 : break;
230 : }
231 :
232 17166 : if (cmd[pos] == ';') {
233 32 : ptr_array_append(array, NULL);
234 32 : pos++;
235 32 : continue;
236 : }
237 :
238 17134 : CommandParseError err;
239 17134 : size_t end = find_end(cmd, pos, &err);
240 17134 : if (err != CMDERR_NONE) {
241 7 : return err;
242 : }
243 :
244 17127 : ptr_array_append(array, parse_command_arg(runner, cmd + pos, end - pos, true));
245 17127 : pos = end;
246 : }
247 :
248 5925 : ptr_array_append(array, NULL);
249 5925 : return CMDERR_NONE;
250 : }
251 :
252 5 : const char *command_parse_error_to_string(CommandParseError err)
253 : {
254 5 : static const char error_strings[][16] = {
255 : [CMDERR_UNCLOSED_SQUOTE] = "unclosed '",
256 : [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"",
257 : [CMDERR_UNEXPECTED_EOF] = "unexpected EOF",
258 : };
259 :
260 5 : BUG_ON(err <= CMDERR_NONE);
261 5 : BUG_ON(err >= ARRAYLEN(error_strings));
262 5 : return error_strings[err];
263 : }
|