/*
 * Copyright (c) 2003-2007 Andrea Luzzardi <scox@sig11.org>
 *
 * This file is part of the pam_usb project. pam_usb is free software;
 * you can redistribute it and/or modify it under the terms of the GNU General
 * Public License version 2, as published by the Free Software Foundation.
 *
 * pam_usb is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <libxml/xpath.h>
#include <ctype.h>
#include <string.h>
#include "mem.h"
#include "xpath.h"
#include "log.h"

static xmlXPathObject *pusb_xpath_match(xmlDocPtr doc, const char *path)
{
	xmlXPathContext	*context = NULL;
	xmlXPathObject	*result = NULL;

	context = xmlXPathNewContext(doc);
	if (context == NULL)
	{
		log_error("Unable to create XML context\n");
		return (NULL);
	}
	result = xmlXPathEvalExpression((xmlChar *)path, context);
	xmlXPathFreeContext(context);
	if (result == NULL)
	{
		log_error("Error in xmlXPathEvalExpression\n");
		return (NULL);
	}
	if (xmlXPathNodeSetIsEmpty(result->nodesetval))
	{
		xmlXPathFreeObject(result);
		return (NULL);
	}
	return (result);
}

static int pusb_xpath_strip_string(char *dest, const char *src,
		size_t size)
{
	int		first_char = -1;
	int		last_char = -1;
	int		i;

	for (i = 0; src[i]; ++i)
	{
		if (isspace(src[i]))
			continue ;

		if (first_char == -1)
			first_char = i;
		last_char = i;
	}

	if (first_char == -1 || last_char == -1)
		return (0);

	if ((last_char - first_char) > (size - 1))
	{
		log_error("Device name is too long: %s", src);
		return (0);
	}

	memset(dest, 0x0, size);
	strncpy(dest, &(src[first_char]), last_char - first_char + 1);
	return (1);
}

int pusb_xpath_get_string(xmlDocPtr doc, const char *path,
		char *value, size_t size)
{
	xmlXPathObject	*result = NULL;
	xmlNode			*node = NULL;
	xmlChar			*result_string = NULL;

	if (!(result = pusb_xpath_match(doc, path)))
		return (0);

	if (result->nodesetval->nodeNr > 1)
	{
		xmlXPathFreeObject(result);
		log_debug("Syntax error: %s: more than one record found\n", path);
		return (0);
	}

	node = result->nodesetval->nodeTab[0]->xmlChildrenNode;
	result_string = xmlNodeListGetString(doc, node, 1);
	if (!result_string)
	{
		xmlXPathFreeObject(result);
		log_debug("Empty value for %s\n", path);
		return (0);
	}
	if (!pusb_xpath_strip_string(value, (const char *)result_string, size))
	{
		xmlFree(result_string);
		xmlXPathFreeObject(result);
		log_debug("Result for %s (%s) is too long (max: %d)\n",
				path, (const char *)result_string, size);
		return (0);
	}
	xmlFree(result_string);
	xmlXPathFreeObject(result);
	return (1);
}

int pusb_xpath_get_string_from(xmlDocPtr doc,
		const char *base,
		const char *path,
		char *value, size_t size)
{
	char	*xpath = NULL;
	size_t	xpath_size;
	int		retval;

	xpath_size = strlen(base) + strlen(path) + 1;
	xpath = xmalloc(xpath_size);
	memset(xpath, 0x00, xpath_size);
	snprintf(xpath, xpath_size, "%s%s", base, path);
	retval = pusb_xpath_get_string(doc, xpath, value, size);
	if (retval)
		log_debug("%s%s -> %s\n", base, path, value);
	xfree(xpath);
	return (retval);
}

int pusb_xpath_get_bool(xmlDocPtr doc, const char *path, int *value)
{
	char	ret[6]; /* strlen("false") + 1 */

	if (!pusb_xpath_get_string(doc, path, ret, sizeof(ret)))
		return (0);

	if (!strcmp(ret, "true"))
	{
		*value = 1;
		return (1);
	}

	if (!strcmp(ret, "false"))
	{
		*value = 0;
		return (1);
	}

	log_debug("Expecting a boolean, got %s\n", ret);
	return (0);
}

int pusb_xpath_get_bool_from(xmlDocPtr doc,
		const char *base,
		const char *path,
		int *value)
{
	char	*xpath = NULL;
	size_t	xpath_size;
	int		retval;

	xpath_size = strlen(base) + strlen(path) + 1;
	xpath = xmalloc(xpath_size);
	memset(xpath, 0x00, xpath_size);
	snprintf(xpath, xpath_size, "%s%s", base, path);
	retval = pusb_xpath_get_bool(doc, xpath, value);
	xfree(xpath);
	return (retval);
}

int pusb_xpath_get_time(xmlDocPtr doc, const char *path, time_t *value)
{
	char	ret[64];
	char	*last;
	int		coef;

	if (!pusb_xpath_get_string(doc, path, ret, sizeof(ret)))
		return (0);

	last = &(ret[strlen(ret) - 1]);
	coef = 1;
	if (*last == 's')
		coef = 1;
	else if (*last == 'm')
		coef = 60;
	else if (*last == 'h')
		coef = 3600;
	else if (*last == 'd')
		coef = 3600 * 24;
	else
		if (!isdigit(*last))
		{
			log_debug("Expecting a time modifier, got %c\n", *last);
			return (0);
		}
	if (!isdigit(*last))
		*last = '\0';
	*value = atoi(ret) * coef;

	return (0);
}

int pusb_xpath_get_time_from(xmlDocPtr doc,
		const char *base,
		const char *path,
		time_t *value)
{
	char	*xpath = NULL;
	size_t	xpath_size;
	int		retval;

	xpath_size = strlen(base) + strlen(path) + 1;
	xpath = xmalloc(xpath_size);
	memset(xpath, 0x00, xpath_size);
	snprintf(xpath, xpath_size, "%s%s", base, path);
	retval = pusb_xpath_get_time(doc, xpath, value);
	xfree(xpath);
	return (retval);
}

int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value)
{
	char	ret[64];

	if (!pusb_xpath_get_string(doc, path, ret, sizeof(ret)))
		return (0);
	*value = atoi(ret);
	return (1);
}

int pusb_xpath_get_int_from(xmlDocPtr doc,
		const char *base,
		const char *path,
		int *value)
{
	char	*xpath = NULL;
	size_t	xpath_size;
	int		retval;

	xpath_size = strlen(base) + strlen(path) + 1;
	xpath = xmalloc(xpath_size);
	memset(xpath, 0x00, xpath_size);
	snprintf(xpath, xpath_size, "%s%s", base, path);
	retval = pusb_xpath_get_int(doc, xpath, value);
	xfree(xpath);
	return (retval);
}
