text: /* $OpenBSD: cmd-string.c,v 1.22 2016/01/19 15:59:12 nicm Exp $ */
  
  /*
   * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
   *
   * 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, len + 1);
  				buf[len] = '\0';
  
  				argv = xreallocarray(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, 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, *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, len + 1);
  		buf[len++] = ch;
  	}
  
  	buf = xrealloc(buf, 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, 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, len + 1);
  				buf[len++] = ch;
  			}
  		}
  	}
  
  	if (fch == '{' && ch != '}')
  		goto error;
  	if (ch != EOF && fch != '{')
  		cmd_string_ungetc(p); /* ch */
  
  	buf = xrealloc(buf, 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);
  }
  