/*
 * Copyright (C) 2003 Ximian, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Author: Chris Toshok (toshok@ximian.com)
 * Author: Armin Bauer (armin.bauer@opensync.org)
 *
 */

#include "vformat.h"
#include "clog.h"

//#ifdef HAVE_CONFIG_H
//#include "config.h"
//#endif

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <iconv.h>
//#include <opensync/opensync.h>

#define TRACE_INTERNAL 1
#define TRACE_ENTRY 1
#define TRACE_EXIT 1
#define TRACE_ERROR 0

static size_t base64_encode_step(const unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save);
static size_t base64_decode_step(const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save);
static size_t base64_decode_simple (char *data, size_t len);
static char  *base64_encode_simple (const char *data, size_t len);

static size_t quoted_decode_simple (char *data, size_t len);
static char *quoted_encode_simple (const unsigned char *string, int len);


/**
 * _helper_is_base64 is helper function to check i a string is "b" or "base64"
 * @param check_string string that should be compared with "b" or "base64"
 * @return 0 if check_string is not base64  and 1 if it is
 */
static int _helper_is_base64(const char *check_string)
{
	if(!g_ascii_strcasecmp ((char *) check_string, "BASE64") ||
	   !g_ascii_strcasecmp ((char *) check_string, "b") )
		return (1);
	return (0);
}

time_t b_vformat_time_to_unix(const char *inptime)
{
	char *date = NULL;
	char *time = NULL;
	char *ftime = NULL;
	if ((ftime = g_strrstr(inptime, "T"))) {

		date = g_strndup(inptime, ftime - inptime);
		if (ftime[3] == ':')
			time = g_strndup(ftime + 1, 8);
		else
			time = g_strndup(ftime + 1, 6);
	} else {
		date = g_strdup(inptime);
	}

	struct tm btime;
	memset(&btime, 0, sizeof(struct tm));
	btime.tm_isdst = -1;

	if (strlen(date) == 10) {
		btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111 - 1900;
		btime.tm_mon = date[5] * 10 + date[6] - '0' * 11 - 1;
		btime.tm_mday = date[8] * 10 + date[9] - '0' * 11;
	} else {
		btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111- 1900;
		btime.tm_mon = date[4] * 10 + date[5] - '0' * 11 - 1;
		btime.tm_mday = date[6] * 10 + date[7] - '0' * 11;
	}

	if (time && strlen(time) == 8) {
		//Time
		btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
		btime.tm_min = time[3] * 10 + time[4] - '0' * 11;
		btime.tm_sec = time[6] * 10 + time[7] - '0' * 11;
	} else if (time && strlen(time) == 6) {
		btime.tm_hour = time[0] * 10 + time[1] - '0' * 11;
		btime.tm_min = time[2] * 10 + time[3] - '0' * 11;
		btime.tm_sec = time[4] * 10 + time[5] - '0' * 11;
	}

	time_t utime = mktime(&btime);
	return utime;
}

static char *_fold_lines (char *buf)
{
	GString *str = g_string_new ("");
	GString *line = g_string_new ("");
	char *p = buf;
	char *next, *next2, *q;
	gboolean newline = TRUE;
	gboolean quotedprintable = FALSE;

	/*
	 *  We're pretty liberal with line folding here. We handle
	 *  lines folded with \r\n<WS>, \n\r<WS>, \n<WS>, =\r\n and =\n\r.
	 *  We also turn single \r's and \n's not followed by <WS> into \r\n's.
	 */

	while (*p) {

		/* search new lines for quoted printable encoding */
		if (newline) {
			for (q=p; *q != '\n' && *q != '\0'; q++)
				line = g_string_append_unichar (line, g_utf8_get_char (q));

			if (strstr(line->str, "ENCODING=QUOTED-PRINTABLE"))
				quotedprintable = TRUE;

			g_string_free(line, TRUE);
			line = g_string_new ("");

			newline = FALSE;
		}


		if ((quotedprintable && *p == '=') || *p == '\r' || *p == '\n') {
			next = g_utf8_next_char (p);
			if (*next == '\n' || *next == '\r') {
				next2 = g_utf8_next_char (next);
				if (*next2 == '\n' || *next2 == '\r' || *next2 == ' ' || *next2 == '\t') {
					p = g_utf8_next_char (next2);
				}
				else if(quotedprintable) {
					p = g_utf8_next_char (next);
				}
				else {
					str = g_string_append (str, CRLF);
					p = g_utf8_next_char (next);
					newline = TRUE;
					quotedprintable = FALSE;
				}
			}
			else if (*p == '=') {
				str = g_string_append_unichar (str, g_utf8_get_char (p));
				p = g_utf8_next_char (p);
			}
			else if (*next == ' ' || *next == '\t') {
				p = g_utf8_next_char (next);
			}
			else {
				str = g_string_append (str, CRLF);
				p = g_utf8_next_char (p);
				newline = TRUE;
				quotedprintable = FALSE;
			}
		}
		else {
			str = g_string_append_unichar (str, g_utf8_get_char (p));
			p = g_utf8_next_char (p);
		}
	}

	g_free (buf);
	g_string_free(line, TRUE);

	return g_string_free (str, FALSE);
}

/* skip forward until we hit the CRLF, or \0 */
static void _skip_to_next_line (char **p)
{
	char *lp;
	lp = *p;

	while (*lp != '\r' && *lp != '\0')
		lp = g_utf8_next_char (lp);

	if (*lp == '\r') {
		lp = g_utf8_next_char (lp); /* \n */
		lp = g_utf8_next_char (lp); /* start of the next line */
	}

	*p = lp;
}

/* skip forward until we hit a character in @s, CRLF, or \0.  leave *p
   pointing at the character that causes us to stop */
static void _skip_until (char **p, char *s)
{
	char *lp;

	lp = *p;

	while (*lp != '\r' && *lp != '\0') {
		gboolean s_matches = FALSE;
		char *ls;
		for (ls = s; *ls; ls = g_utf8_next_char (ls)) {
			if (g_utf8_get_char (ls) == g_utf8_get_char (lp)) {
				s_matches = TRUE;
				break;
			}
		}

		if (s_matches)
			break;
		lp++;
	}

	*p = lp;
}

static void _read_attribute_value_add (b_VFormatAttribute *attr, GString *str, GString *charset)
{
	/* don't convert empty strings */
	if (str->len == 0) {
		b_vformat_attribute_add_value(attr, str->str);
		return;
	}

	char *inbuf, *outbuf, *p;
	size_t inbytesleft, outbytesleft;

	inbuf = str->str;
	p = outbuf = malloc(str->len*2);
	inbytesleft = str->len;
	outbytesleft = str->len*2;

	iconv_t cd;

	/* if a CHARSET was given, let's try to convert inbuf to UTF-8 */
	if (charset) {

		cd = iconv_open("UTF-8", charset->str);
#ifdef SOLARIS
                if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#else
                if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#endif
                        *p = 0;
                        b_vformat_attribute_add_value(attr, outbuf);

                } else {

                        /* hmm, should not happen */
                        b_vformat_attribute_add_value(attr, str->str);

                }

		iconv_close(cd);

	} else {

		/* no CHARSET was given, if inbuf is already UTF-8 we add str->str */
		if (g_utf8_validate (inbuf, -1, NULL)) {

			b_vformat_attribute_add_value (attr, str->str);

                } else {

			/* because inbuf is not UTF-8, we think it is ISO-8859-1 */
                        cd = iconv_open("UTF-8", "ISO-8859-1");
#ifdef SOLARIS
                        if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#else
                        if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) {
#endif
                                *p = 0;
                                b_vformat_attribute_add_value (attr, outbuf);

                        } else {

                                b_vformat_attribute_add_value (attr, str->str);

                        }

			iconv_close(cd);

		}

	}

	free(outbuf);

}

