Add a wrapper function to decode base64, returning a null-terminated string.
[claws.git] / src / ldif.c
index cd1cc38d20c58cf27ef4278921bdc18616a7606f..3bc227b0c2cd01f9ebdd04f05f30148c0b1df630 100644 (file)
@@ -1,10 +1,10 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 2001 Match Grun
+ * Copyright (C) 2001-2012 Match Grun and the Claws Mail team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
@@ -13,8 +13,8 @@
  * 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.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * 
  */
 
 /*
  * files).
  */
 
-#include <sys/stat.h>
 #include <glib.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <sys/stat.h>
 
 #include "mgutils.h"
 #include "ldif.h"
 #include "addritem.h"
 #include "addrcache.h"
 
-#include "base64.h"
+#include "utils.h"
 
-/*
-* Create new object.
-*/
+#define        LDIF_SEP_TAG    ':'
+#define        LDIF_LANG_TAG   ';'
+
+/**
+ * Create new object.
+ * \return Initialized LDIF file object.
+ */
 LdifFile *ldif_create() {
        LdifFile *ldifFile;
        ldifFile = g_new0( LdifFile, 1 );
        ldifFile->path = NULL;
        ldifFile->file = NULL;
-       ldifFile->bufptr = ldifFile->buffer;
        ldifFile->hashFields = g_hash_table_new( g_str_hash, g_str_equal );
        ldifFile->tempList = NULL;
        ldifFile->dirtyFlag = TRUE;
@@ -51,11 +56,13 @@ LdifFile *ldif_create() {
        return ldifFile;
 }
 
-/*
-* Properties...
-*/
+/**
+ * Specify full file specification of LDIF file.
+ * \param ldifFile LDIF import control object.
+ * \param value    Value of access flag.
+ */
 void ldif_set_file( LdifFile *ldifFile, const gchar *value ) {
-       g_return_if_fail( ldifFile != NULL );
+       cm_return_if_fail( ldifFile != NULL );
 
        if( ldifFile->path ) {
                if( strcmp( ldifFile->path, value ) != 0 )
@@ -68,27 +75,22 @@ void ldif_set_file( LdifFile *ldifFile, const gchar *value ) {
        g_strstrip( ldifFile->path );
        ldifFile->importCount = 0;
 }
+
+/**
+ * Set the file access indicator.
+ * \param ldifFile LDIF import control object.
+ * \param value    File specification.
+ */
 void ldif_set_accessed( LdifFile *ldifFile, const gboolean value ) {
-       g_return_if_fail( ldifFile != NULL );
+       cm_return_if_fail( ldifFile != NULL );
        ldifFile->accessFlag = value;
 }
 
-/*
-* Register a callback function. When called, the function will be passed
-* the following arguments:
-*      LdifFile object,
-*      File size (long),
-*      Current position (long)
-* This can be used for a progress indicator.
-*/
-void ldif_set_callback( LdifFile *ldifFile, void *func ) {
-       ldifFile->cbProgress = func;
-}
-
-/*
-* Create field record object.
-*/
-static Ldif_FieldRec *ldif_create_fieldrec( gchar *field ) {
+/**
+ * Create field record object.
+ * \return Initialized LDIF field object.
+ */
+static Ldif_FieldRec *ldif_create_fieldrec( const gchar *field ) {
        Ldif_FieldRec *rec = g_new0( Ldif_FieldRec, 1 );
        rec->tagName = g_strdup( field );
        rec->userName = NULL;
@@ -97,9 +99,10 @@ static Ldif_FieldRec *ldif_create_fieldrec( gchar *field ) {
        return rec;
 }
 
-/*
-* Free field record object.
-*/
+/**
+ * Free field record object.
+ * \param rec LDIF field object.
+ */
 static void ldif_free_fieldrec( Ldif_FieldRec *rec ) {
        if( rec ) {
                g_free( rec->tagName );
@@ -112,21 +115,66 @@ static void ldif_free_fieldrec( Ldif_FieldRec *rec ) {
        }
 }
 
-/*
-* Free hash table entry visitor function.
+/**
+ * Set user name for field record.
+ * \param rec   LDIF field object.
+ * \param value User name to set. Note that reserved fields cannot be
+ *              named.
+ */
+void ldif_field_set_name( Ldif_FieldRec *rec, const gchar *value ) {
+       cm_return_if_fail( rec != NULL );
+
+       if( ! rec->reserved ) {
+               rec->userName = mgu_replace_string( rec->userName, value );
+               g_strstrip( rec->userName );
+       }
+}
+
+/**
+ * Specify selection for field record.
+ * \param rec   LDIF field object.
+ * \param value Set to <i>TRUE</i> to select field. Note that reserved
+ *              fields cannot be unselected.
+ */
+void ldif_field_set_selected( Ldif_FieldRec *rec, const gboolean value ) {
+       cm_return_if_fail( rec != NULL );
+
+       if( ! rec->reserved ) {
+               rec->selected = value;
+       }
+}
+
+/**
+ * Toggle selection for field record. Note that reserved fields cannot be
+ * toggled.
+ * \param rec   LDIF field object.
+ */
+void ldif_field_toggle( Ldif_FieldRec *rec ) {
+       cm_return_if_fail( rec != NULL );
+
+       if( ! rec->reserved ) {
+               rec->selected = !rec->selected;
+       }
+}
+
+/**
+ * Free hash table entry visitor function.
+ * \param  key   Key.
+ * \param  value Value (the LDIF field record).
+ * \param  data  User data.
+ * \return <code>-1</code>.
 */
 static gint ldif_hash_free_vis( gpointer key, gpointer value, gpointer data ) {
        ldif_free_fieldrec( ( Ldif_FieldRec * ) value );
-       value = NULL;
-       key = NULL;
        return -1;
 }
 
-/*
-* Free up object by releasing internal memory.
-*/
+/**
+ * Free up object by releasing internal memory.
+ * \param ldifFile LDIF import control object.
+ */
 void ldif_free( LdifFile *ldifFile ) {
-       g_return_if_fail( ldifFile != NULL );
+       cm_return_if_fail( ldifFile != NULL );
 
        /* Close file */
        if( ldifFile->file ) fclose( ldifFile->file );
@@ -152,119 +200,99 @@ void ldif_free( LdifFile *ldifFile ) {
        g_free( ldifFile );
 }
 
-/*
-* Display field record.
-*/
-void ldif_print_fieldrec( Ldif_FieldRec *rec, FILE *stream ) {
-       fprintf( stream, "\ttag:\t%s", rec->reserved ? "yes" : "no" );
-       fprintf( stream, "\t%s", rec->selected ? "yes" : "no" );
-       fprintf( stream, "\t:%s:\t:%s:\n", rec->userName, rec->tagName );
-}
-
-/*
-* Display field record.
- * 
-*/
-static void ldif_print_file_vis( gpointer key, gpointer value, gpointer data ) {
-       Ldif_FieldRec *rec = value;
-       FILE *stream = data;
-       ldif_print_fieldrec( rec, stream );
-}
-
-/*
-* Display object to specified stream.
-*/
-void ldif_print_file( LdifFile *ldifFile, FILE *stream ) {
-       g_return_if_fail( ldifFile != NULL );
-       fprintf( stream, "LDIF File:\n" );
-       fprintf( stream, "file spec: '%s'\n", ldifFile->path );
-       fprintf( stream, "  ret val: %d\n",   ldifFile->retVal );
-       fprintf( stream, "   fields: {\n" );
-       g_hash_table_foreach( ldifFile->hashFields, ldif_print_file_vis, stream );
-       fprintf( stream, "} ---\n" );
-}
-
-/*
-* Open file for read.
-* return: TRUE if file opened successfully.
-*/
+/**
+ * Open file for read.
+ * \param  ldifFile LDIF import control object.
+ * \return <i>TRUE</i> if file opened successfully.
+ */
 static gint ldif_open_file( LdifFile* ldifFile ) {
-       /* printf( "Opening file\n" ); */
+       /* g_print( "Opening file\n" ); */
        if( ldifFile->path ) {
-               ldifFile->file = fopen( ldifFile->path, "r" );
+               ldifFile->file = g_fopen( ldifFile->path, "rb" );
                if( ! ldifFile->file ) {
-                       /* printf( "can't open %s\n", ldifFile->path ); */
+                       /* g_print( "can't open %s\n", ldifFile->path ); */
                        ldifFile->retVal = MGU_OPEN_FILE;
                        return ldifFile->retVal;
                }
        }
        else {
-               /* printf( "file not specified\n" ); */
+               /* g_print( "file not specified\n" ); */
                ldifFile->retVal = MGU_NO_FILE;
                return ldifFile->retVal;
        }
 
        /* Setup a buffer area */
-       ldifFile->buffer[0] = '\0';
-       ldifFile->bufptr = ldifFile->buffer;
        ldifFile->retVal = MGU_SUCCESS;
        return ldifFile->retVal;
 }
 
-/*
-* Close file.
-*/
+/**
+ * Close file.
+ * \param  ldifFile LDIF import control object.
+ */
 static void ldif_close_file( LdifFile *ldifFile ) {
-       g_return_if_fail( ldifFile != NULL );
+       cm_return_if_fail( ldifFile != NULL );
        if( ldifFile->file ) fclose( ldifFile->file );
        ldifFile->file = NULL;
 }
 
-/*
-* Read line of text from file.
-* Return: ptr to buffer where line starts.
-*/
+/**
+ * Read line of text from file.
+ * \param  ldifFile LDIF import control object.
+ * \return ptr to buffer where line starts.
+ */
 static gchar *ldif_get_line( LdifFile *ldifFile ) {
-       gchar buf[ LDIFBUFSIZE ];
-       gchar ch;
-       gchar *ptr;
+       gchar *buf = g_malloc(LDIFBUFSIZE);
+       gint ch;
+       int i = 0;
+       int cur_alloc = LDIFBUFSIZE;
 
-       if( feof( ldifFile->file ) ) return NULL;
+       if( feof( ldifFile->file ) ) {
+               g_free(buf);
+               return NULL;
+       }
 
-       ptr = buf;
-       while( TRUE ) {
-               *ptr = '\0';
+       while( i < cur_alloc-1 ) {
                ch = fgetc( ldifFile->file );
+               if (ferror( ldifFile->file ))
+                       ldifFile->retVal = MGU_ERROR_READ;
                if( ch == '\0' || ch == EOF ) {
-                       if( *buf == '\0' ) return NULL;
+                       if( i == 0 ) return NULL;
                        break;
                }
 #if HAVE_DOSISH_SYSTEM
 #else
-               if( ch == '\r' ) continue;
+               if( ch == '\r' ) 
+                       continue;
 #endif
-               if( ch == '\n' ) break;
-               *ptr = ch;
-               ptr++;
+               if( ch == '\n' ) 
+                       break;
+               buf[i] = ch;
+               i++;
+               if (i == cur_alloc-1 && cur_alloc < LDIFBUFSIZE * 32) {
+                       cur_alloc += LDIFBUFSIZE;
+                       buf = g_realloc(buf, cur_alloc);
+               }
        }
+       buf[i] = '\0';
 
        /* Return a copy of buffer */
        return g_strdup( buf );
 }
 
-/*
-* Parse tag name from line buffer.
-* Enter: line   Buffer.
-*        flag64 Base-64 encoder flag.
-* Return: Buffer containing the tag name, or NULL if no delimiter char found.
-* If a double delimiter (::) is found, flag64 is set.
-*/
+/**
+ * Parse tag name from line buffer.
+ * \param  line Buffer.
+ * \param  flag64 Base-64 encoder flag.
+ * \return Buffer containing the tag name, or NULL if no delimiter char found.
+ *         If a double delimiter (::) is found, flag64 is set.
+ */
 static gchar *ldif_get_tagname( char* line, gboolean *flag64 ) {
        gint len = 0;
        gchar *tag = NULL;
        gchar *lptr = line;
        gchar *sptr = NULL;
-
+       
        while( *lptr++ ) {
                /* Check for language tag */
                if( *lptr == LDIF_LANG_TAG ) {
@@ -285,19 +313,18 @@ static gchar *ldif_get_tagname( char* line, gboolean *flag64 ) {
 
                        tag = g_strndup( line, len+1 );
                        tag[ len ] = '\0';
-                       g_strdown( tag );
-                       return tag;
+                        return tag;
                }
        }
        return tag;
 }
 
-/*
-* Parse tag value from line buffer.
-* Enter: line   Buffer.
-* Return: Buffer containing the tag value. Empty string is returned if
-* no delimiter char found.
-*/
+/**
+ * Parse tag value from line buffer.
+ * \param  line Buffer.
+ * \return Buffer containing the tag value. Empty string is returned if
+ *         no delimiter char found.
+ */
 static gchar *ldif_get_tagvalue( gchar* line ) {
        gchar *value = NULL;
        gchar *start = NULL;
@@ -324,27 +351,9 @@ static gchar *ldif_get_tagvalue( gchar* line ) {
        return value;
 }
 
-/*
-* Dump linked lists of character strings (for debug).
-*/
-static void ldif_dump_lists( GSList *listName, GSList *listAddr, GSList *listRem, GSList *listID, FILE *stream ) {
-       fprintf( stream, "dump name\n" );
-       fprintf( stream, "------------\n" );
-       mgu_print_list( listName, stdout );
-       fprintf( stream, "dump address\n" );
-       fprintf( stream, "------------\n" );
-       mgu_print_list( listAddr, stdout );
-       fprintf( stream, "dump remarks\n" );
-       fprintf( stdout, "------------\n" );
-       mgu_print_list( listRem, stdout );
-       fprintf( stream, "dump id\n" );
-       fprintf( stdout, "------------\n" );
-       mgu_print_list( listID, stdout );
-}
-
-/*
-* Parsed address data.
-*/
+/**
+ * Parsed address data record.
+ */
 typedef struct _Ldif_ParsedRec_ Ldif_ParsedRec;
 struct _Ldif_ParsedRec_ {
        GSList *listCName;
@@ -356,30 +365,36 @@ struct _Ldif_ParsedRec_ {
        GSList *userAttr;
 };
 
-/*
-* User attribute data.
-*/
+/**
+ * User attribute data record.
+ */
 typedef struct _Ldif_UserAttr_ Ldif_UserAttr;
 struct _Ldif_UserAttr_ {
        gchar *name;
        gchar *value;
 };
 
-/*
-* Build an address list entry and append to list of address items. Name is formatted
-* as "<first-name> <last-name>".
-*/
-static void ldif_build_items( LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCache *cache ) {
+/**
+ * Build an address list entry and append to list of address items in the
+ * address cache. Name is formatted as "<first-name> <last-name>".
+ * \param ldifFile LDIF import control object.
+ * \param rec      LDIF field object.
+ * \param cache    Address cache to be populated with data.
+ */
+static void ldif_build_items(
+               LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCache *cache )
+{
        GSList *nodeFirst;
        GSList *nodeAddress;
        GSList *nodeAttr;
-       gchar *firstName = NULL, *lastName = NULL, *fullName = NULL, *nickName = NULL;
+       gchar *firstName = NULL, *lastName = NULL, *fullName = NULL;
+       gchar *nickName = NULL;
        gint iLen = 0, iLenT = 0;
        ItemPerson *person;
        ItemEMail *email;
 
        nodeAddress = rec->listAddress;
-       if( nodeAddress == NULL ) return;
+//     if( nodeAddress == NULL ) return;
 
        /* Find longest first name in list */
        nodeFirst = rec->listFName;
@@ -404,7 +419,8 @@ static void ldif_build_items( LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCa
 
        if( firstName ) {
                if( lastName ) {
-                       fullName = g_strdup_printf( "%s %s", firstName, lastName );
+                       fullName = g_strdup_printf(
+                               "%s %s", firstName, lastName );
                }
                else {
                        fullName = g_strdup_printf( "%s", firstName );
@@ -415,6 +431,14 @@ static void ldif_build_items( LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCa
                        fullName = g_strdup_printf( "%s", lastName );
                }
        }
+       
+       if (!fullName || strlen(fullName) == 0) {
+               g_free(fullName);
+               fullName = NULL;
+               if (rec->listCName)
+                       fullName = g_strdup(rec->listCName->data);
+       }
+       
        if( fullName ) {
                g_strchug( fullName ); g_strchomp( fullName );
        }
@@ -456,18 +480,23 @@ static void ldif_build_items( LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCa
        nodeAttr = NULL;
 }
 
-/*
-* Add selected field as user attribute.
-*/
-static void ldif_add_user_attr( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue, GHashTable *hashField ) {
+/**
+ * Add selected field as user attribute.
+ * \param rec       LDIF field object.
+ * \param tagName   LDIF tag name.
+ * \param tagValue  Data value.
+ * \param hashField Hash table to populate.
+ */
+static void ldif_add_user_attr(
+               Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue,
+               GHashTable *hashField )
+{
        Ldif_FieldRec *fld = NULL;
        Ldif_UserAttr *attr = NULL;
        gchar *name;
-       gchar *value;
 
        fld = g_hash_table_lookup( hashField, tagName );
        if( fld ) {
-               if( fld->reserved ) return;
                if( ! fld->selected ) return;
 
                name = fld->tagName;
@@ -481,14 +510,20 @@ static void ldif_add_user_attr( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagV
        }
 }
 
-/*
-* Add value to parsed data.
-*/
-static void ldif_add_value( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue, GHashTable *hashField ) {
+/**
+ * Add value to parsed data.
+ * \param rec       LDIF field object.
+ * \param tagName   LDIF tag name.
+ * \param tagValue  Data value.
+ * \param hashField Hash table to populate.
+ */
+static void ldif_add_value(
+              Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue,
+              GHashTable *hashField )
+{
        gchar *nm, *val;
 
-       nm = g_strdup( tagName );
-       g_strdown( nm );
+       nm = g_utf8_strdown( tagName, -1 );
        if( tagValue ) {
                val = g_strdup( tagValue );
        }
@@ -496,19 +531,20 @@ static void ldif_add_value( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue
                val = g_strdup( "" );
        }
        g_strstrip( val );
-       if( g_strcasecmp( nm, LDIF_TAG_COMMONNAME ) == 0 ) {
+
+       if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_COMMONNAME, -1 ) ) == 0 ) {
                rec->listCName = g_slist_append( rec->listCName, val );
        }
-       else if( g_strcasecmp( nm, LDIF_TAG_FIRSTNAME ) == 0 ) {
+       else if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_FIRSTNAME, -1 ) ) == 0 ) {
                rec->listFName = g_slist_append( rec->listFName, val );
        }
-       else if( g_strcasecmp( nm, LDIF_TAG_LASTNAME ) == 0 ) {
+       else if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_LASTNAME, -1 ) ) == 0 ) {
                rec->listLName = g_slist_append( rec->listLName, val );
        }
-       else if( g_strcasecmp( nm, LDIF_TAG_NICKNAME ) == 0 ) {
+       else if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_NICKNAME, -1 ) ) == 0 ) {
                rec->listNName = g_slist_append( rec->listNName, val );
        }
-       else if( g_strcasecmp( nm, LDIF_TAG_EMAIL ) == 0 ) {
+       else if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_EMAIL, -1 ) ) == 0 ) {
                rec->listAddress = g_slist_append( rec->listAddress, val );
        }
        else {
@@ -518,9 +554,10 @@ static void ldif_add_value( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue
        g_free( nm );
 }
 
-/*
-* Clear parsed data.
-*/
+/**
+ * Clear parsed data record.
+ * \param rec LDIF field object.
+ */
 static void ldif_clear_rec( Ldif_ParsedRec *rec ) {
        GSList *list;
 
@@ -551,66 +588,15 @@ static void ldif_clear_rec( Ldif_ParsedRec *rec ) {
        rec->listID = NULL;
 }
 
-/*
-* Print parsed data.
-*/
-static void ldif_print_record( Ldif_ParsedRec *rec, FILE *stream ) {
-       GSList *list;
-
-       fprintf( stream, "LDIF Parsed Record:\n" );
-       fprintf( stream, "common name:" );
-       mgu_print_list( rec->listCName, stream );
-       if( ! rec->listCName ) fprintf( stream, "\n" );
-       fprintf( stream, "first name:" );
-       mgu_print_list( rec->listFName, stream );
-       if( ! rec->listFName ) fprintf( stream, "\n" );
-       fprintf( stream, "last name:" );
-       mgu_print_list( rec->listLName, stream );
-       if( ! rec->listLName ) fprintf( stream, "\n" );
-       fprintf( stream, "nick name:" );
-       mgu_print_list( rec->listNName, stream );
-       if( ! rec->listNName ) fprintf( stream, "\n" );
-       fprintf( stream, "address:" );
-       mgu_print_list( rec->listAddress, stream );
-       if( ! rec->listAddress ) fprintf( stream, "\n" );
-       fprintf( stream, "id:" );
-       mgu_print_list( rec->listID, stream );
-       if( ! rec->listID ) fprintf( stream, "\n" );
-
-       list = rec->userAttr;
-       while( list ) {
-               Ldif_UserAttr *attr = list->data;
-               fprintf( stream, "n/v:\t%s:\t:%s:\n", attr->name, attr->value );
-               list = g_slist_next( list );
-       }
-       list = NULL;
-}
-
-static void ldif_dump_b64( gchar *buf ) {
-       Base64Decoder *decoder = NULL;
-       gchar outBuf[8192];
-       gint len;
-
-       printf( "base-64 : inbuf : %s\n", buf );
-       decoder = base64_decoder_new();
-       len = base64_decoder_decode( decoder, buf, outBuf );
-       if (len < 0) {
-               printf( "base-64 : Bad BASE64 content\n" );
-       }
-       else {
-               outBuf[len] = '\0';
-               printf( "base-64 : %d : %s\n\n", len, outBuf );
-       }
-       base64_decoder_free( decoder );
-       decoder = NULL;
-}
-
-/*
-* Read file data into address cache.
-* Note that one LDIF record identifies one entity uniquely with the
-* distinguished name (dn) tag. Each person can have multiple E-Mail
-* addresses. Also, each person can have many common name (cn) tags.
-*/
+/**
+ * Read file data into address cache.
+ * Note that one LDIF record identifies one entity uniquely with the
+ * distinguished name (dn) tag. Each person can have multiple E-Mail
+ * addresses. Also, each person can have many common name (cn) tags.
+ *
+ * \param  ldifFile LDIF import control object.
+ * \param  cache    Address cache to be populated with data.
+ */
 static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
        gchar *tagName = NULL, *tagValue = NULL;
        gchar *lastTag = NULL, *fullValue = NULL;
@@ -621,6 +607,8 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
        long posEnd = 0L;
        long posCur = 0L;
        GHashTable *hashField;
+       gchar *out;
+       gsize len;
 
        hashField = ldifFile->hashFields;
        rec = g_new0( Ldif_ParsedRec, 1 );
@@ -653,13 +641,11 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
                        if( lastTag ) {
                                /* Save record */
                                fullValue = mgu_list_coalesce( listValue );
-
-                               /* Base-64 encoded data */
-                               /*
-                               if( last64 ) {
-                                       ldif_dump_b64( fullValue );
+                               if (fullValue && last64) {
+                                       gchar *tmp = g_base64_decode_zero(fullValue, &len);
+                                       g_free(fullValue);
+                                       fullValue = tmp;
                                }
-                               */
 
                                ldif_add_value( rec, lastTag, fullValue, hashField );
                                /* ldif_print_record( rec, stdout ); */
@@ -667,6 +653,7 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
                                ldif_clear_rec( rec );
                                g_free( lastTag );
                                mgu_free_list( listValue );
+                               g_free(fullValue);
                                lastTag = NULL;
                                listValue = NULL;
                                last64 = FALSE;
@@ -676,11 +663,13 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
                        flagEOR = FALSE;
                        if( *line == ' ' ) {
                                /* Continuation line */
-                               listValue = g_slist_append( listValue, g_strdup( line+1 ) );
+                               listValue = g_slist_append(
+                                       listValue, g_strdup( line+1 ) );
                        }
                        else if( *line == '=' ) {
                                /* Base-64 encoded continuation field */
-                               listValue = g_slist_append( listValue, g_strdup( line ) );
+                               listValue = g_slist_append(
+                                       listValue, g_strdup( line ) );
                        }
                        else {
                                /* Parse line */
@@ -690,7 +679,13 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
                                        if( tagValue ) {
                                                if( lastTag ) {
                                                        /* Save data */
-                                                       fullValue = mgu_list_coalesce( listValue );
+                                                       fullValue =
+                                                               mgu_list_coalesce( listValue );
+                                                       if (fullValue && last64) {
+                                                               gchar *tmp = g_base64_decode_zero(fullValue, &len);
+                                                               g_free(fullValue);
+                                                               fullValue = tmp;
+                                                       }
                                                        /* Base-64 encoded data */
                                                        /*
                                                        if( last64 ) {
@@ -698,16 +693,19 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
                                                        }
                                                        */
 
-                                                       ldif_add_value( rec, lastTag, fullValue, hashField );
+                                                       ldif_add_value(
+                                                               rec, lastTag, fullValue,
+                                                               hashField );
                                                        g_free( lastTag );
                                                        mgu_free_list( listValue );
                                                        lastTag = NULL;
                                                        listValue = NULL;
-                                                       last64 = FALSE;
                                                }
 
                                                lastTag = g_strdup( tagName );
-                                               listValue = g_slist_append( listValue, g_strdup( tagValue ) );
+                                               listValue = g_slist_append(
+                                                       listValue,
+                                                       g_strdup( tagValue ) );
                                                g_free( tagValue );
                                                last64 = flag64;
                                        }
@@ -725,9 +723,11 @@ static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) {
        mgu_free_list( listValue );
 }
 
-/*
-* Add list of field names to hash table.
-*/
+/**
+ * Add list of field names to hash table.
+ * \param table Hashtable.
+ * \param list  List of fields.
+ */
 static void ldif_hash_add_list( GHashTable *table, GSList *list ) {
        GSList *node = list;
 
@@ -736,23 +736,32 @@ static void ldif_hash_add_list( GHashTable *table, GSList *list ) {
                gchar *tag = node->data;
                if( ! g_hash_table_lookup( table, tag ) ) {
                        Ldif_FieldRec *rec = NULL;
-                       gchar *key = g_strdup( tag );
+                       gchar *key = g_utf8_strdown( tag, -1 );
 
                        rec = ldif_create_fieldrec( tag );
-                       if( g_strcasecmp( tag, LDIF_TAG_COMMONNAME ) == 0 ) {
-                               rec->reserved = TRUE;
+                       if( g_utf8_collate( key, LDIF_TAG_DN ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( "dn" );
                        }
-                       else if( g_strcasecmp( tag, LDIF_TAG_FIRSTNAME ) == 0 ) {
-                               rec->reserved = TRUE;
+                       else if( g_utf8_collate( key, g_utf8_strdown( LDIF_TAG_COMMONNAME, -1 ) ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( _( "Display Name" ) );
                        }
-                       else if( g_strcasecmp( tag, LDIF_TAG_LASTNAME ) == 0 ) {
-                               rec->reserved = TRUE;
+                       else if( g_utf8_collate( key, g_utf8_strdown( LDIF_TAG_FIRSTNAME, -1 ) ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( _( "First Name" ) );
                        }
-                       else if( g_strcasecmp( tag, LDIF_TAG_NICKNAME ) == 0 ) {
-                               rec->reserved = TRUE;
+                       else if( g_utf8_collate( key, g_utf8_strdown( LDIF_TAG_LASTNAME, -1 ) ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( _( "Last Name" ) );
                        }
-                       else if( g_strcasecmp( tag, LDIF_TAG_EMAIL ) == 0 ) {
-                               rec->reserved = TRUE;
+                       else if( g_utf8_collate( key, g_utf8_strdown( LDIF_TAG_NICKNAME, -1 ) ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( _( "Nick Name" ) );
+                       }
+                       else if( g_utf8_collate( key, g_utf8_strdown( LDIF_TAG_EMAIL, -1 ) ) == 0 ) {
+                               rec->reserved = rec->selected = TRUE;
+                               rec->userName = g_strdup( _( "Email Address" ) );
                        }
                        g_hash_table_insert( table, key, rec );
                }
@@ -760,26 +769,46 @@ static void ldif_hash_add_list( GHashTable *table, GSList *list ) {
        }
 }
 
-/*
-* Sorted list comparison function.
-*/
-static int ldif_field_compare( gconstpointer ptr1, gconstpointer ptr2 ) {
+/**
+ * Sorted list comparison function.
+ * \param  ptr1 First field.
+ * \param  ptr2 Second field.
+ * \return <code>-1, 0, +1</code> if first record less than, equal,
+ *         greater than second.
+ */
+static gint ldif_field_compare( gconstpointer ptr1, gconstpointer ptr2 ) {
        const Ldif_FieldRec *rec1 = ptr1;
        const Ldif_FieldRec *rec2 = ptr2;
-       return strcasecmp(rec1->tagName, rec2->tagName );
+
+       if( rec1->reserved ) {
+               if( ! rec2->reserved ) {
+                       return +1;
+               }
+       }
+       else {
+               if( rec2->reserved ) {
+                       return -1;
+               }
+       }
+       return g_utf8_collate( rec1->tagName, rec2->tagName );
 }
 
 /*
-* Append hash table entry to list - visitor function.
-*/
+ * Append hash table entry to list - visitor function.
+ * \param key   Key.
+ * \param value Data value.
+ * \param data  User data (the LDIF import control object).
+ */
 static void ldif_hash2list_vis( gpointer key, gpointer value, gpointer data ) {
        LdifFile *ldf = data;
-       ldf->tempList = g_list_insert_sorted( ldf->tempList, value, ldif_field_compare );
+       ldf->tempList =
+               g_list_insert_sorted( ldf->tempList, value, ldif_field_compare );
 }
 
-/*
-* Read tag names for file data.
-*/
+/**
+ * Read tag names for file data.
+ * \param  ldifFile LDIF import control object.
+ */
 static void ldif_read_tag_list( LdifFile *ldifFile ) {
        gchar *tagName = NULL;
        GSList *listTags = NULL;
@@ -789,17 +818,22 @@ static void ldif_read_tag_list( LdifFile *ldifFile ) {
        long posCur = 0L;
 
        /* Clear hash table */
-       g_hash_table_foreach_remove( ldifFile->hashFields, ldif_hash_free_vis, NULL );
+       g_hash_table_foreach_remove(
+               ldifFile->hashFields, ldif_hash_free_vis, NULL );
 
        /* Find EOF for progress indicator */
        fseek( ldifFile->file, 0L, SEEK_END );
        posEnd = ftell( ldifFile->file );
        fseek( ldifFile->file, 0L, SEEK_SET );
 
+       if (posEnd == 0) {
+               ldifFile->retVal = MGU_EOF;
+               return;
+       }
+               
        /* Process file */
        while( ! flagEOF ) {
                gchar *line = ldif_get_line( ldifFile );
-
                posCur = ftell( ldifFile->file );
                if( ldifFile->cbProgress ) {
                        /* Call progress indicator */
@@ -818,7 +852,8 @@ static void ldif_read_tag_list( LdifFile *ldifFile ) {
                        /* EOR, Output address data */
                        /* Save field list to hash table */
                        if( flagMail ) {
-                               ldif_hash_add_list( ldifFile->hashFields, listTags );
+                               ldif_hash_add_list(
+                                       ldifFile->hashFields, listTags );
                        }
                        mgu_free_list( listTags );
                        listTags = NULL;
@@ -838,9 +873,18 @@ static void ldif_read_tag_list( LdifFile *ldifFile ) {
                                if( tagName ) {
                                        /* Add tag to list */
                                        listTags = g_slist_append( listTags, tagName );
-                                       if( g_strcasecmp( tagName, LDIF_TAG_EMAIL ) == 0 ) {
+
+                                       if( g_utf8_collate(
+                                               tagName, LDIF_TAG_EMAIL ) == 0 )
+                                       {
                                                flagMail = TRUE;
                                        }
+                               } else {
+                                       g_strstrip(line);
+                                       if (*line != '\0') {
+                                               debug_print("ldif: bad format: '%s'\n", line);
+                                               ldifFile->retVal = MGU_BAD_FORMAT;
+                                       }
                                }
                        }
                }
@@ -852,16 +896,14 @@ static void ldif_read_tag_list( LdifFile *ldifFile ) {
        listTags = NULL;
 }
 
-/*
-* ============================================================================
-* Read file into list. Main entry point
-* Enter:  ldifFile LDIF control data.
-*         cache    Address cache to load.
-* Return: Status code.
-* ============================================================================
-*/
+/**
+ * Read file into list. Main entry point
+ * \param  ldifFile LDIF import control object.
+ * \param  cache    Address cache to load.
+ * \return Status code.
+ */
 gint ldif_import_data( LdifFile *ldifFile, AddressCache *cache ) {
-       g_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
+       cm_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
        ldifFile->retVal = MGU_SUCCESS;
        addrcache_clear( cache );
        cache->dataRead = FALSE;
@@ -878,16 +920,14 @@ gint ldif_import_data( LdifFile *ldifFile, AddressCache *cache ) {
        return ldifFile->retVal;
 }
 
-/*
-* ============================================================================
-* Process entire file reading list of unique fields. List of fields may be
-* accessed with the ldif_get_fieldlist() function.
-* Enter:  ldifFile LDIF control data.
-* Return: Status code.
-* ============================================================================
-*/
+/**
+ * Process entire file reading list of unique fields. List of fields may be
+ * accessed with the <code>ldif_get_fieldlist()</code> function.
+ * \param  ldifFile LDIF import control object.
+ * \return Status code.
+ */
 gint ldif_read_tags( LdifFile *ldifFile ) {
-       g_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
+       cm_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
        ldifFile->retVal = MGU_SUCCESS;
        if( ldifFile->dirtyFlag ) {
                ldif_open_file( ldifFile );
@@ -902,18 +942,18 @@ gint ldif_read_tags( LdifFile *ldifFile ) {
        return ldifFile->retVal;
 }
 
-/*
-* Return list of fields for LDIF file.
-* Enter: ldifFile LdifFile object.
-* Return: Linked list of Ldif_FieldRec objects. This list may be g_free'd.
-* Note that the objects in the list should not be freed since they refer to
-* objects inside the internal cache. These objects will be freed when
-* LDIF file object is freed.
-*/
+/**
+ * Return list of fields for LDIF file.
+ * \param  ldifFile LDIF import control object.
+ * \return Linked list of <code>Ldif_FieldRec</code> objects. This list may be
+ *         <code>g_free()</code>. Note that the objects in the list should not
+ *         be freed since they refer to objects inside the internal cache.
+ *         These objects will be freed when LDIF file object is freed.
+ */
 GList *ldif_get_fieldlist( LdifFile *ldifFile ) {
        GList *list = NULL;
 
-       g_return_val_if_fail( ldifFile != NULL, NULL );
+       cm_return_val_if_fail( ldifFile != NULL, NULL );
        if( ldifFile->hashFields ) {
                ldifFile->tempList = NULL;
                g_hash_table_foreach( ldifFile->hashFields, ldif_hash2list_vis, ldifFile );
@@ -923,7 +963,37 @@ GList *ldif_get_fieldlist( LdifFile *ldifFile ) {
        return list;
 }
 
+/**
+ * Output LDIF name-value pair to stream. Only non-empty names and values will
+ * be output to file.
+ * \param stream File output stream.
+ * \param name   Name.
+ * \param value  Data value.
+ * \return <i>TRUE</i> if data output.
+ */
+gboolean ldif_write_value( FILE *stream, const gchar *name, const gchar *value ) {
+       if( name == NULL ) return FALSE;
+       if( value == NULL ) return FALSE;
+       if( strlen( name ) < 1 ) return FALSE;
+       if( strlen( value ) < 1 ) return FALSE;
+       fprintf( stream, "%s: ", name );
+       fprintf( stream, "%s\n", value );
+       return TRUE;
+}
+
+/**
+ * Output LDIF End of Record to stream.
+ * \param stream File output stream.
+ * \return <i>TRUE</i> if data output.
+ */
+void ldif_write_eor( FILE *stream ) {
+       /* Simple but caller should not need to know how to end record. */
+       fprintf( stream, "\n" );
+}
+
 /*
-* End of Source.
-*/
+ * ============================================================================
+ * End of Source.
+ * ============================================================================
+ */