/*
 * Copyright (c) 2004, 2006 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: sm-conf-get.c,v 1.16 2006/01/09 19:06:25 ca Exp $")

#if SM_LIBCONF_ALONE
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include "sm-util.h"
#include "sm-conf-util.h"
#else /* SM_LIBCONF_ALONE */
#include "sm/ctype.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/string.h"
#endif /* SM_LIBCONF_ALONE */

#include "sm-conf-state.h"
#include "sm-conf-node.h"
#include "sm-conf-scan.h"
#include "sm-conf-token.h"
#include "sm-conf-type.h"

/*
**  PATHNAME_TOKEN -- utility: extract token or token pair from a node name
**
**	pathname:	[segment [sep segment]]
**	segment:	name [open sub close]
**	sep:		+sepchar
**	sepchar:	<anything that cannot start an identifier>
**	open:		"<" | "(" | "{" | "[" | <"> | "'" | "`"
**	close:		">" | ")" | "}" | "]" | <"> | "'"
**
**	Opening and closing paren must match, respectively.
**	A closing ' closes both an open ` (m4-style) and an open ' (sh-style).
**
**	If we were to further restrict the separators (".", maybe?) and
**	the open/close syntax, I'd be happy to make the parser code
**	less ambiguous.   This is simply the most generic syntax that
**	lets us try different "looks".				--Jutta
**
**	Parameters:
**		s -- (pointer to) string to scan
**		e -- end of string
**		name_out -- name (output)
**		name_n_out -- length of name_out (output)
**		sub_out -- sub (output)
**		sub_n_out -- length of sub_out (output)
**
**	Returns:
**		true/false: did we get a token?
*/

static bool
pathname_token(
	char const		**s,
	char const		*e,
	char const		**name_out,
	size_t			*name_n_out,
	char const		**sub_out,
	size_t			*sub_n_out)
{
	char const		*p, *q;
	char			cl;
	static char const	obraces[] = "<({[\"'`",
				cbraces[] = ">)}]\"''";

	SM_REQUIRE(name_out != NULL);
	SM_REQUIRE(name_n_out != NULL);
	SM_REQUIRE(sub_out != NULL);
	SM_REQUIRE(sub_n_out != NULL);

	*name_out = NULL;
	*name_n_out = 0;
	*sub_out = NULL;
	*sub_n_out = 0;

	/* skip leading garbage */
	for (p = *s; p < e && !isalnum(*p) && *p != '_'; p++)
		;

	/* skip an identifier */
	*name_out = p;
	for (; p < e && !(*p & 0x80) && (isalnum(*p) || *p == '_'); p++)
		;

	if (p == *name_out)
		return false;

	*name_n_out = p - *name_out;

	/*
	**  If we've got some sort of bracketing action, this names both a
	**  section type and its title.  Assign the title to the "sub"
	**  output parameter.
	*/

	if (*p != '\0' && (q = strchr(obraces, *p)) != NULL)
	{
		p++;
		*sub_out = p;
		cl = cbraces[q - obraces];
		for (; p < e && *p != cl; p++)
		{
			if (*p == '\\')
			{
				p++;

				/* backslash as last char? -> error! */
				if (p >= e)
					return 0;
			}
		}

		*sub_n_out = p - *sub_out;
	}

	/* skip trailing garbage */
	for (; p < e && !isalnum(*p) && *p != '_'; p++)
		;

	*s = p;
	return true;
}

/*
**  SM_CONF_GET_NODE_SCAN -- scan a type
**
**	Parameters:
**		smc -- handle
**		parent -- NULL or node relative to which we're searching
**		name -- path name of the option or section
**		type -- type of the result value
**		type_data -- parameters of the type.
**		flags -- SM_CONF_FLAG_*, controls conversion process.
**		data -- assign the result to the pointed-to address.
**		size -- # of bytes pointed to by <data>.
**
**	Returns:
**		0 on success,
**
**		SM_CONF_ERR_NOT_FOUND if no value is available,
**		other errors as returned by the type.
*/

static int
sm_conf_get_node_scan(
	sm_conf_T		*smc,
	sm_conf_node_T		*parent,
	char const		*name,
	sm_conf_type_T const	*type,
	void const		*type_data,
	unsigned int		flags,
	void			*data,
	size_t			size)
{
	sm_conf_definition_T	def[2];

	def[0].sm_magic		= SM_CONF_DEF_MAGIC;
	def[0].scd_name		= name;
	def[0].scd_type		= type;
	def[0].scd_offset	= 0;
	def[0].scd_size		= size;
	def[0].scd_flags	= flags;
	def[0].scd_contents	= type_data;
	def[0].scd_default	= NULL;
	def[0].scd_check	= NULL;
	def[0].scd_check_data	= NULL;
	def[1].scd_name		= NULL;

	return sm_conf_scan_node(smc, def, parent, data);
}