static void _read_attribute_value (b_VFormatAttribute *attr, char **p, int format_encoding, GString *charset)
{
	char *lp = *p;
	GString *str;

	/* read in the value */
	str = g_string_new ("");
	while (*lp != '\r' && *lp != '\0') {
		if (*lp == '=' && format_encoding == VF_ENCODING_QP) {
			char a, b, x1=0, x2=0;

			if ((a = *(++lp)) == '\0') break;
			if ((b = *(++lp)) == '\0') break;

			if (isalnum(a)) {
				if (isalnum(b)) {
					/* e.g. ...N=C3=BCrnberg\r\n
					 *          ^^^
					 */
					x1=a;
					x2=b;
				}
				else if (b == '=') {
					/* e.g. ...N=C=\r\n
					 *          ^^^
					 * 3=BCrnberg...
					 * ^
					 */
					char *tmplp = lp;
					if (*(++tmplp) == '\r' && *(++tmplp) == '\n' && isalnum(*(++tmplp))) {
						x1 = a;
						x2 = *tmplp;
						lp = tmplp;
					}
				}
				else {
					/* append malformed input, and
					   continue parsing */
					str = g_string_append_c(str, a);
					str = g_string_append_c(str, b);
				}
			}
			else if (a == '=') {
				char *tmplp = lp;
				char c, d, e;
				c = *(++tmplp);
				d = *(++tmplp);
				e = *(++tmplp);
				if (b == '\r' && c == '\n' && isalnum(d) && isalnum(e)) {
					x1 = d;
					x2 = e;
					lp = tmplp;
				}
				else {
					/* append malformed input, and
					   continue parsing */
					str = g_string_append_c(str, a);
					str = g_string_append_c(str, b);
				}
			}
			else {
				/* append malformed input, and
				   continue parsing */
				str = g_string_append_c(str, a);
				str = g_string_append_c(str, b);
			}
			if (x1 && x2) {
				char c;

				a = tolower (x1);
				b = tolower (x2);

				c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4)
					| ((b>='a'?b-'a'+10:b-'0')&0x0f);

				str = g_string_append_c (str, c);
			}
			lp++;
			x1 = x2 = 0;
		}
		else if (format_encoding == VF_ENCODING_BASE64) {
			if((*lp != ' ') && (*lp != '\t') )
				str = g_string_append_unichar (str, g_utf8_get_char (lp));
			lp = g_utf8_next_char(lp);
		}
		else if (*lp == '\\') {
			/* convert back to the non-escaped version of
			   the characters */
			lp = g_utf8_next_char(lp);
			if (*lp == '\0') {
				str = g_string_append_c (str, '\\');
				break;
			}
			switch (*lp) {
				case 'n': str = g_string_append_c (str, '\n'); break;
				case 'r': str = g_string_append_c (str, '\r'); break;
				case ';': str = g_string_append_c (str, ';'); break;
				case ',':
					if (!g_ascii_strcasecmp (attr->name, "CATEGORIES")) {
						//We need to handle categories here to work
						//aroung a bug in evo2
						_read_attribute_value_add (attr, str, charset);
						g_string_assign (str, "");
					} else
						str = g_string_append_c (str, ',');
					break;
				case '\\': str = g_string_append_c (str, '\\'); break;
				case '"': str = g_string_append_c (str, '"'); break;
				  /* \t is (incorrectly) used by kOrganizer, so handle it here */
				case 't': str = g_string_append_c (str, '\t'); break;
				default:
					BarryLogf(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %u", (unsigned int)*lp);
					str = g_string_append_c (str, '\\');
					str = g_string_append_unichar (str, g_utf8_get_char(lp));
					break;
			}
			lp = g_utf8_next_char(lp);
		}
		else if ((*lp == ';') ||
			 (*lp == ',' && !g_ascii_strcasecmp (attr->name, "CATEGORIES"))) {
			_read_attribute_value_add (attr, str, charset);
			g_string_assign (str, "");
			lp = g_utf8_next_char(lp);
		}
		else {
			str = g_string_append_unichar (str, g_utf8_get_char (lp));
			lp = g_utf8_next_char(lp);
		}
	}
	if (str) {
		_read_attribute_value_add (attr, str, charset);
		g_string_free (str, TRUE);
	}

	if (*lp == '\r') {
		lp = g_utf8_next_char (lp); /* \n */
		lp = g_utf8_next_char (lp); /* start of the next line */
	}

	*p = lp;
}

static void _read_attribute_params(b_VFormatAttribute *attr, char **p, int *format_encoding, GString **charset)
{
	char *lp = *p;
	GString *str;
	b_VFormatParam *param = NULL;
	gboolean in_quote = FALSE;
	str = g_string_new ("");

	while (*lp != '\0') {
		if (*lp == '"') {
			in_quote = !in_quote;
			lp = g_utf8_next_char (lp);
		}
		else if (in_quote || g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/' || *lp == '.' || *lp == ' ') {
			str = g_string_append_unichar (str, g_utf8_get_char (lp));
			lp = g_utf8_next_char (lp);
		}
		/* accumulate until we hit the '=' or ';'.  If we hit
		 * a '=' the string contains the parameter name.  if
		 * we hit a ';' the string contains the parameter
		 * value and the name is either ENCODING (if value ==
		 * QUOTED-PRINTABLE) or TYPE (in any other case.)
		 */
		else if (*lp == '=') {
			if (str->len > 0) {
				param = b_vformat_attribute_param_new (str->str);
				g_string_assign (str, "");
				lp = g_utf8_next_char (lp);
			}
			else {
				_skip_until (&lp, ":;");
				if (*lp == '\r') {
					lp = g_utf8_next_char (lp); /* \n */
					lp = g_utf8_next_char (lp); /* start of the next line */
					break;
				}
				else if (*lp == ';')
					lp = g_utf8_next_char (lp);
			}
		}
		else if (*lp == ';' || *lp == ':' || *lp == ',') {
			gboolean colon = (*lp == ':');
			gboolean comma = (*lp == ',');

			if (param) {
				if (str->len > 0) {
					b_vformat_attribute_param_add_value (param, str->str);
					g_string_assign (str, "");
					if (!colon)
						lp = g_utf8_next_char (lp);
				}
				else {
					/* we've got a parameter of the form:
					 * PARAM=(.*,)?[:;]
					 * so what we do depends on if there are already values
					 * for the parameter.  If there are, we just finish
					 * this parameter and skip past the offending character
					 * (unless it's the ':'). If there aren't values, we free
					 * the parameter then skip past the character.
					 */
					if (!param->values) {
						b_vformat_attribute_param_free (param);
						param = NULL;
						if (!colon)
							lp = g_utf8_next_char (lp);
					}
				}

				if (param
				    && !g_ascii_strcasecmp (param->name, "encoding")) {
					if (!g_ascii_strcasecmp (param->values->data, "quoted-printable")) {
						*format_encoding = VF_ENCODING_QP;
						b_vformat_attribute_param_free (param);
						param = NULL;
					} else if ( _helper_is_base64(param->values->data)) {
						*format_encoding = VF_ENCODING_BASE64;
//						b_vformat_attribute_param_free (param);
//						param = NULL;
					}
				} else if (param && !g_ascii_strcasecmp(param->name, "charset")) {
					*charset = g_string_new(param->values->data);
					b_vformat_attribute_param_free (param);
					param = NULL;
				}
			}
			else {
				if (str->len > 0) {
					char *param_name;
					if (!g_ascii_strcasecmp (str->str,
								 "quoted-printable")) {
						param_name = "ENCODING";
						*format_encoding = VF_ENCODING_QP;
					}
					/* apple's broken addressbook app outputs naked BASE64
					   parameters, which aren't even vcard 3.0 compliant. */
					else if (!g_ascii_strcasecmp (str->str,
								      "base64")) {
						param_name = "ENCODING";
						g_string_assign (str, "b");
						*format_encoding = VF_ENCODING_BASE64;
					}
					else {
						param_name = "TYPE";
					}

					if (param_name) {
						param = b_vformat_attribute_param_new (param_name);
						b_vformat_attribute_param_add_value (param, str->str);
					}
					g_string_assign (str, "");
					if (!colon)
						lp = g_utf8_next_char (lp);
				}
				else {
					/* we've got an attribute with a truly empty
					   attribute parameter.  So it's of the form:

					   ATTR;[PARAM=value;]*;[PARAM=value;]*:

					   (note the extra ';')

					   the only thing to do here is, well.. nothing.
					   we skip over the character if it's not a colon,
					   and the rest is handled for us: We'll either
					   continue through the loop again if we hit a ';',
					   or we'll break out correct below if it was a ':' */
					if (!colon)
						lp = g_utf8_next_char (lp);
				}
			}
			if (param && !comma) {
				b_vformat_attribute_add_param (attr, param);
				param = NULL;
			}
			if (colon)
				break;
		}
		else {
			BarryLogf(TRACE_INTERNAL, "invalid character found in parameter spec: \"%i\" String so far: %s", lp[0], str->str);
			g_string_assign (str, "");
			_skip_until (&lp, ":;");
		}
	}

	if (str)
		g_string_free (str, TRUE);

	*p = lp;
}

