/*
 * Unix SMB/CIFS implementation.
 *
 * Registry helper routines
 *
 * Copyright (C) Gregor Beck 2010
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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, see <http://www.gnu.org/licenses/>.
 */
/**
 * @file   reg_parse_internal.h
 * @author Gregor Beck <gb@sernet.de>
 * @date   Sep 2010
 * @brief
 */

#include "reg_parse_internal.h"
#include "cbuf.h"
#include "srprs.h"
#include "registry.h"

size_t iconvert_talloc(const void* ctx,
		       smb_iconv_t cd,
		       const char* src, size_t srclen,
		       char** pdst)
{
	size_t dstlen, ret;
	size_t obytes, ibytes;
	char *optr, *dst, *tmp;
	const char* iptr;

	if (cd == NULL || cd == ((smb_iconv_t)-1)) {
		return -1;
	}

	dst = *pdst;

	if (dst == NULL) {
		/*
		 * Allocate an extra two bytes for the
		 * terminating zero.
		 */
		dstlen = srclen + 2;
		dst = (char *)talloc_size(ctx, dstlen);
		if (dst == NULL) {
			DEBUG(0,("iconver_talloc no mem\n"));
			return -1;
		}
	} else {
		dstlen = talloc_get_size(dst);
	}
convert:
	iptr   = src;
	ibytes = srclen;
	optr   = dst;
	obytes = dstlen-2;

	ret = smb_iconv(cd, &iptr, &ibytes, &optr, &obytes);

	if(ret == -1) {
		const char *reason="unknown error";
		switch(errno) {
		case EINVAL:
			reason="Incomplete multibyte sequence";
			break;
		case E2BIG:
			dstlen = 2*dstlen + 2;
			tmp    = talloc_realloc(ctx, dst, char, dstlen);
			if (tmp == NULL) {
				reason="talloc_realloc failed";
				break;
			}
			dst = tmp;
			goto convert;
		case EILSEQ:
			reason="Illegal multibyte sequence";
			break;
		}
		DEBUG(0,("Conversion error: %s(%.80s) %li\n", reason, iptr,
			 (long int)(iptr-src)));
		talloc_free(dst);
		return -1;
	}

	dstlen = (dstlen-2) - obytes;

	SSVAL(dst, dstlen, 0);

	*pdst = dst;
	return dstlen;
}

#ifndef HKEY_CURRENT_CONFIG
#define HKEY_CURRENT_CONFIG		0x80000005
#endif
#ifndef HKEY_DYN_DATA
#define HKEY_DYN_DATA			0x80000006
#endif
#ifndef HKEY_PERFORMANCE_TEXT
#define HKEY_PERFORMANCE_TEXT		0x80000050
#endif
#ifndef HKEY_PERFORMANCE_NLSTEXT
#define HKEY_PERFORMANCE_NLSTEXT	0x80000060
#endif

