/*
** Copyright 2002 Double Precision, Inc.  See COPYING for
** distribution information.
*/

/*
** $Id: rfc2231.c,v 1.4 2003/03/07 00:47:31 mrsam Exp $
*/

#if    HAVE_CONFIG_H
#include "rfc2045_config.h"
#endif
#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	<ctype.h>
#include	"rfc2045.h"
#include	"rfc822/rfc822.h"

#if HAVE_UNICODE
#include	"unicode/unicode.h"
#endif

/*
** Deallocate a link list of rfc2231param structures.
*/

void rfc2231_paramDestroy(struct rfc2231param *p)
{
	while (p)
	{
		struct rfc2231param *q=p->next;

		free(p);
		p=q;
	}
}

int rfc2231_buildAttrList(struct rfc2231param **paramList,
			  const char *name,

			  const char *attrName,
			  const char *attrValue)
{
	int nameLen=strlen(name);

	if (strncmp(attrName, name, nameLen) == 0 &&
	    (attrName[nameLen] == 0 ||
	     attrName[nameLen] == '*'))
	{
		struct rfc2231param *n
			=malloc(sizeof(struct rfc2231param)), **o;

		const char *p=attrName + nameLen;

		if (!n)
		{
			rfc2231_paramDestroy(*paramList);
			return -1;
		}

		/*
		** A non-rfc 2231 parameter has paramnum set to 0, an
		** rfc 2231 parameter has paramnum set to its number, plus 1.
		*/

		if (*p == 0)
		{
			n->paramnum=0;
		}
		else
		{
			p++;

			n->paramnum=atoi(p)+1;

			if (n->paramnum <= 0)
				n->paramnum=1;
		}

		p=strrchr(attrName, '*');

		n->encoded=p && p[1] == 0;
		n->value=attrValue;

		for (o=paramList; *o; o= &(*o)->next)
			if ( (*o)->paramnum > n->paramnum)
				break;

		n->next= *o;
		*o=n;
	}
	return 0;
}


/*
** Create a link list of rfc2231param structures for a specific attribute
**
** Returns: 0 - ok, < 0 - out of memory.
*/

static int rfc2231_paramCreate(struct rfc2045attr *attr,
			       const char *name,
			       struct rfc2231param **paramList)
{
	*paramList=NULL;

	while (attr)
	{
		if (rfc2231_buildAttrList(paramList, name, attr->name,
					  attr->value) < 0)
			return (-1);
		attr=attr->next;
	}

	return (0);
}

static const char rfc2231_xdigit[]="0123456789ABCDEFabcdef";

static int nyb(char c)
{
	const char *p=strchr(rfc2231_xdigit, c);
	int n;

	if (!p)
		return 0;

	n=p-rfc2231_xdigit;

	if (n >= 16)
		n -= 6;

	return n;
}

/*
** Decode an rfc2231param link list.
**
** charset, language, text, are decoded, if the corresponding args below are
** not null.  Their corresponding lengths (including the null bytes) are
** always saved in the corresponding int args.  rfc2231_decode() is called
** twice to get the lengths, then once again after the buffers are allocated.
*/

void rfc2231_paramDecode(struct rfc2231param *paramList,
			 char *charsetPtr,
			 char *langPtr,
			 char *textPtr,
			 int *charsetLen,
			 int *langLen,
			 int *textLen)
{
	int first=1;

	*charsetLen=*langLen=*textLen=1;	/* null byte */

	if (paramList && paramList->paramnum == 0 &&
	    paramList->next)
		paramList=paramList->next;
	/*
	** Both a non-rfc2231 and an rfc2231 parameter was specified, so
	** take the better one.
	*/

	while (paramList)
	{
		const char *p=paramList->value;

		if (first && paramList->encoded)
		{
			const char *q=strchr(p, '\'');

			if (q && strchr(q+1, '\''))
			{
				while (*p != '\'')
				{
					if (charsetPtr)
						*charsetPtr++ = *p;
					p++;
					(*charsetLen)++;
				}
				p++;
				while (*p != '\'')
				{
					if (langPtr)
						*langPtr++ = *p;
					p++;
					(*langLen)++;
				}
				p++;
			}
		}

		first=0;

		while (*p)
		{
			if (*p == '%' && p[1] && p[2] && paramList->encoded)
			{
				if (textPtr)
					*textPtr++ = nyb(p[1]) * 16 +
						nyb(p[2]);
				p += 3;
			}
			else
			{
				if (textPtr)
					*textPtr++ = *p;

				p++;
			}

			(*textLen)++;
		}

		paramList=paramList->next;
	}

	if (charsetPtr)
		*charsetPtr=0;
	if (langPtr)
		*langPtr=0;
	if (textPtr)
		*textPtr=0;
}

