/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* e-vcard.c
 *
 * Copyright (C) 2003 Ximian, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: Chris Toshok (toshok@ximian.com)
 */

/* This file implements the decoding of the v-card format
 * http://www.imc.org/pdi/vcard-21.txt
 */

#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "e-vcard.h"

#define d(x)

#define CRLF "\r\n"

/** Encoding used in v-card 
 *  Note: v-card spec defines additional 7BIT 8BIT and X- encoding
 */
typedef enum {
	EVC_ENCODING_RAW,    /* no encoding */
	EVC_ENCODING_BASE64, /* base64 */
	EVC_ENCODING_QP      /* quoted-printable */
} EVCardEncoding;

struct _EVCardPrivate {
	GList *attributes;
};

struct _EVCardAttribute {
	char  *group;
	char  *name;
	GList *params; /* EVCardParam */
	GList *values;
	GList *decoded_values;
	EVCardEncoding encoding;
	gboolean encoding_set;
};

struct _EVCardAttributeParam {
	char     *name;
	GList    *values;  /* GList of char*'s*/
};

static GObjectClass *parent_class;

static void   _evc_base64_init(void);
static size_t _evc_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save);
static size_t _evc_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save);
size_t _evc_base64_decode_simple (char *data, size_t len);
char  *_evc_base64_encode_simple (const char *data, size_t len);

static void
e_vcard_dispose (GObject *object)
{
	EVCard *evc = E_VCARD (object);

	if (evc->priv) {

		g_list_foreach (evc->priv->attributes, (GFunc)e_vcard_attribute_free, NULL);
		g_list_free (evc->priv->attributes);

		g_free (evc->priv);
		evc->priv = NULL;
	}

	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
e_vcard_class_init (EVCardClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS(klass);

	parent_class = g_type_class_ref (G_TYPE_OBJECT);

	object_class->dispose = e_vcard_dispose;

	_evc_base64_init();
}

static void
e_vcard_init (EVCard *evc)
{
	evc->priv = g_new0 (EVCardPrivate, 1);
}

GType
e_vcard_get_type (void)
{
	static GType vcard_type = 0;

	if (!vcard_type) {
		static const GTypeInfo vcard_info =  {
			sizeof (EVCardClass),
			NULL,           /* base_init */
			NULL,           /* base_finalize */
			(GClassInitFunc) e_vcard_class_init,
			NULL,           /* class_finalize */
			NULL,           /* class_data */
			sizeof (EVCard),
			0,             /* n_preallocs */
			(GInstanceInitFunc) e_vcard_init,
		};

		vcard_type = g_type_register_static (G_TYPE_OBJECT, "EVCard", &vcard_info, 0);
	}

	return vcard_type;
}



/*  Skip newline characters and return the next character.
 *  This function takes care of folding lines, skipping 
 *  newline characters if found, taking care of equal characters
 *  and other strange things.
 */
static char*
skip_newline (char *str, gboolean quoted_printable)
{
	char *p; 
	char *next;
	char *next2;
	p = str;

	/* -- swallow equal signs at end of line for quoted printable */
	/* note: a quoted_printable linefolding is an equal sign followed by
	   one or more newline characters and optional a whitespace */
	if (quoted_printable && *p == '=' ) {
		next = g_utf8_next_char (p);
		if (*next == '\r' || *next == '\n') {
			p = g_utf8_next_char (next); /* swallow equal and newline */

			if ((*p == '\r' || *p == '\n') && *p != *next ) {
				p = g_utf8_next_char (p); /* swallow second newline */

				if (*p == ' ' || *p == '\t') {
					p = g_utf8_next_char (p); /* swallow whitespace */
				}
			}

		}

	/* -- swallow newline and (if present) following whitespaces */
	} else if (*p == '\r' || *p == '\n') {

		next = g_utf8_next_char (p);
		if ((*next == '\n' || *next == '\r') && *p != *next) {

			next2 = g_utf8_next_char (next);
			if (*next2 == ' ' || *next2 == '\t') {
				p = g_utf8_next_char (next2); /* we found a line folding */
			}

		} else if (*next == ' ' || *next == '\t') {
			p = g_utf8_next_char (next);  /* we found a line folding */
		}
	}

	return p;
}


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

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

	/* -- skip over the endline */
	while( *lp == '\r' || *lp == '\n' ) {
		lp = g_utf8_next_char (lp);
	}

	*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 = g_utf8_next_char (lp);
	}

	*p = lp;
}