/* reads an entire attribute from the input buffer, leaving p pointing
   at the start of the next line (past the \r\n) */
static b_VFormatAttribute *_read_attribute (char **p)
{
	char *attr_group = NULL;
	char *attr_name = NULL;
	b_VFormatAttribute *attr = NULL;
	GString *str, *charset = NULL;
	char *lp = *p;

	gboolean is_qp = FALSE;

	/* first read in the group/name */
	str = g_string_new ("");
	while (*lp != '\r' && *lp != '\0') {
		if (*lp == ':' || *lp == ';') {
			if (str->len != 0) {
				/* we've got a name, break out to the value/attribute parsing */
				attr_name = g_string_free (str, FALSE);
				break;
			}
			else {
				/* a line of the form:
				 * (group.)?[:;]
				 *
				 * since we don't have an attribute
				 * name, skip to the end of the line
				 * and try again.
				 */
				g_string_free (str, TRUE);
				*p = lp;
				_skip_to_next_line(p);
				goto lose;
			}
		}
		else if (*lp == '.') {
			if (attr_group) {
				BarryLogf(TRACE_INTERNAL, "extra `.' in attribute specification.  ignoring extra group `%s'", str->str);
				g_string_free (str, TRUE);
				str = g_string_new ("");
			}
			if (str->len != 0) {
				attr_group = g_string_free (str, FALSE);
				str = g_string_new ("");
			}
		}
		else if (g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/') {
			str = g_string_append_unichar (str, g_utf8_get_char (lp));
		}
		else {
			BarryLogf(TRACE_INTERNAL, "invalid character found in attribute group/name: \"%i\" String so far: %s", lp[0], str->str);
			g_string_free (str, TRUE);
			*p = lp;
			_skip_to_next_line(p);
			goto lose;
		}

		lp = g_utf8_next_char(lp);
	}

	if (!attr_name) {
		_skip_to_next_line (p);
		goto lose;
	}

	attr = b_vformat_attribute_new (attr_group, attr_name);
	g_free (attr_group);
	g_free (attr_name);

	if (*lp == ';') {
		/* skip past the ';' */
		lp = g_utf8_next_char(lp);
		_read_attribute_params (attr, &lp, &is_qp, &charset);
	}
	if (*lp == ':') {
		/* skip past the ':' */
		lp = g_utf8_next_char(lp);
		_read_attribute_value (attr, &lp, is_qp, charset);
	}

	if (charset) g_string_free(charset, TRUE);
	*p = lp;

	if (!attr->values)
		goto lose;

	return attr;
 lose:
	if (attr)
		b_vformat_attribute_free (attr);
	return NULL;
}

static void open_block(char **block, const char *block_name)
{
	char *start = *block ? *block : "";
	char *result = NULL;

	result = g_strconcat(start, "/", block_name, NULL);
	if( *block )
		g_free(*block);
	*block = result;
}

static void close_block(char **block, const char *block_name)
{
	int name_len = strlen(block_name);
	int block_len = *block ? strlen(*block) : 0;
	char *cmp_start = NULL;

	if( block_len < name_len + 1 )
		return;

	cmp_start = *block + (block_len - name_len - 1);
	if( cmp_start[0] == '/' &&
	    g_ascii_strcasecmp(cmp_start+1, block_name) == 0 )
	{
		// end of block hierarchy contains block name,
		// so safe to remove

		// cut off the end of the string... no need to free/realloc
		*cmp_start = '\0';
	}
}

/* we try to be as forgiving as we possibly can here - this isn't a
 * validator.  Almost nothing is considered a fatal error.  We always
 * try to return *something*.
 */
static void _parse(b_VFormat *evc, const char *str)
{
	char *buf = g_strdup (str);
	char *p, *end;
	b_VFormatAttribute *attr;

	/* first validate the string is valid utf8 */
	if (!g_utf8_validate (buf, -1, (const char **)&end)) {
		/* if the string isn't valid, we parse as much as we can from it */
		BarryLogf(TRACE_INTERNAL, "invalid utf8 passed to b_VFormat.  Limping along.");
		*end = '\0';
	}

	buf = _fold_lines (buf);

	p = buf;

	attr = _read_attribute (&p);
	if (!attr)
		attr = _read_attribute (&p);

	if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "begin")) {
		BarryLogf(TRACE_INTERNAL, "vformat began without a BEGIN\n");
	}
	if (attr && !g_ascii_strcasecmp (attr->name, "begin"))
		b_vformat_attribute_free (attr);
	else if (attr)
		b_vformat_add_attribute (evc, attr);

	char *block = NULL;
	while (*p) {
		b_VFormatAttribute *next_attr = _read_attribute (&p);

		if (next_attr) {
			if( g_ascii_strcasecmp(next_attr->name, "begin") == 0 ) {
				// add to block hierarchy string
				char *value = b_vformat_attribute_get_value(next_attr);
				open_block(&block, value);
				//BarryLogf(TRACE_INTERNAL, "open block: %s", block);
				g_free(value);
			}
			else if( g_ascii_strcasecmp(next_attr->name, "end") == 0 ) {
				// close off the block
				char *value = b_vformat_attribute_get_value(next_attr);
				close_block(&block, value);
				//BarryLogf(TRACE_INTERNAL, "close block: %s", block);
				g_free(value);
			}

			// apply the block to the attr
			next_attr->block = g_strdup(block);

			// add!
			b_vformat_add_attribute (evc, next_attr);
			attr = next_attr;
		}
	}

	if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "end")) {
		BarryLogf(TRACE_INTERNAL, "vformat ended without END");
	}

	g_free (buf);
	g_free (block);
}

