/*
 * Trivial smb.conf parsing code
 * iniparser compatibility layer.
 *
 * Copyright Jeremy Allison <jra@samba.org> 2014
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License Version 3 or later, in which case the provisions
 * of the GPL are required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 *
 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <stddef.h>
#include "tini.h"
#include "tiniparser.h"

struct tiniparser_entry {
	struct tiniparser_entry *next_entry;
	char *key;
	char *value;
};

struct tiniparser_section {
	struct tiniparser_section *next_section;
	struct tiniparser_entry *entry_list;
	char section_name[];
};

struct tiniparser_dictionary {
	struct tiniparser_section *section_list;
};

/*
 * Find a section from a given key.
 * Also return start of subkey.
 * Return NULL if section name can't be found,
 * if no section name given, or no subkey given.
 */

static struct tiniparser_section *find_section(struct tiniparser_dictionary *d,
					const char *key,
					const char **subkey)
{
	struct tiniparser_section *curr_section;
	const char *p;
	size_t section_len;

	if (key == NULL) {
		return NULL;
	}
	p = strchr(key, ':');
	if (p == NULL) {
		/* No section. */
		return NULL;
	}

	section_len = p - key;
	/* Ensure we have at least one character of section name. */
	if (section_len == 0) {
		return NULL;
	}
	/* Ensure we have at least one character of subkey. */
	if (p[1] == '\0') {
		return NULL;
	}

	for (curr_section = d->section_list;
			curr_section;
			curr_section = curr_section->next_section) {
		/*
		 * Check if the key section matches the
		 * section name *exactly* (with terminating
		 * null after section_len characters.
		 */
		if ((strncasecmp(key, curr_section->section_name, section_len) == 0) &&
				(curr_section->section_name[section_len] == '\0')) {
			*subkey = p+1;
			return curr_section;
		}
	}
	return NULL;
}

static struct tiniparser_entry *find_entry(struct tiniparser_section *section,
					const char *key)
{
	struct tiniparser_entry *curr_entry;

	for (curr_entry = section->entry_list;
			curr_entry;
			curr_entry = curr_entry->next_entry) {
		if (strcasecmp(key,
				curr_entry->key) == 0) {
			return curr_entry;
		}
	}
	return NULL;
}

const char *tiniparser_getstring(struct tiniparser_dictionary *d,
			const char *key,
			const char *default_value)
{
	struct tiniparser_section *section;
	struct tiniparser_entry *entry;
	const char *subkey;

	section = find_section(d, key, &subkey);
	if (section == NULL) {
		return default_value;
	}

	entry = find_entry(section, subkey);
	if (entry == NULL) {
		return default_value;
	}

	return entry->value;
}


bool tiniparser_getboolean(struct tiniparser_dictionary *d,
			const char *key,
			bool default_value)
{
	const char *value = tiniparser_getstring(d, key, NULL);

	if (value == NULL) {
		return default_value;
	}

	switch(value[0]) {
		case '1':
		case 'T':
		case 't':
		case 'y':
		case 'Y':
			return true;
		case '0':
		case 'F':
		case 'f':
		case 'n':
		case 'N':
			return false;
		default:
			break;
	}

	return default_value;
}

int tiniparser_getint(struct tiniparser_dictionary *d,
			const char *key,
			int default_value)
{
	const char *value = tiniparser_getstring(d, key, NULL);

	if (value == NULL) {
		return default_value;
	}

	return (int)strtol(value, NULL, 0);
}