static void
read_attribute_value (EVCardAttribute *attr, char **p, gboolean quoted_printable)
{
	char *lp = *p;
	GString *str;

	/* read in the value */
	str = g_string_new ("");
	for( lp =  skip_newline( *p, quoted_printable );
	     *lp != '\n' && *lp != '\r' && *lp != '\0'; 
	     lp = skip_newline( lp, quoted_printable ) ) {

		if (*lp == '=' && quoted_printable) {
			char a, b;
			if ((a = *(++lp)) == '\0') break;
			if ((b = *(++lp)) == '\0') break;
			if (isxdigit(a) && isxdigit (b)) {
				char c;

				a = tolower (a);
				b = tolower (b);

				c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4)
					| ((b>='a'?b-'a'+10:b-'0')&0x0f);
				
				g_string_append_c (str, c); /* add decoded byte (this is not a unicode yet) */
			}
			else 
				{
					g_string_append_c (str, a);
					g_string_append_c (str, b);
				}
			
			lp++;

		} else if (*lp == '\\') {
			/* convert back to the non-escaped version of
			   the characters */
			lp = g_utf8_next_char(lp);
			if (*lp == '\0') {
				g_string_append_c (str, '\\');
				break;
			}
			switch (*lp) {
			case 'n': g_string_append_c (str, '\n'); break;
			case 'N': g_string_append_c (str, '\n'); break;
			case 'r': g_string_append_c (str, '\r'); break;
			case 'R': g_string_append_c (str, '\r'); break;
			case ';': g_string_append_c (str, ';'); break;
			case ',': g_string_append_c (str, ','); break;
			case '\\': g_string_append_c (str, '\\'); break;
			default:
				g_warning ("invalid escape, passing it through");
				g_string_append_c (str, '\\');
				g_string_append_unichar (str, g_utf8_get_char(lp));
				break;
			}
			lp = g_utf8_next_char(lp);
		}
		else if ((*lp == ';') ||
			 (*lp == ',' && !strcmp (attr->name, "CATEGORIES"))) {
			e_vcard_attribute_add_value (attr, str->str);
			g_string_assign (str, "");
			lp = g_utf8_next_char(lp);
		}
		else {
			g_string_append_unichar (str, g_utf8_get_char (lp));
			lp = g_utf8_next_char(lp);
		}
	}
	if (str) {
		e_vcard_attribute_add_value (attr, str->str);
		g_string_free (str, TRUE);
	}

	skip_to_next_line( &lp );

	*p = lp;
}

static void
read_attribute_params (EVCardAttribute *attr, char **p, gboolean *quoted_printable)
{
	char *lp;
	GString *str;
	EVCardAttributeParam *param = NULL;
	gboolean in_quote = FALSE;

	str = g_string_new ("");
	for( lp =  skip_newline( *p, *quoted_printable );
	     *lp != '\n' && *lp != '\r' && *lp != '\0'; 
	     lp = skip_newline( lp, *quoted_printable ) ) {

		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 == '_') {
			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 = e_vcard_attribute_param_new (str->str);
				g_string_assign (str, "");
				lp = g_utf8_next_char (lp);
			}
			else {
				skip_until (&lp, ":;");
				if (*lp == ';') {
					lp = g_utf8_next_char (lp);

				} else if (*lp == ':') {
					/* do nothing */

				} else {				
					skip_to_next_line( &lp );
					break;
				}
			}
		}
		else if (*lp == ';' || *lp == ':' || *lp == ',') {
			gboolean colon = (*lp == ':');
			gboolean comma = (*lp == ',');

			if (param) {
				if (str->len > 0) {
					e_vcard_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) {
						e_vcard_attribute_param_free (param);
						param = NULL;
						if (!colon)
							lp = g_utf8_next_char (lp);
					}
				}

				if (param
				    && !g_ascii_strcasecmp (param->name, "encoding")
				    && !g_ascii_strcasecmp (param->values->data, "quoted-printable")) {
					*quoted_printable = TRUE;
					e_vcard_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";
						*quoted_printable = TRUE;
					}
					/* 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");
					}
					else {
						param_name = "TYPE";
					}

					if (param_name) {
						param = e_vcard_attribute_param_new (param_name);
						e_vcard_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) {
				e_vcard_attribute_add_param (attr, param);
				param = NULL;
			}
			if (colon)
				break;
		}
		else {
			g_warning ("invalid character found in parameter spec");
			g_string_assign (str, "");
			/*			skip_until (&lp, ":;"); */
			
			skip_to_next_line( &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 EVCardAttribute*
read_attribute (char **p)
{
	char *attr_group = NULL;
	char *attr_name = NULL;
	EVCardAttribute *attr = NULL;
	GString *str;
	char *lp;
	gboolean is_qp = FALSE;

	/* first read in the group/name */
	str = g_string_new ("");
	for( lp =  skip_newline( *p, is_qp );
	     *lp != '\n' && *lp != '\r' && *lp != '\0'; 
	     lp = skip_newline( lp, is_qp ) ) {

		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) {
				g_warning ("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 == '_') {
			g_string_append_unichar (str, g_utf8_get_char (lp));
		}
		else {
			g_warning ("invalid character found in attribute group/name");
			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 = e_vcard_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);
		if (is_qp)
			attr->encoding = EVC_ENCODING_RAW;
	}
	if (*lp == ':') {
		/* skip past the ':' */
		lp = g_utf8_next_char(lp);
		read_attribute_value (attr, &lp, is_qp);
	}

	*p = lp;

	if (!attr->values)
		goto lose;

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

/* Stolen from glib/glib/gconvert.c */
static gchar *
make_valid_utf8 (const gchar *name)
{
	GString *string;
	const gchar *remainder, *invalid;
	gint remaining_bytes, valid_bytes;
  
	string = NULL;
  	remainder = name;
	remaining_bytes = strlen (name);
  
 	while (remaining_bytes != 0) {
		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) 
			break;
	      valid_bytes = invalid - remainder;
    
		if (string == NULL) 
			string = g_string_sized_new (remaining_bytes);

		g_string_append_len (string, remainder, valid_bytes);
	        /* append U+FFFD REPLACEMENT CHARACTER */
	        g_string_append (string, "\357\277\275");
      
	        remaining_bytes -= valid_bytes + 1;
	        remainder = invalid + 1;
	}
  
  	if (string == NULL)
    		return g_strdup (name);
  
	g_string_append (string, remainder);

        g_assert (g_utf8_validate (string->str, -1, NULL));
  
        return g_string_free (string, FALSE);
}

/* 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 (EVCard *evc, const char *str)
{
	char *buf ;
	char *p;
	EVCardAttribute *attr;
	
	buf = make_valid_utf8 (str);

	//buf = fold_lines (buf);

	d(printf ("BEFORE FOLDING:\n"));
	d(printf (str));
	d(printf ("\n\nAFTER FOLDING:\n"));
	d(printf (buf));

	p = buf;

	attr = read_attribute (&p);
	if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "begin")) {
		g_warning ("vcard began without a BEGIN:VCARD\n");
	}
	if (attr && !g_ascii_strcasecmp (attr->name, "begin"))
		e_vcard_attribute_free (attr);
	else if (attr)
		e_vcard_add_attribute (evc, attr);

	while (*p) {
		EVCardAttribute *next_attr = read_attribute (&p);

		if (next_attr) {
			if (g_ascii_strcasecmp (next_attr->name, "end"))
				e_vcard_add_attribute (evc, next_attr);
			attr = next_attr;
		}
	}

	if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "end")) {
		g_warning ("vcard ended without END:VCARD\n");
	}

	if (attr && !g_ascii_strcasecmp (attr->name, "end"))
		e_vcard_attribute_free (attr);

	g_free (buf);
}

/**
 * e_vcard_escape_string:
 * @s: the string to escape
 *
 * Escapes a string according to RFC2426, section 5.
 *
 * Return value: A newly allocated, escaped string.
 **/
char*
e_vcard_escape_string (const char *s)
{
	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':
			g_string_append (str, "\\n");
			break;
		case '\r':
			if (*(p+1) == '\n')
				p++;
			g_string_append (str, "\\n");
			break;
		case ';':
			g_string_append (str, "\\;");
			break;
		case ',':
			g_string_append (str, "\\,");
			break;
		case '\\':
			g_string_append (str, "\\\\");
			break;
		default:
			g_string_append_c (str, *p);
			break;
		}
	}

	return g_string_free (str, FALSE);
}