char *b_vformat_escape_string (const char *s, b_VFormatType type)
{
	GString *str;
	const char *p;

	str = g_string_new ("");

	/* Escape a string as described in RFC2426, section 5 */
	for (p = s; p && *p; p++) {
		switch (*p) {
		case '\n':
			str = g_string_append (str, "\\n");
			break;
		case '\r':
			if (*(p+1) == '\n')
				p++;
			str = g_string_append (str, "\\n");
			break;
		case ';':
			str = g_string_append (str, "\\;");
			break;
		case ',':
			if (type == VFORMAT_CARD_30 || type == VFORMAT_EVENT_20 || type == VFORMAT_TODO_20)
				str = g_string_append (str, "\\,");
			else
				str = g_string_append_c (str, *p);
			break;
		case '\\':
			/**
			 * We won't escape backslashes
			 * on vcard 2.1, unless it is in the end of a value.
			 * See comments above for a better explanation
			**/
			if (*p != '\0' && type == VFORMAT_CARD_21) {
				BarryLogf(TRACE_INTERNAL, "[%s]We won't escape backslashes", __func__);
				str = g_string_append_c(str, *p);
			}
			else {
				BarryLogf(TRACE_INTERNAL, "[%s] escape backslashes!!", __func__);
				str = g_string_append (str, "\\\\");
			}
			break;
		default:
			str = g_string_append_c (str, *p);
			break;
		}
	}

	return g_string_free (str, FALSE);
}

char*
b_vformat_unescape_string (const char *s)
{
	GString *str;
	const char *p;

	g_return_val_if_fail (s != NULL, NULL);

	str = g_string_new ("");

	/* Unescape a string as described in RFC2426, section 4 (Formal Grammar) */
	for (p = s; *p; p++) {
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				str = g_string_append_c (str, '\\');
				break;
			}
			switch (*p) {
			case 'n':  str = g_string_append_c (str, '\n'); break;
			case 'r':  str = g_string_append_c (str, '\r'); break;
			case ';':  str = g_string_append_c (str, ';'); break;
			case ',':  str = g_string_append_c (str, ','); break;
			case '\\': str = g_string_append_c (str, '\\'); break;
			case '"': str = g_string_append_c (str, '"'); break;
			  /* \t is (incorrectly) used by kOrganizer, so handle it here */
			case 't': str = g_string_append_c (str, '\t'); break;
			default:
				BarryLogf(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %u", (unsigned int)*p);
				str = g_string_append_c (str, '\\');
				str = g_string_append_unichar (str, g_utf8_get_char(p));
				break;
			}
		}
	}

	return g_string_free (str, FALSE);
}

void
b_vformat_construct (b_VFormat *evc, const char *str)
{
	g_return_if_fail (str != NULL);

	if (*str)
		_parse (evc, str);
}

void b_vformat_free(b_VFormat *format)
{
	g_list_foreach (format->attributes, (GFunc)b_vformat_attribute_free, NULL);
	g_list_free (format->attributes);
	g_free(format);
}

b_VFormat *b_vformat_new_from_string (const char *str)
{
	g_return_val_if_fail (str != NULL, NULL);
	b_VFormat *evc = g_malloc0(sizeof(b_VFormat));

	b_vformat_construct (evc, str);

	return evc;
}

b_VFormat *b_vformat_new(void)
{
	return b_vformat_new_from_string ("");
}

static int _block_match(b_VFormatAttribute *attr, const char *block)
{
	// a block matches if the end of the attribute's block
	// string matches a case insensitive compare with block
	//
	// for example, a calendar may or may not start with a
	// BEGIN: VCALENDAR, so DTSTART's block string could be
	// "/vcalendar/vevent" or just "/vevent".  By passing
	// "/vevent" or even "vevent" as the block argument above,
	// we should get a match for any of the above.

	int attr_len = attr->block ? strlen(attr->block) : 0;
	int block_len = block ? strlen(block) : 0;

	if( block == NULL )
		return 1;	// if block is null, match everything

	if( attr_len < block_len )
		return 0;	// not enough string to compare

	if( attr_len == 0 && block_len == 0 )
		return 1;	// empty and null strings match

	if( attr->block == NULL )
		return 0;	// don't compare if one side is null

	return g_ascii_strcasecmp(&attr->block[attr_len - block_len], block) == 0;
}

b_VFormatAttribute *b_vformat_find_attribute(b_VFormat *vcard, const char *name, int nth, const char *block)
{
	GList *attributes = b_vformat_get_attributes(vcard);
	GList *a = NULL;
	int i = 0;
	for (a = attributes; a; a = a->next) {
		b_VFormatAttribute *attr = a->data;
		if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) {
			if( block == NULL || _block_match(attr, block) ) {
				if( i == nth )
					return attr;
				i++;
			}
		}
	}
	return NULL;
}

/*
b_VFormatAttribute *b_vformat_find_attribute_next(b_VFormatAttribute *last,
						const char *name,
						int nth)
{
	GList *attributes = last ? last->next : 0;
	GList *a = NULL;
	int i = 0;
	for (a = attributes; a; a = a->next) {
		b_VFormatAttribute *attr = a->data;
		if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) {
			if( i == nth )
				return attr;
			i++;
		}
	}
	return NULL;
}
*/