#define HIVE_INFO_ENTRY(SHORT,LONG)		\
const struct hive_info HIVE_INFO_##SHORT = {	\
	.handle = LONG,				\
	.short_name = #SHORT,			\
	.short_name_len = sizeof(#SHORT)-1,	\
	.long_name = #LONG,			\
	.long_name_len = sizeof(#LONG)-1,	\
}

HIVE_INFO_ENTRY(HKLM, HKEY_LOCAL_MACHINE);
HIVE_INFO_ENTRY(HKCU, HKEY_CURRENT_USER);
HIVE_INFO_ENTRY(HKCR, HKEY_CLASSES_ROOT);
HIVE_INFO_ENTRY(HKU , HKEY_USERS);
HIVE_INFO_ENTRY(HKCC, HKEY_CURRENT_CONFIG);
HIVE_INFO_ENTRY(HKDD, HKEY_DYN_DATA);
HIVE_INFO_ENTRY(HKPD, HKEY_PERFORMANCE_DATA);
HIVE_INFO_ENTRY(HKPT, HKEY_PERFORMANCE_TEXT);
HIVE_INFO_ENTRY(HKPN, HKEY_PERFORMANCE_NLSTEXT);
#undef HIVE_INFO_ENTRY

const struct hive_info* HIVE_INFO[] = {
	&HIVE_INFO_HKLM, &HIVE_INFO_HKCU, &HIVE_INFO_HKCR, &HIVE_INFO_HKU,
	&HIVE_INFO_HKCC, &HIVE_INFO_HKDD, &HIVE_INFO_HKPD, &HIVE_INFO_HKPT,
	&HIVE_INFO_HKPN, NULL
};

#define TOINT(A,B) ((int)(A) << 8) + (int)(B)

bool srprs_hive(const char** ptr, const struct hive_info** result)
{
	const char* str = *ptr;
	const struct hive_info* info = NULL;
	bool long_hive = false;

	if ((toupper(str[0]) != 'H') || (toupper(str[1]) != 'K')
	    || (str[2] == '\0') )
	{
		return false;
	}

	switch ( TOINT(toupper(str[2]), toupper(str[3])) ) {
	case TOINT('E', 'Y'):
		if (str[4] == '_') {
			int i;
			for (i=0; (info = HIVE_INFO[i]); i++) {
				if (strncmp(&str[5], &info->long_name[5],
					    info->long_name_len-5) == 0)
				{
					long_hive = true;
					break;
				}
			}
		}
		break;
	case TOINT('L', 'M'):
		info = &HIVE_INFO_HKLM;
		break;
	case TOINT('C', 'U'):
		info = &HIVE_INFO_HKCU;
		break;
	case TOINT('C', 'R'):
		info = &HIVE_INFO_HKCR;
		break;
	case TOINT('C', 'C'):
		info = &HIVE_INFO_HKCC;
		break;
	case TOINT('D', 'D'):
		info = &HIVE_INFO_HKDD;
		break;
	case TOINT('P', 'D'):
		info = &HIVE_INFO_HKPD;
		break;
	case TOINT('P', 'T'):
		info = &HIVE_INFO_HKPT;
		break;
	case TOINT('P', 'N'):
		info = &HIVE_INFO_HKPN;
		break;
	default:
		if (toupper(str[2]) == 'U') {
			info = &HIVE_INFO_HKU;
		}
		break;
	}
	if (info != NULL) {
		if (result != NULL) {
			*result = info;
		}
		*ptr += long_hive ? info->long_name_len : info->short_name_len;
		return true;
	}
	return false;
}

const struct hive_info* hive_info(const char* name)
{
	const struct hive_info* info = NULL;
	srprs_hive(&name, &info);
	return info;
}

const char *smbreg_get_charset(const char *c)
{
	if (strcmp(c, "dos") == 0) {
		return lp_dos_charset();
	} else if (strcmp(c, "unix") == 0) {
		return lp_unix_charset();
	} else {
		return c;
	}
}

bool set_iconv(smb_iconv_t* t, const char* to, const char* from)
{
	smb_iconv_t cd = (smb_iconv_t)-1;

	if (to && from) {
		to   = smbreg_get_charset(to);
		from = smbreg_get_charset(from);
		cd   = smb_iconv_open(to, from);
		if (cd == ((smb_iconv_t)-1)) {
			return false;
		}
	}
	if ((*t != (smb_iconv_t)NULL) && (*t != (smb_iconv_t)-1)) {
		smb_iconv_close(*t);
	}
	*t = cd;
	return true;
}

/**
 * Parse option string
 * @param[in,out] ptr parse position
 * @param[in] mem_ctx talloc context
 * @param[out] name ptr 2 value
 * @param[out] value ptr 2 value
 * @return true on success
 */
bool srprs_option(const char** ptr, const void* mem_ctx, char** name, char** value)
{
	const char* pos = *ptr;
	void* ctx = talloc_new(mem_ctx);

	cbuf* key = cbuf_new(ctx);
	cbuf* val = NULL;

	while(srprs_charsetinv(&pos, ",= \t\n\r", key))
		;
	if (pos == *ptr) {
		talloc_free(ctx);
		return false;
	}

	if (name != NULL) {
		*name = talloc_steal(mem_ctx, cbuf_gets(key, 0));
	}

	if (*pos == '=') {
		val = cbuf_new(ctx);
		pos++;
		if (!srprs_quoted_string(ptr, val, NULL)) {
			while(srprs_charsetinv(&pos, ", \t\n\r", val))
				;
		}
		if (value != NULL) {
			*value = talloc_steal(mem_ctx, cbuf_gets(val, 0));
		}
	} else {
		if (value != NULL) {
			*value = NULL;
		}
	}

	while(srprs_char(&pos, ','))
		;

	*ptr = pos;
	return true;
}

#define CH_INVALID ((charset_t)-1)
static const struct {
	const char* const name;
	charset_t ctype;
	int  len;
	uint8_t seq[4];
} BOM[] = {
	{"UTF-8",    CH_UTF8,    3, {0xEF, 0xBB, 0xBF}},
	{"UTF-32LE", CH_INVALID, 4, {0xFF, 0xFE, 0x00, 0x00}},
	{"UTF-16LE", CH_UTF16LE, 2, {0xFF, 0xFE}},
	{"UTF-16BE", CH_UTF16BE, 2, {0xFE, 0xFF}},
	{"UTF-32BE", CH_INVALID, 4, {0x00, 0x00, 0xFE, 0xFF}},
	{ .name = NULL }
};

bool srprs_bom(const char** ptr, const char** name, charset_t* ctype)
{
	int i;
	for (i=0; BOM[i].name; i++) {
		if (memcmp(*ptr, BOM[i].seq, BOM[i].len) == 0) {
			break;
		}
	}

	if (BOM[i].name != NULL) {
		DEBUG(0, ("Found Byte Order Mark for : %s\n", BOM[i].name));

		if (name != NULL) {
			*name  = BOM[i].name;
		}

		if (ctype != NULL) {
			*ctype = BOM[i].ctype;
		}

		*ptr  += BOM[i].len;

		return true;
	}
	return false;
}

int write_bom(FILE* file, const char* charset, charset_t ctype)
{
	int i;
	if ( charset == NULL ) {
		for (i=0; BOM[i].name; i++) {
			if (BOM[i].ctype == ctype) {
				return fwrite(BOM[i].seq, 1, BOM[i].len, file);
			}
		}
		DEBUG(0, ("No Byte Order Mark for charset_t: %u\n", (unsigned)ctype));
	} else {
		for (i=0; BOM[i].name; i++) {
			if (strcasecmp_m(BOM[i].name, charset) == 0) {
				return fwrite(BOM[i].seq, 1, BOM[i].len, file);
			}
		}
		DEBUG(0, ("No Byte Order Mark for charset_t: %s\n", charset));
	}
	return 0;
}


int cbuf_puts_case(cbuf* s, const char* str, size_t len, enum fmt_case fmt)
{
	size_t pos = cbuf_getpos(s);
	int ret = cbuf_puts(s, str, len);
	char* ptr = cbuf_gets(s,pos);

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

	switch (fmt) {
	case FMT_CASE_PRESERVE:
		break;
	case FMT_CASE_UPPER:
		while(*ptr != '\0') {
			*ptr = toupper(*ptr);
			ptr++;
		}
		break;
	case FMT_CASE_TITLE:
		*ptr = toupper(*ptr);
		ptr++;
		FALL_THROUGH;
	case FMT_CASE_LOWER:
		while(*ptr != '\0') {
			*ptr = tolower(*ptr);
			ptr++;
		}
	}
	return ret;
}
