/*
 * qstat
 * by Steve Jankowski
 * steve@qstat.org
 * http://www.qstat.org
 *
 * Copyright 1996,1997,1998,1999 by Steve Jankowski
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifndef _WIN32
 #include <unistd.h>
 #include <sys/param.h>
#endif


#include "config.h"

#ifdef __hpux
	#define STATIC    static
#else
	#define STATIC
#endif

#ifdef _WIN32
	#define strcasecmp    stricmp
#endif

#define CONFIG_FILE			"qstat.cfg"

#ifdef _WIN32
	#define HOME_CONFIG_FILE	CONFIG_FILE
	#define SEP			"\\"
#else
	#define HOME_CONFIG_FILE	".qstatrc"
	#define SEP			"/"
#endif

static server_type **config_types;
static int n_config_types;
static int max_config_types;
static int next_gametype_id = LAST_BUILTIN_SERVER + 1;

static int load_config_file(char const *filename);
static int try_load_config_file(char const *filename, int const show_error);
static int pf_top_level(char *text, void *context);
static int pf_gametype_new(char *text, void *context);
static int pf_gametype_modify(char *text, void *context);

static int set_game_type_value(server_type *gametype, int key, char *value);
static int modify_game_type_value(server_type *gametype, int key, char *value);
static int set_packet_value(server_type *gametype, char const *value,
    char const *packet_name, char **packet, int *len);

static char *first_token(char *text);
static char *next_token();
static char *next_token_dup();
static char *next_value();
static char *parse_value(char *source, int len);
static unsigned int parse_hex(char *hex, int len, int *error);
static unsigned int parse_oct(char *oct, int len, int *error);
static char *force_lower_case(char *string);
static char *force_upper_case(char *string);
static char *get_token();
static void *memdup(const void *mem, unsigned int len);

int parse_config_file(FILE *file);

static int (*parse_func)(char *, void *);
static void *parse_context;
static int line;
static char const *current_file_name;

static char *parse_text;
static char *parse_end;
static char *lex;
static char *token_end;
static char token_buf[1024];
static int value_len;

static int debug = 0;

#define REPORT_ERROR(a)    print_location(); fprintf a; fprintf(stderr, "\n")

enum {
	CK_NONE = 0,
	CK_MASTER_PROTOCOL,
	CK_MASTER_QUERY,
	CK_MASTER_PACKET,
	CK_FLAGS,
	CK_NAME,
	CK_DEFAULT_PORT,
	CK_GAME_RULE,
	CK_TEMPLATE_VAR,
	CK_MASTER_TYPE,
	CK_STATUS_PACKET,
	CK_STATUS2_PACKET,
	CK_PLAYER_PACKET,
	CK_RULE_PACKET,
	CK_PORT_OFFSET
};

typedef struct _config_key {
	int key;
	char *key_name;
} ConfigKey;

static ConfigKey const new_keys[] =
{
	{ CK_MASTER_PROTOCOL, "master protocol"	    },
	{ CK_MASTER_QUERY,    "master query"	    },
	{ CK_MASTER_PACKET,   "master packet"	    },
	{ CK_FLAGS,	      "flags"		    },
	{ CK_NAME,	      "name"		    },
	{ CK_DEFAULT_PORT,    "default port"	    },
	{ CK_GAME_RULE,	      "game rule"	    },
	{ CK_TEMPLATE_VAR,    "template var"	    },
	{ CK_MASTER_TYPE,     "master for gametype" },
	{ CK_STATUS_PACKET,   "status packet"	    },
	{ CK_STATUS2_PACKET,  "status2 packet"	    },
	{ CK_PLAYER_PACKET,   "player packet"	    },
	{ CK_RULE_PACKET,     "rule packet"	    },
	{ CK_PORT_OFFSET,     "status port offset"  },
	{		   0, NULL		    },
};

static ConfigKey const modify_keys[] =
{
	{ CK_MASTER_PROTOCOL, "master protocol"	    },
	{ CK_MASTER_QUERY,    "master query"	    },
	{ CK_MASTER_PACKET,   "master packet"	    },
	{ CK_FLAGS,	      "flags"		    },
	{ CK_MASTER_TYPE,     "master for gametype" },
	{		   0, NULL		    },
};

typedef struct {
	const char *name;
	int value;
} ServerFlag;

#define SERVER_FLAG(x)    { # x, x }
ServerFlag const server_flags[] =
{
	SERVER_FLAG(TF_SINGLE_QUERY),
	SERVER_FLAG(TF_OUTFILE),
	SERVER_FLAG(TF_MASTER_MULTI_RESPONSE),
	SERVER_FLAG(TF_TCP_CONNECT),
	SERVER_FLAG(TF_QUERY_ARG),
	SERVER_FLAG(TF_QUERY_ARG_REQUIRED),
	SERVER_FLAG(TF_QUAKE3_NAMES),
	SERVER_FLAG(TF_TRIBES2_NAMES),
	SERVER_FLAG(TF_SOF_NAMES),
	SERVER_FLAG(TF_U2_NAMES),
	SERVER_FLAG(TF_RAW_STYLE_QUAKE),
	SERVER_FLAG(TF_RAW_STYLE_TRIBES),
	SERVER_FLAG(TF_RAW_STYLE_GHOSTRECON),
	SERVER_FLAG(TF_NO_PORT_OFFSET),
	SERVER_FLAG(TF_SHOW_GAME_PORT),
	{ NULL, 0 }
};
#undef SERVER_FLAG

static int get_config_key(char *first_token, const ConfigKey *keys);
static void add_config_type(server_type *gametype);
static server_type *get_config_type(char *game_type);
static void copy_server_type(server_type *dest, server_type *source);
static server_type *get_server_type(char *game_type);
static server_type *get_builtin_type(char *game_type);

typedef struct _gametype_context {
	char *type;
	char *extend_type;
	server_type *gametype;
} GameTypeContext;

static void
print_location()
{
	fprintf(stderr, "%s line %d: ", current_file_name, line);
}


/*
 * 1. $QSTAT_CONFIG
 * 2. UNIX: $HOME/.qstatrc         WIN: $HOME/qstat.cfg
 * 3. UNIX: sysconfdir/qstat.cfg   WIN: qstat.exe-dir/qstat.cfg
 */