char *b_vformat_to_string (b_VFormat *evc, b_VFormatType type)
{
	BarryLogf(TRACE_ENTRY, "%s(%p, %i)", __func__, evc, type);
	GList *l;
	GList *v;

	GString *str = g_string_new ("");

	switch (type) {
		case VFORMAT_CARD_21:
			str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:2.1\r\n");
			break;
		case VFORMAT_CARD_30:
			str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:3.0\r\n");
			break;
		case VFORMAT_TODO_10:
		case VFORMAT_EVENT_10:
			str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:1.0\r\n");
			break;
		case VFORMAT_TODO_20:
		case VFORMAT_EVENT_20:
		case VFORMAT_JOURNAL:
			str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n");
			break;
		case VFORMAT_NOTE:
			str = g_string_append (str, "BEGIN:VNOTE\r\nVERSION:1.1\r\n");
			break;
	}

	for (l = evc->attributes; l; l = l->next) {
		GList *p;
		b_VFormatAttribute *attr = l->data;
		GString *attr_str;
		int l;
		int format_encoding = VF_ENCODING_RAW;

		attr_str = g_string_new ("");

		/* From rfc2425, 5.8.2
		 *
		 * contentline  = [group "."] name *(";" param) ":" value CRLF
		 */

		if (attr->group) {
			attr_str = g_string_append (attr_str, attr->group);
			attr_str = g_string_append_c (attr_str, '.');
		}
		attr_str = g_string_append (attr_str, attr->name);
		/* handle the parameters */
		for (p = attr->params; p; p = p->next) {
			b_VFormatParam *param = p->data;
			/* 5.8.2:
			 * param        = param-name "=" param-value *("," param-value)
			 */
			if( type == VFORMAT_CARD_30 || type == VFORMAT_TODO_20
			    || type == VFORMAT_EVENT_20 || type == VFORMAT_JOURNAL) {

				/**
				 * Character set can only be specified on the CHARSET
				 * parameter on the Content-Type MIME header field.
				**/
				if (!g_ascii_strcasecmp (param->name, "CHARSET"))
					continue;
				attr_str = g_string_append_c (attr_str, ';');
				attr_str = g_string_append (attr_str, param->name);
				if (param->values) {
					attr_str = g_string_append_c (attr_str, '=');
				}
				for (v = param->values; v; v = v->next) {
					if (_helper_is_base64((const char *) v->data)) {
						format_encoding = VF_ENCODING_BASE64;
						/*Only the "B" encoding of [RFC 2047] is an allowed*/
						v->data=g_strdup("B");
					}
					/**
					 * QUOTED-PRINTABLE inline encoding has been
					 * eliminated.
					**/
					if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE")) {
						BarryLogf(TRACE_ERROR, "%s false encoding QUOTED-PRINTABLE is not allowed", __func__);
						format_encoding = VF_ENCODING_QP;
					}
					attr_str = g_string_append (attr_str, v->data);

					if (v->next)
						attr_str = g_string_append_c (attr_str, ',');
				}
			}
			else {
				attr_str = g_string_append_c (attr_str, ';');
				/**
				 * The "TYPE=" is optional skip it.
				 * LOGO, PHOTO and SOUND multimedia formats MUST
				 * have a "TYPE=" parameter
				**/
				gboolean must_have_type = FALSE;
				if (!g_ascii_strcasecmp (attr->name, "PHOTO") || !g_ascii_strcasecmp (attr->name, "LOGO") || !g_ascii_strcasecmp (attr->name, "SOUND") )
					must_have_type = TRUE;
				if ( must_have_type || g_ascii_strcasecmp (param->name, "TYPE") )
					attr_str = g_string_append (attr_str, param->name);
				if ( param->values && (must_have_type || g_ascii_strcasecmp (param->name, "TYPE")) )
					attr_str = g_string_append_c (attr_str, '=');
				for (v = param->values; v; v = v->next) {
					// check for quoted-printable encoding
					if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE"))
						format_encoding = VF_ENCODING_QP;
					// check for base64 encoding
					if (_helper_is_base64((const char *) v->data)) {
						format_encoding = VF_ENCODING_BASE64;
						v->data=g_strdup("BASE64");
					}
					attr_str = g_string_append (attr_str, v->data);
					if (v->next)
						attr_str = g_string_append_c (attr_str, ',');
				}
			}
		}

		attr_str = g_string_append_c (attr_str, ':');

		for (v = attr->values; v; v = v->next) {
			char *value = v->data;
			char *escaped_value = NULL;

			if (!g_ascii_strcasecmp (attr->name, "RRULE") &&
				  strstr (value, "BYDAY") == v->data) {
				attr_str = g_string_append (attr_str, value);
			} else {
				escaped_value = b_vformat_escape_string (value, type);
				attr_str = g_string_append (attr_str, escaped_value);
			}

			if (v->next) {

				/* XXX toshok - i hate you, rfc 2426.
				   why doesn't CATEGORIES use a ; like
				   a normal list attribute? */
				if (!g_ascii_strcasecmp (attr->name, "CATEGORIES"))
					attr_str = g_string_append_c (attr_str, ',');
				else
					attr_str = g_string_append_c (attr_str, ';');
			}

			g_free (escaped_value);
		}

		/* Folding lines:
		 * ^^^^^^^^^^^^^^
		 *
		 * rfc 2426 (vCard), 2.6 Line Delimiting and Folding:
		 * After generating a content line,
		 * lines longer than 75 characters SHOULD be folded according to the
		 * folding procedure described in [MIME-DIR].
		 *
		 * rfc 2445 (iCalendar), 4.1 Content Lines:
		 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
		 * break. Long content lines SHOULD be split into a multiple line
		 * representations using a line "folding" technique. That is, a long
		 * line can be split between any two characters by inserting a CRLF
		 * immediately followed by a single linear white space character (i.e.,
		 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
		 * of CRLF followed immediately by a single linear white space character
		 *  is ignored (i.e., removed) when processing the content type.
		 *
		 * SUMMARY: When generating a content line, lines longer then 75 characters SHOULD be folded!
		 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		 *
		 * Differences between encodings:
		 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		 *
		 * rfc 2425 [MIME-DIR], 5.8.1:
		 * A logical line MAY be continued on the next physical line anywhere
		 * between two characters by inserting a CRLF immediately followed by a
		 * single <WS> (white space) character.
		 *
		 * rfc 2045, 6.7, chapter 5:
		 * The quoted-printable specs says that softbreaks should be generated by inserting a =\r\n
		 * without follwing <WS>
		 *
		 * UTF-8
		 * ^^^^^
		 *
		 * Note that all the line folding above is described in terms of characters
		 * not bytes.  In particular, it would be an error to put a line break
		 * within a UTF-8 character.
		*/

		l = 0;
		do {
			if (g_utf8_strlen(attr_str->str, attr_str->len) - l > 75) {
				l += 75;

				/* If using QP, must be sure that we do not fold within a quote sequence */
				if (format_encoding == VF_ENCODING_QP) {
				  if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-1)) == '=') l--;
				  else if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-2)) == '=') l -= 2;
				}

				char *p = g_utf8_offset_to_pointer(attr_str->str, l);

				if (format_encoding == VF_ENCODING_QP)
					attr_str = g_string_insert_len (attr_str, p - attr_str->str, "=" CRLF "", sizeof ("=" CRLF "") - 1);
				else
					attr_str = g_string_insert_len (attr_str, p - attr_str->str, CRLF " ", sizeof (CRLF " ") - 1);
			}
			else
				break;
		} while (l < g_utf8_strlen(attr_str->str, attr_str->len));

		attr_str = g_string_append (attr_str, CRLF);
		/**
		 * base64= <MIME RFC 1521 base64 text>
		 * the end of the text is marked with two CRLF sequences
		 * this results in one blank line before the start of the
		 * next property
		**/
		if( format_encoding == VF_ENCODING_BASE64
		   && (type == VFORMAT_CARD_21))
			attr_str = g_string_append (attr_str, CRLF);

		str = g_string_append (str, attr_str->str);
		g_string_free (attr_str, TRUE);
	}

	switch (type) {
		case VFORMAT_CARD_21:
			str = g_string_append (str, "END:VCARD\r\n");
			break;
		case VFORMAT_CARD_30:
			str = g_string_append (str, "END:VCARD\r\n");
			break;
		case VFORMAT_TODO_10:
		case VFORMAT_EVENT_10:
			str = g_string_append (str, "END:VCALENDAR\r\n");
			break;
		case VFORMAT_TODO_20:
		case VFORMAT_EVENT_20:
		case VFORMAT_JOURNAL:
			str = g_string_append (str, "END:VCALENDAR\r\n");
			break;
		case VFORMAT_NOTE:
			str = g_string_append (str, "END:VNOTE\r\n");
			break;
	}

	BarryLogf(TRACE_EXIT, "%s", __func__);
	return g_string_free (str, FALSE);
}

