/*
**  Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers.
**    All rights reserved.
*/

#ifndef lint
static char util_c_id[] = "@(#)$Id: util.c,v 1.15 2007/12/17 23:59:01 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/types.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

/* libsm includes */
#include <sm/string.h>

/* libdkim includes */
#include "dkim.h"
#include "util.h"

/*
**  DKIM_COLLAPSE -- remove spaces from a string
**
**  Parameters:
**  	str -- string to process
**
**  Return value:
**  	None.
*/

void
dkim_collapse(u_char *str)
{
	u_char *q;
	u_char *r;

	assert(str != NULL);

	for (q = str, r = str; *q != '\0'; q++)
	{
		if (!isspace(*q))
		{
			if (q != r)
				*r = *q;
			r++;
		}
	}

	*r = '\0';
}

/*
**  DKIM_ISLWSP -- return true iff a character is some kind of linear
**                 whitespace
**
**  Parameters:
**  	c -- character being examined
**
**  Return value:
**  	TRUE iff 'c' refers to something considered linear whitespace.
*/

bool
dkim_islwsp(u_int c)
{
	return (isascii(c) && isspace(c) && c != '\n' && c != '\r');
}

/*
**  DKIM_ADDRCMP -- like strcmp(), except for e-mail addresses (i.e. local-part
**                  is case-sensitive, while the domain part is not)
**
**  Parameters:
**  	s1, s2 -- the strings
**
**  Return value:
**  	Like strcmp().  However, if "s2" contains only a domain name,
**  	don't bother comparing the local-parts.
*/

int
dkim_addrcmp(u_char *s1, u_char *s2)
{
	int ret;
	u_char *at1, *at2;

	assert(s1 != NULL);
	assert(s2 != NULL);

	at1 = (u_char *) strchr(s1, '@');
	at2 = (u_char *) strchr(s2, '@');

	/* if one or both contain no "@"s, just be strcmp() */
	if (at1 == NULL || at2 == NULL)
		return strcmp(s1, s2);

	/* first check the local-parts */
	*at1 = '\0';
	*at2 = '\0';

	/* if "s2" contains only a domain name, skip the local-part check */
	if (at2 != s2)
	{
		/* compare the local-parts, case-sensitive */
		ret = strcmp(s1, s2);
		if (ret != 0)
		{
			*at1 = '@';
			*at2 = '@';

			return ret;
		}
	}

	/* now compare the domains, case-insensitive */
	ret = strcasecmp(at1 + 1, at2 + 1);

	*at1 = '@';
	*at2 = '@';

	return ret;
}

/*
**  DKIM_HDRLIST -- build up a header list for use in a regexp
**
**  Parameters:
**  	buf -- where to write
**  	buflen -- bytes at "buf"
**  	hdrlist -- array of header names
**  	first -- first call
**
**  Return value:
**  	TRUE iff everything fit.
*/

bool
dkim_hdrlist(u_char *buf, size_t buflen, u_char **hdrlist, bool first)
{
	int c;
	int len;
	char *p;
	char *q;
	char *end;

	assert(buf != NULL);
	assert(hdrlist != NULL);

	for (c = 0; ; c++)
	{
		if (hdrlist[c] == NULL)
			break;

		if (!first)
		{
			len = sm_strlcat(buf, "|", buflen);
			if (len >= buflen)
				return FALSE;
		}
		else
		{
			len = strlen(buf);
		}

		first = FALSE;

		q = &buf[len];
		end = &buf[buflen - 1];

		for (p = hdrlist[c]; *p != '\0'; p++)
		{
			if (q >= end)
				return FALSE;

			switch (*p)
			{
			  case '*':
				*q = '.';
				q++;
				if (q >= end)
					return FALSE;
				*q = '*';
				q++;
				break;

			  case '.':
				*q = '\\';
				q++;
				if (q >= end)
					return FALSE;
				*q = '.';
				q++;
				break;

			  default:
				*q = *p;
				q++;
				break;
			}
		}
	}

	return TRUE;
}

