/*
 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
 * Copyright (C) 2001-2026 the Claws Mail team and Match Grun
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * Functions necessary to access LDIF files (LDAP Data Interchange Format
 * files).
 */

#include "config.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 "utils.h"
#include "file-utils.h"

#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->hashFields = g_hash_table_new( g_str_hash, g_str_equal );
	ldifFile->dirtyFlag = TRUE;
	ldifFile->accessFlag = FALSE;
	ldifFile->retVal = MGU_SUCCESS;
	return ldifFile;
}

/**
 * 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 ) {
	cm_return_if_fail( ldifFile != NULL );

	if( ldifFile->path ) {
		if( strcmp( ldifFile->path, value ) != 0 )
			ldifFile->dirtyFlag = TRUE;
	}
	else {
		ldifFile->dirtyFlag = TRUE;
	}
	ldifFile->path = mgu_replace_string( ldifFile->path, 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 ) {
	cm_return_if_fail( ldifFile != NULL );
	ldifFile->accessFlag = value;
}

/**
 * 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->reserved = FALSE;
	rec->selected = FALSE;
	return rec;
}

/**
 * Free field record object.
 * \param rec LDIF field object.
 */
static void ldif_free_fieldrec( Ldif_FieldRec *rec ) {
	if( rec ) {
		g_free( rec->tagName );
		g_free( rec->userName );
		rec->tagName = NULL;
		rec->userName = NULL;
		rec->reserved = FALSE;
		rec->selected = FALSE;
		g_free( rec );
	}
}

/**
 * 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 );
	return -1;
}

/**
 * Free up object by releasing internal memory.
 * \param ldifFile LDIF import control object.
 */
void ldif_free( LdifFile *ldifFile ) {
	cm_return_if_fail( ldifFile != NULL );

	/* Close file */
	if( ldifFile->file ) claws_fclose( ldifFile->file );

	/* Free internal stuff */
	g_free( ldifFile->path );

	/* Free field list */
	g_hash_table_foreach_remove( ldifFile->hashFields, ldif_hash_free_vis, NULL );
	g_hash_table_destroy( ldifFile->hashFields );
	ldifFile->hashFields = NULL;

	/* Clear pointers */
	ldifFile->file = NULL;
	ldifFile->path = NULL;
	ldifFile->retVal = MGU_SUCCESS;
	ldifFile->tempList = NULL;
	ldifFile->dirtyFlag = FALSE;
	ldifFile->accessFlag = FALSE;
	ldifFile->cbProgress = NULL;

	/* Now release file object */
	g_free( ldifFile );
}

/**
 * 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 ) {
	/* g_print( "Opening file\n" ); */
	if( ldifFile->path ) {
		ldifFile->file = claws_fopen( ldifFile->path, "rb" );
		if( ! ldifFile->file ) {
			/* g_print( "can't open %s\n", ldifFile->path ); */
			ldifFile->retVal = MGU_OPEN_FILE;
			return ldifFile->retVal;
		}
	}
	else {
		/* g_print( "file not specified\n" ); */
		ldifFile->retVal = MGU_NO_FILE;
		return ldifFile->retVal;
	}

	/* Setup a buffer area */
	ldifFile->retVal = MGU_SUCCESS;
	return ldifFile->retVal;
}

/**
 * Close file.
 * \param  ldifFile LDIF import control object.
 */
static void ldif_close_file( LdifFile *ldifFile ) {
	cm_return_if_fail( ldifFile != NULL );
	if( ldifFile->file ) claws_fclose( ldifFile->file );
	ldifFile->file = NULL;
}

/**
 * Read line of text from file.
 * \param  ldifFile LDIF import control object.
 * \return ptr to buffer where line starts (must be freed by caller).
 */