/*
** Retrieve RFC 2231 information from a specific rfc2045attr list
**
** Returns 0 success, -1 for failure
*/

static int rfc2231_decode(struct rfc2045attr *attrList,
			  const char *name,

			  char **chsetPtr,
			  char **langPtr,
			  char **textPtr)
{
	int chsetLen;
	int langLen;
	int textLen;

	struct rfc2231param *paramList;

	if (rfc2231_paramCreate(attrList, name, &paramList) < 0)
		return -1;

	rfc2231_paramDecode(paramList, NULL, NULL, NULL,
			    &chsetLen,
			    &langLen,
			    &textLen);

	if (chsetPtr)
		*chsetPtr=NULL;

	if (langPtr)
		*langPtr=NULL;

	if (textPtr)
		*textPtr=NULL;


	if ((chsetPtr && (*chsetPtr=malloc(chsetLen)) == NULL)
	    || (langPtr && (*langPtr=malloc(langLen)) == NULL)
	    || (textPtr && (*textPtr=malloc(textLen)) == NULL))
	{
		rfc2231_paramDestroy(paramList);

		if (*chsetPtr)
			free(*chsetPtr);

		if (*langPtr)
			free(*langPtr);

		if (*textPtr)
			free(*textPtr);
		return (-1);
	}

	rfc2231_paramDecode(paramList,
			    chsetPtr ? *chsetPtr:NULL,
			    langPtr ? *langPtr:NULL,
			    textPtr ? *textPtr:NULL,
			    &chsetLen,
			    &langLen,
			    &textLen);
	return 0;
}

int rfc2231_decodeType(struct rfc2045 *rfc, const char *name,
		       char **chsetPtr,
		       char **langPtr,
		       char **textPtr)
{
	return rfc2231_decode(rfc->content_type_attr, name,
			      chsetPtr, langPtr, textPtr);
}

int rfc2231_decodeDisposition(struct rfc2045 *rfc, const char *name,
			      char **chsetPtr,
			      char **langPtr,
			      char **textPtr)
{
	return rfc2231_decode(rfc->content_disposition_attr, name,
			      chsetPtr, langPtr, textPtr);
}

static int conv_unicode(char **text, const char *fromChset,
			const struct unicode_info *toChset)
{
#if HAVE_UNICODE

	const struct unicode_info *u=unicode_find(fromChset);

	if (toChset == NULL)
	{
		toChset=unicode_find(unicode_default_chset());

		if (!toChset)
			toChset=&unicode_ISO8859_1;
	}

	if (u)
	{
		char *p=unicode_xconvert(*text, u, toChset);

		if (!p)
			return (-1);

		free(*text);
		*text=p;
	}
#endif
	return (0);
}

int rfc2231_udecodeType(struct rfc2045 *rfc, const char *name,
			const struct unicode_info *myCharset,
			char **textPtr)
{
	char *text, *chset;

	if (rfc2231_decodeType(rfc, name, &chset, NULL, &text) < 0)
		return (-1);

	if (conv_unicode(&text, chset, myCharset) < 0)
	{
		free(text);
		free(chset);
		return (-1);
	}

	*textPtr=text;
	free(chset);
	return (0);
}

int rfc2231_udecodeDisposition(struct rfc2045 *rfc, const char *name,
			       const struct unicode_info *myCharset,
			       char **textPtr)
{
	char *text, *chset;

	if (rfc2231_decodeDisposition(rfc, name, &chset, NULL, &text) < 0)
		return (-1);

	if (conv_unicode(&text, chset, myCharset) < 0)
	{
		free(text);
		free(chset);
		return (-1);
	}

	*textPtr=text;
	free(chset);
	return (0);
}
