| /* $Id$ */ | 
 |  | 
 | /* | 
 |  * Copyright (c) 2008 Nicholas Marriott <nicm@users.sourceforge.net> | 
 |  * | 
 |  * Permission to use, copy, modify, and distribute this software for any | 
 |  * purpose with or without fee is hereby granted, provided that the above | 
 |  * copyright notice and this permission notice appear in all copies. | 
 |  * | 
 |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
 |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
 |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
 |  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
 |  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | 
 |  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | 
 |  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
 |  */ | 
 |  | 
 | #include <sys/types.h> | 
 |  | 
 | #include <errno.h> | 
 | #include <pwd.h> | 
 | #include <stdio.h> | 
 | #include <string.h> | 
 | #include <stdlib.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include "tmux.h" | 
 |  | 
 | /* | 
 |  * Parse a command from a string. | 
 |  */ | 
 |  | 
 | int	 cmd_string_getc(const char *, size_t *); | 
 | void	 cmd_string_ungetc(size_t *); | 
 | void	 cmd_string_copy(char **, char *, size_t *); | 
 | char	*cmd_string_string(const char *, size_t *, char, int); | 
 | char	*cmd_string_variable(const char *, size_t *); | 
 | char	*cmd_string_expand_tilde(const char *, size_t *); | 
 |  | 
 | int | 
 | cmd_string_getc(const char *s, size_t *p) | 
 | { | 
 | 	const u_char	*ucs = s; | 
 |  | 
 | 	if (ucs[*p] == '\0') | 
 | 		return (EOF); | 
 | 	return (ucs[(*p)++]); | 
 | } | 
 |  | 
 | void | 
 | cmd_string_ungetc(size_t *p) | 
 | { | 
 | 	(*p)--; | 
 | } | 
 |  | 
 | /* | 
 |  * Parse command string. Returns -1 on error. If returning -1, cause is error | 
 |  * string, or NULL for empty command. | 
 |  */ | 
 | int | 
 | cmd_string_parse(const char *s, struct cmd_list **cmdlist, const char *file, | 
 |     u_int line, char **cause) | 
 | { | 
 | 	size_t		p; | 
 | 	int		ch, i, argc, rval; | 
 | 	char	      **argv, *buf, *t; | 
 | 	const char     *whitespace, *equals; | 
 | 	size_t		len; | 
 |  | 
 | 	argv = NULL; | 
 | 	argc = 0; | 
 |  | 
 | 	buf = NULL; | 
 | 	len = 0; | 
 |  | 
 | 	*cause = NULL; | 
 |  | 
 | 	*cmdlist = NULL; | 
 | 	rval = -1; | 
 |  | 
 | 	p = 0; | 
 | 	for (;;) { | 
 | 		ch = cmd_string_getc(s, &p); | 
 | 		switch (ch) { | 
 | 		case '\'': | 
 | 			if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL) | 
 | 				goto error; | 
 | 			cmd_string_copy(&buf, t, &len); | 
 | 			break; | 
 | 		case '"': | 
 | 			if ((t = cmd_string_string(s, &p, '"', 1)) == NULL) | 
 | 				goto error; | 
 | 			cmd_string_copy(&buf, t, &len); | 
 | 			break; | 
 | 		case '$': | 
 | 			if ((t = cmd_string_variable(s, &p)) == NULL) | 
 | 				goto error; | 
 | 			cmd_string_copy(&buf, t, &len); | 
 | 			break; | 
 | 		case '#': | 
 | 			/* Comment: discard rest of line. */ | 
 | 			while ((ch = cmd_string_getc(s, &p)) != EOF) | 
 | 				; | 
 | 			/* FALLTHROUGH */ | 
 | 		case EOF: | 
 | 		case ' ': | 
 | 		case '\t': | 
 | 			if (buf != NULL) { | 
 | 				buf = xrealloc(buf, 1, len + 1); | 
 | 				buf[len] = '\0'; | 
 |  | 
 | 				argv = xrealloc(argv, argc + 1, sizeof *argv); | 
 | 				argv[argc++] = buf; | 
 |  | 
 | 				buf = NULL; | 
 | 				len = 0; | 
 | 			} | 
 |  | 
 | 			if (ch != EOF) | 
 | 				break; | 
 |  | 
 | 			while (argc != 0) { | 
 | 				equals = strchr(argv[0], '='); | 
 | 				whitespace = argv[0] + strcspn(argv[0], " \t"); | 
 | 				if (equals == NULL || equals > whitespace) | 
 | 					break; | 
 | 				environ_put(&global_environ, argv[0]); | 
 | 				argc--; | 
 | 				memmove(argv, argv + 1, argc * (sizeof *argv)); | 
 | 			} | 
 | 			if (argc == 0) | 
 | 				goto out; | 
 |  | 
 | 			*cmdlist = cmd_list_parse(argc, argv, file, line, cause); | 
 | 			if (*cmdlist == NULL) | 
 | 				goto out; | 
 |  | 
 | 			rval = 0; | 
 | 			goto out; | 
 | 		case '~': | 
 | 			if (buf == NULL) { | 
 | 				t = cmd_string_expand_tilde(s, &p); | 
 | 				if (t == NULL) | 
 | 					goto error; | 
 | 				cmd_string_copy(&buf, t, &len); | 
 | 				break; | 
 | 			} | 
 | 			/* FALLTHROUGH */ | 
 | 		default: | 
 | 			if (len >= SIZE_MAX - 2) | 
 | 				goto error; | 
 |  | 
 | 			buf = xrealloc(buf, 1, len + 1); | 
 | 			buf[len++] = ch; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | error: | 
 | 	xasprintf(cause, "invalid or unknown command: %s", s); | 
 |  | 
 | out: | 
 | 	free(buf); | 
 |  | 
 | 	if (argv != NULL) { | 
 | 		for (i = 0; i < argc; i++) | 
 | 			free(argv[i]); | 
 | 		free(argv); | 
 | 	} | 
 |  | 
 | 	return (rval); | 
 | } | 
 |  | 
 | void | 
 | cmd_string_copy(char **dst, char *src, size_t *len) | 
 | { | 
 | 	size_t srclen; | 
 |  | 
 | 	srclen = strlen(src); | 
 |  | 
 | 	*dst = xrealloc(*dst, 1, *len + srclen + 1); | 
 | 	strlcpy(*dst + *len, src, srclen + 1); | 
 |  | 
 | 	*len += srclen; | 
 | 	free(src); | 
 | } | 
 |  | 
 | char * | 
 | cmd_string_string(const char *s, size_t *p, char endch, int esc) | 
 | { | 
 | 	int	ch; | 
 | 	char   *buf, *t; | 
 | 	size_t	len; | 
 |  | 
 | 	buf = NULL; | 
 | 	len = 0; | 
 |  | 
 | 	while ((ch = cmd_string_getc(s, p)) != endch) { | 
 | 		switch (ch) { | 
 | 		case EOF: | 
 | 			goto error; | 
 | 		case '\\': | 
 | 			if (!esc) | 
 | 				break; | 
 | 			switch (ch = cmd_string_getc(s, p)) { | 
 | 			case EOF: | 
 | 				goto error; | 
 | 			case 'e': | 
 | 				ch = '\033'; | 
 | 				break; | 
 | 			case 'r': | 
 | 				ch = '\r'; | 
 | 				break; | 
 | 			case 'n': | 
 | 				ch = '\n'; | 
 | 				break; | 
 | 			case 't': | 
 | 				ch = '\t'; | 
 | 				break; | 
 | 			} | 
 | 			break; | 
 | 		case '$': | 
 | 			if (!esc) | 
 | 				break; | 
 | 			if ((t = cmd_string_variable(s, p)) == NULL) | 
 | 				goto error; | 
 | 			cmd_string_copy(&buf, t, &len); | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		if (len >= SIZE_MAX - 2) | 
 | 			goto error; | 
 | 		buf = xrealloc(buf, 1, len + 1); | 
 | 		buf[len++] = ch; | 
 | 	} | 
 |  | 
 | 	buf = xrealloc(buf, 1, len + 1); | 
 | 	buf[len] = '\0'; | 
 | 	return (buf); | 
 |  | 
 | error: | 
 | 	free(buf); | 
 | 	return (NULL); | 
 | } | 
 |  | 
 | char * | 
 | cmd_string_variable(const char *s, size_t *p) | 
 | { | 
 | 	int			ch, fch; | 
 | 	char		       *buf, *t; | 
 | 	size_t			len; | 
 | 	struct environ_entry   *envent; | 
 |  | 
 | #define cmd_string_first(ch) ((ch) == '_' || \ | 
 | 	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z')) | 
 | #define cmd_string_other(ch) ((ch) == '_' || \ | 
 | 	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \ | 
 | 	((ch) >= '0' && (ch) <= '9')) | 
 |  | 
 | 	buf = NULL; | 
 | 	len = 0; | 
 |  | 
 | 	fch = EOF; | 
 | 	switch (ch = cmd_string_getc(s, p)) { | 
 | 	case EOF: | 
 | 		goto error; | 
 | 	case '{': | 
 | 		fch = '{'; | 
 |  | 
 | 		ch = cmd_string_getc(s, p); | 
 | 		if (!cmd_string_first(ch)) | 
 | 			goto error; | 
 | 		/* FALLTHROUGH */ | 
 | 	default: | 
 | 		if (!cmd_string_first(ch)) { | 
 | 			xasprintf(&t, "$%c", ch); | 
 | 			return (t); | 
 | 		} | 
 |  | 
 | 		buf = xrealloc(buf, 1, len + 1); | 
 | 		buf[len++] = ch; | 
 |  | 
 | 		for (;;) { | 
 | 			ch = cmd_string_getc(s, p); | 
 | 			if (ch == EOF || !cmd_string_other(ch)) | 
 | 				break; | 
 | 			else { | 
 | 				if (len >= SIZE_MAX - 3) | 
 | 					goto error; | 
 | 				buf = xrealloc(buf, 1, len + 1); | 
 | 				buf[len++] = ch; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (fch == '{' && ch != '}') | 
 | 		goto error; | 
 | 	if (ch != EOF && fch != '{') | 
 | 		cmd_string_ungetc(p); /* ch */ | 
 |  | 
 | 	buf = xrealloc(buf, 1, len + 1); | 
 | 	buf[len] = '\0'; | 
 |  | 
 | 	envent = environ_find(&global_environ, buf); | 
 | 	free(buf); | 
 | 	if (envent == NULL) | 
 | 		return (xstrdup("")); | 
 | 	return (xstrdup(envent->value)); | 
 |  | 
 | error: | 
 | 	free(buf); | 
 | 	return (NULL); | 
 | } | 
 |  | 
 | char * | 
 | cmd_string_expand_tilde(const char *s, size_t *p) | 
 | { | 
 | 	struct passwd		*pw; | 
 | 	struct environ_entry	*envent; | 
 | 	char			*home, *path, *user, *cp; | 
 | 	int			 last; | 
 |  | 
 | 	home = NULL; | 
 |  | 
 | 	last = cmd_string_getc(s, p); | 
 | 	if (last == EOF || last == '/' || last == ' '|| last == '\t') { | 
 | 		envent = environ_find(&global_environ, "HOME"); | 
 | 		if (envent != NULL && *envent->value != '\0') | 
 | 			home = envent->value; | 
 | 		else if ((pw = getpwuid(getuid())) != NULL) | 
 | 			home = pw->pw_dir; | 
 | 	} else { | 
 | 		cmd_string_ungetc(p); | 
 |  | 
 | 		cp = user = xmalloc(strlen(s)); | 
 | 		for (;;) { | 
 | 			last = cmd_string_getc(s, p); | 
 | 			if (last == EOF || last == '/' || last == ' '|| last == '\t') | 
 | 				break; | 
 | 			*cp++ = last; | 
 | 		} | 
 | 		*cp = '\0'; | 
 |  | 
 | 		if ((pw = getpwnam(user)) != NULL) | 
 | 			home = pw->pw_dir; | 
 | 		free(user); | 
 | 	} | 
 |  | 
 | 	if (home == NULL) | 
 | 		return (NULL); | 
 |  | 
 | 	if (last != EOF) | 
 | 		xasprintf(&path, "%s%c", home, last); | 
 | 	else | 
 | 		xasprintf(&path, "%s", home); | 
 | 	return (path); | 
 | } |