static gchar *ldif_get_line( LdifFile *ldifFile ) {
	gchar *buf = g_malloc(LDIFBUFSIZE);
	gint ch;
	int i = 0;
	int cur_alloc = LDIFBUFSIZE;

	if( claws_feof( ldifFile->file ) ) {
		g_free(buf);
		return NULL;
	}

	while( i < cur_alloc-1 ) {
		ch = fgetc( ldifFile->file );
		if (claws_ferror( ldifFile->file ))
			ldifFile->retVal = MGU_ERROR_READ;
		if( ch == '\0' || ch == EOF ) {
			if( i == 0 ) {
				g_free(buf);
				return NULL;
			}
			break;
		}
#if HAVE_DOSISH_SYSTEM
#else
		if( ch == '\r' ) 
			continue;
#endif
		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 buf;
}

/**
 * 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 ) {
			if( sptr == NULL ) sptr = lptr;
		}

		/* Check for delimiter */
		if( *lptr == LDIF_SEP_TAG ) {
			if( sptr ) {
				len = sptr - line;
			}
			else {
				len = lptr - line;
			}

			/* Base-64 encoding? */
			if( * ++lptr == LDIF_SEP_TAG ) *flag64 = TRUE;

			tag = g_strndup( line, len+1 );
			tag[ len ] = '\0';
                        return tag;
		}
	}
	return tag;
}

/**
 * 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;
	gchar *lptr;
	gint len = 0;

	for( lptr = line; *lptr; lptr++ ) {
		if( *lptr == LDIF_SEP_TAG ) {
			if( ! start )
				start = lptr + 1;
		}
	}
	if( start ) {
		if( *start == LDIF_SEP_TAG ) start++;
		len = lptr - start;
		value = g_strndup( start, len+1 );
		g_strstrip( value );
	}
	else {
		/* Ensure that we get an empty string */
		value = g_strndup( "", 1 );
	}
	value[ len ] = '\0';
	return value;
}

/**
 * Parsed address data record.
 */
typedef struct _Ldif_ParsedRec_ Ldif_ParsedRec;
struct _Ldif_ParsedRec_ {
	GSList *listCName;
	GSList *listFName;
	GSList *listLName;
	GSList *listNName;
	GSList *listAddress;
	GSList *listID;
	GSList *userAttr;
};

/**
 * 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 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;
	gchar *nickName = NULL;
	gint iLen = 0, iLenT = 0;
	ItemPerson *person;
	ItemEMail *email;

	nodeAddress = rec->listAddress;
//	if( nodeAddress == NULL ) return;

	/* Find longest first name in list */
	nodeFirst = rec->listFName;
	while( nodeFirst ) {
		if( firstName == NULL ) {
			firstName = nodeFirst->data;
			iLen = strlen( firstName );
		}
		else {
			if( ( iLenT = strlen( nodeFirst->data ) ) > iLen ) {
				firstName = nodeFirst->data;
				iLen = iLenT;
			}
		}
		nodeFirst = g_slist_next( nodeFirst );
	}

	/* Format name */
	if( rec->listLName ) {
		lastName = rec->listLName->data;
	}

	if( firstName ) {
		if( lastName ) {
			fullName = g_strdup_printf(
				"%s %s", firstName, lastName );
		}
		else {
			fullName = g_strdup_printf( "%s", firstName );
		}
	}
	else {
		if( lastName ) {
			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_strstrip( fullName );
	}

	if( rec->listNName ) {
		nickName = rec->listNName->data;
	}

	person = addritem_create_item_person();
	addritem_person_set_common_name( person, fullName );
	addritem_person_set_first_name( person, firstName );
	addritem_person_set_last_name( person, lastName );
	addritem_person_set_nick_name( person, nickName );
	addrcache_id_person( cache, person );
	addrcache_add_person( cache, person );
	++ldifFile->importCount;

	/* Add address item */
	while( nodeAddress ) {
		email = addritem_create_item_email();
		addritem_email_set_address( email, nodeAddress->data );
		addrcache_id_email( cache, email );
		addrcache_person_add_email( cache, person, email );
		nodeAddress = g_slist_next( nodeAddress );
	}
	g_free( fullName );
	fullName = firstName = lastName = NULL;

	/* Add user attributes */
	nodeAttr = rec->userAttr;
	while( nodeAttr ) {
		Ldif_UserAttr *attr = nodeAttr->data;
		UserAttribute *attrib = addritem_create_attribute();
		addritem_attrib_set_name( attrib, attr->name );
		addritem_attrib_set_value( attrib, attr->value );
		addritem_person_add_attribute( person, attrib );
		nodeAttr = g_slist_next( nodeAttr );
	}
	nodeAttr = NULL;
}

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

	fld = g_hash_table_lookup( hashField, tagName );
	if( fld ) {
		if( ! fld->selected ) return;

		name = fld->tagName;
		if( fld->userName ) {
			name = fld->userName;
		}
		attr = g_new0( Ldif_UserAttr, 1 );
		attr->name = g_strdup( name );
		attr->value = g_strdup( tagValue );
		rec->userAttr = g_slist_append( rec->userAttr, attr );
	}
}