int
qsc_load_default_config_files()
{
	int rc = 0;
	char *filename = NULL, *var;
	char path[PATH_MAX];

	var = getenv("QSTAT_CONFIG");
	if ((var != NULL) && (var[0] != '\0')) {
		rc = try_load_config_file(var, 1);
		if ((rc == 0) || (rc == -1)) {
			return (rc);
		}
	}

	var = getenv("HOME");
	if ((var != NULL) && (var[0] != '\0')) {
		int len = strlen(var) + 1;
		if (len > PATH_MAX - strlen(HOME_CONFIG_FILE) + 2) {
			fprintf(stderr, "Path for HOME \"%s\" too long\n", var);
			return (-1);
		}
		strncpy(path, var, len);
		strcat(path, SEP HOME_CONFIG_FILE);
		rc = try_load_config_file(path, 0);
		if ((rc == 0) || (rc == -1)) {
			return (rc);
		}
	}

#ifdef sysconfdir
	strcpy(path, sysconfdir SEP CONFIG_FILE);
	filename = path;
#elif defined(_WIN32)
	// Look in the binaries directory
	rc = GetModuleFileName(NULL, path, PATH_MAX);
	if (rc == PATH_MAX) {
		fprintf(stderr, "Module path too long\n");
		return (1);
	}
	var = strrchr(path, '\\');
	if (var == NULL) {
		fprintf(stderr, "Unexpected module path \"%s\" (no seperator %s)\n", path, SEP);
		return (-1);
	}
	*var = '\0';
	if (strlen(path) >= PATH_MAX - 11) {
		fprintf(stderr, "Module path \"%s\" too long\n", path);
		return (-1);
	}
	strcat(path, SEP CONFIG_FILE);
	filename = path;
#endif

	if (filename != NULL) {
		rc = try_load_config_file(filename, 0);
	}
	return (rc);
}


int
qsc_load_config_file(char const *filename)
{
	int rc = load_config_file(filename);

	if (rc == -2) {
		perror(filename);
		fprintf(stderr, "Could not open config file \"%s\"\n",
		    filename);
		return (-1);
	}
	return (rc);
}