/*
**  SM_CONF_GET_RELATIVE -- get a property relative to a node.
**
**	Parameters:
**		smc -- handle
**		node -- NULL or node relative to which we're searching
**		name -- path name of the option or section
**		type -- type of the result value
**		type_data -- ancillary data for result value.
**		flags -- SM_CONF_FLAG_* flags, or'ed together
**		data -- assign the result to the pointed-to address.
**		size -- # of bytes pointed to by <data>.
**
**	Returns:
**		0 on success,
**		SM_CONF_ERR_NOT_FOUND if no value is available,
**		other errors as returned by the type.
*/

int
sm_conf_get_relative(
	sm_conf_T		*smc,
	sm_conf_node_T		*node,
	char const		*path,
	sm_conf_type_T const	*type,
	void const		*type_data,
	unsigned int		flags,
	void			*data,
	size_t			size)
{
	char const		*kw, *name, *elem;
	size_t			kw_n, name_n, elem_n;
	int			err;
	sm_conf_node_T		*sub;

	if (path == NULL)
		return sm_conf_get_node_scan(smc, node, "", type,
			type_data, flags, data, size);

	if (!pathname_token(&path, path + strlen(path),
			&kw, &kw_n, &name, &name_n))
		return sm_conf_get_node_scan(smc, node, "", type,
			type_data, flags, data, size);


	if (node == NULL)
		return SM_CONF_ERR_NOT_FOUND;

	if (  type != sm_conf_type_section
	   && *path == '\0'
	   && name == NULL)
	{
		/* We may be looking for a property. */
		sub = NULL;
		while ((sub = sm_conf_section_next(smc,
			node, &elem, &elem_n, sub)) != NULL)
		{
			if (sm_memncaseeq(elem, elem_n, kw, kw_n))
			{
				return sm_conf_get_node_scan(smc, sub,
					elem, type, type_data, flags,
					data, size);
			}
		}
	}

	/* We are looking only for a subsection. */
	sub = NULL;
	while ((sub = sm_conf_section_next_subsection(smc, node,
				kw, kw_n, NULL, 0, sub)) != NULL)
	{
		if (sm_conf_section_name(smc, sub, &elem, &elem_n))
			continue;

		if ( name == NULL
		   ? elem == NULL
		   : sm_conf_token_match(
			smc, name, name_n, elem, elem_n))
		{
			err = sm_conf_get_relative(smc, sub, path,
				type, type_data, flags, data, size);
			if (err == 0)
				return err;
		}
	}
	return SM_CONF_ERR_NOT_FOUND;
}

/*
**  SM_CONF_GET	-- get the value of a configuration option.
**
**	Parameters:
**		smc -- handle
**		name -- path name of the option or section
**		type -- type of the result value
**		type_data -- ancillary data for result value.
**		flags -- SM_CONF_FLAG_* flags, or'ed together
**		data -- assign the result to the pointed-to address.
**		size -- # of bytes pointed to by <data>.
**
**	Returns:
**		0 on success,
**		SM_CONF_ERR_NOT_FOUND if no value is available,
**		other errors as returned by the type.
*/

int
sm_conf_get(
	sm_conf_T		*smc,
	char const		*name,
	sm_conf_type_T const	*type,
	void const		*type_data,
	unsigned int		flags,
	void			*data,
	size_t			size)
{
	if (smc == NULL)
		return SM_CONF_ERR_INVALID;

	return sm_conf_get_relative(
		smc, smc->smc_root, name, type, type_data, flags, data, size);
}

/*
**  SM_CONF_SCAN_NEXT_RELATIVE -- get a property relative to a parent node.
**
**	Parameters:
**		smc -- handle
**		parent -- a node that <path> is relative to, or NULL
**		path -- pathname of the section we're searching for
**		defs -- definition of the section elements
**		flags -- SM_CONF_FLAG_* flags for the section
**		name_out -- NULL or a place for a pointer to the section's name
**		name_n_out -- NULL or a place for the section's name's length
**		data -- assign the scanned section data to the pointed-to
**			address.
**		iter -- iteration pointer.  Must point to NULL initially;
**			otherwise opaque to the application.
**
**	Returns:
**		0 on success,
**		SM_CONF_ERR_NOT_FOUND if no value is available,
**		other errors as returned by the type.
*/