/**
 * 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_utf8_strdown( tagName, -1 );
	if( tagValue ) {
		val = g_strdup( tagValue );
	}
	else {
		val = g_strdup( "" );
	}
	g_strstrip( val );

	if( g_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_COMMONNAME, -1 ) ) == 0 ) {
		rec->listCName = g_slist_append( rec->listCName, val );
	}
	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_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_LASTNAME, -1 ) ) == 0 ) {
		rec->listLName = g_slist_append( rec->listLName, val );
	}
	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_utf8_collate( nm, g_utf8_strdown( LDIF_TAG_EMAIL, -1 ) ) == 0 ) {
		rec->listAddress = g_slist_append( rec->listAddress, val );
	}
	else {
		/* Add field as user attribute */
		ldif_add_user_attr( rec, tagName, tagValue, hashField );
	}
	g_free( nm );
}

/**
 * Clear parsed data record.
 * \param rec LDIF field object.
 */
static void ldif_clear_rec( Ldif_ParsedRec *rec ) {
	GSList *list;

	/* Free up user attributes */
	list = rec->userAttr;
	while( list ) {
		Ldif_UserAttr *attr = list->data;
		g_free( attr->name );
		g_free( attr->value );
		g_free( attr );
		list = g_slist_next( list );
	}
	g_slist_free( rec->userAttr );

	g_slist_free( rec->listCName );
	g_slist_free( rec->listFName );
	g_slist_free( rec->listLName );
	g_slist_free( rec->listNName );
	g_slist_free( rec->listAddress );
	g_slist_free( rec->listID );

	rec->userAttr = NULL;
	rec->listCName = NULL;
	rec->listFName = NULL;
	rec->listLName = NULL;
	rec->listNName = NULL;
	rec->listAddress = NULL;
	rec->listID = 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.
 *
 * \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;
	GSList *listValue = NULL;
	gboolean flagEOF = FALSE, flagEOR = FALSE;
	gboolean flag64 = FALSE, last64 = FALSE;
	Ldif_ParsedRec *rec;
	long posEnd = 0L;
	long posCur = 0L;
	GHashTable *hashField;
	gsize len;

	hashField = ldifFile->hashFields;
	rec = g_new0( Ldif_ParsedRec, 1 );
	ldif_clear_rec( rec );

	/* Find EOF for progress indicator */
	fseek( ldifFile->file, 0L, SEEK_END );
	posEnd = ftell( ldifFile->file );
	fseek( ldifFile->file, 0L, SEEK_SET );

	while( ! flagEOF ) {
		gchar *line =  ldif_get_line( ldifFile );

		posCur = ftell( ldifFile->file );
		if( ldifFile->cbProgress ) {
			/* Call progress indicator */
			( ldifFile->cbProgress ) ( ldifFile, & posEnd, & posCur );
		}

		flag64 = FALSE;
		if( line == NULL ) {
			flagEOF = flagEOR = TRUE;
		}
		else if( *line == '\0' ) {
			flagEOR = TRUE;
		}

		if( flagEOR ) {
			/* EOR, Output address data */
			if( lastTag ) {
				/* Save record */
				fullValue = mgu_list_coalesce( listValue );
				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 ); */
				ldif_build_items( ldifFile, rec, cache );
				ldif_clear_rec( rec );
				g_free( lastTag );
				g_slist_free_full( listValue, g_free );
				g_free(fullValue);
				lastTag = NULL;
				listValue = NULL;
				last64 = FALSE;
			}
		}
		if( line ) {
			flagEOR = FALSE;
			if( *line == ' ' ) {
				/* Continuation line */
				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 ) );
			}
			else {
				/* Parse line */
				tagName = ldif_get_tagname( line, &flag64 );
				if( tagName ) {
					tagValue = ldif_get_tagvalue( line );
					if( tagValue ) {
						if( lastTag ) {
							/* Save data */
							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 ) {
								ldif_dump_b64( fullValue );
							}
							*/

							ldif_add_value(
								rec, lastTag, fullValue,
								hashField );
							g_free( lastTag );
							g_slist_free_full( listValue, g_free );
							lastTag = NULL;
							listValue = NULL;
						}

						lastTag = g_strdup( tagName );
						listValue = g_slist_append(
							listValue,
							g_strdup( tagValue ) );
						g_free( tagValue );
						last64 = flag64;
					}
					g_free( tagName );
				}
			}
			g_free( line );
		}
	}

	/* Release data */
	ldif_clear_rec( rec );
	g_free( rec );
	g_free( lastTag );
	g_slist_free_full( listValue, g_free );
}

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

	/* mgu_print_list( list, stdout ); */
	while( node ) {
		gchar *tag = node->data;
		if( ! g_hash_table_lookup( table, tag ) ) {
			Ldif_FieldRec *rec = NULL;
			gchar *key = g_utf8_strdown( tag, -1 );

			rec = ldif_create_fieldrec( tag );
			if( g_utf8_collate( key, LDIF_TAG_DN ) == 0 ) {
				rec->reserved = rec->selected = TRUE;
				rec->userName = g_strdup( "dn" );
			}
			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_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_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_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 );
		}
		node = g_slist_next( node );
	}
}

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

	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.
 * \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 );
}