server_type **
qsc_get_config_server_types(int *n_config_types_ref)
{
	if (n_config_types_ref) {
		*n_config_types_ref = n_config_types;
	}
	return (config_types);
}


STATIC int
try_load_config_file(char const *filename, int const show_error)
{
	int rc = load_config_file(filename);

	if ((rc == -2) && show_error) {
		perror(filename);
		fprintf(stderr, "Error: Could not open config file \"%s\"\n",
		    filename);
	} else if (rc == -1) {
		fprintf(stderr, "Error: Could not load config file \"%s\"\n",
		    filename);
	}
	return (rc);
}


STATIC int
load_config_file(char const *filename)
{
	FILE *file;
	int rc;

	file = fopen(filename, "r");
	if (file == NULL) {
		return (-2);
	}

	current_file_name = filename;
	rc = parse_config_file(file);
	fclose(file);
	return (rc);
}


int
parse_config_file(FILE *file)
{
	char buf[4096];
	int rc;

	line = 0;
	parse_func = pf_top_level;

	while (fgets(buf, sizeof(buf), file) != NULL) {
		int len = strlen(buf);
		while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
			len--;
		}
		buf[len] = '\0';
		line++;
		rc = parse_func(buf, parse_context);
		if (rc != 0) {
			return (rc);
		}
	}
	return (0);
}


/* Top level
 *   Keywords: gametype
 */
STATIC int
pf_top_level(char *text, void *_context)
{
	GameTypeContext *context;
	server_type *extend;
	char *token, *game_type, *extend_type;

	token = first_token(text);
	if (token == NULL) {
		return (0);
	}

	if (strcmp(token, "gametype") != 0) {
		REPORT_ERROR((stderr, "Unknown config command \"%s\"", token));
		return (-1);
	}

	game_type = next_token_dup();
	if (game_type == NULL) {
		REPORT_ERROR((stderr, "Missing game type"));
		return (-1);
	}

	force_lower_case(game_type);

	token = next_token();
	if (token == NULL) {
		REPORT_ERROR((stderr, "Expecting \"new\" or \"modify\""));
		return (-1);
	}

	if (strcmp(token, "new") == 0) {
		parse_func = pf_gametype_new;
	} else if (strcmp(token, "modify") == 0) {
		parse_func = pf_gametype_modify;
	} else {
		REPORT_ERROR((stderr, "Expecting \"new\" or \"modify\""));
		return (-1);
	}

	context = (GameTypeContext *)malloc(sizeof(GameTypeContext));
	context->type = game_type;
	context->extend_type = NULL;
	context->gametype = NULL;

	token = next_token();

	if (parse_func == pf_gametype_modify) {
		if (token != NULL) {
			REPORT_ERROR((stderr, "Extra text after gametype modify"));
			return (-1);
		}
		context->gametype = get_server_type(game_type);
		if (context->gametype == NULL) {
			REPORT_ERROR((stderr, "Unknown game type \"%s\"", game_type));
			free(context);
			return (-1);
		}
		parse_context = context;
		return (0);
	}

	if ((token == NULL) || (strcmp(token, "extend") != 0)) {
		REPORT_ERROR((stderr, "Expecting \"extend\""));
		return (-1);
	}

	extend_type = next_token();

	if (extend_type == NULL) {
		REPORT_ERROR((stderr, "Missing extend game type"));
		return (-1);
	}

	force_lower_case(extend_type);

	if (strcasecmp(extend_type, game_type) == 0) {
		REPORT_ERROR((stderr, "Identical game type and extend type"));
		return (-1);
	}

	context->extend_type = extend_type;
	extend = get_server_type(extend_type);
	if (extend == NULL) {
		REPORT_ERROR((stderr, "Unknown extend game type \"%s\"", extend_type));
		return (-1);
	}

	/* Over-write a previous gametype new */
	context->gametype = get_config_type(game_type);
	if (context->gametype == NULL) {
		context->gametype = (server_type *)malloc(sizeof(server_type));
	}
	copy_server_type(context->gametype, extend);

	/* Set flag for new type-id if not re-defining previous config type */
	if (get_config_type(game_type) == NULL) {
		context->gametype->id = 0;
	}

	context->gametype->type_string = game_type;
	context->gametype->type_prefix = strdup(game_type);
	force_upper_case(context->gametype->type_prefix);
	context->gametype->type_option = (char *)malloc(strlen(game_type) + 2);
	context->gametype->type_option[0] = '-';
	strcpy(&context->gametype->type_option[1], game_type);

	parse_context = context;

	return (0);
}


