/*
 * Copyright (c) 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: strexpmac.c,v 1.3 2005/07/05 17:20:49 ca Exp $")

#include "sm/assert.h"
#include "sm/magic.h"
#include "sm/ctype.h"
#include "sm/str-int.h"
#include "sm/strexp.h"

/*
**  SM_FIND_MACRO -- Find macro in array macros
**
**	Parameters:
**		str -- str to expand (read)
**		mac_begin -- begin of macro in str
**		mac_end -- end of macro in str
**		pidx -- (pointer to) index in macros if found (otput)
**		nmacros -- number of valid elements in macros/repl
**		macros -- array of macros
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_find_macro(const sm_str_P src, uint len, uint mac_begin, uint mac_end, uint *pidx, uint nmacros, sm_str_P *macros)
{
	uint i, j, k, l;
	bool match;

	if (mac_begin >= mac_end ||
	    mac_begin >= len ||
	    mac_end > len)
		return sm_err_perm(EINVAL);
	SM_REQUIRE(pidx != NULL);
	SM_REQUIRE(macros != NULL);
	for (i = 0; i < nmacros; i++)
	{
		if (macros[i] == NULL)
			continue;
		l = sm_str_getlen(macros[i]);
		if (l != mac_end - mac_begin + 1)
			continue;
		match = true;
		for (k = 0, j = mac_begin;
		     match && k < l && j <= mac_end;
		     k++, j++)
		{
			match = sm_str_rd_elem(src, j) ==
				sm_str_rd_elem(macros[i], k);
		}
		if (match && k == l && j == mac_end + 1)
		{
			*pidx = i;
			return SM_SUCCESS;
		}
	}
	return sm_err_perm(SM_E_NOTFOUND);
}

/*
**  SM_STR_EXPMAC -- Expand ${macro} in string with repl[macro]
**  \$ will be replaced by $ to suppress expansion of ${macro}
**
**	Parameters:
**		src -- str to expand (read)
**		dst -- str into which the expanded sequence is written (output)
**		flags -- flags (currently unused)
**		macros -- array of macros
**		repl -- array of replacement strings to use for expansion
**
**	Returns:
**		==0: no macro in src, NOTHING written to dst!
**		>=0: number of replacements
**		<0: usual sm_error code
**
**	Change API to use a callback to get a string for a macro?
*/

sm_ret_T
sm_str_expmac(const sm_str_P src, sm_str_P dst, uint flags, uint nmacros, sm_str_P *macros, sm_str_P *repl)
{
	uint i, len, idx, replacements, mac_begin, mac_end;
	uchar ch;
	sm_ret_T ret;
	bool backslash, found;

	SM_IS_BUF(dst);
	SM_IS_BUF(src);
	if (nmacros == 0)
		return 0;
	SM_REQUIRE(macros != NULL);
	SM_REQUIRE(repl != NULL);

#define MACPUTCH(ch)				\
	do {					\
		ret = sm_str_put(dst, (ch));	\
		if (sm_is_err(ret))		\
			goto error;		\
	} while (0)

	len = sm_str_getlen(src);

	backslash = false;
	found = false;
	for (i = 0; i < len; i++)
	{
		ch = sm_str_rd_elem(src, i);

		/* need at least ${A} */
		if (!backslash &&
		    ch == '$' && i + 3 < len &&
		    (ch = sm_str_rd_elem(src, i + 1)) == '{')
		{
			found = true;
			break;
		}
		if (!backslash)
			backslash = ch == '\\';
		else
		{
			/* \$ will be replaced */
			if (ch == '$')
			{
				found = true;
				break;
			}
			backslash = false;
		}
	}
	if (!found)
		return 0;

	replacements = 0;
	backslash = false;
	for (i = 0; i < len; i++)
	{
		ch = sm_str_rd_elem(src, i);

		/* need at least ${A} */
		if (!backslash &&
		    ch == '$' && i + 3 < len &&
		    (ch = sm_str_rd_elem(src, i + 1)) == '{')
		{
			i += 2;
			mac_begin = i;
			for (;
			     i < len && (ch = sm_str_rd_elem(src, i)) != '}';
			     i++)
				;
			if (i < len && ch == '}')
			{
				mac_end = i - 1;
				ret = sm_find_macro(src, len, mac_begin,
					mac_end, &idx, nmacros, macros);
				++replacements;

				/* on error: replace with empty string */
				/* make this an option to preserve string?? */
				if (sm_is_err(ret))
					continue;
				if (idx >= nmacros || repl[idx] == NULL)
					continue;
				ret = sm_str_cat(dst, repl[idx]);
				if (sm_is_err(ret))
					goto error;
			}
			else
			{
				/* copy "skipped" text over */
				MACPUTCH('$');
				MACPUTCH('{');
				for (idx = mac_begin; idx < i; idx++)
				{
					ch = sm_str_rd_elem(src, idx);
					MACPUTCH(ch);
				}
			}
		}
		else
		{
			if (!backslash)
			{
				backslash = ch == '\\';
				if (backslash)
				{
					++replacements;
					continue;
				}
			}
			else
			{
				backslash = false;
				if (ch != '$')
					MACPUTCH('\\');
				else
					++replacements;
			}
			ret = sm_str_put(dst, ch);
			if (sm_is_err(ret))
				goto error;
		}
	}
	if (backslash)
		MACPUTCH('\\');

	if (replacements > (uint)SM_RET_MAX)
		replacements = SM_RET_MAX;
	return (sm_ret_T) replacements;

  error:
	/* cleanup? */
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1