static bool value_parser(const char *key,
			const char *value,
			void *private_data)
{
	struct tiniparser_dictionary *d =
		(struct tiniparser_dictionary *)private_data;
	struct tiniparser_section *section = d->section_list;
	struct tiniparser_entry *entry = NULL;
	size_t val_len;
	size_t key_len;

	if (section == NULL) {
		return false;
	}
	if (key == NULL) {
		return false;
	}
	if (value == NULL) {
		return false;
	}

	key_len = strlen(key) + 1;
	val_len = strlen(value) + 1;

	entry = find_entry(section, key);
	if (entry) {
		/* Replace current value. */
		char *new_val = malloc(val_len);
		if (new_val == NULL) {
			return false;
		}
		memcpy(new_val, value, val_len);
		free(entry->value);
		entry->value = new_val;
		return true;
	}

	/* Create a new entry. */
	entry = malloc(sizeof(struct tiniparser_entry));
	if (entry == NULL) {
		return false;
	}
	entry->key = malloc(key_len);
	if (entry->key == NULL) {
		free(entry);
		return false;
	}
	memcpy(entry->key, key, key_len);

	entry->value = malloc(val_len);
	if (entry->value == NULL) {
		free(entry->key);
		free(entry);
		return false;
	}
	memcpy(entry->value, value, val_len);

	entry->next_entry = section->entry_list;
	section->entry_list = entry;
	return true;
}

static bool section_parser(const char *section_name,
			void *private_data)
{
	struct tiniparser_section **pp_section;
	struct tiniparser_section *new_section;
	struct tiniparser_dictionary *d =
		(struct tiniparser_dictionary *)private_data;
	size_t section_name_len;

	if (section_name == NULL) {
		return false;
	}

	/* Section names can't contain ':' */
	if (strchr(section_name, ':') != NULL) {
		return false;
	}

	/* Do we already have this section ? */
	for (pp_section = &d->section_list;
			*pp_section;
			pp_section = &(*pp_section)->next_section) {
		if (strcasecmp(section_name,
				(*pp_section)->section_name) == 0) {
			/*
			 * Move to the front of the list for
			 * value_parser() to find it.
			 */

			/* First save current entry. */
			struct tiniparser_section *curr_section = *pp_section;

			/* Now unlink current entry from list. */
			*pp_section = curr_section->next_section;

			/* Make current entry next point to whole list. */
			curr_section->next_section = d->section_list;

			/* And replace list with current entry at start. */
			d->section_list = curr_section;

			return true;
		}
	}

	section_name_len = strlen(section_name) + 1;

	/* Create new section. */
	new_section = malloc(
		offsetof(struct tiniparser_section, section_name) +
		section_name_len);
	if (new_section == NULL) {
		return false;
	}

	memcpy(new_section->section_name, section_name, section_name_len);

	new_section->entry_list = NULL;

	/* Add it to the head of the singly linked list. */
	new_section->next_section = d->section_list;
	d->section_list = new_section;
	return true;
}

struct tiniparser_dictionary *tiniparser_load_stream(FILE *fp)
{
	bool ret;
	struct tiniparser_dictionary *d = NULL;

	d = malloc(sizeof(struct tiniparser_dictionary));
	if (d == NULL) {
		return NULL;
	}
	d->section_list = NULL;

	ret = tini_parse(fp,
			false,
			section_parser,
			value_parser,
			d);
	if (ret == false) {
		tiniparser_freedict(d);
		d = NULL;
	}
	return d;
}

struct tiniparser_dictionary *tiniparser_load(const char *filename)
{
	struct tiniparser_dictionary *d;
	FILE *fp = fopen(filename, "r");

	if (fp == NULL) {
		return NULL;
	}

	d = tiniparser_load_stream(fp);

	fclose(fp);

	return d;
}

void tiniparser_freedict(struct tiniparser_dictionary *d)
{
	struct tiniparser_section *curr_section, *next_section;

	if (d == NULL) {
		return;
	}

	for (curr_section = d->section_list;
			curr_section;
			curr_section = next_section) {
		struct tiniparser_entry *curr_entry, *next_entry;

		next_section = curr_section->next_section;

		for (curr_entry = curr_section->entry_list;
				curr_entry;
				curr_entry = next_entry) {
			next_entry = curr_entry->next_entry;

			free(curr_entry->key);
			free(curr_entry->value);
			free(curr_entry);
		}
		free(curr_section);
	}
	free(d);
}
