/*
 * PERMISSIONS module
 *
 * Copyright (C) 2003 Miklós Tirpák (mtirpak@sztaki.hu)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio 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
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "../../core/sr_module.h"
#include "rule.h"
#include "parse_config.h"


/*
 * parse a comma separated expression list like a, b, c
 * return 0 on success, -1 on error
 * parsed expressions are returned in **e
 */
static int parse_expression_list(char *str, expression **e)
{
	int start = 0, i = -1, j = -1, apost = 0;
	char str2[EXPRESSION_LENGTH];
	expression *e1 = NULL, *e2;

	if(!str || !e)
		return -1;

	*e = NULL;
	do {
		i++;
		switch(str[i]) {
			case '"':
				apost = !apost;
				break;
			case ',':
				if(apost)
					break;
			case '\0': /* word found */
				while((str[start] == ' ') || (str[start] == '\t'))
					start++;
				if(str[start] == '"')
					start++;
				j = i - 1;
				while((0 < j) && ((str[j] == ' ') || (str[j] == '\t')))
					j--;
				if((0 < j) && (str[j] == '"'))
					j--;
				if(start <= j) {
					/* valid word */
					if(j - start + 1 + 1 > EXPRESSION_LENGTH) {
						LM_ERR("expression too long "
							   "<%.*s>(%d)\n",
								j - start + 1, str + start, j - start + 1);
						goto error;
					}
					strncpy(str2, str + start, j - start + 1);
					str2[j - start + 1] = '\0';

					e2 = new_expression(str2);
					if(!e2)
						/* memory error */
						goto error;

					if(e1) {
						/* it is not the first */
						e1->next = e2;
						e1 = e2;
					} else {
						/* it is the first */
						*e = e1 = e2;
					}
				} else {
					/* parsing error */
					goto error;
				}
				/* for the next word */
				start = i + 1;
		}
	} while(str[i] != '\0');

	return 0;
error:
	if(*e) {
		free_expression(*e);
		*e = NULL;
	}
	return -1;
}


/*
 * parse a complex expression list like a, b, c EXCEPT d, e
 * return 0 on success, -1 on error
 * parsed expressions are returned in **e, and exceptions are returned in **e_exceptions
 */
static int parse_expression(char *sv, expression **e, expression **e_exceptions)
{
	char *except, str2[LINE_LENGTH + 1];
	int i, j;

	if(!sv || !e || !e_exceptions)
		return -1;

	if(strlen(sv) >= LINE_LENGTH) {
		LM_ERR("expression string is too long (%s)\n", sv);
		return -1;
	}

	except = strstr(sv, " EXCEPT ");
	if(except) {
		/* exception found */
		strncpy(str2, sv, except - sv);
		str2[except - sv] = '\0';
		/* except+8 points to the exception */
		if(parse_expression_list(except + 8, e_exceptions)) {
			/* error */
			*e = *e_exceptions = NULL;
			return -1;
		}
	} else {
		/* no exception */
		strncpy(str2, sv, LINE_LENGTH);
		*e_exceptions = NULL;
	}

	for(i = 0; isspace((int)str2[i]); i++)
		;
	for(j = strlen(str2) - 1; isspace((int)str2[j]); str2[j--] = 0)
		;

	if(strcmp("ALL", str2 + i) == 0) {
		*e = NULL;
	} else {
		if(parse_expression_list(str2 + i, e)) {
			/* error */
			if(*e_exceptions)
				free_expression(*e_exceptions);
			*e = *e_exceptions = NULL;
			return -1;
		}
	}
	return 0;
}


/*
 * parse one line of the config file
 * return the rule according to line
 */
static rule *parse_config_line(char *line)
{
	rule *rule1;
	expression *left, *left_exceptions, *right, *right_exceptions;
	int i = -1, exit = 0, apost = 0, colon = -1, eval = 0;
	static char str1[LINE_LENGTH], str2[LINE_LENGTH + 1];

	if(!line)
		return 0;

	rule1 = 0;
	left = left_exceptions = right = right_exceptions = 0;

	while(!exit) {
		i++;
		switch(line[i]) {
			case '"':
				apost = !apost;
				eval = 1;
				break;

			case ':':
				if(!apost)
					colon = i;
				eval = 1;
				break;

			case '#':
				if(apost)
					break;
			case '\0':
			case '\n':
				exit = 1;
				break;
			case ' ':
				break;
			case '\t':
				break;

			default:
				eval = 1;
		}
	}

	if(eval) {
		if((0 < colon) && (colon + 1 < i)) {
			/* valid line */

			/* left expression */
			strncpy(str1, line, colon);
			str1[colon] = '\0';
			if(parse_expression(str1, &left, &left_exceptions)) {
				/* error */
				LM_ERR("failed to parse line-left: %s\n", line);
				goto error;
			}

			/* right expression */
			strncpy(str2, line + colon + 1, i - colon - 1);
			str2[i - colon - 1] = '\0';
			if(parse_expression(str2, &right, &right_exceptions)) {
				/* error */
				LM_ERR("failed to parse line-right: %s\n", line);
				goto error;
			}

			rule1 = new_rule();
			if(!rule1) {
				LM_ERR("can't create new rule\n");
				goto error;
			}

			rule1->left = left;
			rule1->left_exceptions = left_exceptions;
			rule1->right = right;
			rule1->right_exceptions = right_exceptions;
			return rule1;
		} else {
			/* error */
			LM_ERR("failed to parse line: %s\n", line);
		}
	}
	return 0;

error:
	if(left)
		free_expression(left);
	if(left_exceptions)
		free_expression(left_exceptions);

	if(right)
		free_expression(right);
	if(right_exceptions)
		free_expression(right_exceptions);

	return 0;
}


/*
 * parse a config file
 * return a list of rules
 */
rule *parse_config_file(char *filename)
{
	FILE *file;
	char line[LINE_LENGTH + 1];
	rule *start_rule = NULL, *rule1 = NULL, *rule2 = NULL;

	file = fopen(filename, "r");
	if(!file) {
		LM_INFO("file not found: %s\n", filename);
		return NULL;
	}

	while(fgets(line, LINE_LENGTH, file)) {
		rule2 = parse_config_line(line);
		if(rule2) {
			if(rule1) {
				/* it is not the first rule */
				rule1->next = rule2;
			} else {
				/* it is the first rule */
				start_rule = rule2;
			}
			rule1 = rule2;
		}
	}

	fclose(file);
	return start_rule; /* returns the linked list */
}