/**
 * 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;
	gboolean flagEOF = FALSE, flagEOR = FALSE, flagMail = FALSE;
	gboolean flag64 = FALSE;
	long posEnd = 0L;
	long posCur = 0L;

	/* Clear hash table */
	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 */
			( ldifFile->cbProgress ) ( ldifFile, & posEnd, & posCur );
		}

		flag64 = FALSE;
		if( line == NULL ) {
			flagEOF = flagEOR = TRUE;
		}
		else if( *line == '\0' ) {
			flagEOR = TRUE;
		}

		if( flagEOR ) {
			/* EOR, Output address data */
			/* Save field list to hash table */
			if( flagMail ) {
				ldif_hash_add_list(
					ldifFile->hashFields, listTags );
			}
			g_slist_free_full( listTags, g_free );
			listTags = NULL;
			flagMail = FALSE;
		}
		if( line ) {
			flagEOR = FALSE;
			if( *line == ' ' ) {
				/* Continuation line */
			}
			else if( *line == '=' ) {
				/* Base-64 encoded continuation field */
			}
			else {
				/* Parse line */
				tagName = ldif_get_tagname( line, &flag64 );
				if( tagName ) {
					/* Add tag to list */
					listTags = g_slist_append( listTags, tagName );

					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;
					}
				}
			}
			g_free( line );
		}
	}

	/* Release data */
	g_slist_free_full( listTags, g_free );
	listTags = NULL;
}

/**
 * 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 ) {
	cm_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
	ldifFile->retVal = MGU_SUCCESS;
	addrcache_clear( cache );
	cache->dataRead = FALSE;
	ldif_open_file( ldifFile );
	if( ldifFile->retVal == MGU_SUCCESS ) {
		/* Read data into the cache */
		ldif_read_file( ldifFile, cache );
		ldif_close_file( ldifFile );

		/* Mark cache */
		cache->modified = FALSE;
		cache->dataRead = TRUE;
	}
	return ldifFile->retVal;
}

/**
 * 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 ) {
	cm_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS );
	ldifFile->retVal = MGU_SUCCESS;
	if( ldifFile->dirtyFlag ) {
		ldif_open_file( ldifFile );
		if( ldifFile->retVal == MGU_SUCCESS ) {
			/* Read data into the cache */
			ldif_read_tag_list( ldifFile );
			ldif_close_file( ldifFile );
			ldifFile->dirtyFlag = FALSE;
			ldifFile->accessFlag = TRUE;
		}
	}
	return ldifFile->retVal;
}

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

	cm_return_val_if_fail( ldifFile != NULL, NULL );
	if( ldifFile->hashFields ) {
		ldifFile->tempList = NULL;
		g_hash_table_foreach( ldifFile->hashFields, ldif_hash2list_vis, ldifFile );
		list = ldifFile->tempList;
		ldifFile->tempList = NULL;
	}
	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.
 * ============================================================================
 */