/**
 * e_vcard_unescape_string:
 * @s: the string to unescape
 *
 * Unescapes a string according to RFC2426, section 5.
 *
 * Return value: A newly allocated, unescaped string.
 **/
char*
e_vcard_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 5 */
	for (p = s; *p; p++) {
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				g_string_append_c (str, '\\');
				break;
			}
			switch (*p) {
			case 'n':  g_string_append_c (str, '\n'); break;
			case 'r':  g_string_append_c (str, '\r'); break;
			case ';':  g_string_append_c (str, ';'); break;
			case ',':  g_string_append_c (str, ','); break;
			case '\\': g_string_append_c (str, '\\'); break;
			default:
				g_warning ("invalid escape, passing it through");
				g_string_append_c (str, '\\');
				g_string_append_unichar (str, g_utf8_get_char(p));
				break;
			}
		}
	}

	return g_string_free (str, FALSE);
}

void
e_vcard_construct (EVCard *evc, const char *str)
{
	g_return_if_fail (E_IS_VCARD (evc));
	g_return_if_fail (str != NULL);

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

/**
 * e_vcard_new:
 * 
 * Creates a new, blank #EVCard.
 *
 * Return value: A new, blank #EVCard.
 **/
EVCard *
e_vcard_new ()
{
	return e_vcard_new_from_string ("");
}

/**
 * e_vcard_new_from_string:
 * @str: a string representation of the vcard to create
 *
 * Creates a new #EVCard from the passed-in string
 * representation.
 *
 * Return value: A new #EVCard.
 **/
EVCard *
e_vcard_new_from_string (const char *str)
{
	EVCard *evc;

	g_return_val_if_fail (str != NULL, NULL);

	evc = g_object_new (E_TYPE_VCARD, NULL);

	e_vcard_construct (evc, str);

	return evc;
}

static char*
e_vcard_to_string_vcard_21  (EVCard *evc)
{
	g_warning ("need to implement e_vcard_to_string_vcard_21");
	return g_strdup ("");
}

static char*
e_vcard_to_string_vcard_30 (EVCard *evc)
{
	GList *l;
	GList *v;

	GString *str = g_string_new ("");

	g_string_append (str, "BEGIN:VCARD" CRLF);

	/* we hardcode the version (since we're outputting to a
	   specific version) and ignore any version attributes the
	   vcard might contain */
	g_string_append (str, "VERSION:3.0" CRLF);

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

		if (!g_ascii_strcasecmp (attr->name, "VERSION"))
			continue;

		attr_str = g_string_new ("");

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

		if (attr->group) {
			g_string_append (attr_str, attr->group);
			g_string_append_c (attr_str, '.');
		}
		g_string_append (attr_str, attr->name);

		/* handle the parameters */
		for (p = attr->params; p; p = p->next) {
			EVCardAttributeParam *param = p->data;
			/* 5.8.2:
			 * param        = param-name "=" param-value *("," param-value)
			 */
			g_string_append_c (attr_str, ';');
			g_string_append (attr_str, param->name);
			if (param->values) {
				g_string_append_c (attr_str, '=');
				for (v = param->values; v; v = v->next) {
					char *value = v->data;
					char *p = value;
					gboolean quotes = FALSE;
					while (*p) {
						if (!g_unichar_isalnum (g_utf8_get_char (p))) {
							quotes = TRUE;
							break;
						}
						p = g_utf8_next_char (p);
					}
					if (quotes)
						g_string_append_c (attr_str, '"');
					g_string_append (attr_str, value);
					if (quotes)
						g_string_append_c (attr_str, '"');
					if (v->next)
						g_string_append_c (attr_str, ',');
				}
			}
		}

		g_string_append_c (attr_str, ':');

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

			escaped_value = e_vcard_escape_string (value);

			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 (!strcmp (attr->name, "CATEGORIES"))
					g_string_append_c (attr_str, ',');
				else
					g_string_append_c (attr_str, ';');
			}

			g_free (escaped_value);
		}

		/* 5.8.2:
		 * When generating a content line, lines longer than 75
		 * characters SHOULD be folded
		 */
		l = 0;
		do {
			
			if ((g_utf8_strlen (attr_str->str, -1) -l) > 75) {
				char *p;

				l += 75;
				p = g_utf8_offset_to_pointer (attr_str->str, l);
			
				g_string_insert_len (attr_str, (p - attr_str->str), CRLF " ", sizeof (CRLF " ") - 1);
			}
			else
				break;
		} while (l < g_utf8_strlen (attr_str->str, -1));

		g_string_append (attr_str, CRLF);

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

	g_string_append (str, "END:VCARD");

	return g_string_free (str, FALSE);
}