STATIC int
pf_gametype_modify(char *text, void *_context)
{
	GameTypeContext *context = (GameTypeContext *)_context;
	char *token;
	int key;

	token = first_token(text);
	if (token == NULL) {
		return (0);
	}

	if (strcmp(token, "end") == 0) {
		parse_func = pf_top_level;
		parse_context = NULL;
		return (0);
	}

	key = get_config_key(token, modify_keys);

	token = next_token();

	if (strcmp(token, "=") != 0) {
		REPORT_ERROR((stderr, "Expecting \"=\", found \"%s\"", token));
		return (-1);
	}

	token = next_value();
	if (token == NULL) {
		REPORT_ERROR((stderr, "Missing value after \"=\""));
		return (-1);
	}

	if (debug) {
		printf("%d %s = <%s>\n", key, modify_keys[key - 1].key_name,
		    token ? token : "");
	}

	return (modify_game_type_value(context->gametype, key, token));
}


STATIC int
pf_gametype_new(char *text, void *_context)
{
	GameTypeContext *context = (GameTypeContext *)_context;
	char *token;
	int key;

	token = first_token(text);
	if (token == NULL) {
		return (0);
	}

	if (strcmp(token, "end") == 0) {
		add_config_type(context->gametype);
		parse_func = pf_top_level;
		parse_context = NULL;
		return (0);
	}

	key = get_config_key(token, new_keys);

	if (key <= 0) {
		return (key);
	}

	token = next_token();
	if (strcmp(token, "=") != 0) {
		REPORT_ERROR((stderr, "Expecting \"=\", found \"%s\"", token));
		return (-1);
	}

	token = next_value();
	if ((token == NULL) && ((key != CK_MASTER_PROTOCOL) && (key != CK_MASTER_QUERY))) {
		REPORT_ERROR((stderr, "Missing value after \"=\""));
		return (-1);
	}

	if (debug) {
		printf("%d %s = <%s>\n", key, new_keys[key - 1].key_name, token ? token : "");
	}

	return (set_game_type_value(context->gametype, key, token));
}


STATIC int
get_config_key(char *first_token, const ConfigKey *keys)
{
	char key_name[1024], *token;
	int key = 0;

	strcpy(key_name, first_token);
	do {
		int k;
		for (k = 0; keys[k].key_name; k++) {
			if (strcmp(keys[k].key_name, key_name) == 0) {
				key = keys[k].key;
				break;
			}
		}
		if (key) {
			break;
		}
		token = next_token();
		if ((token == NULL) || (strcmp(token, "=") == 0)) {
			break;
		}
		if (strlen(key_name) + strlen(token) > sizeof(key_name) - 2) {
			REPORT_ERROR((stderr, "Key name too long"));
			return (-1);
		}
		strcat(key_name, " ");
		strcat(key_name, token);
	} while (1);

	if (key == 0) {
		REPORT_ERROR((stderr, "Unknown config key \"%s\"", key_name));
		return (-1);
	}

	return (key);
}


STATIC int
get_server_flag_value(const char *value, unsigned len)
{
	int i = 0;

	for (i = 0; server_flags[i].name; ++i) {
		if ((len == strlen(server_flags[i].name)) &&
		    (strncmp(server_flags[i].name, value, len) == 0)) {
			return (server_flags[i].value);
		}
	}

	return (-1);
}