/*
**  DKIM_LOWERHDR -- convert a string (presumably a header) to all lowercase,
**                   but only up to a colon
**
**  Parameters:
**  	str -- string to modify
**
**  Return value:
**  	None.
*/

void
dkim_lowerhdr(unsigned char *str)
{
	unsigned char *p;

	assert(str != NULL);

	for (p = str; *p != '\0'; p++)
	{
		if (*p == ':')
			return;

		if (isascii(*p) && isupper(*p))
			*p = tolower(*p);
	}
}

/*
**  DKIM_HEXCHAR -- translate a hexadecimal character
**  
**  Parameters:
**  	c -- character to translate
**
**  Return value:
**  	Decimal equivalent.
*/

int
dkim_hexchar(int c)
{
	switch (c)
	{
	  case '0':
	  case '1':
	  case '2':
	  case '3':
	  case '4':
	  case '5':
	  case '6':
	  case '7':
	  case '8':
	  case '9':
		return c - '0';

	  case 'A':
	  case 'B':
	  case 'C':
	  case 'D':
	  case 'E':
	  case 'F':
		return 10 + c - 'A';

	  case 'a':
	  case 'b':
	  case 'c':
	  case 'd':
	  case 'e':
	  case 'f':
		return 10 + c - 'a';

	  default:
		assert(0);
		return -1;
	}
}

/*
**  DKIM_QP_DECODE -- decode a quoted-printable string
**
**  Parameters:
**  	in -- input
**  	out -- output
**  	outlen -- bytes available at "out"
**
**  Return value:
**  	>= 0 -- number of bytes in output
**  	-1 -- parse error
*/

int
dkim_qp_decode(unsigned char *in, unsigned char *out, int outlen)
{
	unsigned char next1;
	unsigned char next2 = 0;
	int xl;
	unsigned char const *p;
	unsigned char *q;
	unsigned char *pos;
	unsigned char const *start;
	unsigned char const *stop;
	unsigned char *end;
	unsigned char const *hexdigits = "0123456789ABCDEF";

	assert(in != NULL);
	assert(out != NULL);

	start = NULL;
	stop = NULL;

	end = out + outlen;

	for (p = in, q = out; *p != '\0' && q <= end; p++)
	{
		switch (*p)
		{
		  case '=':
			next1 = *(p + 1);
			if (next1 != '\0')
				next2 = *(p + 2);

			/* = at EOL */
			if (next1 == '\n' ||
			    (next1 == '\r' && next2 == '\n'))
			{
				stop = p;
				if (start != NULL)
				{
					unsigned char const *x;

					for (x = start; x <= stop; x++)
					{
						if (q <= end)
						{
							*q = *x;
							q++;
						}
					}
				}
				start = NULL;
				stop = NULL;

				p++;
				if (next2 == '\n')
					p++;
				break;
			}

			/* = elsewhere */
			pos = (unsigned char *) strchr(hexdigits, next1);
			if (pos == NULL)
				return -1;
			xl = (pos - (unsigned char *) hexdigits) * 16;

			pos = (unsigned char *) strchr(hexdigits, next2);
			if (pos == NULL)
				return -1;
			xl += (pos - (unsigned char *) hexdigits);

			stop = p;
			if (start != NULL)
			{
				unsigned char const *x;

				for (x = start; x < stop; x++)
				{
					if (q <= end)
					{
						*q = *x;
						q++;
					}
				}
			}
			start = NULL;
			stop = NULL;
			*q = xl;
			q++;

			p += 2;

			break;

		  case ' ':
		  case '\t':
			if (start == NULL)
				start = p;
			break;

		  case '\r':
			break;

		  case '\n':
			if (stop == NULL)
				stop = p;
			if (start != NULL)
			{
				unsigned char const *x;

				for (x = start; x <= stop; x++)
				{
					if (q <= end)
					{
						*q = *x;
						q++;
					}
				}
			}

			if (p > in && *(p - 1) != '\r')
			{
				if (q <= end)
				{
					*q = '\n';
					q++;
				}
			}
			else
			{
				if (q <= end)
				{
					*q = '\r';
					q++;
				}
				if (q <= end)
				{
					*q = '\n';
					q++;
				}
			}

			start = NULL;
			stop = NULL;
			break;

		  default:
			if (start == NULL)
				start = p;
			stop = p;
			break;
		}
	}

	if (start != NULL)
	{
		unsigned char const *x;

		for (x = start; x < p; x++)
		{
			if (q <= end)
			{
				*q = *x;
				q++;
			}
		}
	}

	return q - out;
}