/**
 * e_vcard_to_string:
 * @evc: the #EVCard to export
 * @format: the format to export to
 *
 * Exports @evc to a string representation, specified
 * by the @format argument.
 *
 * Return value: A newly allocated string representing the vcard.
 **/
char*
e_vcard_to_string (EVCard *evc, EVCardFormat format)
{
	g_return_val_if_fail (E_IS_VCARD (evc), NULL);

	switch (format) {
	case EVC_FORMAT_VCARD_21:
		return e_vcard_to_string_vcard_21 (evc);
	case EVC_FORMAT_VCARD_30:
		return e_vcard_to_string_vcard_30 (evc);
	default:
		g_warning ("invalid format specifier passed to e_vcard_to_string");
		return g_strdup ("");
	}
}

/**
 * e_vcard_dump_structure:
 * @evc: the #EVCard to dump
 *
 * Prints a dump of @evc's structure to stdout. Used for
 * debugging.
 **/
void
e_vcard_dump_structure (EVCard *evc)
{
	GList *a;
	GList *v;
	int i;

	g_return_if_fail (E_IS_VCARD (evc));

	printf ("vCard\n");
	for (a = evc->priv->attributes; a; a = a->next) {
		GList *p;
		EVCardAttribute *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++) {
				EVCardAttributeParam *param = p->data;
				printf ("    |   [%d] = %s", i,param->name);
				printf ("(");
				for (v = param->values; v; v = v->next) {
					char *value = e_vcard_escape_string ((char*)v->data);
					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);
		}
	}
}

/**
 * e_vcard_attribute_new:
 * @attr_group: a group name
 * @attr_name: an attribute name
 *
 * Creates a new #EVCardAttribute with the specified group and
 * attribute names.
 *
 * Return value: A new #EVCardAttribute.
 **/
EVCardAttribute*
e_vcard_attribute_new (const char *attr_group, const char *attr_name)
{
	EVCardAttribute *attr;

	attr = g_new0 (EVCardAttribute, 1);

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

	return attr;
}

/**
 * e_vcard_attribute_free:
 * @attr: attribute to free
 *
 * Frees an attribute, its values and its parameters.
 **/
void
e_vcard_attribute_free (EVCardAttribute *attr)
{
	g_return_if_fail (attr != NULL);

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

	e_vcard_attribute_remove_values (attr);

	e_vcard_attribute_remove_params (attr);

	g_free (attr);
}

/**
 * e_vcard_attribute_copy:
 * @attr: attribute to copy
 *
 * Makes a copy of @attr.
 *
 * Return value: A new #EVCardAttribute identical to @attr.
 **/
EVCardAttribute*
e_vcard_attribute_copy (EVCardAttribute *attr)
{
	EVCardAttribute *a;
	GList *p;

	g_return_val_if_fail (attr != NULL, NULL);

	a = e_vcard_attribute_new (e_vcard_attribute_get_group (attr),
				   e_vcard_attribute_get_name (attr));

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

	for (p = attr->params; p; p = p->next)
		e_vcard_attribute_add_param (a, e_vcard_attribute_param_copy (p->data));

	return a;
}

