/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2011 AT&T Intellectual Property          *
*          Copyright (c) 2020-2023 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                  Martijn Dekker <martijn@inlv.org>                   *
*            Johnothan King <johnothanking@protonmail.com>             *
*                                                                      *
***********************************************************************/
/*
 * Glenn Fowler
 * AT&T Research
 *
 * apply file permission expression expr to perm
 *
 * each expression term must match
 *
 *	[ugoa]*[-&+|^=]?[rwxst0-7]*
 *
 * terms may be combined using ,
 *
 * if non-null, e points to the first unrecognized char in expr
 */

#include <ast.h>
#include <ls.h>
#include <modex.h>

int
strperm(const char* aexpr, char** e, int perm)
{
	char*	expr = (char*)aexpr;
	int	c;
	int	typ;
	int	who;
	int	num;
	int	op;
	int	mask;
	int	masked;

	if (perm == -1)
	{
		perm = 0;
		masked = 1;
		mask = ~0;
	}
	else
		masked = 0;
	for (;;)
	{
		op = num = who = typ = 0;
		for (;;)
		{
			switch (c = *expr++)
			{
			case 'u':
				who |= S_ISVTX|S_ISUID|S_IRWXU;
				continue;
			case 'g':
				who |= S_ISVTX|S_ISGID|S_IRWXG;
				continue;
			case 'o':
				who |= S_ISVTX|S_IRWXO;
				continue;
			case 'a':
				who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
				continue;
			default:
				if (c >= '0' && c <= '7')
				{
					if (!who)
						who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
					c = '=';
				}
				expr--;
				/* FALLTHROUGH */
			case '=':
				if (who)
					perm &= ~who;
				else
					perm = 0;
				/* FALLTHROUGH */
			case '+':
			case '|':
			case '-':
			case '&':
			case '^':
				op = c;
				for (;;)
				{
					switch (c = *expr++)
					{
					case 'r':
						typ |= S_IRUSR|S_IRGRP|S_IROTH;
						continue;
					case 'w':
						typ |= S_IWUSR|S_IWGRP|S_IWOTH;
						continue;
					case 'X':
						if (!S_ISDIR(perm) && !(perm & (S_IXUSR|S_IXGRP|S_IXOTH)))
							continue;
						/* FALLTHROUGH */
					case 'x':
						typ |= S_IXUSR|S_IXGRP|S_IXOTH;
						continue;
					case 's':
						typ |= S_ISUID|S_ISGID;
						continue;
					case 't':
						typ |= S_ISVTX;
						continue;
					case 'l':
						if (perm & S_IXGRP)
						{
							if (e)
								*e = expr - 1;
							return perm & S_IPERM;
						}
						typ |= S_ISGID;
						continue;
					case '=':
					case '+':
					case '|':
					case '-':
					case '&':
					case '^':
					case ',':
					case 0:
						if (who)
							typ &= who;
						else
							switch (op)
							{
							case '=':
							case '+':
							case '|':
							case '-':
							case '&':
								if (!masked)
								{
									masked = 1;
									umask(mask = umask(0));
									mask = ~mask;
								}
								typ &= mask;
								break;
							}
						switch (op)
						{
						default:
							if (who)
								perm &= ~who;
							else
								perm = 0;
							/* FALLTHROUGH */
						case '+':
						case '|':
							perm |= typ;
							typ = 0;
							break;
						case '-':
							perm &= ~typ;
							typ = 0;
							break;
						case '&':
							perm &= typ;
							typ = 0;
							break;
						case '^':
							if (typ &= perm)
							{
								/*
								 * propagate least restrictive to most restrictive
								 */

								if (typ & S_IXOTH)
									perm |= who & (S_IXUSR|S_IXGRP);
								if (typ & S_IWOTH)
									perm |= who & (S_IWUSR|S_IWGRP);
								if (typ & S_IROTH)
									perm |= who & (S_IRUSR|S_IRGRP);
								if (typ & S_IXGRP)
									perm |= who & S_IXUSR;
								if (typ & S_IWGRP)
									perm |= who & S_IWUSR;
								if (typ & S_IRGRP)
									perm |= who & S_IRUSR;

								/*
								 * if any execute then read => execute
								 */

								if ((typ |= perm) & (S_IXUSR|S_IXGRP|S_IXOTH))
								{
									if (typ & S_IRUSR)
										perm |= who & S_IXUSR;
									if (typ & S_IRGRP)
										perm |= who & S_IXGRP;
									if (typ & S_IROTH)
										perm |= who & S_IXOTH;
								}
								typ = 0;
							}
							break;
						}
						switch (c)
						{
						case '=':
						case '+':
						case '|':
						case '-':
						case '&':
						case '^':
							op = c;
							typ = 0;
							continue;
						}
						if (c)
							break;
						/* FALLTHROUGH */
					default:
						if (c < '0' || c > '7')
						{
							if (e)
								*e = expr - 1;
							if (typ)
							{
								if (who)
								{
									typ &= who;
									perm &= ~who;
								}
								perm |= typ;
							}
							return perm & S_IPERM;
						}
						num = (num << 3) | (c - '0');
						if (!who && (op == '+' || op == '-'))
							who = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
						if (*expr < '0' || *expr > '7')
						{
							typ |= modei(num);
							num = 0;
						}
						continue;
					}
					break;
				}
				break;
			}
			break;
		}
	}
}
