/*
 * POP3Lite - 3lite POP3 Daemon
 * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>
 *
 * This file is part of POP3Lite.
 *
 * POP3Lite is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * POP3Lite 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 "main.h"

#include <pop3lite.h>
#include <glib.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>

#include "cfg.h"

static const char rcsid[]="$Id: cfg.c,v 1.8 2001/01/24 14:01:47 algernon Exp $";

static char *cfg_strip_str ( char *str );
static char *cfg_strip_quote ( char *str );

/**
 * cfg_strip_quote: strip leading & trailing quotes
 * @str: the victim
 *
 * Strips the leading & trailing quotes if BOTH are
 * present.
 *
 * Returns: the bare truth ;)
 **/

static char *
cfg_strip_quote ( char *str )
{
	char *mystr;

	if ( str[0] == '"' && str[strlen(str)-1] == '"' )
	{
		mystr = &str[1];
		mystr[strlen(mystr)-1] = '\0';
	} else
		mystr = str;
	return mystr;
}

/**
 * cfg_strip_str: strips leading & trailing whitespace
 * @str: the victim
 *
 * Strips spaces and tabs from the beggining and from
 * the end of a string.
 *
 * Returns: a stripped string
 **/

static char *
cfg_strip_str ( char *str )
{
	int i = 0;
	char *mystr;

	while ( str[i] == ' ' || str[i] == '\t' )
		i++;
	mystr = &str[i];

	i = strlen(mystr)-1;
	while ( mystr[i] == ' ' || mystr[i] == '\t' )
		i--;
	mystr[i+1] = '\0';

	return mystr;
}

/**
 * cfg_parse_line: parse a configuration file line
 * @control: the control to insert result into
 * @line: the line itself
 * @linecnt: the line number, for error message generation
 * @prev: previous key
 *
 * Parses a configuration file line.
 *
 * Returns: the config key
 **/