int
sm_conf_scan_next_relative(
	sm_conf_T			*smc,
	sm_conf_node_T			*parent,
	char const			*path,
	sm_conf_definition_T const	*defs,
	unsigned int			flags,
	char const			**name_out,
	size_t				*name_n_out,
	void				*data,
	void				**iter)
{
	int				err;
	sm_conf_node_T			*prev, *my_prev;
	sm_conf_definition_T		section_def[2];
	char const			*name, *elem, *kw;
	size_t				name_n, elem_n, kw_n;

	if (smc == NULL)
		return SM_CONF_ERR_INVALID;
	if (defs == NULL || defs->scd_name == NULL)
		return SM_CONF_ERR_INVALID;
	SM_IS_CONF_DEF(defs);

	if (name_out != NULL)
		*name_out = NULL;
	if (name_n_out != NULL)
		*name_n_out = 0;

	if (  parent == NULL
	   && (parent = smc->smc_root) == NULL)
		return SM_CONF_ERR_NOT_FOUND;

	if (path == NULL)
		path = "";

	prev = (iter == NULL ? NULL : *iter);

	/*
	**  If we run out of <path> here, <parent> is the node we're
	**  looking for; assign it.
	*/

	if (!pathname_token(&path, path + strlen(path),
		&kw, &kw_n, &name, &name_n))
	{
		if (prev != NULL)
			return SM_CONF_ERR_NOT_FOUND;

		/*
		**  If our definitions have a non-empty name, they're
		**  the contents of a section that we're looking for.
		**  Make the obvious definition for that section.
		*/

		if (defs->scd_name[0] != '\0')
		{
			sm_memset(&section_def, 0, sizeof section_def);

			section_def[0].sm_magic	= SM_CONF_DEF_MAGIC;
			section_def[0].scd_name	= "";
			section_def[0].scd_type	= sm_conf_type_section;
			section_def[0].scd_default	= NULL;
			section_def[0].scd_flags	= flags;
			section_def[0].scd_contents	= defs;
			section_def[1].scd_name	= NULL;

			defs = section_def;
		}

		err = sm_conf_scan_node(smc, defs, parent, data);
		if (err == 0)
		{
			if (sm_conf_section_name(smc, parent,
				&name, &name_n) == 0)
			{
				if (name_out != NULL)
					*name_out = name;
				if (name_n_out != NULL)
					*name_n_out = name_n;
			}

			if (iter != NULL)
				*iter = parent;
		}
		return err;
	}

	/*
	**  my_prev := the node directly below <parent> that's above
	**		or equal to <prev>
	*/

	my_prev = prev;
	while (my_prev != NULL)
	{
		sm_conf_node_T	*par;

		par = sm_conf_node_parent(smc, my_prev);
		if (par == parent)
			break;
		my_prev = par;
	}

	/* The `prev' pointer must be either NULL or below <parent>. */
	if (prev != NULL && my_prev == NULL)
		return SM_CONF_ERR_INVALID;

	/*
	**  If we have a <my_prev>, and we can iterate successfully below
	**  that, use the result of that iteration.
	*/

	if (my_prev != NULL && my_prev != prev)
	{
		err = sm_conf_scan_next_relative(
			smc, my_prev, path, defs, flags,
			name_out, name_n_out, data, iter);
		if (err != SM_CONF_ERR_NOT_FOUND)
			return err;
	}

	/*
	**  We need to step outside the subtree controlled
	**  by <my_prev>.
	*/

	*iter = NULL;
	while ((my_prev = sm_conf_section_next_subsection(smc,
		parent, kw, kw_n, NULL, 0, my_prev)) != NULL)
	{
		/* The kw matches.  How about the name? */
		if (sm_conf_section_name(smc, my_prev,
			&elem, &elem_n) != 0)
			continue;

		if (  name == NULL
		   || (  elem != NULL
		      && sm_conf_token_match(smc,
				name, name_n, elem, elem_n)))
		{
			err = sm_conf_scan_next_relative(
				smc, my_prev, path, defs,
				flags, name_out, name_n_out,
				data, iter);
			if (err != SM_CONF_ERR_NOT_FOUND)
				return err;
		}
	}

	return SM_CONF_ERR_NOT_FOUND;
}

/*
**  SM_CONF_SCAN_NEXT -- scan a named section.
**
**	Successive calls to sm_conf_scan_next() return scanned
**	successive instances of sections that match a pathname.
**
**	Parameters:
**		smc -- handle
**		path -- pathname of the section we're searching for
**		defs -- array of definitions of the section elements,
**			terminated by a definition with NULL name.
**		flags -- SM_CONF_FLAG_* flags for the section
**		name_out -- NULL or a place for a pointer to the section's name
**		name_n_out -- NULL or a place for the section's name's length
**		data -- assign the scanned section data to the pointed-to
**			address.
**		iter -- iteration pointer.  Must points to NULL initially;
**			otherwise opaque to the application.
**
**	Returns:
**		0 on success,
**		SM_CONF_ERR_NOT_FOUND if no value is available,
**		other errors as returned by the type.
*/

int
sm_conf_scan_next(
	sm_conf_T			*smc,
	char const			*path,
	sm_conf_definition_T const	*defs,
	unsigned int			flags,
	char const			**name_out,
	size_t				*name_n_out,
	void				*data,
	void				**iter)
{
	return sm_conf_scan_next_relative(
		smc, NULL, path, defs, flags, name_out, name_n_out, data, iter);
}


syntax highlighted by Code2HTML, v. 0.9.1