void b_vformat_dump_structure (b_VFormat *evc)
{
	GList *a;
	GList *v;
	int i;

	printf ("b_VFormat\n");
	for (a = evc->attributes; a; a = a->next) {
		GList *p;
		b_VFormatAttribute *attr = a->data;
		printf ("+-- %s\n", attr->name);
		if (attr->params) {
			printf ("    +- params=\n");

			for (p = attr->params, i = 0; p; p = p->next, i++) {
				b_VFormatParam *param = p->data;
				printf ("    |   [%d] = %s", i,param->name);
				printf ("(");
				for (v = param->values; v; v = v->next) {
					char *value = b_vformat_escape_string ((char*)v->data, VFORMAT_CARD_21);
					printf ("%s", value);
					if (v->next)
						printf (",");
					g_free (value);
				}

				printf (")\n");
			}
		}
		printf ("    +- values=\n");
		for (v = attr->values, i = 0; v; v = v->next, i++) {
			printf ("        [%d] = `%s'\n", i, (char*)v->data);
		}
	}
}

b_VFormatAttribute *b_vformat_attribute_new (const char *attr_group, const char *attr_name)
{
	b_VFormatAttribute *attr;

	attr = g_new0 (b_VFormatAttribute, 1);

	attr->group = g_strdup (attr_group);
	attr->name = g_strdup (attr_name);

	return attr;
}

void
b_vformat_attribute_free (b_VFormatAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	g_free (attr->block);
	g_free (attr->group);
	g_free (attr->name);

	b_vformat_attribute_remove_values (attr);

	b_vformat_attribute_remove_params (attr);

	g_free (attr);
}

b_VFormatAttribute*
b_vformat_attribute_copy (b_VFormatAttribute *attr)
{
	b_VFormatAttribute *a;
	GList *p;

	g_return_val_if_fail (attr != NULL, NULL);

	a = b_vformat_attribute_new (b_vformat_attribute_get_group (attr),
				   b_vformat_attribute_get_name (attr));

	for (p = attr->values; p; p = p->next)
		b_vformat_attribute_add_value (a, p->data);

	for (p = attr->params; p; p = p->next)
		b_vformat_attribute_add_param (a, b_vformat_attribute_param_copy (p->data));

	return a;
}

void
b_vformat_remove_attributes (b_VFormat *evc, const char *attr_group, const char *attr_name)
{
	GList *attr;

	g_return_if_fail (attr_name != NULL);

	attr = evc->attributes;
	while (attr) {
		GList *next_attr;
		b_VFormatAttribute *a = attr->data;

		next_attr = attr->next;

		if (((!attr_group && !a->group) ||
		     (attr_group && !g_ascii_strcasecmp (attr_group, a->group))) &&
		    ((!attr_name && !a->name) || !g_ascii_strcasecmp (attr_name, a->name))) {

			/* matches, remove/delete the attribute */
			evc->attributes = g_list_remove_link (evc->attributes, attr);

			b_vformat_attribute_free (a);
		}

		attr = next_attr;
	}
}

void
b_vformat_remove_attribute (b_VFormat *evc, b_VFormatAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	evc->attributes = g_list_remove (evc->attributes, attr);
	b_vformat_attribute_free (attr);
}

void
b_vformat_add_attribute (b_VFormat *evc, b_VFormatAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	evc->attributes = g_list_append (evc->attributes, attr);
}

void
b_vformat_add_attribute_with_value (b_VFormat *b_VFormat,
				  b_VFormatAttribute *attr, const char *value)
{
	g_return_if_fail (attr != NULL);

	b_vformat_attribute_add_value (attr, value);

	b_vformat_add_attribute (b_VFormat, attr);
}

void
b_vformat_add_attribute_with_values (b_VFormat *b_VFormat, b_VFormatAttribute *attr, ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (attr != NULL);

	va_start (ap, attr);

	while ((v = va_arg (ap, char*))) {
		b_vformat_attribute_add_value (attr, v);
	}

	va_end (ap);

	b_vformat_add_attribute (b_VFormat, attr);
}

void
b_vformat_attribute_add_value (b_VFormatAttribute *attr, const char *value)
{
	g_return_if_fail (attr != NULL);

	attr->values = g_list_append (attr->values, g_strdup (value));
}

void
b_vformat_attribute_add_value_decoded (b_VFormatAttribute *attr, const char *value, int len)
{
	g_return_if_fail (attr != NULL);

	switch (attr->encoding) {
		case VF_ENCODING_RAW:
			BarryLogf(TRACE_INTERNAL, "can't add_value_decoded with an attribute using RAW encoding.  you must set the ENCODING parameter first");
			break;
		case VF_ENCODING_BASE64: {
			char *b64_data = base64_encode_simple (value, len);
			GString *decoded = g_string_new_len (value, len);

			/* make sure the decoded list is up to date */
			b_vformat_attribute_get_values_decoded (attr);

			attr->values = g_list_append (attr->values, b64_data);
			attr->decoded_values = g_list_append (attr->decoded_values, decoded);
			break;
		}
		case VF_ENCODING_QP: {
			char *qp_data = quoted_encode_simple ((unsigned char*)value, len);
			GString *decoded = g_string_new (value);

			/* make sure the decoded list is up to date */
			b_vformat_attribute_get_values_decoded (attr);

			attr->values = g_list_append (attr->values, qp_data);
			attr->decoded_values = g_list_append (attr->decoded_values, decoded);
			break;
		}
		case VF_ENCODING_8BIT: {
			char *data = g_strdup(value);
			GString *decoded = g_string_new (value);

			/* make sure the decoded list is up to date */
			b_vformat_attribute_get_values_decoded (attr);

			attr->values = g_list_append (attr->values, data);
			attr->decoded_values = g_list_append (attr->decoded_values, decoded);
			break;
		}
	}
}

void
b_vformat_attribute_add_values (b_VFormatAttribute *attr, ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (attr != NULL);

	va_start (ap, attr);

	while ((v = va_arg (ap, char*))) {
		b_vformat_attribute_add_value (attr, v);
	}

	va_end (ap);
}

static void
free_gstring (GString *str)
{
	g_string_free (str, TRUE);
}

void
b_vformat_attribute_remove_values (b_VFormatAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	g_list_foreach (attr->values, (GFunc)g_free, NULL);
	g_list_free (attr->values);
	attr->values = NULL;

	g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL);
	g_list_free (attr->decoded_values);
	attr->decoded_values = NULL;
}

void
b_vformat_attribute_remove_params (b_VFormatAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	g_list_foreach (attr->params, (GFunc)b_vformat_attribute_param_free, NULL);
	g_list_free (attr->params);
	attr->params = NULL;

	/* also remove the cached encoding on this attribute */
	attr->encoding_set = FALSE;
	attr->encoding = VF_ENCODING_RAW;
}

b_VFormatParam*
b_vformat_attribute_param_new (const char *name)
{
	b_VFormatParam *param = g_new0 (b_VFormatParam, 1);
	param->name = g_strdup (name);

	return param;
}

void
b_vformat_attribute_param_free (b_VFormatParam *param)
{
	g_return_if_fail (param != NULL);

	g_free (param->name);

	b_vformat_attribute_param_remove_values (param);

	g_free (param);
}

b_VFormatParam*
b_vformat_attribute_param_copy (b_VFormatParam *param)
{
	b_VFormatParam *p;
	GList *l;

	g_return_val_if_fail (param != NULL, NULL);

	p = b_vformat_attribute_param_new (b_vformat_attribute_param_get_name (param));

	for (l = param->values; l; l = l->next) {
		b_vformat_attribute_param_add_value (p, l->data);
	}

	return p;
}