char *
cfg_parse_line ( P3LControl *control,
		 const char *ln, unsigned long linecnt,
		 const char *prev, GHashTable *table )
{
	long j, i = 0;
	int seen_eq = -1;
	char *args, *carg, *lv1, *cmd = NULL, *line;
	gboolean ows = TRUE;
	gboolean is_cont = FALSE, to_cont = FALSE;
	GList *arglist = NULL;
	GList *arglist_c = NULL;
	GList *arglist_a = NULL;

	if ( ln == NULL )
		return g_strdup ( prev );
	if ( ! strcmp ( ln, "" ) )
		return g_strdup ( prev );

	line = g_strdup ( ln );

	/*
	 * Strip \r, \n and comments
	 */
	while ( 1 )
	{
		switch ( line[i] )
		{
			case '\r':
			case '\n':
				line[i]='\0';
				break;
			case '#':
				/* Escaped # is not a comment */
				if ( line [i-1] != '\\' )
					line[i] = '\0';
				break;
			case '=':
				if ( seen_eq < 0 )
					seen_eq = i;
				break;
			default:
				break;
		}
		if ( line[i] == '\0' )
			break;
		i++;
	}

	/*
	 * Detect if we must do anything about this...
	 * RATIONALE: a line that only contains whitespace,
	 * is ignored. a line with no euqation signs, is 
	 * considered an error.
	 */
	i=0;
	while ( i < strlen ( line ) )
	{
		if ( line[i] != ' ' )
		{
			if ( line [i] == '=' )
				seen_eq = -2;
			ows = FALSE;
			break;
		}
		i++;
	}

	/*
	 * Whitespace only lines are ignored.
	 * FIXME: if it is continued...
	 */
	if ( ows )
	{
		g_free ( line );
		return g_strdup ( prev );
	}

	/*
	 * If it is non-whitespace, and has no =, it is
	 * an error. However, if it is a continued line...
	 */
	if ( seen_eq == -1 && prev == NULL )
	{
		control->system->log ( control, LOG_CRIT,
			"CFG: Syntax error on line %lu (no equation sign).", linecnt );
		exit ( 1 );
	}

	/*
	 * A line that begins with [\t ]*= is illegal
	 */
	if ( seen_eq == -2 )
	{
		control->system->log ( control, LOG_CRIT,
			"CFG: Syntax error on line %lu (no field name).", linecnt );
		exit ( 1 );
	}

	/*
	 * Now we split it into two...
	 * The first equation sign is marked by seen_eq, so use it!
	 */
	if ( prev == NULL )
	{
		cmd = g_strndup ( line, seen_eq );
		args = g_strdup ( &line[seen_eq+1] );
	}
	else
	{
		is_cont = TRUE;
		cmd = g_strdup ( prev );
		args = g_strdup ( line );
	}

	/*
	 * Strip whitespace...
	 */
	
	args = cfg_strip_str ( args );
	cmd = cfg_strip_str ( cmd );

	g_strup ( cmd );

	/*
	 * Here comes a hard part...
	 * We have to split up the args by [\t ,], but only if
	 * they aren't inside quotes
	 */
	i = 0;
	carg = g_strdup("");
	while ( i < strlen ( args ) )
	{
		switch ( args[i] )
		{
			case '"':
				/* Escaped quotes are treated specially. */
				if ( args[i-1] == '\\' )
				{
					carg = g_strdup_printf ( "%s%c", carg, args[i] );
					break;
				}
				/* Find matching quote ... */
				j=i;
				i++;
				while ( args[i] != '"' && args[i-1] != '\\' && i < strlen(args) )
					i++;
				if ( i == strlen (args) && args[i] != '"' )
				{
					control->system->log ( control, LOG_CRIT,
						"CFG: Syntax error on line %lu (unmatched quotes)",
						linecnt );
					exit ( 1 );
				}
				/* ... and copy it */
				carg = g_strdup_printf ( "%s%s", carg, g_strndup ( &args[j], i-j+1 ) );
				break;
			case ' ':
			case ',':
			case '\t':
				/* 
				 * These are separators, they cannot be escaped,
				 * only quoted, and that is captured above.
				 */
				if ( strlen ( carg ) > 0 )
					arglist = g_list_append ( arglist, 
							cfg_strip_quote ( g_strdup ( carg ) ) );
				g_free ( carg );
				carg = g_strdup ( "" );
				break;
			default:
				/* Everything else gets appended */
				carg = g_strdup_printf ( "%s%c", carg, args[i] );
				break;
		}
		i++;
	}

	/*
	 * If there are pending things, append them too
	 */
	if ( strlen ( carg ) > 0 )
		arglist = g_list_append ( arglist, cfg_strip_quote ( g_strdup ( carg ) ) );

	g_free ( carg );

	/*
	 * If the value ends with \, it will be
	 * continued
	 */
	lv1 = (char *) g_list_nth_data ( arglist, g_list_length ( arglist ) - 1 );

	if ( lv1 && ! strcmp ( lv1, "\\" ) )
	{
		to_cont = 1;
		g_list_remove_link ( arglist, g_list_last ( arglist ) );
	}

	if ( is_cont == TRUE )
	{
		arglist_c = g_hash_table_lookup ( table, cmd );

		for ( i = 0; i < g_list_length ( arglist_c ); i++ )
			arglist_a = g_list_append ( arglist_a,
					g_list_nth_data ( arglist_c, i ) );

		arglist = g_list_concat ( arglist_a, arglist );
	}

	/*
	 * And finally add it to the hash.
	 */
	g_hash_table_insert ( table, cmd, arglist );

	g_free ( line );

	return ( to_cont ) ? cmd : NULL;
}

/**
 * p3l_load_config: load configuration
 * @control: the usual control struct
 * @filename: where to load configuration from
 * @table: destination
 *
 * This loads the a configuration file.
 *
 * Returns: nothing
 **/

void
p3l_load_config ( P3LControl *control, const char *filename,
		  GHashTable *table )
{
	char *fn, *line, *buffer, *prev=NULL;
	P3LString **lines;
	int i = 0;
	size_t size, linenum;

	fn = ( filename != NULL ) ?
		g_strdup ( filename ) : g_strdup ( SYSCONFDIR "/pop3lite.conf" );

	buffer = (char *) p3l_read_file ( fn, &size );

	if ( !buffer )
	{
		g_free ( fn );
		return;
	}

	lines = p3l_split_lines ( buffer, size, &linenum );

	for ( i = 0; i < linenum; i++ )
	{
		line = (char *) g_malloc ( lines[i]->length + 1 );
		memcpy ( line, lines[i]->str, lines[i]->length );
		line[lines[i]->length]=0;
		prev = cfg_parse_line ( control, line, i, prev, table );
		g_free ( line );
	}

	g_free ( buffer );
	g_free ( fn );
}


syntax highlighted by Code2HTML, v. 0.9.1