1 |
|
|
/* $OpenBSD: cmd-string.c,v 1.29 2017/06/14 07:42:41 nicm Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 |
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 |
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
|
21 |
|
|
#include <errno.h> |
22 |
|
|
#include <pwd.h> |
23 |
|
|
#include <stdio.h> |
24 |
|
|
#include <string.h> |
25 |
|
|
#include <stdlib.h> |
26 |
|
|
#include <unistd.h> |
27 |
|
|
|
28 |
|
|
#include "tmux.h" |
29 |
|
|
|
30 |
|
|
/* |
31 |
|
|
* Parse a command from a string. |
32 |
|
|
*/ |
33 |
|
|
|
34 |
|
|
static int cmd_string_getc(const char *, size_t *); |
35 |
|
|
static void cmd_string_ungetc(size_t *); |
36 |
|
|
static void cmd_string_copy(char **, char *, size_t *); |
37 |
|
|
static char *cmd_string_string(const char *, size_t *, char, int); |
38 |
|
|
static char *cmd_string_variable(const char *, size_t *); |
39 |
|
|
static char *cmd_string_expand_tilde(const char *, size_t *); |
40 |
|
|
|
41 |
|
|
static int |
42 |
|
|
cmd_string_getc(const char *s, size_t *p) |
43 |
|
|
{ |
44 |
|
|
const u_char *ucs = s; |
45 |
|
|
|
46 |
|
|
if (ucs[*p] == '\0') |
47 |
|
|
return (EOF); |
48 |
|
|
return (ucs[(*p)++]); |
49 |
|
|
} |
50 |
|
|
|
51 |
|
|
static void |
52 |
|
|
cmd_string_ungetc(size_t *p) |
53 |
|
|
{ |
54 |
|
|
(*p)--; |
55 |
|
|
} |
56 |
|
|
|
57 |
|
|
int |
58 |
|
|
cmd_string_split(const char *s, int *rargc, char ***rargv) |
59 |
|
|
{ |
60 |
|
|
size_t p = 0; |
61 |
|
|
int ch, argc = 0, append = 0; |
62 |
|
|
char **argv = NULL, *buf = NULL, *t; |
63 |
|
|
const char *whitespace, *equals; |
64 |
|
|
size_t len = 0; |
65 |
|
|
|
66 |
|
|
for (;;) { |
67 |
|
|
ch = cmd_string_getc(s, &p); |
68 |
|
|
switch (ch) { |
69 |
|
|
case '\'': |
70 |
|
|
if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL) |
71 |
|
|
goto error; |
72 |
|
|
cmd_string_copy(&buf, t, &len); |
73 |
|
|
break; |
74 |
|
|
case '"': |
75 |
|
|
if ((t = cmd_string_string(s, &p, '"', 1)) == NULL) |
76 |
|
|
goto error; |
77 |
|
|
cmd_string_copy(&buf, t, &len); |
78 |
|
|
break; |
79 |
|
|
case '$': |
80 |
|
|
if ((t = cmd_string_variable(s, &p)) == NULL) |
81 |
|
|
goto error; |
82 |
|
|
cmd_string_copy(&buf, t, &len); |
83 |
|
|
break; |
84 |
|
|
case '#': |
85 |
|
|
/* Comment: discard rest of line. */ |
86 |
|
|
while ((ch = cmd_string_getc(s, &p)) != EOF) |
87 |
|
|
; |
88 |
|
|
/* FALLTHROUGH */ |
89 |
|
|
case EOF: |
90 |
|
|
case ' ': |
91 |
|
|
case '\t': |
92 |
|
|
if (buf != NULL) { |
93 |
|
|
buf = xrealloc(buf, len + 1); |
94 |
|
|
buf[len] = '\0'; |
95 |
|
|
|
96 |
|
|
argv = xreallocarray(argv, argc + 1, |
97 |
|
|
sizeof *argv); |
98 |
|
|
argv[argc++] = buf; |
99 |
|
|
|
100 |
|
|
buf = NULL; |
101 |
|
|
len = 0; |
102 |
|
|
} |
103 |
|
|
|
104 |
|
|
if (ch != EOF) |
105 |
|
|
break; |
106 |
|
|
|
107 |
|
|
while (argc != 0) { |
108 |
|
|
equals = strchr(argv[0], '='); |
109 |
|
|
whitespace = argv[0] + strcspn(argv[0], " \t"); |
110 |
|
|
if (equals == NULL || equals > whitespace) |
111 |
|
|
break; |
112 |
|
|
environ_put(global_environ, argv[0]); |
113 |
|
|
argc--; |
114 |
|
|
memmove(argv, argv + 1, argc * (sizeof *argv)); |
115 |
|
|
} |
116 |
|
|
goto done; |
117 |
|
|
case '~': |
118 |
|
|
if (buf != NULL) { |
119 |
|
|
append = 1; |
120 |
|
|
break; |
121 |
|
|
} |
122 |
|
|
t = cmd_string_expand_tilde(s, &p); |
123 |
|
|
if (t == NULL) |
124 |
|
|
goto error; |
125 |
|
|
cmd_string_copy(&buf, t, &len); |
126 |
|
|
break; |
127 |
|
|
default: |
128 |
|
|
append = 1; |
129 |
|
|
break; |
130 |
|
|
} |
131 |
|
|
if (append) { |
132 |
|
|
if (len >= SIZE_MAX - 2) |
133 |
|
|
goto error; |
134 |
|
|
buf = xrealloc(buf, len + 1); |
135 |
|
|
buf[len++] = ch; |
136 |
|
|
} |
137 |
|
|
append = 0; |
138 |
|
|
} |
139 |
|
|
|
140 |
|
|
done: |
141 |
|
|
*rargc = argc; |
142 |
|
|
*rargv = argv; |
143 |
|
|
|
144 |
|
|
free(buf); |
145 |
|
|
return (0); |
146 |
|
|
|
147 |
|
|
error: |
148 |
|
|
if (argv != NULL) |
149 |
|
|
cmd_free_argv(argc, argv); |
150 |
|
|
free(buf); |
151 |
|
|
return (-1); |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
struct cmd_list * |
155 |
|
|
cmd_string_parse(const char *s, const char *file, u_int line, char **cause) |
156 |
|
|
{ |
157 |
|
|
struct cmd_list *cmdlist = NULL; |
158 |
|
|
int argc; |
159 |
|
|
char **argv; |
160 |
|
|
|
161 |
|
|
*cause = NULL; |
162 |
|
|
if (cmd_string_split(s, &argc, &argv) != 0) { |
163 |
|
|
xasprintf(cause, "invalid or unknown command: %s", s); |
164 |
|
|
return (NULL); |
165 |
|
|
} |
166 |
|
|
if (argc != 0) { |
167 |
|
|
cmdlist = cmd_list_parse(argc, argv, file, line, cause); |
168 |
|
|
if (cmdlist == NULL) { |
169 |
|
|
cmd_free_argv(argc, argv); |
170 |
|
|
return (NULL); |
171 |
|
|
} |
172 |
|
|
} |
173 |
|
|
cmd_free_argv(argc, argv); |
174 |
|
|
return (cmdlist); |
175 |
|
|
} |
176 |
|
|
|
177 |
|
|
static void |
178 |
|
|
cmd_string_copy(char **dst, char *src, size_t *len) |
179 |
|
|
{ |
180 |
|
|
size_t srclen; |
181 |
|
|
|
182 |
|
|
srclen = strlen(src); |
183 |
|
|
|
184 |
|
|
*dst = xrealloc(*dst, *len + srclen + 1); |
185 |
|
|
strlcpy(*dst + *len, src, srclen + 1); |
186 |
|
|
|
187 |
|
|
*len += srclen; |
188 |
|
|
free(src); |
189 |
|
|
} |
190 |
|
|
|
191 |
|
|
static char * |
192 |
|
|
cmd_string_string(const char *s, size_t *p, char endch, int esc) |
193 |
|
|
{ |
194 |
|
|
int ch; |
195 |
|
|
char *buf, *t; |
196 |
|
|
size_t len; |
197 |
|
|
|
198 |
|
|
buf = NULL; |
199 |
|
|
len = 0; |
200 |
|
|
|
201 |
|
|
while ((ch = cmd_string_getc(s, p)) != endch) { |
202 |
|
|
switch (ch) { |
203 |
|
|
case EOF: |
204 |
|
|
goto error; |
205 |
|
|
case '\\': |
206 |
|
|
if (!esc) |
207 |
|
|
break; |
208 |
|
|
switch (ch = cmd_string_getc(s, p)) { |
209 |
|
|
case EOF: |
210 |
|
|
goto error; |
211 |
|
|
case 'e': |
212 |
|
|
ch = '\033'; |
213 |
|
|
break; |
214 |
|
|
case 'r': |
215 |
|
|
ch = '\r'; |
216 |
|
|
break; |
217 |
|
|
case 'n': |
218 |
|
|
ch = '\n'; |
219 |
|
|
break; |
220 |
|
|
case 't': |
221 |
|
|
ch = '\t'; |
222 |
|
|
break; |
223 |
|
|
} |
224 |
|
|
break; |
225 |
|
|
case '$': |
226 |
|
|
if (!esc) |
227 |
|
|
break; |
228 |
|
|
if ((t = cmd_string_variable(s, p)) == NULL) |
229 |
|
|
goto error; |
230 |
|
|
cmd_string_copy(&buf, t, &len); |
231 |
|
|
continue; |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
if (len >= SIZE_MAX - 2) |
235 |
|
|
goto error; |
236 |
|
|
buf = xrealloc(buf, len + 1); |
237 |
|
|
buf[len++] = ch; |
238 |
|
|
} |
239 |
|
|
|
240 |
|
|
buf = xrealloc(buf, len + 1); |
241 |
|
|
buf[len] = '\0'; |
242 |
|
|
return (buf); |
243 |
|
|
|
244 |
|
|
error: |
245 |
|
|
free(buf); |
246 |
|
|
return (NULL); |
247 |
|
|
} |
248 |
|
|
|
249 |
|
|
static char * |
250 |
|
|
cmd_string_variable(const char *s, size_t *p) |
251 |
|
|
{ |
252 |
|
|
int ch, fch; |
253 |
|
|
char *buf, *t; |
254 |
|
|
size_t len; |
255 |
|
|
struct environ_entry *envent; |
256 |
|
|
|
257 |
|
|
#define cmd_string_first(ch) ((ch) == '_' || \ |
258 |
|
|
((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z')) |
259 |
|
|
#define cmd_string_other(ch) ((ch) == '_' || \ |
260 |
|
|
((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \ |
261 |
|
|
((ch) >= '0' && (ch) <= '9')) |
262 |
|
|
|
263 |
|
|
buf = NULL; |
264 |
|
|
len = 0; |
265 |
|
|
|
266 |
|
|
fch = EOF; |
267 |
|
|
switch (ch = cmd_string_getc(s, p)) { |
268 |
|
|
case EOF: |
269 |
|
|
goto error; |
270 |
|
|
case '{': |
271 |
|
|
fch = '{'; |
272 |
|
|
|
273 |
|
|
ch = cmd_string_getc(s, p); |
274 |
|
|
if (!cmd_string_first(ch)) |
275 |
|
|
goto error; |
276 |
|
|
/* FALLTHROUGH */ |
277 |
|
|
default: |
278 |
|
|
if (!cmd_string_first(ch)) { |
279 |
|
|
xasprintf(&t, "$%c", ch); |
280 |
|
|
return (t); |
281 |
|
|
} |
282 |
|
|
|
283 |
|
|
buf = xrealloc(buf, len + 1); |
284 |
|
|
buf[len++] = ch; |
285 |
|
|
|
286 |
|
|
for (;;) { |
287 |
|
|
ch = cmd_string_getc(s, p); |
288 |
|
|
if (ch == EOF || !cmd_string_other(ch)) |
289 |
|
|
break; |
290 |
|
|
else { |
291 |
|
|
if (len >= SIZE_MAX - 3) |
292 |
|
|
goto error; |
293 |
|
|
buf = xrealloc(buf, len + 1); |
294 |
|
|
buf[len++] = ch; |
295 |
|
|
} |
296 |
|
|
} |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
if (fch == '{' && ch != '}') |
300 |
|
|
goto error; |
301 |
|
|
if (ch != EOF && fch != '{') |
302 |
|
|
cmd_string_ungetc(p); /* ch */ |
303 |
|
|
|
304 |
|
|
buf = xrealloc(buf, len + 1); |
305 |
|
|
buf[len] = '\0'; |
306 |
|
|
|
307 |
|
|
envent = environ_find(global_environ, buf); |
308 |
|
|
free(buf); |
309 |
|
|
if (envent == NULL) |
310 |
|
|
return (xstrdup("")); |
311 |
|
|
return (xstrdup(envent->value)); |
312 |
|
|
|
313 |
|
|
error: |
314 |
|
|
free(buf); |
315 |
|
|
return (NULL); |
316 |
|
|
} |
317 |
|
|
|
318 |
|
|
static char * |
319 |
|
|
cmd_string_expand_tilde(const char *s, size_t *p) |
320 |
|
|
{ |
321 |
|
|
struct passwd *pw; |
322 |
|
|
struct environ_entry *envent; |
323 |
|
|
char *home, *path, *user, *cp; |
324 |
|
|
int last; |
325 |
|
|
|
326 |
|
|
home = NULL; |
327 |
|
|
|
328 |
|
|
last = cmd_string_getc(s, p); |
329 |
|
|
if (last == EOF || last == '/' || last == ' '|| last == '\t') { |
330 |
|
|
envent = environ_find(global_environ, "HOME"); |
331 |
|
|
if (envent != NULL && *envent->value != '\0') |
332 |
|
|
home = envent->value; |
333 |
|
|
else if ((pw = getpwuid(getuid())) != NULL) |
334 |
|
|
home = pw->pw_dir; |
335 |
|
|
} else { |
336 |
|
|
cmd_string_ungetc(p); |
337 |
|
|
|
338 |
|
|
cp = user = xmalloc(strlen(s)); |
339 |
|
|
for (;;) { |
340 |
|
|
last = cmd_string_getc(s, p); |
341 |
|
|
if (last == EOF || |
342 |
|
|
last == '/' || |
343 |
|
|
last == ' '|| |
344 |
|
|
last == '\t') |
345 |
|
|
break; |
346 |
|
|
*cp++ = last; |
347 |
|
|
} |
348 |
|
|
*cp = '\0'; |
349 |
|
|
|
350 |
|
|
if ((pw = getpwnam(user)) != NULL) |
351 |
|
|
home = pw->pw_dir; |
352 |
|
|
free(user); |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
if (home == NULL) |
356 |
|
|
return (NULL); |
357 |
|
|
|
358 |
|
|
if (last != EOF) |
359 |
|
|
xasprintf(&path, "%s%c", home, last); |
360 |
|
|
else |
361 |
|
|
xasprintf(&path, "%s", home); |
362 |
|
|
return (path); |
363 |
|
|
} |