STATIC int
parse_server_flags(const char *value)
{
	int val = 0, v, first = 1;
	const char *s = value;
	const char *e;

	while (*s) {
		while (isspace((unsigned char)*s)) {
			++s;
		}

		if (!first) {
			if (*s != '|') {
				REPORT_ERROR((stderr, "Syntax error: expecting |"));
				val = -1;
				break;
			}

			++s;

			while (isspace((unsigned char)*s)) {
				++s;
			}
		} else {
			first = 0;
		}

		e = s;

		while (isalnum((unsigned char)*e) || *e == '_') {
			++e;
		}

		if (e == s) {
			REPORT_ERROR((stderr, "Syntax error: expecting flag"));
			val = -1;
			break;
		}

		v = get_server_flag_value(s, e - s);
		if (v == -1) {
			REPORT_ERROR((stderr, "Syntax error: invalid flag"));
			val = -1;
			break;
		}

		s = e;
		val |= v;
	}

	return (val);
}


STATIC int
set_game_type_value(server_type *gametype, int key, char *value)
{
	switch (key) {
	case CK_NAME:
		gametype->game_name = strdup(value);
		break;

	case CK_FLAGS:
	{
		int flags = parse_server_flags(value);
		if (flags == -1) {
			return (-1);
		}
		gametype->flags = flags;
	}
	break;

	case CK_DEFAULT_PORT:
	{
		unsigned short port;
		if (sscanf(value, "%hu", &port) != 1) {
			REPORT_ERROR((stderr, "Syntax error on port.  Should be a number between 1 and 65535."));
			return (-1);
		}
		gametype->default_port = port;
		break;
	}

	case CK_GAME_RULE:
		gametype->game_rule = strdup(value);
		break;

	case CK_TEMPLATE_VAR:
		gametype->template_var = strdup(value);
		force_upper_case(gametype->template_var);
		break;

	case CK_MASTER_PROTOCOL:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type\n"));
			return (-1);
		}
		if (value) {
			gametype->master_protocol = strdup(value);
		} else {
			gametype->master_protocol = NULL;
		}
		break;

	case CK_MASTER_QUERY:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master query on non-master game type\n"));
			return (-1);
		}
		if (value) {
			gametype->master_query = strdup(value);
		} else {
			gametype->master_query = NULL;
		}
		break;

	case CK_MASTER_TYPE:
	{
		server_type *type;
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n"));
			return (-1);
		}
		force_lower_case(value);
		type = get_server_type(value);
		if (type == NULL) {
			REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value));
			return (-1);
		}
		gametype->master = type->id;
	}
	break;

	case CK_MASTER_PACKET:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
			return (-1);
		}
		if ((value == NULL) || (value[0] == '\0')) {
			REPORT_ERROR((stderr, "Empty master packet"));
			return (-1);
		}
		gametype->master_packet = memdup(value, value_len);
		gametype->master_len = value_len;
		break;

	case CK_STATUS_PACKET:
		return (set_packet_value(gametype, value, "status",
		       &gametype->status_packet, &gametype->status_len));

	case CK_STATUS2_PACKET:
		return (set_packet_value(gametype, value, "status2",
		       &gametype->rule_packet, &gametype->rule_len));

	case CK_PLAYER_PACKET:
		return (set_packet_value(gametype, value, "player",
		       &gametype->player_packet, &gametype->player_len));

	case CK_RULE_PACKET:
		return (set_packet_value(gametype, value, "rule",
		       &gametype->rule_packet, &gametype->rule_len));

	case CK_PORT_OFFSET:
	{
		short port;
		if (sscanf(value, "%hd", &port) != 1) {
			REPORT_ERROR((stderr, "Syntax error on port.  Should be a number between 32767 and -32768."));
			return (-1);
		}
		gametype->port_offset = port;
		break;
	}
	}

	return (0);
}


STATIC int
set_packet_value(server_type *gametype, char const *value, char const *packet_name,
    char **packet, int *len)
{
	if (gametype->master) {
		REPORT_ERROR((stderr, "Cannot set info packet on master game type"));
		return (-1);
	}
	if ((value == NULL) || (value[0] == '\0')) {
		REPORT_ERROR((stderr, "Empty %s packet", packet_name));
		return (-1);
	}
	// Removed as this doesn't seem to be any reason why we can't do this and it works just fine for warsow
	//if ( *packet == NULL)  {
	//    REPORT_ERROR((stderr, "Cannot set %s packet; extend game type does not define a %s packet", packet_name, packet_name));
	//    return -1;
	//}

	*packet = memdup(value, value_len);
	*len = value_len;
	return (0);
}