/**
 * e_vcard_remove_attributes:
 * @evc: vcard object
 * @attr_group: group name of attributes to be removed
 * @attr_name: name of the arributes to be removed
 *
 * Removes all the attributes with group name and attribute name equal to 
 * passed in values. If @attr_group is %NULL or an empty string,
 * it removes all the attributes with passed in name irrespective of
 * their group names.
 **/
void
e_vcard_remove_attributes (EVCard *evc, const char *attr_group, const char *attr_name)
{
	GList *attr;

	g_return_if_fail (E_IS_VCARD (evc));
	g_return_if_fail (attr_name != NULL);

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

		next_attr = attr->next;

		if (((!attr_group || *attr_group == '\0') ||
		     (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->priv->attributes = g_list_delete_link (evc->priv->attributes, attr);

			e_vcard_attribute_free (a);
		}

		attr = next_attr;
	}
}

/**
 * e_vcard_remove_attribute:
 * @evc: an #EVCard
 * @attr: an #EVCardAttribute to remove
 *
 * Removes @attr from @evc and frees it.
 **/
void
e_vcard_remove_attribute (EVCard *evc, EVCardAttribute *attr)
{
	g_return_if_fail (E_IS_VCARD (evc));
	g_return_if_fail (attr != NULL);

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

/**
 * e_vcard_add_attribute:
 * @evc: an #EVCard
 * @attr: an #EVCardAttribute to add
 *
 * Adds @attr to @evc.
 **/
void
e_vcard_add_attribute (EVCard *evc, EVCardAttribute *attr)
{
	g_return_if_fail (E_IS_VCARD (evc));
	g_return_if_fail (attr != NULL);

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

/**
 * e_vcard_add_attribute_with_value:
 * @evcard: an #EVCard
 * @attr: an #EVCardAttribute to add
 * @value: a value to assign to the attribute
 *
 * Adds @attr to @evcard, setting it to @value.
 **/
void
e_vcard_add_attribute_with_value (EVCard *evcard,
				  EVCardAttribute *attr, const char *value)
{
	g_return_if_fail (E_IS_VCARD (evcard));
	g_return_if_fail (attr != NULL);

	e_vcard_attribute_add_value (attr, value);

	e_vcard_add_attribute (evcard, attr);
}

/**
 * e_vcard_add_attribute_with_values:
 * @evcard: an @EVCard
 * @attr: an #EVCardAttribute to add
 * @...: a %NULL-terminated list of values to assign to the attribute
 *
 * Adds @attr to @evcard, assigning the list of values to it.
 **/
void
e_vcard_add_attribute_with_values (EVCard *evcard, EVCardAttribute *attr, ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (E_IS_VCARD (evcard));
	g_return_if_fail (attr != NULL);

	va_start (ap, attr);

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

	va_end (ap);

	e_vcard_add_attribute (evcard, attr);
}

/**
 * e_vcard_attribute_add_value:
 * @attr: an #EVCardAttribute
 * @value: a string value
 *
 * Adds @value to @attr's list of values.
 **/
void
e_vcard_attribute_add_value (EVCardAttribute *attr, const char *value)
{
	g_return_if_fail (attr != NULL);

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

/**
 * e_vcard_attribute_add_value_decoded:
 * @attr: an #EVCardAttribute
 * @value: an encoded value
 * @len: the length of the encoded value, in bytes
 *
 * Decodes @value according to the encoding used for @attr, and
 * adds it to @attr's list of values.
 **/
void
e_vcard_attribute_add_value_decoded (EVCardAttribute *attr, const char *value, int len)
{
	g_return_if_fail (attr != NULL);

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

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

		d(printf ("base64 encoded value: %s\n", b64_data));
		d(printf ("original length: %d\n", len));

		attr->values = g_list_append (attr->values, b64_data);
		attr->decoded_values = g_list_append (attr->decoded_values, decoded);
		break;
	}
	case EVC_ENCODING_QP:
		g_warning ("need to implement quoted printable decoding");
		break;
	}
}

/**
 * e_vcard_attribute_add_values:
 * @attr: an #EVCardAttribute
 * @Varargs: a %NULL-terminated list of strings
 *
 * Adds a list of values to @attr.
 **/
void
e_vcard_attribute_add_values (EVCardAttribute *attr,
			      ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (attr != NULL);

	va_start (ap, attr);

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

	va_end (ap);
}

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

/**
 * e_vcard_attribute_remove_values:
 * @attr: an #EVCardAttribute
 *
 * Removes all values from @attr.
 **/
void
e_vcard_attribute_remove_values (EVCardAttribute *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;
}

/**
 * e_vcard_attribute_remove_value:
 * @attr: an #EVCardAttribute
 * @s: an value to remove
 *
 * Removes from the value list in @attr the value @s.
 **/
void
e_vcard_attribute_remove_value (EVCardAttribute *attr, const char *s)
{
	GList *l;

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

	l = g_list_find_custom (attr->values, s, (GCompareFunc)strcmp);
	if (l == NULL) {
		return;
	}
	
	attr->values = g_list_delete_link (attr->values, l);
}

/**
 * e_vcard_attribute_remove_params:
 * @attr: an #EVCardAttribute
 *
 * Removes all parameters from @attr.
 **/
void
e_vcard_attribute_remove_params (EVCardAttribute *attr)
{
	g_return_if_fail (attr != NULL);

	g_list_foreach (attr->params, (GFunc)e_vcard_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 = EVC_ENCODING_RAW;
}

/**
 * e_vcard_attribute_param_new:
 * @name: the name of the new parameter
 *
 * Creates a new parameter named @name.
 *
 * Return value: A new #EVCardAttributeParam.
 **/
EVCardAttributeParam*
e_vcard_attribute_param_new (const char *name)
{
	EVCardAttributeParam *param = g_new0 (EVCardAttributeParam, 1);
	param->name = g_strdup (name);

	return param;
}

/**
 * e_vcard_attribute_param_free:
 * @param: an #EVCardAttributeParam
 *
 * Frees @param and its values.
 **/
void
e_vcard_attribute_param_free (EVCardAttributeParam *param)
{
	g_return_if_fail (param != NULL);

	g_free (param->name);

	e_vcard_attribute_param_remove_values (param);

	g_free (param);
}

/**
 * e_vcard_attribute_param_copy:
 * @param: an #EVCardAttributeParam
 *
 * Makes a copy of @param.
 *
 * Return value: a new #EVCardAttributeParam identical to @param.
 **/
EVCardAttributeParam*
e_vcard_attribute_param_copy (EVCardAttributeParam *param)
{
	EVCardAttributeParam *p;
	GList *l;

	g_return_val_if_fail (param != NULL, NULL);

	p = e_vcard_attribute_param_new (e_vcard_attribute_param_get_name (param));

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

	return p;
}

/**
 * e_vcard_attribute_add_param:
 * @attr: an #EVCardAttribute
 * @param: an #EVCardAttributeParam to add
 *
 * Adds @param to @attr's list of parameters.
 **/
void
e_vcard_attribute_add_param (EVCardAttribute *attr,
			     EVCardAttributeParam *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, EVC_ENCODING)) {
		if (attr->encoding_set) {
			g_warning ("ENCODING specified twice");
			return;
		}

		if (param->values && param->values->data) {
			if (!g_ascii_strcasecmp ((char*)param->values->data, "b") ||
			    !g_ascii_strcasecmp ((char*)param->values->data, "BASE64"))
				attr->encoding = EVC_ENCODING_BASE64;
			else if (!g_ascii_strcasecmp ((char*)param->values->data, EVC_QUOTEDPRINTABLE))
				attr->encoding = EVC_ENCODING_QP;
			else {
				g_warning ("Unknown value `%s' for ENCODING parameter.  values will be treated as raw",
					   (char*)param->values->data);
			}

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

/**
 * e_vcard_attribute_param_add_value:
 * @param: an #EVCardAttributeParam
 * @value: a string value to add
 *
 * Adds @value to @param's list of values.
 **/
void
e_vcard_attribute_param_add_value (EVCardAttributeParam *param,
				   const char *value)
{
	g_return_if_fail (param != NULL);

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

/**
 * e_vcard_attribute_param_add_values:
 * @param: an #EVCardAttributeParam
 * @Varargs: a %NULL-terminated list of strings
 *
 * Adds a list of values to @param.
 **/
void
e_vcard_attribute_param_add_values (EVCardAttributeParam *param,
				    ...)
{
	va_list ap;
	char *v;

	g_return_if_fail (param != NULL);

	va_start (ap, param);

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

	va_end (ap);
}

/**
 * e_vcard_attribute_add_param_with_value:
 * @attr: an #EVCardAttribute
 * @param: an #EVCardAttributeParam
 * @value: a string value
 *
 * Adds @value to @param, then adds @param to @attr.
 **/
void
e_vcard_attribute_add_param_with_value (EVCardAttribute *attr,
					EVCardAttributeParam *param, const char *value)
{
	g_return_if_fail (attr != NULL);
	g_return_if_fail (param != NULL);

	e_vcard_attribute_param_add_value (param, value);

	e_vcard_attribute_add_param (attr, param);
}

/**
 * e_vcard_attribute_add_param_with_values:
 * @attr: an #EVCardAttribute
 * @param: an #EVCardAttributeParam
 * @Varargs: a %NULL-terminated list of strings
 *
 * Adds the list of values to @param, then adds @param
 * to @attr.
 **/
void
e_vcard_attribute_add_param_with_values (EVCardAttribute *attr,
					 EVCardAttributeParam *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*))) {
		e_vcard_attribute_param_add_value (param, v);
	}

	va_end (ap);

	e_vcard_attribute_add_param (attr, param);
}

/**
 * e_vcard_attribute_param_remove_values:
 * @param: an #EVCardAttributeParam
 *
 * Removes and frees all values from @param.
 **/
void
e_vcard_attribute_param_remove_values (EVCardAttributeParam *param)
{
	g_return_if_fail (param != NULL);

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

/**
 * e_vcard_attribute_remove_param_value:
 * @attr: an #EVCardAttribute
 * @param_name: a parameter name
 * @s: a value
 *
 * Removes the value @s from the parameter @param_name on the attribute @attr.
 **/
void
e_vcard_attribute_remove_param_value (EVCardAttribute *attr, const char *param_name, const char *s)
{
	GList *l, *params;
	EVCardAttributeParam *param;

	g_return_if_fail (attr != NULL);
	g_return_if_fail (param_name != NULL);
	g_return_if_fail (s != NULL);

	params = e_vcard_attribute_get_params (attr);

	for (l = params; l; l = l->next) {
		param = l->data;
		if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), param_name) == 0) {
			l = g_list_find_custom (param->values, s, (GCompareFunc)strcmp);
			if (l == NULL) {
				return;
			}
			
			param->values = g_list_delete_link (param->values, l);
			if (param->values == NULL) {
				e_vcard_attribute_param_free (param);
				attr->params = g_list_remove (attr->params, param);
			}
			break;
		}
	}
	return;
}

