/* Synchronet find string routines */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "genwrap.h"
#include "findstr.h"

/****************************************************************************/
/* Pattern matching string search of 'search' in 'pattern'.					*/
/* pattern matching is case-insensitive										*/
/* patterns beginning with ';' are comments (never match)					*/
/* patterns beginning with '!' are reverse-matched (returns FALSE if match)	*/
/* patterns ending in '~' will match string anywhere (sub-string search)	*/
/* patterns ending in '^' will match left string fragment only				*/
/* patterns including '*' must match both left and right string fragments	*/
/* all other patterns are exact-match checking								*/
/****************************************************************************/
BOOL findstr_in_string(const char* search, const char* pattern)
{
	char	buf[256];
	char*	p = (char*)pattern;
	char*	last;
	const char*	splat;
	size_t	len;
	BOOL	found = FALSE;

	if(pattern == NULL)
		return FALSE;

	if(*p == ';')		/* comment */
		return FALSE;

	if(*p == '!')	{	/* reverse-match */
		found = TRUE;
		p++;
	}

	if(search == NULL)
		return found;

	SAFECOPY(buf, p);
	p = buf;

	truncsp(p);
	len = strlen(p);
	if(len > 0) {
		last = p + len - 1;
		if(*last == '~') {
			*last = '\0';
			if(strcasestr(search, p) != NULL)
				found = !found; 
		}

		else if(*last == '^') {
			if(strnicmp(p, search, len - 1) == 0)
				found = !found; 
		}

		else if((splat = strchr(p, '*')) != NULL) {
			int left = splat - p;
			int right = len - (left + 1);
			int slen = strlen(search);
			if(slen < left + right)
				return found;
			if(strnicmp(search, p, left) == 0
				&& strnicmp(p + left + 1, search + (slen - right), right) == 0)
				found = !found;
		}

		else if(stricmp(p, search) == 0)
			found = !found; 
	} 
	return found;
}

static uint32_t encode_ipv4_address(unsigned int byte[])
{
	if(byte[0] > 0xff || byte[1] > 0xff || byte[2] > 0xff || byte[3] > 0xff)
		return 0;
	return (byte[0]<<24) | (byte[1]<<16) | (byte[2]<<8) | byte[3];
}

static uint32_t parse_ipv4_address(const char* str)
{
	unsigned int byte[4];

	if(str == NULL)
		return 0;
	if(sscanf(str, "%u.%u.%u.%u", &byte[0], &byte[1], &byte[2], &byte[3]) != 4)
		return 0;
	return encode_ipv4_address(byte);
}

static uint32_t parse_cidr(const char* p, unsigned* subnet)
{
	unsigned int byte[4];

	if(*p == '!')
		p++;

	*subnet = 0;
	if(sscanf(p, "%u.%u.%u.%u/%u", &byte[0], &byte[1], &byte[2], &byte[3], subnet) != 5 || *subnet > 32)
		return 0;
	return encode_ipv4_address(byte);
}

static BOOL is_cidr_match(const char *p, uint32_t ip_addr, uint32_t cidr, unsigned subnet)
{
	BOOL	match = FALSE;

	if(*p == '!')
		match = TRUE;

	if(((ip_addr ^ cidr) >> (32-subnet)) == 0)
		match = !match;

	return match;
}

static BOOL findstr_compare(const char* str, uint32_t ip_addr, const char* pattern)
{
	uint32_t cidr;
	unsigned subnet;

	if(ip_addr != 0 && (cidr = parse_cidr(pattern, &subnet)) != 0)
		return is_cidr_match(pattern, ip_addr, cidr, subnet);
	return findstr_in_string(str, pattern);
}

/****************************************************************************/
/* Pattern matching string search of 'insearchof' in 'list'.				*/
/****************************************************************************/
BOOL findstr_in_list(const char* insearchof, str_list_t list)
{
	return find2strs_in_list(insearchof, NULL, list);
}

/****************************************************************************/
/* Pattern matching string search of 'str1' or 'str2' in 'list'.			*/
/****************************************************************************/
BOOL find2strs_in_list(const char* str1, const char* str2, str_list_t list)
{
	size_t	index;
	BOOL	found=FALSE;
	char*	p;
	uint32_t ip_addr1, ip_addr2;

	if(list == NULL)
		return FALSE;
	ip_addr1 = parse_ipv4_address(str1);
	ip_addr2 = parse_ipv4_address(str2);
	for(index=0; list[index]!=NULL; index++) {
		p=list[index];
		if(*p == '\0')
			continue;
		found = findstr_compare(str1, ip_addr1, p);
		if(!found && str2 != NULL)
			found = findstr_compare(str2, ip_addr2, p);
		if(found != (*p=='!'))
			break;
	}
	return found;
}

/****************************************************************************/
/* Pattern matching string search of 'insearchof' in 'fname'.				*/
/****************************************************************************/
BOOL findstr(const char* insearchof, const char* fname)
{
	return find2strs(insearchof, NULL, fname);
}

/****************************************************************************/
/* Pattern matching string search of 'str1' or 'str2' in 'fname'.			*/
/****************************************************************************/
BOOL find2strs(const char* str1, const char* str2, const char* fname)
{
	char		str[256];
	BOOL		found=FALSE;
	FILE*		fp;
	uint32_t	ip_addr1, ip_addr2;

	if(fname == NULL || *fname == '\0')
		return FALSE;

	if((fp=fopen(fname,"r"))==NULL)
		return FALSE; 

	ip_addr1 = parse_ipv4_address(str1);
	ip_addr2 = parse_ipv4_address(str2);
	while(!feof(fp) && !ferror(fp)) {
		if(!fgets(str,sizeof(str),fp))
			break;
		char* p = str;
		SKIP_WHITESPACE(p);
		if(*p == '\0')
			continue;
		c_unescape_str(p);
		found = findstr_compare(str1, ip_addr1, p);
		if(!found && str2 != NULL)
			found = findstr_compare(str2, ip_addr2, p);
		if(found != (*p=='!'))
			break;
	}

	fclose(fp);
	return found;
}

static char* process_findstr_item(size_t index, char *str, void* cbdata)
{
	SKIP_WHITESPACE(str);
	return c_unescape_str(str);
}

/****************************************************************************/
str_list_t findstr_list(const char* fname)
{
	FILE*	fp;
	str_list_t	list;

	if((fp=fopen(fname,"r"))==NULL)
		return NULL;

	list=strListReadFile(fp, NULL, /* Max line length: */255);
	strListModifyEach(list, process_findstr_item, /* cbdata: */NULL);

	fclose(fp);

	return list;
}