STATIC int
modify_game_type_value(server_type *gametype, int key, char *value)
{
	switch (key) {
	case CK_FLAGS:
	{
		int flags = parse_server_flags(value);
		if (flags == -1) {
			return (-1);
		}
		gametype->flags = flags;
	}
	break;

	case CK_MASTER_PROTOCOL:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type"));
			return (-1);
		}
		if (value) {
			gametype->master_protocol = strdup(value);
		} else {
			gametype->master_protocol = NULL;
		}
		break;

	case CK_MASTER_QUERY:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master query on non-master game type"));
			return (-1);
		}
		if (value) {
			gametype->master_query = strdup(value);
		} else {
			gametype->master_query = NULL;
		}
		break;

	case CK_MASTER_PACKET:
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
			return (-1);
		}
		if ((value == NULL) || (value[0] == '\0')) {
			REPORT_ERROR((stderr, "Empty master packet"));
			return (-1);
		}
		gametype->master_packet = memdup(value, value_len);
		gametype->master_len = value_len;
		break;

	case CK_MASTER_TYPE:
	{
		server_type *type;
		if (!gametype->master) {
			REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n"));
			return (-1);
		}
		force_lower_case(value);
		type = get_server_type(value);
		if (type == NULL) {
			REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value));
			return (-1);
		}
		gametype->master = type->id;
	}
	break;
	}
	return (0);
}


STATIC server_type *
get_server_type(char *game_type)
{
	server_type *result;

	result = get_builtin_type(game_type);
	if (result != NULL) {
		return (result);
	}

	return (get_config_type(game_type));
}


STATIC server_type *
get_builtin_type(char *type_string)
{
	server_type *type = &builtin_types[0];

	for ( ; type->id != Q_UNKNOWN_TYPE; type++) {
		if (strcmp(type->type_string, type_string) == 0) {
			return (type);
		}
	}
	return (NULL);
}


STATIC void
add_config_type(server_type *gametype)
{
	if (gametype->id == 0) {
		if (next_gametype_id >= MASTER_SERVER) {
			REPORT_ERROR((stderr, "Exceeded game type limit, ignoring \"%s\"",
			    gametype->type_string));
			return;
		}
		gametype->id = next_gametype_id;
		if (gametype->master) {
			gametype->id |= MASTER_SERVER;
		}
		next_gametype_id++;
	}

	if (max_config_types == 0) {
		max_config_types = 4;
		config_types = (server_type **)malloc(sizeof(server_type *) * (max_config_types + 1));
	} else if (n_config_types >= max_config_types) {
		max_config_types *= 2;
		config_types = (server_type **)realloc(config_types,
			sizeof(server_type *) * (max_config_types + 1));
	}
	config_types[n_config_types] = gametype;
	n_config_types++;
}


STATIC server_type *
get_config_type(char *game_type)
{
	int i;

	for (i = 0; i < n_config_types; i++) {
		if (strcmp(config_types[i]->type_string, game_type) == 0) {
			return (config_types[i]);
		}
	}

	return (NULL);
}


STATIC void
copy_server_type(server_type *dest, server_type *source)
{
	*dest = *source;
}


STATIC void *
memdup(const void *mem, unsigned int len)
{
	void *result = malloc(len);

	if (NULL == result) {
		REPORT_ERROR((stderr, "Failed to malloc %d bytes of memory", len));
		return (NULL);
	}
	memcpy(result, mem, len);
	return (result);
}


/* Parsing primitives
 */
STATIC char *
first_token(char *text)
{
	parse_text = text;
	parse_end = parse_text + strlen(parse_text);
	lex = text;
	token_end = text;

	if (*lex == '#') {
		return (NULL);
	}

	return (get_token());
}


STATIC char *
next_token()
{
	lex = token_end;
	return (get_token());
}


STATIC char *
next_token_dup()
{
	return (strdup(next_token()));
}


STATIC char *
get_token()
{
	char *token = &token_buf[0];

	while (isspace((unsigned char)*token_end)) {
		token_end++;
	}

	if (token_end == parse_end) {
		return (NULL);
	}

	while ((isalnum((unsigned char)*token_end) || *token_end == '.' || *token_end == '_' || *token_end == '-') &&
	    token < &token_buf[0] + sizeof(token_buf)) {
		*token++ = *token_end++;
	}

	if (token == &token_buf[0]) {
		*token++ = *token_end++;
	}
	*token = '\0';
	return (&token_buf[0]);
}


