aboutsummaryrefslogblamecommitdiffstats
path: root/addressbook/backend/ebook/e-vcard.c
blob: 5207d0fefe7efb38606b1e7d29c1f8e53be92561 (plain) (tree)
1
2
                                                                           
            





















                                                                    
                   




                    





                                                   








                                        


                                








                                                





                                                                                                                                      



























                                                                                    

                           



























































































































































                                                                                              

                                      
                                                  

                                      

                                                                     






                                                                                  



                                                             


























                                                                                   
                                                          



















                                                                                                
                                                                  









































                                                                                                       
                                                                  




















                                                                                                
                                                  
























































































































                                                                                                              
                 

                                     
      

                               
                 

                                        
      





                                                                               

                                              













                                                                             
 


                                                            
                     








                                                                
                                   




























































                                                                                        






                                                


              
                                            




                                         
                    
 




                                                



                   








                                                                   










































































                                                                                                              

                                                           






                                                 













                                                                                   



















































                                                                                     


                             







































                                                                                                 
 

                                                   
 
                                 
         
 
 




                                                                            








































                                                                              


























                                                                                                                                       














                                                      





                                  




                                                           

                                                                         









                                                                                 













                                                                       


                                                      


                       












                                                                                                         




                                                           
























                                                                                                                       



















































                                                                                       







                                                                   
























                                                    


























                                                                                                                             















                                                                









































































































































































































































                                                                                                                          
/* -*- 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 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 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)
 */

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

#define CRLF "\r\n"

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)
        return;

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



static char*
fold_lines (char *buf)
{
    GString *str = g_string_new ("");
    char *p = buf;
    char *next, *next2;

    /* we're pretty liberal with line folding here.  We handle
       lines folded with \r\n<WS>... and \n\r<WS>... and
       \n<WS>... We also turn single \r's and \n's not followed by
       WS into \r\n's. */
    while (*p) {
        if (*p == '\r' || *p == '\n') {
            next = g_utf8_next_char (p);
            if (*next == '\n' || *next == '\r') {
                next2 = g_utf8_next_char (next);
                if (*next2 == ' ' || *next2 == '\t') {
                    p = g_utf8_next_char (next2);
                }
                else {
                    str = g_string_append (str, CRLF);
                    p = g_utf8_next_char (next);
                }
            }
            else if (*next == ' ' || *next == '\t') {
                p = g_utf8_next_char (next);
            }
            else {
                str = g_string_append (str, CRLF);
                p = g_utf8_next_char (p);
            }
        }
        else {
            str = g_string_append_unichar (str, g_utf8_get_char (p));
            p = g_utf8_next_char (p);
        }
    }

    g_free (buf);

    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 */
static void
skip_until (char **p, char *s)
{
    /* XXX write me plz k thx */
    g_assert_not_reached();
}

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 ("");
    while (*lp != '\r' && *lp != '\0') {
        if (*lp == '=' && quoted_printable) {
            char a, b;
            if ((a = *(++lp)) == '\0') break;
            if ((b = *(++lp)) == '\0') break;
            if (a == '\r' && b == '\n') {
                /* it was a = at the end of the line,
                 * just ignore this and continue
                 * parsing on the next line.  yay for
                 * 2 kinds of line folding
                 */
            }
            else if (isalnum(a) && isalnum (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);

                str = g_string_append_c (str, c);
            }
            /* silently consume malformed input, and
               continue parsing */
            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 ',': str = g_string_append_c (str, ','); break;
            case '\\': str = g_string_append_c (str, '\\'); break;
            default:
                g_warning ("invalid escape, passing it through");
                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 == ';') {
            e_vcard_attribute_add_value (attr, str->str);
            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) {
        e_vcard_attribute_add_value (attr, str->str);
        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 (EVCardAttribute *attr, char **p, gboolean *quoted_printable)
{
    char *lp = *p;
    GString *str;
    EVCardAttributeParam *param = NULL;

    str = g_string_new ("");
    while (*lp != '\0') {
        /* 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.)
         */
        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 == '\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) {
                    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 (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 = NULL;
                        *quoted_printable = TRUE;
                    }
                    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 {
                    /* XXX more here */
                    g_assert_not_reached ();
                }
            }
            if (param && !comma) {
                e_vcard_attribute_add_param (attr, param);
                param = NULL;
            }
            if (colon)
                break;
        }
        else if (g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_') {
            str = g_string_append_unichar (str, g_utf8_get_char (lp));
            lp = g_utf8_next_char (lp);
        }
        else {
            g_warning ("invalid character found in parameter spec");
            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 EVCardAttribute*
read_attribute (char **p)
{
    char *attr_group = NULL;
    char *attr_name = NULL;
    EVCardAttribute *attr = NULL;
    GString *str;
    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) {
                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 == '_') {
            str = 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 (*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;
}

/* 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 = g_strdup (str);
    char *p, *end;
    EVCardAttribute *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 */
        g_warning ("invalid utf8 passed to EVCard.  Limping along.");
        *end = '\0';
    }
    
#if DEBUG_FOLDING
    printf ("BEFORE FOLDING:\n");
    printf (str);
#endif
    buf = fold_lines (buf);

#if DEBUG_FOLDING
    printf ("\n\nAFTER FOLDING:\n");
    printf (buf);
#endif
    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)
        e_vcard_attribute_free (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);
}

static char*
escape_string (const char *s)
{
    GString *str = g_string_new ("");
    const char *p;

    /* 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 ',':
            str = g_string_append (str, "\\,");
            break;
        case '\\':
            str = g_string_append (str, "\\\\");
            break;
        default:
            str = g_string_append_c (str, *p);
            break;
        }
    }

    return g_string_free (str, FALSE);
}

#if notyet
static char*
unescape_string (const char *s)
{
    GString *str = g_string_new ("");
    const char *p;

    /* Unescape a string as described in RFC2426, section 5 */
    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;
            default:
                g_warning ("invalid escape, passing it through");
                str = g_string_append_c (str, '\\');
                str = g_string_append_unichar (str, g_utf8_get_char(p));
                break;
            }
        }
    }

    return g_string_free (str, FALSE);
}
#endif