/**
 * e_vcard_get_attributes:
 * @evcard: an #EVCard
 *
 * Gets the list of attributes from @evcard. The list and its
 * contents are owned by @evcard, and must not be freed.
 *
 * Return value: A list of attributes of type #EVCardAttribute.
 **/
GList*
e_vcard_get_attributes (EVCard *evcard)
{
	g_return_val_if_fail (E_IS_VCARD (evcard), NULL);

	return evcard->priv->attributes;
}

/**
 * e_vcard_get_attribute:
 * @evc: an #EVCard
 * @name: the name of the attribute to get
 *
 * Get the attribute @name from @evc.  The #EVCardAttribute is owned by
 * @evcard and should not be freed. If the attribute does not exist, #NULL is
 * returned.
 *
 * Return value: An #EVCardAttribute if found, or #NULL.
 **/
EVCardAttribute *
e_vcard_get_attribute (EVCard     *evc,
		       const char *name)
{
        GList *attrs, *l;

        g_return_val_if_fail (E_IS_VCARD (evc), NULL);
        g_return_val_if_fail (name != NULL, NULL);

        attrs = e_vcard_get_attributes (evc);
        for (l = attrs; l; l = l->next) {
                EVCardAttribute *attr;
		
                attr = (EVCardAttribute *) l->data;
                if (strcmp (attr->name, name) == 0)
                        return attr;
        }

        return NULL;
}
/**
 * e_vcard_attribute_get_group:
 * @attr: an #EVCardAttribute
 *
 * Gets the group name of @attr.
 *
 * Return value: The attribute's group name.
 **/