STATIC char *
next_value()
{
	char *token = &token_buf[0];

	while (isspace((unsigned char)*token_end)) {
		token_end++;
	}

	if (token_end == parse_end) {
		return (NULL);
	}

	while (token_end < parse_end && token < &token_buf[0] + sizeof(token_buf)) {
		*token++ = *token_end++;
	}

	*token-- = '\0';

	if (strchr(token_buf, '\\')) {
		return (parse_value(token_buf, (token - token_buf) + 1));
	}

	while (isspace((unsigned char)*token)) {
		*token-- = '\0';
	}

	value_len = token - token_buf + 1;

	return (&token_buf[0]);
}


STATIC char *
parse_value(char *source, int len)
{
	char *value, *v, *end;
	int error = 0;

	value = (char *)malloc(len + 1);
	end = v = value;

/*
 * printf( "parse_value <%.*s>\n", len, source);
 */

	for ( ; len; len--, source++) {
		if (*source != '\\') {
			*v++ = *source;
			if (*source != ' ') {
				end = v;
			}
			continue;
		}
		source++;
		len--;
		if (len == 0) {
			break;
		}
		if (*source == '\\') {
			*v++ = *source;
		} else if (*source == 'n') {
			*v++ = '\n';
		} else if (*source == 'r') {
			*v++ = '\r';
		} else if (*source == ' ') {
			*v++ = ' ';
		} else if (*source == 'x') {
			source++;
			len--;
			if (len < 2) {
				break;
			}
			*v++ = parse_hex(source, 2, &error);
			if (error) {
				break;
			}
			source++;
			len--;
		} else if (isdigit((unsigned char)*source)) {
			if (len < 3) {
				break;
			}
			*v++ = parse_oct(source, 3, &error);
			if (error) {
				break;
			}
			source++;
			len--;
			source++;
			len--;
		} else {
			error = 1;
			REPORT_ERROR((stderr, "Invalid character escape \"%.*s\"", 2,
			    source - 1));
			break;
		}
		end = v;
	}

	if (error) {
		free(value);
		return (NULL);
	}

	value_len = end - value;
	memcpy(token_buf, value, value_len);
	token_buf[value_len] = '\0';

	free(value);
	return (&token_buf[0]);
}


STATIC unsigned int
parse_hex(char *hex, int len, int *error)
{
	char *save_hex = hex;
	int save_len = len;
	unsigned int result = 0;

	*error = 0;
	for ( ; len; len--, hex++) {
		result <<= 4;
		if (!isxdigit((unsigned char)*hex)) {
			*error = 1;
			REPORT_ERROR((stderr, "Invalid hex \"%.*s\"", save_len + 2,
			    save_hex - 2));
			return (0);
		}
		if ((*hex >= '0') && (*hex <= '9')) {
			result |= *hex - '0';
		} else if ((*hex >= 'A') && (*hex <= 'F')) {
			result |= ((*hex - 'A') + 10);
		} else if ((*hex >= 'a') && (*hex <= 'f')) {
			result |= ((*hex - 'a') + 10);
		}
	}
	return (result);
}


STATIC unsigned int
parse_oct(char *oct, int len, int *error)
{
	char *save_oct = oct;
	int save_len = len;
	unsigned int result = 0;

	*error = 0;
	for ( ; len; len--, oct++) {
		result <<= 3;
		if ((*oct >= '0') && (*oct <= '7')) {
			result |= *oct - '0';
		} else {
			*error = 1;
			REPORT_ERROR((stderr, "Invalid octal \"%.*s\"", save_len + 1,
			    save_oct - 1));
			return (0);
		}
	}
	return (result);
}


STATIC char *
force_lower_case(char *string)
{
	char *s = string;

	for ( ; *s; s++) {
		*s = tolower(*s);
	}
	return (string);
}


STATIC char *
force_upper_case(char *string)
{
	char *s = string;

	for ( ; *s; s++) {
		*s = toupper(*s);
	}
	return (string);
}