void
e_vcard_construct (EVCard *evc, const char *str)
{
    if (*str)
        parse (evc, str);
}

EVCard *
e_vcard_new ()
{
    return e_vcard_new_from_string ("");
}

EVCard *
e_vcard_new_from_string (const char *str)
{
    EVCard *evc;

    g_return_val_if_fail (str, 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 ("");

    str = g_string_append (str, "BEGIN:vCard" CRLF);

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

        /* 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) {
            EVCardAttributeParam *param = p->data;
            /* 5.8.2:
             * param        = param-name "=" param-value *("," param-value)
             */
            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) {
                    char *value = v->data;
                    attr_str = g_string_append (attr_str, value);
                    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;

            escaped_value = escape_string (value);

            attr_str = g_string_append (attr_str, escaped_value);
            if (v->next)
                attr_str = 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 (attr_str->len - l > 75) {
                l += 75;
                attr_str = g_string_insert_len (attr_str, l, CRLF " ", sizeof (CRLF " ") - 1);
            }
            else
                break;
        } while (l < attr_str->len);

        attr_str = g_string_append (attr_str, CRLF);

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

    str = g_string_append (str, "END:vCard");

    return g_string_free (str, FALSE);
}

char*
e_vcard_to_string (EVCard *evc, EVCardFormat format)
{
    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 ("");
    }
}

