1/* $Id: cmd-string.c,v 1.4 2011/10/07 10:38:02 joerg Exp $ */
2
3/*
4 * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net>
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
34int	cmd_string_getc(const char *, size_t *);
35void	cmd_string_ungetc(size_t *);
36char   *cmd_string_string(const char *, size_t *, char, int);
37char   *cmd_string_variable(const char *, size_t *);
38char   *cmd_string_expand_tilde(const char *, size_t *);
39
40int
41cmd_string_getc(const char *s, size_t *p)
42{
43	const char	*ucs = s;
44
45	if (ucs[*p] == '\0')
46		return (EOF);
47	return (u_char)(ucs[(*p)++]);
48}
49
50void
51cmd_string_ungetc(size_t *p)
52{
53	(*p)--;
54}
55
56/*
57 * Parse command string. Returns -1 on error. If returning -1, cause is error
58 * string, or NULL for empty command.
59 */
60int
61cmd_string_parse(const char *s, struct cmd_list **cmdlist, char **cause)
62{
63	size_t		p;
64	int		ch, i, argc, rval;
65	char	      **argv, *buf, *t;
66	const char     *whitespace, *equals;
67	size_t		len, len2;
68
69	argv = NULL;
70	argc = 0;
71
72	buf = NULL;
73	len = 0;
74
75	*cause = NULL;
76
77	*cmdlist = NULL;
78	rval = -1;
79
80	p = 0;
81	for (;;) {
82		ch = cmd_string_getc(s, &p);
83		switch (ch) {
84		case '\'':
85			if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL)
86				goto error;
87			len2 = strlen(t);
88			buf = xrealloc(buf, 1, len + len2 + 1);
89			memcpy(buf + len, t, len2 + 1);
90			len += len2;
91			xfree(t);
92			break;
93		case '"':
94			if ((t = cmd_string_string(s, &p, '"', 1)) == NULL)
95				goto error;
96			len2 = strlen(t);
97			buf = xrealloc(buf, 1, len + len2 + 1);
98			memcpy(buf + len, t, len2 + 1);
99			len += len2;
100			xfree(t);
101			break;
102		case '$':
103			if ((t = cmd_string_variable(s, &p)) == NULL)
104				goto error;
105			len2 = strlen(t);
106			buf = xrealloc(buf, 1, len + len2 + 1);
107			strlcpy(buf + len, t, len2 + 1);
108			len += len2;
109			xfree(t);
110			break;
111		case '#':
112			/* Comment: discard rest of line. */
113			while ((ch = cmd_string_getc(s, &p)) != EOF)
114				;
115			/* FALLTHROUGH */
116		case EOF:
117		case ' ':
118		case '\t':
119			if (buf != NULL) {
120				buf = xrealloc(buf, 1, len + 1);
121				buf[len] = '\0';
122
123				argv = xrealloc(argv, argc + 1, sizeof *argv);
124				argv[argc++] = buf;
125
126				buf = NULL;
127				len = 0;
128			}
129
130			if (ch != EOF)
131				break;
132
133			while (argc != 0) {
134				equals = strchr(argv[0], '=');
135				whitespace = argv[0] + strcspn(argv[0], " \t");
136				if (equals == NULL || equals > whitespace)
137					break;
138				environ_put(&global_environ, argv[0]);
139				argc--;
140				memmove(argv, argv + 1, argc * (sizeof *argv));
141			}
142			if (argc == 0)
143				goto out;
144
145			*cmdlist = cmd_list_parse(argc, argv, cause);
146			if (*cmdlist == NULL)
147				goto out;
148
149			rval = 0;
150			goto out;
151		case '~':
152			if (buf == NULL) {
153				if ((t = cmd_string_expand_tilde(s, &p)) == NULL)
154					goto error;
155				len2 = strlen(t);
156				buf = xrealloc(buf, 1, len + len2 + 1);
157				memcpy(buf + len, t, len2 + 1);
158				len += len2;
159				xfree(t);
160				break;
161			}
162			/* FALLTHROUGH */
163		default:
164			if (len >= SIZE_MAX - 2)
165				goto error;
166
167			buf = xrealloc(buf, 1, len + 1);
168			buf[len++] = ch;
169			break;
170		}
171	}
172
173error:
174	xasprintf(cause, "invalid or unknown command: %s", s);
175
176out:
177	if (buf != NULL)
178		xfree(buf);
179
180	if (argv != NULL) {
181		for (i = 0; i < argc; i++)
182			xfree(argv[i]);
183		xfree(argv);
184	}
185
186	return (rval);
187}
188
189char *
190cmd_string_string(const char *s, size_t *p, char endch, int esc)
191{
192	int	ch;
193	char   *buf, *t;
194	size_t	len, len2;
195
196	buf = NULL;
197	len = 0;
198
199	while ((ch = cmd_string_getc(s, p)) != endch) {
200		switch (ch) {
201		case EOF:
202			goto error;
203		case '\\':
204			if (!esc)
205				break;
206			switch (ch = cmd_string_getc(s, p)) {
207			case EOF:
208				goto error;
209			case 'e':
210				ch = '\033';
211				break;
212			case 'r':
213				ch = '\r';
214				break;
215			case 'n':
216				ch = '\n';
217				break;
218			case 't':
219				ch = '\t';
220				break;
221			}
222			break;
223		case '$':
224			if (!esc)
225				break;
226			if ((t = cmd_string_variable(s, p)) == NULL)
227				goto error;
228			len2 = strlen(t);
229			buf = xrealloc(buf, 1, len + len2 + 1);
230			memcpy(buf + len, t, len2 + 1);
231			len += len2;
232			xfree(t);
233			continue;
234		}
235
236		if (len >= SIZE_MAX - 2)
237			goto error;
238		buf = xrealloc(buf, 1, len + 1);
239		buf[len++] = ch;
240	}
241
242	buf = xrealloc(buf, 1, len + 1);
243	buf[len] = '\0';
244	return (buf);
245
246error:
247	if (buf != NULL)
248		xfree(buf);
249	return (NULL);
250}
251
252char *
253cmd_string_variable(const char *s, size_t *p)
254{
255	int			ch, fch;
256	char		       *buf, *t;
257	size_t			len;
258	struct environ_entry   *envent;
259
260#define cmd_string_first(ch) ((ch) == '_' || \
261	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z'))
262#define cmd_string_other(ch) ((ch) == '_' || \
263	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \
264	((ch) >= '0' && (ch) <= '9'))
265
266	buf = NULL;
267	len = 0;
268
269	fch = EOF;
270	switch (ch = cmd_string_getc(s, p)) {
271	case EOF:
272		goto error;
273	case '{':
274		fch = '{';
275
276		ch = cmd_string_getc(s, p);
277		if (!cmd_string_first(ch))
278			goto error;
279		/* FALLTHROUGH */
280	default:
281		if (!cmd_string_first(ch)) {
282			xasprintf(&t, "$%c", ch);
283			return (t);
284		}
285
286		buf = xrealloc(buf, 1, len + 1);
287		buf[len++] = ch;
288
289		for (;;) {
290			ch = cmd_string_getc(s, p);
291			if (ch == EOF || !cmd_string_other(ch))
292				break;
293			else {
294				if (len >= SIZE_MAX - 3)
295					goto error;
296				buf = xrealloc(buf, 1, len + 1);
297				buf[len++] = ch;
298			}
299		}
300	}
301
302	if (fch == '{' && ch != '}')
303		goto error;
304	if (ch != EOF && fch != '{')
305		cmd_string_ungetc(p); /* ch */
306
307	buf = xrealloc(buf, 1, len + 1);
308	buf[len] = '\0';
309
310	envent = environ_find(&global_environ, buf);
311	xfree(buf);
312	if (envent == NULL)
313		return (xstrdup(""));
314	return (xstrdup(envent->value));
315
316error:
317	if (buf != NULL)
318		xfree(buf);
319	return (NULL);
320}
321
322char *
323cmd_string_expand_tilde(const char *s, size_t *p)
324{
325	struct passwd		*pw;
326	struct environ_entry	*envent;
327	char			*home, *path, *username;
328
329	home = NULL;
330	if (cmd_string_getc(s, p) == '/') {
331		envent = environ_find(&global_environ, "HOME");
332		if (envent != NULL && *envent->value != '\0')
333			home = envent->value;
334		else if ((pw = getpwuid(getuid())) != NULL)
335			home = pw->pw_dir;
336	} else {
337		cmd_string_ungetc(p);
338		if ((username = cmd_string_string(s, p, '/', 0)) == NULL)
339			return (NULL);
340		if ((pw = getpwnam(username)) != NULL)
341			home = pw->pw_dir;
342		xfree(username);
343	}
344	if (home == NULL)
345		return (NULL);
346
347	xasprintf(&path, "%s/", home);
348	return (path);
349}
350