const char*
e_vcard_attribute_get_group (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->group;
}

/**
 * e_vcard_attribute_get_name:
 * @attr: an #EVCardAttribute
 *
 * Gets the name of @attr.
 *
 * Return value: The attribute's name.
 **/
const char*
e_vcard_attribute_get_name (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->name;
}

/**
 * e_vcard_attribute_get_values:
 * @attr: an #EVCardAttribute
 *
 * Gets the list of values from @attr. The list and its
 * contents are owned by @attr, and must not be freed.
 * 
 * Return value: A list of string values.
 **/
GList*
e_vcard_attribute_get_values (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->values;
}

/**
 * e_vcard_attribute_get_values_decoded:
 * @attr: an #EVCardAttribute
 *
 * Gets the list of values from @attr, decoding them if
 * necessary. The list and its contents are owned by @attr,
 * and must not be freed.
 *
 * Return value: A list of values of type #GString.
 **/
GList*
e_vcard_attribute_get_values_decoded (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	if (!attr->decoded_values) {
		GList *l;
		switch (attr->encoding) {
		case EVC_ENCODING_RAW:
			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 EVC_ENCODING_BASE64:
			for (l = attr->values; l; l = l->next) {
				char *decoded = g_strdup ((char*)l->data);
				int len = _evc_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 EVC_ENCODING_QP:
			g_warning ("need to implement quoted printable decoding");
			break;
		}
	}

	return attr->decoded_values;
}

/**
 * e_vcard_attribute_is_single_valued:
 * @attr: an #EVCardAttribute
 *
 * Checks if @attr has a single value.
 *
 * Return value: %TRUE if the attribute has exactly one value, %FALSE otherwise.
 **/
gboolean
e_vcard_attribute_is_single_valued (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, FALSE);

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

	return TRUE;
}

/**
 * e_vcard_attribute_get_value:
 * @attr: an #EVCardAttribute
 *
 * Gets the value of a single-valued #EVCardAttribute, @attr.
 *
 * Return value: A newly allocated string representing the value.
 **/