/*
**  DKIM_STRTOUL -- convert string to unsigned long
**
**  Parameters:
**  	str -- string to convert
**  	endptr -- pointer to store string after value
**  	base -- base to convert from
**
**  Return value:
**  	Value of string as unsigned long
*/

unsigned long
dkim_strtoul(const char *str, char **endptr, int base)
{
	char start = '+';
	static char cutlim = ULONG_MAX % 10;
	char c;
	bool erange = FALSE;
	static unsigned long cutoff = ULONG_MAX / 10;
	unsigned long value = 0;
	const char *subj;
	const char *cur;

	if (base != 10)
		return strtoul(str, endptr, base);

	if (str == NULL)
	{
		errno = EINVAL;
		return value;
	}

	subj = str;

	while (*subj != '\0' && isspace(*subj))
		subj++;

	if (*subj == '-' || *subj == '+')
		start = *subj++;

	for (cur = subj; *cur >= '0' && *cur <= '9'; cur++)
	{
		if (erange)
			continue;

		c = *cur - '0';

		if ((value > cutoff) || (value == cutoff && c > cutlim))
		{
			erange = TRUE;
			continue;
		}

		value = (value * 10) + c;
	}

	if (cur == subj)
	{
		if (endptr != NULL)
			*endptr = (char *) str;
		errno = EINVAL;
		return 0;
	}

	if (endptr != NULL)
		*endptr = (char *) cur;

	if (erange)
	{
		errno = ERANGE;
		return ULONG_MAX;
	}

	if (start == '-')
		return -value;
	else
		return value;
}

/*
**  DKIM_STRTOULL -- convert string to unsigned long long
**
**  Parameters:
**  	str -- string to convert
**  	endptr -- pointer to store string after value
**  	base -- base to convert from
**
**  Return value:
**  	Value of string as unsigned long long
*/

unsigned long long
dkim_strtoull(const char *str, char **endptr, int base)
{
	char start = '+';
	char c;
	bool erange = FALSE;
	static char cutlim = ULLONG_MAX % 10;
	static unsigned long long cutoff = ULLONG_MAX / 10;
	unsigned long long value = 0;
	const char *subj;
	const char *cur;

	if (base != 10)
		return strtoull(str, endptr, base);

	if (str == NULL)
	{
		errno = EINVAL;
		return value;
	}

	subj = str;

	while (*subj && isspace(*subj))
		subj++;

	if (*subj == '-' || *subj == '+')
		start = *subj++;

	for (cur = subj; *cur >= '0' && *cur <= '9'; cur++)
	{
		if (erange)
			continue;

		c = *cur - '0';

		if ((value > cutoff) || (value == cutoff && c > cutlim))
		{
			erange = 1;
			continue;
		}

		value = (value * 10) + c;
	}

	if (cur == subj)
	{
		if (endptr != NULL)
			*endptr = (char *) str;
		errno = EINVAL;
		return 0;
	}

	if (endptr != NULL)
		*endptr = (char *) cur;

	if (erange != 0)
	{
		errno = ERANGE;
		return ULLONG_MAX;
	}

	if (start == '-')
		return -value;
	else
		return value;
}


syntax highlighted by Code2HTML, v. 0.9.1