void
b_vformat_attribute_add_param (b_VFormatAttribute *attr,
			     b_VFormatParam *param)
{
	g_return_if_fail (attr != NULL);
	g_return_if_fail (param != NULL);

	attr->params = g_list_append (attr->params, param);

	/* we handle our special encoding stuff here */

	if (!g_ascii_strcasecmp (param->name, "ENCODING")) {
		if (attr->encoding_set) {
			BarryLogf(TRACE_INTERNAL, "ENCODING specified twice");
			return;
		}

		if (param->values && param->values->data) {
			if (_helper_is_base64((const char*)param->values->data))
				attr->encoding = VF_ENCODING_BASE64;
			else if (!g_ascii_strcasecmp ((char*)param->values->data, "QUOTED-PRINTABLE"))
				attr->encoding = VF_ENCODING_QP;
			else if (!g_ascii_strcasecmp ((char *)param->values->data, "8BIT"))
				attr->encoding = VF_ENCODING_8BIT;
			else {
				BarryLogf(TRACE_INTERNAL, "Unknown value `%s' for ENCODING parameter.  values will be treated as raw", (char*)param->values->data);
			}

			attr->encoding_set = TRUE;
		}
		else {
			BarryLogf(TRACE_INTERNAL, "ENCODING parameter added with no value");
		}
	}
}

b_VFormatParam *b_vformat_attribute_find_param(b_VFormatAttribute *attr, const char *name, int level)
{
	g_return_val_if_fail (attr != NULL, NULL);
	GList *p = NULL;
	for (p = attr->params; p; p = p->next) {
		b_VFormatParam *param = p->data;
		if (!g_ascii_strcasecmp (param->name, name)) {
			if( level == 0 )
				return param;
			else
				level--;
		}
	}
	return NULL;
}

void
b_vformat_attribute_set_value (b_VFormatAttribute *attr,
				int nth, const char *value)
{
	GList *param = g_list_nth(attr->values, nth);
	g_free(param->data);
	param->data = g_strdup(value);
}

void
b_vformat_attribute_param_add_value (b_VFormatParam *param,
				   const char *value)
{
	g_return_if_fail (param != NULL);

	param->values = g_list_append (param->values, g_strdup (value));
}

void
b_vformat_attribute_param_add_values (b_VFormatParam *param,
				    ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (param != NULL);

	va_start (ap, param);

	while ((v = va_arg (ap, char*))) {
		b_vformat_attribute_param_add_value (param, v);
	}

	va_end (ap);
}

void
b_vformat_attribute_add_param_with_value (b_VFormatAttribute *attr, const char *name, const char *value)
{
	g_return_if_fail (attr != NULL);
	g_return_if_fail (name != NULL);

	if (!value)
		return;

	b_VFormatParam *param = b_vformat_attribute_param_new(name);

	b_vformat_attribute_param_add_value (param, value);

	b_vformat_attribute_add_param (attr, param);
}

void
b_vformat_attribute_add_param_with_values (b_VFormatAttribute *attr,
					 b_VFormatParam *param, ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (attr != NULL);
	g_return_if_fail (param != NULL);

	va_start (ap, param);

	while ((v = va_arg (ap, char*))) {
		b_vformat_attribute_param_add_value (param, v);
	}

	va_end (ap);

	b_vformat_attribute_add_param (attr, param);
}

void
b_vformat_attribute_param_remove_values (b_VFormatParam *param)
{
	g_return_if_fail (param != NULL);

	g_list_foreach (param->values, (GFunc)g_free, NULL);
	g_list_free (param->values);
	param->values = NULL;
}

GList*
b_vformat_get_attributes (b_VFormat *format)
{
	return format->attributes;
}

const char*
b_vformat_attribute_get_group (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->group;
}

const char*
b_vformat_attribute_get_name (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->name;
}

const char*
b_vformat_attribute_get_block (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->block;
}

GList*
b_vformat_attribute_get_values (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->values;
}

GList*
b_vformat_attribute_get_values_decoded (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	if (!attr->decoded_values) {
		GList *l;
		switch (attr->encoding) {
		case VF_ENCODING_RAW:
		case VF_ENCODING_8BIT:
			for (l = attr->values; l; l = l->next)
				attr->decoded_values = g_list_append (attr->decoded_values, g_string_new ((char*)l->data));
			break;
		case VF_ENCODING_BASE64:
			for (l = attr->values; l; l = l->next) {
				char *decoded = g_strdup ((char*)l->data);
				int len = base64_decode_simple (decoded, strlen (decoded));
				attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
				g_free (decoded);
			}
			break;
		case VF_ENCODING_QP:
			for (l = attr->values; l; l = l->next) {
				if (!(l->data))
					continue;
				char *decoded = g_strdup ((char*)l->data);
				int len = quoted_decode_simple (decoded, strlen (decoded));
				attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len));
				g_free (decoded);
			}
			break;
		}
	}

	return attr->decoded_values;
}

gboolean
b_vformat_attribute_is_single_valued (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, FALSE);

	if (attr->values == NULL
	    || attr->values->next != NULL)
		return FALSE;

	return TRUE;
}

char*
b_vformat_attribute_get_value (b_VFormatAttribute *attr)
{
	GList *values;

	g_return_val_if_fail (attr != NULL, NULL);

	values = b_vformat_attribute_get_values (attr);

	if (!b_vformat_attribute_is_single_valued (attr))
		BarryLogf(TRACE_INTERNAL, "b_vformat_attribute_get_value called on multivalued attribute");

	return values ? g_strdup ((char*)values->data) : NULL;
}

GString*
b_vformat_attribute_get_value_decoded (b_VFormatAttribute *attr)
{
	GList *values;
	GString *str = NULL;

	g_return_val_if_fail (attr != NULL, NULL);

	values = b_vformat_attribute_get_values_decoded (attr);

	if (!b_vformat_attribute_is_single_valued (attr))
		BarryLogf(TRACE_INTERNAL, "b_vformat_attribute_get_value_decoded called on multivalued attribute");

	if (values)
		str = values->data;

	return str ? g_string_new_len (str->str, str->len) : NULL;
}

const char *b_vformat_attribute_get_nth_value(b_VFormatAttribute *attr, int nth)
{
	GList *values = b_vformat_attribute_get_values_decoded(attr);
	if (!values)
		return NULL;
	GString *retstr = (GString *)g_list_nth_data(values, nth);
	if (!retstr)
		return NULL;

	if (!g_utf8_validate(retstr->str, -1, NULL)) {
		values = b_vformat_attribute_get_values(attr);
		if (!values)
			return NULL;
		return g_list_nth_data(values, nth);
	}

	return retstr->str;
}

gboolean
b_vformat_attribute_has_type (b_VFormatAttribute *attr, const char *typestr)
{
	GList *params;
	GList *p;

	g_return_val_if_fail (attr != NULL, FALSE);
	g_return_val_if_fail (typestr != NULL, FALSE);

	params = b_vformat_attribute_get_params (attr);

	for (p = params; p; p = p->next) {
		b_VFormatParam *param = p->data;

		if (!strcasecmp (b_vformat_attribute_param_get_name (param), "TYPE")) {
			GList *values = b_vformat_attribute_param_get_values (param);
			GList *v;

			for (v = values; v; v = v->next) {
				if (!strcasecmp ((char*)v->data, typestr))
					return TRUE;
			}
		}
	}

	return FALSE;
}