char*
e_vcard_attribute_get_value (EVCardAttribute *attr)
{
	GList *values;

	g_return_val_if_fail (attr != NULL, NULL);

	values = e_vcard_attribute_get_values (attr);

	if (!e_vcard_attribute_is_single_valued (attr))
		g_warning ("e_vcard_attribute_get_value called on multivalued attribute");

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

/**
 * e_vcard_attribute_get_value_decoded:
 * @attr: an #EVCardAttribute
 *
 * Gets the value of a single-valued #EVCardAttribute, @attr, decoding
 * it if necessary.
 *
 * Note: this function seems currently to be unused. Could be removed.
 *
 * Return value: A newly allocated #GString representing the value.
 **/
GString*
e_vcard_attribute_get_value_decoded (EVCardAttribute *attr)
{
	GList *values;
	GString *str = NULL;

	g_return_val_if_fail (attr != NULL, NULL);

	values = e_vcard_attribute_get_values_decoded (attr);

	if (!e_vcard_attribute_is_single_valued (attr))
		g_warning ("e_vcard_attribute_get_value_decoded called on multivalued attribute");

	if (values)
		str = values->data;

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

/**
 * e_vcard_attribute_has_type:
 * @attr: an #EVCardAttribute
 * @typestr: a string representing the type
 *
 * Checks if @attr has an #EVCardAttributeParam of the specified type.
 *
 * Return value: %TRUE if such a parameter exists, %FALSE otherwise.
 **/
gboolean
e_vcard_attribute_has_type (EVCardAttribute *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 = e_vcard_attribute_get_params (attr);

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

		if (!g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), EVC_TYPE)) {
			GList *values = e_vcard_attribute_param_get_values (param);
			GList *v;

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

	return FALSE;
}

/**
 * e_vcard_attribute_get_params:
 * @attr: an #EVCardAttribute
 * 
 * Gets the list of parameters from @attr. The list and its
 * contents are owned by @attr, and must not be freed.
 *
 * Return value: A list of elements of type #EVCardAttributeParam.
 **/
GList*
e_vcard_attribute_get_params (EVCardAttribute *attr)
{
	g_return_val_if_fail (attr != NULL, NULL);

	return attr->params;
}

/**
 * e_vcard_attribute_get_param:
 * @attr: an #EVCardAttribute
 * @name: a parameter name
 * 
 * Gets the list of values for the paramater @name from @attr. The list and its
 * contents are owned by @attr, and must not be freed.
 *
 * Return value: A list of string elements representing the parameter's values.
 **/
GList *
e_vcard_attribute_get_param (EVCardAttribute *attr, const char *name)
{
	GList *params, *p;
	
	g_return_val_if_fail (attr != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);
	
	params = e_vcard_attribute_get_params (attr);

	for (p = params; p; p = p->next) {
		EVCardAttributeParam *param = p->data;
		if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), name) == 0) {
			return e_vcard_attribute_param_get_values (param);
		}
	}

	return NULL;
}

/**
 * e_vcard_attribute_param_get_name:
 * @param: an #EVCardAttributeParam
 *
 * Gets the name of @param.
 *
 * Return value: The name of the parameter.
 **/
const char*
e_vcard_attribute_param_get_name (EVCardAttributeParam *param)
{
	g_return_val_if_fail (param != NULL, NULL);

	return param->name;
}

/**
 * e_vcard_attribute_param_get_values:
 * @param: an #EVCardAttributeParam
 * 
 * Gets the list of values from @param. The list and its
 * contents are owned by @param, and must not be freed.
 *
 * Return value: A list of string elements representing the parameter's values.
 **/
GList*
e_vcard_attribute_param_get_values (EVCardAttributeParam *param)
{
	g_return_val_if_fail (param != NULL, NULL);

	return param->values;
}



/* encoding/decoding stuff ripped from camel-mime-utils.c */

static char *_evc_base64_alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static unsigned char _evc_base64_rank[256];

static void
_evc_base64_init(void)
{
	int i;

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

/* call this when finished encoding everything, to
   flush off the last little bit */
static size_t
_evc_base64_encode_close(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 += _evc_base64_encode_step(in, inlen, break_lines, outptr, state, save);

	c1 = ((unsigned char *)save)[1];
	c2 = ((unsigned char *)save)[2];
	
	d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
		 (int)((char *)save)[0],
		 (int)((char *)save)[1],
		 (int)((char *)save)[2]));

	switch (((char *)save)[0]) {
	case 2:
		outptr[2] = _evc_base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
		g_assert(outptr[2] != 0);
		goto skip;
	case 1:
		outptr[2] = '=';
	skip:
		outptr[0] = _evc_base64_alphabet[ c1 >> 2 ];
		outptr[1] = _evc_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
_evc_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
{
	register unsigned char *inptr, *outptr;

	if (len<=0)
		return 0;

	inptr = in;
	outptr = out;

	d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));

	if (len + ((char *)save)[0] > 2) {
		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++ = _evc_base64_alphabet[ c1 >> 2 ];
			*outptr++ = _evc_base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
			*outptr++ = _evc_base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
			*outptr++ = _evc_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;
	}

	d(printf("state = %d, len = %d\n",
		 (int)((char *)save)[0],
		 len));

	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;
	}

	d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
		 (int)((char *)save)[0],
		 (int)((char *)save)[1],
		 (int)((char *)save)[2]));

	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
_evc_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
{
	register unsigned char *inptr, *outptr;
	unsigned char *inend, 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 = _evc_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 (_evc_base64_rank[*inptr] != 0xff) {
			if (*inptr == '=' && outptr>out)
				outptr--;
			i--;
		}
	}

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

char *
_evc_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 = _evc_base64_encode_close ((unsigned char *)data, len, FALSE,
				      out, &state, &save);
	out[outlen] = '\0';
	return (char *)out;
}

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

	g_return_val_if_fail (data != NULL, 0);

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