void
e_vcard_dump_structure (EVCard *evc)
{
    GList *a;
    GList *v;
    int i;

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


EVCardAttribute*
e_vcard_attribute_new (const char *attr_group, const char *attr_name)
{
    EVCardAttribute *attr = g_new0 (EVCardAttribute, 1);

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

    return attr;
}

void
e_vcard_attribute_free (EVCardAttribute *attr)
{
    g_free (attr->group);
    g_free (attr->name);

    e_vcard_attribute_remove_values (attr);

    e_vcard_attribute_remove_params (attr);

    g_free (attr);
}

EVCardAttribute*
e_vcard_attribute_copy (EVCardAttribute *attr)
{
    EVCardAttribute *a = e_vcard_attribute_new (e_vcard_attribute_get_group (attr),
                            e_vcard_attribute_get_name (attr));
    GList *p;

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

void
e_vcard_remove_attributes (EVCard *evc, const char *attr_group, const char *attr_name)
{
    GList *attr;

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

        next_attr = attr->next;

        if (((!attr_group && !a->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_remove_link (evc->priv->attributes, attr);

            e_vcard_attribute_free (a);
        }

        attr = next_attr;
    }
}

void
e_vcard_remove_attribute (EVCard *evc, EVCardAttribute *attr)
{
    evc->priv->attributes = g_list_remove (evc->priv->attributes, attr);
    e_vcard_attribute_free (attr);
}

void
e_vcard_add_attribute (EVCard *evc, EVCardAttribute *attr)
{
    evc->priv->attributes = g_list_append (evc->priv->attributes, attr);
}

void
e_vcard_add_attribute_with_value (EVCard *evcard,
                  EVCardAttribute *attr, const char *value)
{
    e_vcard_attribute_add_value (attr, value);

    e_vcard_add_attribute (evcard, attr);
}

void
e_vcard_add_attribute_with_values (EVCard *evcard, EVCardAttribute *attr, ...)
{
    va_list ap;
    char *v;

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

void
e_vcard_attribute_add_value (EVCardAttribute *attr, const char *value)
{
    attr->values = g_list_append (attr->values, g_strdup (value));
}

void
e_vcard_attribute_add_value_decoded (EVCardAttribute *attr, const char *value, int len)
{
    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);

        printf ("base64 encoded value: %s\n", b64_data);
        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;
    }
}

void
e_vcard_attribute_add_values (EVCardAttribute *attr,
                  ...)
{
    va_list ap;
    char *v;

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

void
e_vcard_attribute_remove_values (EVCardAttribute *attr)
{
    g_list_foreach (attr->values, (GFunc)g_free, NULL);
    g_list_free (attr->values);
    g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL);
    g_list_free (attr->decoded_values);
    attr->values = NULL;
}

void
e_vcard_attribute_remove_params (EVCardAttribute *attr)
{
    g_list_foreach (attr->params, (GFunc)e_vcard_attribute_param_free, NULL);
    g_list_free (attr->params);
    attr->params = NULL;
}

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

    return param;
}

void
e_vcard_attribute_param_free (EVCardAttributeParam *param)
{
    g_free (param->name);

    e_vcard_attribute_param_remove_values (param);

    g_free (param);
}

EVCardAttributeParam*
e_vcard_attribute_param_copy (EVCardAttributeParam *param)
{
    EVCardAttributeParam *p = e_vcard_attribute_param_new (e_vcard_attribute_param_get_name (param));
    GList *l;

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

    return p;
}

void
e_vcard_attribute_add_param (EVCardAttribute *attr,
                 EVCardAttributeParam *param)
{
    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"))
                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");
        }
    }
}

void
e_vcard_attribute_param_add_value (EVCardAttributeParam *param,
                   const char *value)
{
    param->values = g_list_append (param->values, g_strdup (value));
}

void
e_vcard_attribute_param_add_values (EVCardAttributeParam *param,
                    ...)
{
    va_list ap;
    char *v;

    va_start (ap, param);

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

    va_end (ap);
}

void
e_vcard_attribute_add_param_with_value (EVCardAttribute *attr,
                    EVCardAttributeParam *param, const char *value)
{
    e_vcard_attribute_param_add_value (param, value);

    e_vcard_attribute_add_param (attr, param);
}

void
e_vcard_attribute_add_param_with_values (EVCardAttribute *attr,
                     EVCardAttributeParam *param, ...)
{
    va_list ap;
    char *v;

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

void
e_vcard_attribute_param_remove_values (EVCardAttributeParam *param)
{
    g_list_foreach (param->values, (GFunc)g_free, NULL);
    g_list_free (param->values);
    param->values = NULL;
}

GList*
e_vcard_get_attributes (EVCard *evcard)
{
    return evcard->priv->attributes;
}

const char*
e_vcard_attribute_get_group (EVCardAttribute *attr)
{
    return attr->group;
}

const char*
e_vcard_attribute_get_name (EVCardAttribute *attr)
{
    return attr->name;
}

GList*
e_vcard_attribute_get_values (EVCardAttribute *attr)
{
    return attr->values;
}

GList*
e_vcard_attribute_get_values_decoded (EVCardAttribute *attr)
{
    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;
}

GList*
e_vcard_attribute_get_params (EVCardAttribute *attr)
{
    return attr->params;
}

const char*
e_vcard_attribute_param_get_name (EVCardAttributeParam *param)
{
    return param->name;
}

GList*
e_vcard_attribute_param_get_values (EVCardAttributeParam *param)
{
    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];
    
#if 0
    d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
         (int)((char *)save)[0],
         (int)((char *)save)[1],
         (int)((char *)save)[2]));
#endif

    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;

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

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

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

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

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

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

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