gboolean b_vformat_attribute_has_param(b_VFormatAttribute *attr, const char *name)
{
	g_return_val_if_fail (attr != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	GList *params = b_vformat_attribute_get_params(attr);
	GList *p;
	for (p = params; p; p = p->next) {
		b_VFormatParam *param = p->data;
		if (!strcasecmp(name, b_vformat_attribute_param_get_name(param)))
			return TRUE;
	}
	return FALSE;
}

GList*
b_vformat_attribute_get_params (b_VFormatAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->params;
}

const char*
b_vformat_attribute_param_get_name (b_VFormatParam *param)
{
	g_return_val_if_fail (param != NULL, NULL);

	return param->name;
}

GList*
b_vformat_attribute_param_get_values (b_VFormatParam *param)
{
	g_return_val_if_fail (param != NULL, NULL);

	return param->values;
}

const char *b_vformat_attribute_param_get_nth_value(b_VFormatParam *param, int nth)
{
	const char *ret = NULL;
	GList *values = b_vformat_attribute_param_get_values(param);
	if (!values)
		return NULL;
	ret = g_list_nth_data(values, nth);
	return ret;
}

static const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

//static unsigned char _evc_base64_rank[256];

static void base64_init(char *rank)
{
	int i;

	memset(rank, 0xff, sizeof(rank));
	for (i=0;i<64;i++) {
		rank[(unsigned int)base64_alphabet[i]] = i;
	}
	rank['='] = 0;
}

/* call this when finished encoding everything, to
   flush off the last little bit */
static size_t base64_encode_close(const unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
{
	int c1, c2;
	unsigned char *outptr = out;

	if (inlen>0)
		outptr += base64_encode_step(in, inlen, break_lines, outptr, state, save);

	c1 = ((unsigned char *)save)[1];
	c2 = ((unsigned char *)save)[2];

	switch (((char *)save)[0]) {
	case 2:
		outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
		g_assert(outptr[2] != 0);
		goto skip;
	case 1:
		outptr[2] = '=';
	skip:
		outptr[0] = base64_alphabet[ c1 >> 2 ];
		outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
		outptr[3] = '=';
		outptr += 4;
		break;
	}
	if (break_lines)
		*outptr++ = '\n';

	*save = 0;
	*state = 0;

	return outptr-out;
}

/*
  performs an 'encode step', only encodes blocks of 3 characters to the
  output at a time, saves left-over state in state and save (initialise to
  0 on first invocation).
*/
static size_t base64_encode_step(const unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
{
	register const unsigned char *inptr;
	register unsigned char *outptr;

	if (len<=0)
		return 0;

	inptr = in;
	outptr = out;

	if (len + ((char *)save)[0] > 2) {
		const unsigned char *inend = in+len-2;
		register int c1, c2, c3;
		register int already;

		already = *state;

		switch (((char *)save)[0]) {
		case 1:	c1 = ((unsigned char *)save)[1]; goto skip1;
		case 2:	c1 = ((unsigned char *)save)[1];
			c2 = ((unsigned char *)save)[2]; goto skip2;
		}

		/* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
		while (inptr < inend) {
			c1 = *inptr++;
		skip1:
			c2 = *inptr++;
		skip2:
			c3 = *inptr++;
			*outptr++ = base64_alphabet[ c1 >> 2 ];
			*outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
			*outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
			*outptr++ = base64_alphabet[ c3 & 0x3f ];
			/* this is a bit ugly ... */
			if (break_lines && (++already)>=19) {
				*outptr++='\n';
				already = 0;
			}
		}

		((char *)save)[0] = 0;
		len = 2-(inptr-inend);
		*state = already;
	}

	if (len>0) {
		register char *saveout;

		/* points to the slot for the next char to save */
		saveout = & (((char *)save)[1]) + ((char *)save)[0];

		/* len can only be 0 1 or 2 */
		switch(len) {
		case 2:	*saveout++ = *inptr++;
		case 1:	*saveout++ = *inptr++;
		}
		((char *)save)[0]+=len;
	}

	return outptr-out;
}


/**
 * base64_decode_step: decode a chunk of base64 encoded data
 * @in: input stream
 * @len: max length of data to decode
 * @out: output stream
 * @state: holds the number of bits that are stored in @save
 * @save: leftover bits that have not yet been decoded
 *
 * Decodes a chunk of base64 encoded data
 **/
static size_t base64_decode_step(const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
{
	unsigned char base64_rank[256];
	base64_init((char*)base64_rank);

	register const unsigned char *inptr;
	register unsigned char *outptr;
	const unsigned char *inend;
	unsigned char c;
	register unsigned int v;
	int i;

	inend = in+len;
	outptr = out;

	/* convert 4 base64 bytes to 3 normal bytes */
	v=*save;
	i=*state;
	inptr = in;
	while (inptr<inend) {
		c = base64_rank[*inptr++];
		if (c != 0xff) {
			v = (v<<6) | c;
			i++;
			if (i==4) {
				*outptr++ = v>>16;
				*outptr++ = v>>8;
				*outptr++ = v;
				i=0;
			}
		}
	}

	*save = v;
	*state = i;

	/* quick scan back for '=' on the end somewhere */
	/* fortunately we can drop 1 output char for each trailing = (upto 2) */
	i=2;
	while (inptr>in && i) {
		inptr--;
		if (base64_rank[*inptr] != 0xff) {
			if (*inptr == '=' && outptr>out)
				outptr--;
			i--;
		}
	}

	/* if i!= 0 then there is a truncation error! */
	return outptr-out;
}

static char *base64_encode_simple (const char *data, size_t len)
{
	unsigned char *out;
	int state = 0, outlen;
	unsigned int save = 0;

	g_return_val_if_fail (data != NULL, NULL);

	out = g_malloc (len * 4 / 3 + 5);
	outlen = base64_encode_close ((unsigned char *)data, len, FALSE,
				      out, &state, (int*)&save);
	out[outlen] = '\0';
	return (char *)out;
}

static size_t base64_decode_simple (char *data, size_t len)
{
	int state = 0;
	unsigned int save = 0;

	g_return_val_if_fail (data != NULL, 0);

	return base64_decode_step ((unsigned char *)data, len,
					(unsigned char *)data, &state, &save);
}

static char *quoted_encode_simple(const unsigned char *string, int len)
{
	GString *tmp = g_string_new("");

	int i = 0;
	while(string[i] != 0) {
		if (string[i] > 127 || string[i] == 13 || string[i] == 10 || string[i] == '=') {
			g_string_append_printf(tmp, "=%02X", string[i]);
		} else {
			g_string_append_c(tmp, string[i]);
		}
		i++;
	}

	char *ret = tmp->str;
	g_string_free(tmp, FALSE);
	return ret;
}


static size_t quoted_decode_simple (char *data, size_t len)
{
	g_return_val_if_fail (data != NULL, 0);

	GString *string = g_string_new(data);
	if (!string)
		return 0;

	char hex[5];
	hex[4] = 0;

	while (1) {
		//Get the index of the next encoded char
		int i = strcspn(string->str, "=");
		if (i >= strlen(string->str))
			break;

		strcpy(hex, "0x");
		strncat(hex, &string->str[i + 1], 2);
		char rep = ((int)(strtod(hex, NULL)));
		g_string_erase(string, i, 2);
		g_string_insert_c(string, i, rep);
	}

	memset(data, 0, strlen(data));
	strcpy(data, string->str);
	g_string_free(string, 1);

	return strlen(data);
}
