/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* dirprefs.c -- directory server preferences */
#include "rosetta.h"
#include "xp.h"

#include "xp_mcom.h"
#include "xpgetstr.h"
#include "dirprefs.h"
#include "prefapi.h"
#include "libi18n.h"
#ifdef MOZ_LDAP
	#include "ldap.h"
HG82168
#else
	#define LDAP_PORT 389
	#define LDAPS_PORT 636
#endif /* MOZ_LDAP */

#if !defined(MOZADDRSTANDALONE)

extern int MK_OUT_OF_MEMORY;
extern int MK_ADDR_PAB;

extern int MK_LDAP_COMMON_NAME;
extern int MK_LDAP_GIVEN_NAME;
extern int MK_LDAP_SURNAME;
extern int MK_LDAP_EMAIL_ADDRESS; 
extern int MK_LDAP_PHONE_NUMBER;
extern int MK_LDAP_ORGANIZATION; 
extern int MK_LDAP_ORG_UNIT; 
extern int MK_LDAP_LOCALITY;
extern int MK_LDAP_STREET;
extern int MK_LDAP_CUSTOM1; 
extern int MK_LDAP_CUSTOM2;
extern int MK_LDAP_CUSTOM3; 
extern int MK_LDAP_CUSTOM4;
extern int MK_LDAP_CUSTOM5;
extern int MK_LDAP_REPL_CANT_SYNC_REPLICA;

#else

#define MK_OUT_OF_MEMORY -1;

#endif /* #if !defined(MOZADDRSTANDALONE) */


/*****************************************************************************
 * Private structs and stuff
 */

/* DIR_Server.flags */
#define DIR_AUTO_COMPLETE_ENABLED 0x00000001
#define DIR_ENABLE_AUTH           0x00000002
#define DIR_SAVE_PASSWORD         0x00000004
#define DIR_DEFEAT_UTF8           0x00000008
#define DIR_IS_SECURE             0x00000010
#define DIR_SAVE_RESULTS          0x00000020
#define DIR_EFFICIENT_WILDCARDS   0x00000040

/* DIR_Server.customAttributes is a list of DIR_Attribute structures */
typedef struct DIR_Attribute
{
	DIR_AttributeId id;
	char *prettyName;
	char **attrNames;
} DIR_Attribute;

struct _DIR_ReplicationInfo
{
	XP_Bool enabled;					/* duh */
	char *description;					/* human readable description of replica */
	char *filter;						/* LDAP filter string which constrains the repl search */
	int32 lastChangeNumber;				/* Last change we saw -- start replicating here */
	int32 generation;	/* LDAP server's scoping of the lastChangeNumber */
										/* this changes when the server's DB gets reloaded from LDIF */
	char **excludedAttributes;			/* list of attributes we shouldn't replicate */
	int excludedAttributesCount;		/* duh */

};

/* Our internal view of a default attribute is a resourceId for the pretty
 * name and the real attribute name. These are catenated to create a string
 * of the form "Pretty Name:attrname"
 */
typedef struct DIR_DefaultAttribute
{
	DIR_AttributeId id;
	int resourceId;
	char *name;
} DIR_DefaultAttribute;

/* DIR_Filter.flags */
#define DIR_F_SUBST_STARS_FOR_SPACES   0x00000001
#define DIR_F_REPEAT_FILTER_FOR_TOKENS 0x00000002

/* DIR_Server.filters is a list of DIR_Filter structures */
typedef struct DIR_Filter
{
	char *string;
	uint32 flags;
} DIR_Filter;

/* Default settings for site-configurable prefs */
#define kDefaultTokenSeps " ,."
#define kDefaultSubstStarsForSpaces TRUE
#define kDefaultRepeatFilterForTokens TRUE
#define kDefaultEfficientWildcards TRUE
#define kDefaultFilter "(cn=*%s*)"
#define kDefaultEfficientFilter "(|(givenname=%s)(sn=%s))"
#define kDefaultStopOnHit TRUE
#define kDefaultMaxHits 100
#define kDefaultIsOffline TRUE
#define kDefaultEnableAuth FALSE
#define kDefaultSavePassword FALSE
#define kDefaultAutoCompleteEnabled FALSE

#define kDefaultReplicaExcludedAttributes ""
#define kDefaultReplicaEnabled FALSE
#define kDefaultReplicaFilter "(objectclass=*)"
#define kDefaultReplicaChangeNumber 0
#define kDefaultReplicaChangeNumberGeneration 0
#define kDefaultReplicaDescription ""
#define kDefaultUtf8Disabled FALSE

#define kLdapServersPrefName "network.hosts.ldap_servers"

const char *DIR_GetAttributeName (DIR_Server *server, DIR_AttributeId id);
void DIR_SetServerFileName(DIR_Server* pServer, const char* leafName);
static DIR_DefaultAttribute *DIR_GetDefaultAttribute (DIR_AttributeId id);


/*****************************************************************************
 * Functions for creating DIR_Servers
 */

int DIR_InitServer (DIR_Server *server)
{
	XP_ASSERT(server);
	if (server)
	{
		XP_BZERO(server, sizeof(DIR_Server));
		server->saveResults = TRUE;
		server->efficientWildcards = kDefaultEfficientWildcards;
		server->port = LDAP_PORT;
		server->maxHits = kDefaultMaxHits;
		server->isOffline = kDefaultIsOffline;
	}
	return 0;
}


/*****************************************************************************
 * Functions for cloning DIR_Servers
 */

static DIR_Attribute *DIR_CopyAttribute (DIR_Attribute *inAttribute)
{
	DIR_Attribute *outAttribute = (DIR_Attribute*) XP_ALLOC(sizeof(DIR_Attribute));
	if (outAttribute)
	{
		int count = 0;
		outAttribute->id = inAttribute->id;
		outAttribute->prettyName = XP_STRDUP(inAttribute->prettyName);
		while (inAttribute->attrNames[count])
			count++;
		outAttribute->attrNames = (char**) XP_ALLOC((count + 1) * sizeof(char*));
		if (outAttribute->attrNames)
		{
			int i;
			for (i = 0; i < count; i++)
				outAttribute->attrNames[i] = XP_STRDUP(inAttribute->attrNames[i]);
			outAttribute->attrNames[i] = NULL;
		}
	}
	return outAttribute;
}


static DIR_Filter *DIR_CopyFilter (DIR_Filter *inFilter)
{
	DIR_Filter *outFilter = (DIR_Filter*) XP_ALLOC(sizeof(DIR_Filter));
	if (outFilter)
	{
		outFilter->flags = inFilter->flags;
		outFilter->string = XP_STRDUP(inFilter->string);
	}
	return outFilter;
}


static int dir_CopyTokenList (char **inList, int inCount, char ***outList, int *outCount)
{
	int status = 0;
	if (0 != inCount && NULL != inList)
	{
		*outList = (char**) XP_ALLOC(inCount * sizeof(char*));
		if (*outList)
		{
			int i;
			for (i = 0; i < inCount; i++)
				(*outList)[i] = XP_STRDUP (inList[i]);
			*outCount = inCount;
		}
		else
			status = MK_OUT_OF_MEMORY;
	}
	return status;
}


static DIR_ReplicationInfo *dir_CopyReplicationInfo (DIR_ReplicationInfo *inInfo)
{
	DIR_ReplicationInfo *outInfo = (DIR_ReplicationInfo*) XP_CALLOC (sizeof(DIR_ReplicationInfo), 1);
	if (outInfo)
	{
		outInfo->lastChangeNumber = inInfo->lastChangeNumber;
		outInfo->generation = inInfo->generation;
		outInfo->enabled = inInfo->enabled;
		if (inInfo->description)
			outInfo->description = XP_STRDUP (inInfo->description);
		if (inInfo->filter)
			outInfo->filter = XP_STRDUP (inInfo->filter);
		dir_CopyTokenList (inInfo->excludedAttributes, inInfo->excludedAttributesCount,
			&outInfo->excludedAttributes, &outInfo->excludedAttributesCount);
	}
	return outInfo;
}


int DIR_CopyServer (DIR_Server *in, DIR_Server **out)
{
	int err = 0;

	if (in) {
		*out = XP_ALLOC(sizeof(DIR_Server));
		if (*out)
		{
			XP_BZERO (*out, sizeof(DIR_Server));

			if (in->description)
			{
				(*out)->description = XP_STRDUP(in->description);
				if (!(*out)->description)
					err = MK_OUT_OF_MEMORY;
			}

			if (in->serverName)
			{
				(*out)->serverName = XP_STRDUP(in->serverName);
				if (!(*out)->serverName)
					err = MK_OUT_OF_MEMORY;
			}

			if (in->searchBase)
			{
				(*out)->searchBase = XP_STRDUP(in->searchBase);
				if (!(*out)->searchBase)
					err = MK_OUT_OF_MEMORY;
			}

			if (in->fileName)
			{
				(*out)->fileName = XP_STRDUP(in->fileName);
				if (!(*out)->fileName)
					err = MK_OUT_OF_MEMORY;
 			}

			if (in->prefId)
			{
				(*out)->prefId = XP_STRDUP(in->prefId);
				if (!(*out)->prefId)
					err = MK_OUT_OF_MEMORY;
			}

			(*out)->port = in->port;
			(*out)->maxHits = in->maxHits;
			(*out)->isSecure = in->isSecure;
			(*out)->saveResults = in->saveResults;
			(*out)->isOffline = in->isOffline;
			(*out)->efficientWildcards = in->efficientWildcards;
			(*out)->dirType = in->dirType;

			(*out)->flags = in->flags;
			
			(*out)->enableAuth = in->enableAuth;
			(*out)->savePassword = in->savePassword;
			if (in->authDn)
			{
				(*out)->authDn = XP_STRDUP (in->authDn);
				if (!(*out)->authDn)
					err = MK_OUT_OF_MEMORY;
			}
			if (in->password)
			{
				(*out)->password = XP_STRDUP (in->password);
				if (!(*out)->password)
					err = MK_OUT_OF_MEMORY;
			}

			if (in->customAttributes)
			{
				(*out)->customAttributes = XP_ListNew();
				if ((*out)->customAttributes)
				{
					XP_List *list = in->customAttributes;
					DIR_Attribute *attribute = NULL;

					while ((attribute = XP_ListNextObject (list)) != NULL)
					{
						DIR_Attribute *outAttr = DIR_CopyAttribute (attribute);
						if (outAttr)
							XP_ListAddObject ((*out)->customAttributes, outAttr);
						else
							err = MK_OUT_OF_MEMORY;
 					}
				}
				else
					err = MK_OUT_OF_MEMORY;
			}

			if (in->customFilters)
			{
				(*out)->customFilters = XP_ListNew();
				if ((*out)->customFilters)
				{
					XP_List *list = in->customFilters;
					DIR_Filter *filter = NULL;

					while ((filter = XP_ListNextObject (list)) != NULL)
					{
						DIR_Filter *outFilter = DIR_CopyFilter (filter);
						if (outFilter)
							XP_ListAddObject ((*out)->customFilters, outFilter);
						else
							err = MK_OUT_OF_MEMORY;
					}
				}
				else
					err = MK_OUT_OF_MEMORY;
			}

			if (in->replInfo)
				(*out)->replInfo = dir_CopyReplicationInfo (in->replInfo);

			if (in->basicSearchAttributesCount > 0)
			{
				int bsaLength = in->basicSearchAttributesCount * sizeof(DIR_AttributeId);
				(*out)->basicSearchAttributes = (DIR_AttributeId*) XP_ALLOC(bsaLength);
				if ((*out)->basicSearchAttributes)
				{
					XP_MEMCPY ((*out)->basicSearchAttributes, in->basicSearchAttributes, bsaLength);
					(*out)->basicSearchAttributesCount = in->basicSearchAttributesCount;
				}
			}

			dir_CopyTokenList (in->dnAttributes, in->dnAttributesCount,
				&(*out)->dnAttributes, &(*out)->dnAttributesCount);

			dir_CopyTokenList (in->suppressedAttributes, in->suppressedAttributesCount,
				&(*out)->suppressedAttributes, &(*out)->suppressedAttributesCount);

			if (in->customDisplayUrl)
				(*out)->customDisplayUrl = XP_STRDUP (in->customDisplayUrl);
		}
		else {
			err = MK_OUT_OF_MEMORY;
			(*out) = NULL;
		}
	}
	else {
		XP_ASSERT (0);
		err = -1;
		(*out) = NULL;
	}

	return err;
}


/*****************************************************************************
 * Function for comparing DIR_Servers 
 */

XP_Bool	DIR_AreServersSame (DIR_Server *first, DIR_Server *second)
{
	XP_Bool result = FALSE;

	if (first && second) 
	{
		/* assume for right now one personal address book type where offline is false */
		if ((first->dirType == PABDirectory) && (second->dirType == PABDirectory)) {
			if ((first->isOffline == FALSE) && (second->isOffline == FALSE))
				return TRUE;
			else {
				XP_ASSERT (first->serverName && second->serverName);
				if (first->serverName && second->serverName)
				{
					if (XP_STRCASECMP (first->serverName, second->serverName) == 0) 
					{
						if (first->port == second->port) 
						{
							/* allow for a null search base */
							if (first->searchBase == NULL && second->searchBase == NULL)
								return TRUE;
							/* otherwise check the strings */
							if (first->searchBase && second->searchBase)
							{
								if (XP_STRCASECMP (first->searchBase, second->searchBase) == 0) 
								{
										return TRUE;
								}
								else
									return FALSE;
							}
							else
								return FALSE;
						}
					}
					else
						return FALSE;
				}
			}
		}

		if (first->dirType != second->dirType)
			return FALSE;

		/* otherwise check for ldap servers */
		XP_ASSERT (first->serverName && second->serverName);
		if (first->serverName && second->serverName)
		{
			if (!XP_STRCASECMP (first->serverName, second->serverName)) 
			{
				if (first->port == second->port) 
				{
					/* allow for a null search base */
					if (first->searchBase == NULL && second->searchBase == NULL)
						result = TRUE;
					/* otherwise check the strings */
					if (first->searchBase && second->searchBase)
					{
						if (!XP_STRCASECMP (first->searchBase, second->searchBase)) 
						{
								result = TRUE;
						}
					}
				}
			}
		}
	}
	return result;
}


/*****************************************************************************
 * Functions for destroying DIR_Servers 
 */

static void dir_DeleteTokenList (char **tokenList, int tokenListCount)
{
	int tokenIdx;
	for (tokenIdx = 0; tokenIdx < tokenListCount; tokenIdx++)
		XP_FREE(tokenList[tokenIdx]);
	XP_FREE(tokenList);
}


static int DIR_DeleteFilter (DIR_Filter *filter)
{
	if (filter->string)
		XP_FREE(filter->string);
	XP_FREE(filter);
	return 0;
}


static int DIR_DeleteAttribute (DIR_Attribute *attribute)
{
	int i = 0;
	if (attribute->prettyName)
		XP_FREE(attribute->prettyName);
	if (attribute->attrNames)
	{
		while (attribute->attrNames[i])
			XP_FREE((char**)attribute->attrNames[i++]);
		XP_FREE(attribute->attrNames);
	}
	XP_FREE(attribute);
	return 0;
}


static void dir_DeleteReplicationInfo (DIR_Server *server)
{
	DIR_ReplicationInfo *info = NULL;
	if (server && (info = server->replInfo) != NULL)
	{
		dir_DeleteTokenList (info->excludedAttributes, info->excludedAttributesCount);
		
		XP_FREEIF(info->description);
		XP_FREEIF(info->filter);
		XP_FREE(info);
	}
}


int DIR_DeleteServer (DIR_Server *server)
{
	if (server)
	{
		int i;

		XP_FREEIF (server->description);
		XP_FREEIF (server->serverName);
		XP_FREEIF (server->searchBase);
		XP_FREEIF (server->fileName);
		XP_FREEIF (server->lastSearchString);
		XP_FREEIF (server->tokenSeps);
		XP_FREEIF (server->authDn);
		XP_FREEIF (server->password);
		XP_FREEIF (server->prefId);

		if (server->customFilters)
		{
			for (i = 1; i <= XP_ListCount(server->customFilters); i++)
				DIR_DeleteFilter ((DIR_Filter*) XP_ListGetObjectNum (server->customFilters, i));
			XP_ListDestroy (server->customFilters);
		}

		if (server->customAttributes)
		{
			XP_List *list = server->customAttributes;
			DIR_Attribute *walkAttrStruct = NULL;
			while ((walkAttrStruct = XP_ListNextObject(list)) != NULL)
				DIR_DeleteAttribute (walkAttrStruct);
			XP_ListDestroy (server->customAttributes);
		}

		if (server->suppressedAttributes)
			dir_DeleteTokenList (server->suppressedAttributes, server->suppressedAttributesCount);
		if (server->dnAttributes)
			dir_DeleteTokenList (server->dnAttributes, server->dnAttributesCount);
		XP_FREEIF (server->basicSearchAttributes);
		if (server->replInfo)
			dir_DeleteReplicationInfo (server);

		XP_FREEIF (server->customDisplayUrl);

		XP_FREE (server);
	}
	return 0;
}

int DIR_DeleteServerList(XP_List *wholeList)
{
	int i;
	if (wholeList)
	{
		for (i = 1; i <= XP_ListCount(wholeList); i++)
			DIR_DeleteServer(XP_ListGetObjectNum (wholeList, i));
		XP_ListDestroy (wholeList);
	}
	return 0;
}


int DIR_CleanUpServerPreferences(XP_List *deletedList)
{
	int i;
	if (deletedList)
	{
		for (i = 1; i <= XP_ListCount(deletedList); i++)
		{
			DIR_Server *server = (DIR_Server *) (XP_ListGetObjectNum (deletedList, i));
			if (server)
			{
#if !defined(MOZADDRSTANDALONE)
				if (server->fileName)
					XP_FileRemove (server->fileName, xpAddrBookNew);
#endif
			}
		}
		DIR_DeleteServerList(deletedList);
	}
	return 0;
}


/*****************************************************************************
 * Functions for retrieving subsets of the DIR_Server list 
 */

int DIR_GetHtmlServers (XP_List *wholeList, XP_List *subList)
{
	int i;
	if (wholeList && subList)
	{
		for (i = 1; i <= XP_ListCount(wholeList); i++)
		{
			DIR_Server *s = (DIR_Server*) XP_ListGetObjectNum (wholeList, i);
			if (HTMLDirectory == s->dirType)
				XP_ListAddObjectToEnd (subList, s);
		}
		return 0;
	}
	return -1;
}

int DIR_GetLdapServers (XP_List *wholeList, XP_List *subList)
{
	int i;
	if (wholeList && subList)
	{
		for (i = 1; i <= XP_ListCount(wholeList); i++)
		{
			DIR_Server *s = (DIR_Server*) XP_ListGetObjectNum (wholeList, i);
			if (LDAPDirectory == s->dirType)
				XP_ListAddObjectToEnd (subList, s);
		}
		return 0;
	}
	return -1;
}

int	DIR_ReorderLdapServers (XP_List *wholeList)
{
	int status = 0;
	int length = 0;
	DIR_Server *s = NULL;
	char *prefValue = NULL;

	XP_List *walkList = wholeList;
	while (s = (DIR_Server*) XP_ListNextObject (walkList))
	{
		length += XP_STRLEN (s->prefId) + 2; /* +2 for ", " */
	}

	prefValue = (char*) XP_ALLOC (length + 1); /* +1 for null term */
	if (prefValue)
	{
		*prefValue = '\0';
		walkList = wholeList;
		while (s = (DIR_Server*) XP_ListNextObject (walkList))
		{
			XP_STRCAT (prefValue, s->prefId);
			XP_STRCAT (prefValue, ", ");
		}
		if (PREF_SetCharPref (kLdapServersPrefName, prefValue) < 0)
			status = -1; /* not sure how to make a pref error into an mk error */
		XP_FREE(prefValue);
	}
	else
		status = MK_OUT_OF_MEMORY;

	return status;
}

int DIR_GetPersonalAddressBook (XP_List *wholeList, DIR_Server **pab)
{
	int i;
	if (wholeList && pab)
	{
		*pab = NULL;
		for (i = 1; i <= XP_ListCount(wholeList); i++)
		{
			DIR_Server *s = (DIR_Server*) XP_ListGetObjectNum (wholeList, i);
			if ((PABDirectory == s->dirType) && (FALSE == s->isOffline))
			{
				if (s->serverName) {
					if (XP_STRLEN (s->serverName) == 0) {
						*pab = s;
						return 0;
					}
				}
				if (s->serverName == NULL) {
						*pab = s;
						return 0;
				}
			}
		}
	}
	return -1;
}

int DIR_GetComposeNameCompletionAddressBook (XP_List *wholeList, DIR_Server **cab)
{
	int i;
	if (wholeList && cab)
	{
		*cab = NULL;
		for (i = 1; i <= XP_ListCount(wholeList); i++)
		{
			DIR_Server *s = (DIR_Server*) XP_ListGetObjectNum (wholeList, i);
			if (PABDirectory == s->dirType)
			{
				*cab = s;
				return 0;
			}
		}
	}
	return -1;
}


/*****************************************************************************
 * Functions for managing JavaScript prefs for the DIR_Servers 
 */

#if !defined(MOZADDRSTANDALONE)

static char *DIR_GetStringPref (const char *prefRoot, const char *prefLeaf, char *scratch, const char *defaultValue)
{
	int valueLength = 0;
	char *value = NULL;
	XP_STRCPY(scratch, prefRoot);
	XP_STRCAT (scratch, prefLeaf);

	if (PREF_NOERROR == PREF_CopyCharPref(scratch, &value))
	{
		/* unfortunately, there may be some prefs out there which look like this */
		if (!XP_STRCMP(value, "(null)")) 
			value = XP_STRDUP(defaultValue);
	}
	else
		value = XP_STRDUP(defaultValue);
	return value;
}


static int32 DIR_GetIntPref (const char *prefRoot, const char *prefLeaf, char *scratch, int32 defaultValue)
{
	int32 value;
	XP_STRCPY(scratch, prefRoot);
	if (PREF_NOERROR != PREF_GetIntPref(XP_STRCAT (scratch, prefLeaf), &value))
		value = defaultValue;
	return value;
}


static XP_Bool DIR_GetBoolPref (const char *prefRoot, const char *prefLeaf, char *scratch, XP_Bool defaultValue)
{
	XP_Bool value;
	XP_STRCPY(scratch, prefRoot);
	if (PREF_NOERROR != PREF_GetBoolPref(XP_STRCAT (scratch, prefLeaf), &value))
		value = defaultValue;
	return value;
}


int DIR_AttributeNameToId (const char *attrName, DIR_AttributeId *id)
{
	int status = 0;

	switch (attrName[0])
	{
	case 'a':
		if (!XP_STRCASECMP(attrName, "auth"))
			*id = auth;
		else
			status = -1;
		break;
	case 'c' :
		if (!XP_STRCASECMP(attrName, "cn"))
			*id = cn;
		else if (!XP_STRNCASECMP(attrName, "custom", 6))
		{
			switch (attrName[6])
			{
			case '1': *id = custom1; break;
			case '2': *id = custom2; break;
			case '3': *id = custom3; break;
			case '4': *id = custom4; break;
			case '5': *id = custom5; break;
			default: status = -1; 
			}
		}
		else
			status = -1;
		break;
	case 'g':
		if (!XP_STRCASECMP(attrName, "givenname"))
			*id = givenname; 
		else
			status = -1;
		break;
	case 's': 
		if (!XP_STRCASECMP(attrName, "street"))
			*id = street;
		else if (!XP_STRCASECMP(attrName, "sn"))
			*id = sn;
		else
			status = -1;
		break;
	case 'm':
		if (!XP_STRCASECMP(attrName, "mail"))
			*id = mail;
		else
			status = -1;
		break;
	case 'o':
		if (!XP_STRCASECMP(attrName, "o"))
			*id = o;
		else if (!XP_STRCASECMP(attrName, "ou"))
			*id = ou;
		else
			status = -1;
		break;
	case 'l':
		if (!XP_STRCASECMP(attrName, "l"))
			*id = l;
		else
			status = -1;
		break;
	case 't':
		if (!XP_STRCASECMP(attrName, "telephoneNumber"))
			*id = telephonenumber;
		else
			status = -1;
		break;
	default:
		status = -1;
	}

	return status;
}


static int DIR_AddCustomAttribute (DIR_Server *server, const char *attrName, char *jsAttr)
{
	int status = 0;
	char *jsCompleteAttr = NULL;
	char *jsAttrForTokenizing = jsAttr;

	DIR_AttributeId id;
	status = DIR_AttributeNameToId (attrName, &id);

	/* If the string they gave us doesn't have a ':' in it, assume it's one or more
	 * attributes without a pretty name. So find the default pretty name, and generate
	 * a "complete" string to use for tokenizing.
	 */
	if (status == 0 && !XP_STRCHR(jsAttr, ':'))
	{
		const char *defaultPrettyName = DIR_GetAttributeName (server, id);
		if (defaultPrettyName)
		{
			jsCompleteAttr = PR_smprintf ("%s:%s", defaultPrettyName, jsAttr);
			if (jsCompleteAttr)
				jsAttrForTokenizing = jsCompleteAttr;
			else
				status = MK_OUT_OF_MEMORY;
		}
	}

	if (status == 0)
	{
		char *scratchAttr = XP_STRDUP(jsAttrForTokenizing);
		DIR_Attribute *attrStruct = (DIR_Attribute*) XP_ALLOC(sizeof(DIR_Attribute));
		if (!server->customAttributes)
			server->customAttributes = XP_ListNew();

		if (attrStruct && server->customAttributes && scratchAttr)
		{
			char *attrToken = NULL;
			int attrCount = 0;

			XP_BZERO(attrStruct, sizeof(DIR_Attribute));

			/* Try to pull out the pretty name into the struct */
			attrStruct->id = id;
			attrStruct->prettyName = XP_STRDUP(XP_STRTOK(scratchAttr, ":")); 

			/* Count up the attribute names */
			while ((attrToken = XP_STRTOK(NULL, ", ")) != NULL)
				attrCount++;

			/* Pull the attribute names into the struct */
			XP_STRCPY(scratchAttr, jsAttrForTokenizing);
			XP_STRTOK(scratchAttr, ":"); 
			attrStruct->attrNames = (char**) XP_ALLOC((attrCount + 1) * sizeof(char*));
			if (attrStruct->attrNames)
			{
				int i = 0;
				while ((attrToken = XP_STRTOK(NULL, ", ")) != NULL)
					attrStruct->attrNames[i++] = XP_STRDUP(attrToken);
				attrStruct->attrNames[i] = NULL; /* null-terminate the array */
			}

			if (status == 0)
				XP_ListAddObject (server->customAttributes, attrStruct);
			else
				DIR_DeleteAttribute (attrStruct);

			XP_FREE(scratchAttr);
		}
		else 
			status = MK_OUT_OF_MEMORY;
	}

	if (jsCompleteAttr)
		XP_FREE(jsCompleteAttr);

	return status;
}


static int dir_CreateTokenListFromWholePref (const char *pref, char ***outList, int *outCount)
{
	int result = 0;
	char *commaSeparatedList = NULL;

	if (PREF_NOERROR == PREF_CopyCharPref (pref, &commaSeparatedList) && commaSeparatedList)
	{
		char *tmpList = commaSeparatedList;
		*outCount = 1;
		while (*tmpList)
			if (*tmpList++ == ',')
				(*outCount)++;

		*outList = (char**) XP_ALLOC (*outCount * sizeof(char*));
		if (*outList)
		{
			int i;
			char *token = XP_STRTOK (commaSeparatedList, ", ");
			for (i = 0; i < *outCount; i++)
			{
				(*outList)[i] = XP_STRDUP (token);
				token = XP_STRTOK(NULL, ", ");
			}
		}
		else
			result = MK_OUT_OF_MEMORY;

		XP_FREE (commaSeparatedList);
	}
	else
		result = -1;
	return result;
}


static int dir_CreateTokenListFromPref (const char *prefBase, const char *prefLeaf, char *scratch, char ***outList, int *outCount)
{
	XP_STRCPY (scratch, prefBase);
	XP_STRCAT (scratch, prefLeaf);

	return dir_CreateTokenListFromWholePref (scratch, outList, outCount);
}


static int dir_ConvertTokenListToIdList (char **tokenList, int tokenCount, DIR_AttributeId **outList)
{
	*outList = (DIR_AttributeId*) XP_ALLOC (sizeof(DIR_AttributeId) * tokenCount);
	if (*outList)
	{
		int i;
		for (i = 0; i < tokenCount; i++)
			DIR_AttributeNameToId (tokenList[i], &(*outList)[i]);
	}
	else
		return MK_OUT_OF_MEMORY;
	return 0;
}


static void dir_GetReplicationInfo (const char *prefName, DIR_Server *server, char *scratch)
{
	char *childList = NULL;
	XP_STRCPY (scratch, prefName);
	if (PREF_NOERROR == PREF_CreateChildList (XP_STRCAT(scratch, "replication"), &childList))
	{
		XP_ASSERT (server->replInfo == NULL);
		if (childList && childList[0])
		{
			server->replInfo = (DIR_ReplicationInfo *) XP_CALLOC (sizeof (DIR_ReplicationInfo), 1);
			if (server->replInfo)
			{
				char *child = NULL;
				int index = 0;
				while ((child = PREF_NextChild (childList, &index)) != NULL)
				{
					char *leaf = XP_STRRCHR (child, '.');
					if (leaf)
					{
						leaf++; /* skip over the '.' */

						/* Note: JS prefs are case-sensitive, and so is this code */
						switch (leaf[0])
						{
							case 'd':
								if (!XP_STRCMP (leaf, "description"))
									PREF_CopyCharPref (child, &server->replInfo->description);
								else
									XP_ASSERT(FALSE);
								break;
							case 'e':
								if (!XP_STRCMP (leaf, "enabled"))
									PREF_GetBoolPref (child, &server->replInfo->enabled);
								else if (!XP_STRCMP (leaf, "excludedAttributes"))
									dir_CreateTokenListFromWholePref (child, &server->replInfo->excludedAttributes, 
																	&server->replInfo->excludedAttributesCount);
								else
									XP_ASSERT(FALSE);
								break;
							case 'f' :
								if (!XP_STRCMP (leaf, "filter"))
									PREF_CopyCharPref (child, &server->replInfo->filter);
								else
									XP_ASSERT(FALSE);
								break;
							case 'g':
								if (!XP_STRCMP (leaf, "generation"))
									PREF_GetIntPref (child, &server->replInfo->generation);
								else
									XP_ASSERT(FALSE);
								break;
							case 'l':
								if (!XP_STRCMP (leaf, "lastChangeNumber"))
									PREF_GetIntPref (child, &server->replInfo->lastChangeNumber);
								else
									XP_ASSERT(FALSE);
								break;
							default:
								XP_ASSERT(FALSE); 
						}
					}
				}
			}
		}
		XP_FREE(childList);
	}
}


/* Called at startup-time to read whatever overrides the LDAP site administrator has
 * done to the attribute names
 */
static int DIR_GetCustomAttributePrefs (const char *prefName, DIR_Server *server, char *scratch)
{
	char **tokenList = NULL;
	char *childList = NULL;
	XP_STRCPY(scratch, prefName);

	if (PREF_NOERROR == PREF_CreateChildList(XP_STRCAT(scratch, "attributes"), &childList))
	{
		if (childList && childList[0])
		{
			char *child = NULL;
			int index = 0;
			while ((child = PREF_NextChild (childList, &index)) != NULL)
			{
				char *jsValue = NULL;
				if (PREF_NOERROR == PREF_CopyCharPref (child, &jsValue))
				{
					if (jsValue && jsValue[0])
					{
						char *attrName = child + XP_STRLEN(scratch) + 1;
						DIR_AddCustomAttribute (server, attrName, jsValue);
					}
					XP_FREEIF(jsValue);
				}
			}
		}

		XP_FREEIF(childList);
	}

	/* The replicated attributes and basic search attributes can only be
	 * attributes which are in our predefined set (DIR_AttributeId) so
	 * store those in an array of IDs for more convenient access
	 */
	dir_GetReplicationInfo (prefName, server, scratch);

	if (0 == dir_CreateTokenListFromPref (prefName, "basicSearchAttributes", scratch, 
		&tokenList, &server->basicSearchAttributesCount))
	{
		dir_ConvertTokenListToIdList (tokenList, server->basicSearchAttributesCount, 
			&server->basicSearchAttributes);
		dir_DeleteTokenList (tokenList, server->basicSearchAttributesCount);
	}

	/* The DN attributes and suppressed attributes can be attributes that
	 * we've never heard of, so they're stored by name, so we can match 'em
	 * as we get 'em from the server
	 */
	dir_CreateTokenListFromPref (prefName, "html.dnAttributes", scratch, 
		&server->dnAttributes, &server->dnAttributesCount);
	dir_CreateTokenListFromPref (prefName, "html.excludedAttributes", scratch, 
		&server->suppressedAttributes, &server->suppressedAttributesCount);

	return 0;
}


/* Called at startup-time to read whatever overrides the LDAP site administrator has
 * done to the filtering logic
 */
static int DIR_GetCustomFilterPrefs (const char *prefName, DIR_Server *server, char *scratch)
{
	int status = 0;
	XP_Bool keepGoing = TRUE;
	int filterNum = 1;
	char *localScratch = XP_ALLOC(128);
	if (!localScratch)
		return MK_OUT_OF_MEMORY;

	while (keepGoing && !status)
	{
		char *childList = NULL;

		PR_snprintf (scratch, 128, "%sfilter%d", prefName, filterNum);
		if (PREF_NOERROR == PREF_CreateChildList(scratch, &childList))
		{
			if ('\0' != childList[0])
			{
				DIR_Filter *filter = (DIR_Filter*) XP_ALLOC (sizeof(DIR_Filter));
				if (filter)
				{
					XP_Bool tempBool;
					XP_BZERO(filter, sizeof(DIR_Filter));

					/* Pull per-directory filter preferences out of JS values */
					if (1 == filterNum)
					{
						server->tokenSeps = DIR_GetStringPref (prefName, "wordSeparators", localScratch, kDefaultTokenSeps);
#if 0
						/* This is left over from when I thought I'd have time to let the
						** admin specify a list of filters, and we'd run them until we got 
						** a hit. Still a good idea, but probably too late for Dogbert.
						*/
						server->stopFiltersOnHit = DIR_GetBoolPref (prefName, "stopFiltersOnHit", localScratch, kDefaultStopOnHit);
#endif
						XP_STRCAT(scratch, ".");
					}

					/* Pull per-filter preferences out of JS values */
					filter->string = DIR_GetStringPref (scratch, "string", localScratch, 
						server->efficientWildcards ? kDefaultFilter : kDefaultEfficientFilter);
					tempBool = DIR_GetBoolPref (scratch, "repeatFilterForWords", localScratch, kDefaultRepeatFilterForTokens);
					if (tempBool)
						filter->flags |= DIR_F_REPEAT_FILTER_FOR_TOKENS;
					tempBool = DIR_GetBoolPref (scratch, "substituteStarsForSpaces", localScratch, kDefaultSubstStarsForSpaces);
					if (tempBool)
						filter->flags |= DIR_F_SUBST_STARS_FOR_SPACES;

					/* Add resulting DIR_Filter to the list */
					if (!server->customFilters)
						server->customFilters = XP_ListNew();
					if (server->customFilters)
						XP_ListAddObject (server->customFilters, filter);
					else
						status = MK_OUT_OF_MEMORY;
				}
				else
					status = MK_OUT_OF_MEMORY;
				filterNum++;
			}
			else
				keepGoing = FALSE;
			XP_FREE(childList);
		}
		else
			keepGoing = FALSE;
	}

	XP_FREE(localScratch);
	return status;
}

/* This will convert from the old preference that was a path and filename */
/* to a just a filename */
void DIR_ConvertServerFileName(DIR_Server* pServer)
{
	char* leafName = pServer->fileName;
	char* newLeafName = NULL;
#if defined(XP_WIN) || defined(XP_OS2)
	/* jefft -- bug 73349 This is to allow users share same address book.
	 * It only works if the user specify a full path filename.
	 */
	if (! XP_FileIsFullPath(leafName))
		newLeafName = XP_STRRCHR (leafName, '\\');
#else
	newLeafName = XP_STRRCHR (leafName, '/');
#endif
	pServer->fileName = newLeafName ? XP_STRDUP(newLeafName + 1) : XP_STRDUP(leafName);
	if (leafName) XP_FREE(leafName);
}

/* This will generate a correct filename and then remove the path */
void DIR_SetServerFileName(DIR_Server* pServer, const char* leafName)
{
	char* tempName = WH_TempName(xpAddrBook, leafName);
	char* nativeName = WH_FileName(tempName, xpAddrBook);
	char* urlName = XP_PlatformFileToURL(nativeName);
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MAC) || defined(XP_OS2)
	char* newLeafName = XP_STRRCHR (urlName + XP_STRLEN("file://"), '/');
	pServer->fileName = newLeafName ? XP_STRDUP(newLeafName + 1) : XP_STRDUP(urlName + XP_STRLEN("file://"));
#else
	pServer->fileName = XP_STRDUP(urlName + XP_STRLEN("file://"));
#endif
	if (urlName) XP_FREE(urlName);
	if (nativeName) XP_FREE(nativeName);
	if (tempName) XP_FREE(tempName);
}

/* This will reconstruct a correct filename including the path */
void DIR_GetServerFileName(char** filename, const char* leafName)
{
#ifdef XP_MAC
	char* realLeafName;
	char* nativeName;
	char* urlName;
	if (XP_STRCHR(leafName, ':') != NULL)
		realLeafName = XP_STRRCHR(leafName, ':') + 1;	// makes sure that leafName is not a fullpath
	else
		realLeafName = leafName;

	nativeName = WH_FileName(realLeafName, xpAddrBookNew);
	urlName = XP_PlatformFileToURL(nativeName);
	(*filename) = XP_STRDUP(urlName + XP_STRLEN("file://"));
	if (urlName) XP_FREE(urlName);
#elif defined(XP_WIN)
	/* jefft -- Bug 73349. To allow users share same address book.
	 * This only works if user sepcified a full path name in his
	 * prefs.js
	 */ 
	char *nativeName = WH_FilePlatformName (leafName);
	char *fullnativeName;
	if (XP_FileIsFullPath(nativeName)) {
		fullnativeName = nativeName;
		nativeName = NULL;
	}
	else {
		fullnativeName = WH_FileName(nativeName, xpAddrBookNew);
	}
	(*filename) = fullnativeName;
#else
	char* nativeName = WH_FilePlatformName (leafName);
	char* fullnativeName = WH_FileName(nativeName, xpAddrBookNew);
	(*filename) = fullnativeName;
#endif
	if (nativeName) XP_FREE(nativeName);
}

static int DIR_GetPrefsFromBranch (XP_List **list, const char *pabFile, const char *branch)
{
	int32 numDirectories = 0;
	int i = 0;
	char *prefstring = NULL;
	char *tempString = NULL;
	int result = 0;
	XP_Bool hasPAB = FALSE;
	DIR_Server *pNewServer = NULL;
	char *newfilename = NULL;

	if (*list)
		DIR_DeleteServerList(*list);

	prefstring = (char *) XP_ALLOC(128);
	tempString = (char *) XP_ALLOC(256);

	(*list) = XP_ListNew();
	/* get the preference for how many directories */
	if (prefstring && tempString && (*list) && branch && *branch)
	{
		char *numberOfDirs = PR_smprintf ("%s.number_of_directories", branch);
		if (numberOfDirs)
			PREF_GetIntPref(numberOfDirs, &numDirectories);	
		for (i = 1; i <= numDirectories; i++)
		{
			pNewServer = (DIR_Server *) XP_ALLOC(sizeof(DIR_Server));
			if (pNewServer)
			{
				XP_Bool prefBool;

				XP_BZERO(pNewServer, sizeof(DIR_Server));
				XP_SPRINTF(prefstring, "%s.directory%i.", branch, i);

				pNewServer->isSecure = DIR_GetBoolPref (prefstring, "isSecure", tempString, FALSE);
				pNewServer->saveResults = DIR_GetBoolPref (prefstring, "saveResults", tempString, TRUE);				
				pNewServer->efficientWildcards = DIR_GetBoolPref (prefstring, "efficientWildcards", tempString, TRUE);				
				pNewServer->port = DIR_GetIntPref (prefstring, "port", tempString, pNewServer->isSecure ? LDAPS_PORT : LDAP_PORT);
				if (pNewServer->port == 0)
					pNewServer->port = pNewServer->isSecure ? LDAPS_PORT : LDAP_PORT;
				pNewServer->maxHits = DIR_GetIntPref (prefstring, "maxHits", tempString, kDefaultMaxHits);
				pNewServer->description = DIR_GetStringPref (prefstring, "description", tempString, "");
				pNewServer->serverName = DIR_GetStringPref (prefstring, "serverName", tempString, "");
				pNewServer->searchBase = DIR_GetStringPref (prefstring, "searchBase", tempString, "");
				if (!XP_STRCASECMP(pNewServer->serverName, "ldap.infospace.com") && !*pNewServer->searchBase)
				{
					/* 4.0 unfortunately shipped with the wrong searchbase for Infospace, so
					 * if we find Infospace, and their searchBase hasn't been set, make it right
					 * It's not legal to do this in all.js unless we rename the LDAP prefs tree (ugh)
					 */
					pNewServer->searchBase = XP_STRDUP ("c=US");
					PREF_SetCharPref (tempString, "c=US");
				}
				
				pNewServer->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", tempString, (int) LDAPDirectory);
				if (pNewServer->dirType == PABDirectory)
				{
					hasPAB = TRUE;
					/* make sure there is a true PAB */
					if (XP_STRLEN (pNewServer->serverName) == 0)
						pNewServer->isOffline = FALSE;
					pNewServer->saveResults = TRUE; /* never let someone delete their PAB this way */
				}

				pNewServer->fileName = DIR_GetStringPref (prefstring, "filename", tempString, "");
				if (!pNewServer->fileName || !*(pNewServer->fileName)) 
				{
					if (pNewServer->dirType != PABDirectory)
						DIR_SetServerFileName (pNewServer, pNewServer->serverName);
					else
					{
						XP_FREEIF(pNewServer->fileName);
						pNewServer->fileName = XP_STRDUP (pabFile);
					}
				}
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MAC) || defined(XP_OS2)
				DIR_ConvertServerFileName(pNewServer);
#endif
				pNewServer->lastSearchString = DIR_GetStringPref (prefstring, "searchString", tempString, "");
				pNewServer->isOffline = DIR_GetBoolPref (prefstring, "isOffline", tempString, kDefaultIsOffline);

				/* This is where site-configurable attributes and filters are read from JavaScript */
				DIR_GetCustomAttributePrefs (prefstring, pNewServer, tempString);
				DIR_GetCustomFilterPrefs (prefstring, pNewServer, tempString);

				/* Get authentication prefs */
				pNewServer->enableAuth = DIR_GetBoolPref (prefstring, "enableAuth", tempString, kDefaultEnableAuth);
				pNewServer->savePassword = DIR_GetBoolPref (prefstring, "savePassword", tempString, kDefaultSavePassword);
				if (pNewServer->savePassword)
				{
					pNewServer->authDn = DIR_GetStringPref (prefstring, "authDn", tempString, "");
					pNewServer->password = DIR_GetStringPref (prefstring, "password", tempString, "");
				}

				prefBool = DIR_GetBoolPref (prefstring, "autoCompleteEnabled", tempString, kDefaultAutoCompleteEnabled);
				DIR_ForceFlag (pNewServer, DIR_AUTO_COMPLETE_ENABLED, prefBool);

				prefBool = DIR_GetBoolPref (prefstring, "utf8Disabled", tempString, kDefaultUtf8Disabled);
				DIR_ForceFlag (pNewServer, DIR_UTF8_DISABLED, prefBool);

				pNewServer->customDisplayUrl = DIR_GetStringPref (prefstring, "customDisplayUrl", tempString, "");

				XP_ListAddObjectToEnd((*list), pNewServer);
			}
		}

		XP_FREEIF(numberOfDirs);

		/* all.js should have filled this stuff in */
		XP_ASSERT(hasPAB);
		XP_ASSERT(numDirectories != 0); 
	}
	else
		result = -1;

	XP_FREEIF (prefstring);
	XP_FREEIF (tempString);

	return result;
}


int DIR_GetServerPreferences (XP_List **list, const char* pabFile)
{
	int err = 0;
	XP_List *oldList = NULL;
	XP_List *newList = NULL;
	char *oldChildren = NULL;

	int32 listVersion = -1;
	XP_Bool userHasOldPrefs = FALSE;

	if (PREF_NOERROR == PREF_GetIntPref ("ldapList.version", &listVersion))
		userHasOldPrefs = (kCurrentListVersion > listVersion);
	else
		userHasOldPrefs = TRUE;
	
	/* Look to see if there's an old-style "directories" tree in prefs */
	if (PREF_NOERROR == PREF_CreateChildList ("directories", &oldChildren))
	{
		if (oldChildren)
		{
			if (userHasOldPrefs)
				err = DIR_GetPrefsFromBranch (&oldList, pabFile, "directories");
			PREF_DeleteBranch ("directories");
			XP_FREEIF(oldChildren);
		}
	}
	/* Look to see if there's an old-style "ldap" tree in prefs */
	else if (PREF_NOERROR == PREF_CreateChildList ("ldap", &oldChildren))
	{
		if (oldChildren)
		{
			if (userHasOldPrefs)
				err = DIR_GetPrefsFromBranch (&oldList, pabFile, "ldap");
			PREF_DeleteBranch ("ldap");
			XP_FREEIF(oldChildren);
		}
	}

	/* Find the new-style "ldap_1" tree in prefs */
	DIR_GetPrefsFromBranch (&newList, pabFile, "ldap_1");

	if (oldList && newList)
	{
		/* Merge the new tree onto the old tree, new on top, unique old at bottom */
		DIR_Server *oldServer;
		XP_List *walkOldList = oldList;

		while (NULL != (oldServer = XP_ListNextObject(walkOldList)))
		{
			XP_Bool addOldServer = TRUE;
			DIR_Server *newServer;
			XP_List *walkNewList = newList;

			while (NULL != (newServer = XP_ListNextObject(walkNewList)) && addOldServer)
			{
				if (DIR_AreServersSame (oldServer, newServer))
					addOldServer = FALSE; /* don't add servers which are in the new list */
				else if (PABDirectory == oldServer->dirType)
					addOldServer = FALSE; /* don't need the old PAB; there's already one in ALL.JS */
				else if (!XP_STRCMP (oldServer->serverName, "ldap-trace.fedex.com"))
					addOldServer = FALSE;
			}

			if (addOldServer)
			{
				DIR_Server *copyOfOldServer;
				DIR_CopyServer (oldServer, &copyOfOldServer);
				XP_ListAddObjectToEnd (newList, copyOfOldServer);
			}
		}

		/* Delete the list of old-style prefs */
		DIR_DeleteServerList (oldList);

		/* Write the new/merged list so we get it next time we ask */
		DIR_SaveServerPreferences (newList);
	}

	PREF_SetIntPref ("ldapList.version", kCurrentListVersion);

	*list = newList;
	return err;
}


#define DIR_GOOD_WAY 1


static void DIR_ClearPrefBranch (const char *branch)
{
	/* This little function provides a way to delete a prefs object but still
	 * allow reassignment of that object later. 
	 */
	char *recreateBranch = NULL;

	PREF_DeleteBranch (branch);
	recreateBranch = PR_smprintf ("pref_inittree(\"%s\")", branch);
	if (recreateBranch)
	{
		PREF_QuietEvaluateJSBuffer (recreateBranch, XP_STRLEN(recreateBranch));
		XP_FREE(recreateBranch);
	}
}


static void DIR_ClearIntPref (const char *pref)
{
	int32 oldDefault;
	int prefErr = PREF_GetDefaultIntPref (pref, &oldDefault);
	DIR_ClearPrefBranch (pref);
	if (prefErr >= 0)
		PREF_SetDefaultIntPref (pref, oldDefault);
}


static void DIR_ClearStringPref (const char *pref)
{
	char *oldDefault = NULL;
	int prefErr = PREF_CopyDefaultCharPref (pref, &oldDefault);
	DIR_ClearPrefBranch (pref);
	if (prefErr >= 0)
		PREF_SetDefaultCharPref (pref, oldDefault);
	XP_FREEIF(oldDefault);
}


static void DIR_ClearBoolPref (const char *pref)
{
	XP_Bool oldDefault;
	int prefErr = PREF_GetDefaultBoolPref (pref, &oldDefault);
	DIR_ClearPrefBranch (pref);
	if (prefErr >= 0)
		PREF_SetDefaultBoolPref (pref, oldDefault);
}


static void DIR_SetStringPref (const char *prefRoot, const char *prefLeaf, char *scratch, const char *value, const char *defaultValue)
{
	char *defaultPref = NULL;
	int prefErr = PREF_NOERROR;

	XP_STRCPY(scratch, prefRoot);
	XP_STRCAT (scratch, prefLeaf);

	if (PREF_NOERROR == PREF_CopyDefaultCharPref (scratch, &defaultPref))
	{
		/* If there's a default pref, just set ours in and let libpref worry 
		 * about potential defaults in all.js
		 */
		prefErr = PREF_SetCharPref (scratch, value);
		XP_FREE(defaultPref);
	}
	else
	{
		/* If there's no default pref, look for a user pref, and only set our value in
		 * if the user pref is different than one of them.
		 */
		char *userPref = NULL;
		if (PREF_NOERROR == PREF_CopyCharPref (scratch, &userPref))
		{
#if DIR_GOOD_WAY
			if (value && XP_STRCASECMP(value, defaultValue))
				prefErr = PREF_SetCharPref (scratch, value);
			else
				DIR_ClearStringPref (scratch); 
#else
				prefErr = PREF_SetCharPref (scratch, value);
#endif
		}
		else
		{
			if (value && XP_STRCASECMP(value, defaultValue))
				prefErr = PREF_SetCharPref (scratch, value); 
		}
	}

	XP_ASSERT(prefErr >= 0);
}


static void DIR_SetIntPref (const char *prefRoot, const char *prefLeaf, char *scratch, int32 value, int32 defaultValue)
{
	int32 defaultPref;
	int prefErr = 0;

	XP_STRCPY(scratch, prefRoot);
	XP_STRCAT (scratch, prefLeaf);

	if (PREF_NOERROR == PREF_GetDefaultIntPref (scratch, &defaultPref))
	{
		/* solve the problem where reordering user prefs must override default prefs */
		PREF_SetIntPref (scratch, value);
	}
	else
	{
		int32 userPref;
		if (PREF_NOERROR == PREF_GetIntPref (scratch, &userPref))
		{
#if DIR_GOOD_WAY
			if (value != defaultValue)
				prefErr = PREF_SetIntPref(scratch, value);
			else
				DIR_ClearIntPref (scratch);
#else
				prefErr = PREF_SetIntPref(scratch, value);
#endif
		}
		else
		{
			if (value != defaultValue)
				prefErr = PREF_SetIntPref (scratch, value); 
		}
	}

	XP_ASSERT(prefErr >= 0);
}


static void DIR_SetBoolPref (const char *prefRoot, const char *prefLeaf, char *scratch, XP_Bool value, XP_Bool defaultValue)
{
	XP_Bool defaultPref;
	int prefErr = PREF_NOERROR;

	XP_STRCPY(scratch, prefRoot);
	XP_STRCAT (scratch, prefLeaf);

	if (PREF_NOERROR == PREF_GetDefaultBoolPref (scratch, &defaultPref))
	{
		/* solve the problem where reordering user prefs must override default prefs */
		prefErr = PREF_SetBoolPref (scratch, value);
	}
	else
	{
		XP_Bool userPref;
		if (PREF_NOERROR == PREF_GetBoolPref (scratch, &userPref))
		{
#if DIR_GOOD_WAY
			if (value != defaultValue)
				prefErr = PREF_SetBoolPref(scratch, value);
			else
				DIR_ClearBoolPref (scratch);
#else
				prefErr = PREF_SetBoolPref(scratch, value);
#endif
		}
		else
		{
			if (value != defaultValue)
				prefErr = PREF_SetBoolPref (scratch, value);
		}

	}

	XP_ASSERT(prefErr >= 0);
}


static int DIR_ConvertAttributeToPrefsString (DIR_Attribute *attrib, char **ppPrefsString)
{
	int err = 0;

	/* Compute size in bytes req'd for prefs string */
	int length = XP_STRLEN(attrib->prettyName);
	int i = 0;
	while (attrib->attrNames[i])
	{
		length += XP_STRLEN(attrib->attrNames[i]) + 1; /* +1 for comma separator */
		i++;
	}
	length += 1; /* +1 for colon */

	/* Allocate prefs string */
	*ppPrefsString = (char*) XP_ALLOC(length + 1); /* +1 for null term */

	/* Unravel attrib struct back out into prefs */
	if (*ppPrefsString)
	{
		int j = 0;
		XP_STRCPY (*ppPrefsString, attrib->prettyName);
		XP_STRCAT (*ppPrefsString, ":");
		while (attrib->attrNames[j])
		{
			XP_STRCAT (*ppPrefsString, attrib->attrNames[j]);
			if (j + 1 < i)
				XP_STRCAT (*ppPrefsString, ",");
			j++;
		}
	}
	else
		err = MK_OUT_OF_MEMORY;

	return err;
}


static int DIR_SaveOneCustomAttribute (const char *prefRoot, char *scratch, DIR_Server *server, DIR_AttributeId id)
{
	int err;

	const char *name = DIR_GetDefaultAttribute (id)->name;
	err = 0;

	if (server->customAttributes)
	{
		DIR_Attribute *attrib = NULL;
		XP_List *walkList = server->customAttributes;
		while ((attrib = XP_ListNextObject(walkList)) != NULL)
		{
			if (attrib->id == id)
			{
				char *jsString = NULL;
				if (0 == DIR_ConvertAttributeToPrefsString(attrib, &jsString))
				{
					DIR_SetStringPref (prefRoot, name, scratch, jsString, "");
					XP_FREE(jsString);
					return err;
				}
			}
		}

	}

	/* This server doesn't have a custom attribute for the requested ID
	 * so set it to the null string just in case there's an ALL.JS setting
	 * or had a previous user value
	 */
	DIR_SetStringPref (prefRoot, name, scratch, "", "");

	return err;
}


static int DIR_SaveCustomAttributes (const char *prefRoot, char *scratch, DIR_Server *server)
{
	int err;
	char *localScratch = (char*) XP_ALLOC(256);
	err = 0;

	XP_STRCPY (scratch, prefRoot);
	XP_STRCAT (scratch, "attributes.");
	if (localScratch)
	{
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, cn);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, givenname);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, sn);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, mail);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, telephonenumber);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, o);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, ou);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, l);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, street);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, custom1);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, custom2);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, custom3);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, custom4);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, custom5);
		DIR_SaveOneCustomAttribute (scratch, localScratch, server, auth);
		XP_FREE(localScratch);
	}
	else
		err = MK_OUT_OF_MEMORY;

	return err;
}


static int DIR_SaveCustomFilters (const char *prefRoot, char *scratch, DIR_Server *server)
{
	int err;
	char *localScratch = (char*) XP_ALLOC(256);
	err = 0;

	XP_STRCPY (scratch, prefRoot);
	XP_STRCAT (scratch, "filter1.");
	if (server->customFilters)
	{
		/* Save the custom filters into the JS prefs */
		DIR_Filter *filter = NULL;
		XP_List *walkList = server->customFilters;
		if (localScratch)
		{
			while ((filter = XP_ListNextObject(walkList)) != NULL)
			{
				DIR_SetBoolPref (scratch, "repeatFilterForWords", localScratch, 
					(filter->flags & DIR_F_REPEAT_FILTER_FOR_TOKENS) != 0, kDefaultRepeatFilterForTokens);
				DIR_SetStringPref (scratch, "string", localScratch, filter->string, kDefaultFilter);
			}
			XP_FREE(localScratch);
		}
		else
			err = MK_OUT_OF_MEMORY;
	}
	else
	{
		/* The DIR_Server object doesn't think it has any custom filters,
		 * so make sure the prefs settings are empty too
		 */
		DIR_SetBoolPref (scratch, "repeatFilterForWords", localScratch, 
			kDefaultRepeatFilterForTokens, kDefaultRepeatFilterForTokens);
		DIR_SetStringPref (scratch, "string", localScratch, kDefaultFilter, kDefaultFilter);
	}

	return err;
}


static int dir_SaveReplicationInfo (const char *prefRoot, char *scratch, DIR_Server *server)
{
	int err = 0;
	char *localScratch = (char*) XP_ALLOC(256);
	if (!localScratch)
		return MK_OUT_OF_MEMORY;

	XP_STRCPY (scratch, prefRoot);
	XP_STRCAT (scratch, "replication.");

	if (server->replInfo)
	{
		char *excludedList = NULL;
		int i;
		int excludedLength = 0;
		for (i = 0; i < server->replInfo->excludedAttributesCount; i++)
			excludedLength += XP_STRLEN (server->replInfo->excludedAttributes[i]) + 2; /* +2 for ", " */
		if (excludedLength)
		{
			excludedList = (char*) XP_ALLOC (excludedLength + 1);
			if (excludedList)
			{
				excludedList[0] = '\0';
				for (i = 0; i < server->replInfo->excludedAttributesCount; i++)
				{
					XP_STRCAT (excludedList, server->replInfo->excludedAttributes[i]);
					XP_STRCAT (excludedList, ", ");
				}
			}
			else
				err = MK_OUT_OF_MEMORY;
		}

		DIR_SetStringPref (scratch, "excludedAttributes", scratch, excludedList, kDefaultReplicaExcludedAttributes);

		DIR_SetBoolPref (scratch, "enabled", localScratch, server->replInfo->enabled, kDefaultReplicaEnabled);
		DIR_SetStringPref (scratch, "description", localScratch, server->replInfo->description, kDefaultReplicaDescription);
		DIR_SetStringPref (scratch, "filter", localScratch, server->replInfo->filter, kDefaultReplicaFilter);
		DIR_SetIntPref (scratch, "lastChangeNumber", localScratch, server->replInfo->lastChangeNumber, kDefaultReplicaChangeNumber);
		DIR_SetIntPref (scratch, "generation", localScratch, server->replInfo->generation, kDefaultReplicaChangeNumberGeneration);
	}

	return err;
}


int DIR_SaveServerPreferences (XP_List *wholeList)
{
	int i;
	DIR_Server *s;
	char * prefstring = NULL;
	char * tempString = NULL;

	if (wholeList)
	{
		prefstring = (char *) XP_ALLOC(128);
		tempString = (char *) XP_ALLOC(256);
		if (prefstring && tempString) 
		{
			PREF_SetIntPref("ldap_1.number_of_directories", XP_ListCount (wholeList));	

			for (i = 1; i <= XP_ListCount(wholeList); i++)
			{
				s = (DIR_Server*) XP_ListGetObjectNum (wholeList, i);
				if (s)
				{
					XP_SPRINTF(prefstring, "ldap_1.directory%i.", i);

					DIR_SetStringPref (prefstring, "description", tempString, s->description, "");
					DIR_SetStringPref (prefstring, "serverName", tempString, s->serverName, "");
					DIR_SetStringPref (prefstring, "searchBase", tempString, s->searchBase, "");
					DIR_SetStringPref (prefstring, "filename", tempString, s->fileName, "");
					if (s->port == 0)
						s->port = s->isSecure ? LDAPS_PORT : LDAP_PORT;
					DIR_SetIntPref (prefstring, "port", tempString, s->port, s->isSecure ? LDAPS_PORT : LDAP_PORT);
					DIR_SetIntPref (prefstring, "maxHits", tempString, s->maxHits, kDefaultMaxHits);
					DIR_SetBoolPref (prefstring, "isSecure", tempString, s->isSecure, FALSE);
					DIR_SetBoolPref (prefstring, "saveResults", tempString, s->saveResults, TRUE);
					DIR_SetBoolPref (prefstring, "efficientWildcards", tempString, s->efficientWildcards, TRUE);
					DIR_SetStringPref (prefstring, "searchString", tempString, s->lastSearchString, "");
					DIR_SetIntPref (prefstring, "dirType", tempString, s->dirType, (int) LDAPDirectory);
					DIR_SetBoolPref (prefstring, "isOffline", tempString, s->isOffline, kDefaultIsOffline);

					DIR_SetBoolPref (prefstring, "autoCompleteEnabled", tempString, DIR_TestFlag(s, DIR_AUTO_COMPLETE_ENABLED), kDefaultAutoCompleteEnabled);
					DIR_SetBoolPref (prefstring, "utf8Disabled", tempString, DIR_TestFlag(s, DIR_UTF8_DISABLED), kDefaultUtf8Disabled);

					DIR_SetBoolPref (prefstring, "enableAuth", tempString, s->enableAuth, kDefaultEnableAuth);
					DIR_SetBoolPref (prefstring, "savePassword", tempString, s->savePassword, kDefaultSavePassword);
					if (s->savePassword)
					{
						DIR_SetStringPref (prefstring, "authDn", tempString, s->authDn, "");
						DIR_SetStringPref (prefstring, "password", tempString, s->password, "");
					}

					DIR_SaveCustomAttributes (prefstring, tempString, s);
					DIR_SaveCustomFilters (prefstring, tempString, s);
					dir_SaveReplicationInfo (prefstring, tempString, s);

					DIR_SetStringPref (prefstring, "customDisplayUrl", tempString, s->customDisplayUrl, "");
				}
			}
			XP_SPRINTF(tempString, "%i", tempString);
			PREF_SetCharPref("ldap_1.end_of_directories", tempString);
		} 

		XP_FREEIF (prefstring);
		XP_FREEIF (tempString);
	}

	return 0;
}

/*****************************************************************************
 * Functions for getting site-configurable preferences, from JavaScript if
 * the site admin has provided them, else out of thin air.
 */

static DIR_DefaultAttribute *DIR_GetDefaultAttribute (DIR_AttributeId id)
{
	int i = 0;

	static DIR_DefaultAttribute defaults[15];
	defaults[0].id = cn;
	defaults[0].resourceId = MK_LDAP_COMMON_NAME;
	defaults[0].name = "cn";
	
	defaults[1].id = givenname;
	defaults[1].resourceId = MK_LDAP_GIVEN_NAME;
	defaults[1].name = "givenName";
	
	defaults[2].id = sn;
	defaults[2].resourceId = MK_LDAP_SURNAME;
	defaults[2].name = "sn";
	
	defaults[3].id = mail;
	defaults[3].resourceId = MK_LDAP_EMAIL_ADDRESS;
	defaults[3].name = "mail";
	
	defaults[4].id = telephonenumber;
	defaults[4].resourceId = MK_LDAP_PHONE_NUMBER;
	defaults[4].name = "telephoneNumber";
	
	defaults[5].id = o;
	defaults[5].resourceId = MK_LDAP_ORGANIZATION;
	defaults[5].name = "o";
	
	defaults[6].id = ou;
	defaults[6].resourceId = MK_LDAP_ORG_UNIT;
	defaults[6].name = "ou";
	
	defaults[7].id = l;
	defaults[7].resourceId = MK_LDAP_LOCALITY;
	defaults[7].name = "l";
	
	defaults[8].id = street;
	defaults[8].resourceId = MK_LDAP_STREET;
	defaults[8].name = "street";
	
	defaults[9].id = custom1;
	defaults[9].resourceId = MK_LDAP_CUSTOM1;
	defaults[9].name = "custom1";
	
	defaults[10].id = custom2;
	defaults[10].resourceId = MK_LDAP_CUSTOM2;
	defaults[10].name = "custom2";
	
	defaults[11].id = custom3;
	defaults[11].resourceId = MK_LDAP_CUSTOM3;
	defaults[11].name = "custom3";
	
	defaults[12].id = custom4;
	defaults[12].resourceId = MK_LDAP_CUSTOM4;
	defaults[12].name = "custom4";
	
	defaults[13].id = custom5;
	defaults[13].resourceId = MK_LDAP_CUSTOM5;
	defaults[13].name = "custom5";

	defaults[14].id = auth;
	defaults[14].resourceId = MK_LDAP_EMAIL_ADDRESS;
	defaults[14].name = "mail";

	defaults[15].id = cn;
	defaults[15].resourceId = 0;
	defaults[15].name = NULL;

	while (defaults[i].name)
	{
		if (defaults[i].id == id)
			return &defaults[i];
		i++;
	}

	return NULL;
}

const char *DIR_GetAttributeName (DIR_Server *server, DIR_AttributeId id)
{
	char *result = NULL;

	/* First look in the custom attributes in case the attribute is overridden */
	XP_List *list = server->customAttributes;
	DIR_Attribute *walkList = NULL;

	while ((walkList = XP_ListNextObject(list)) != NULL)
	{
		if (walkList->id == id)
			result = walkList->prettyName;
	}

	/* If we didn't find it, look in our own static list of attributes */
	if (!result)
	{
		DIR_DefaultAttribute *def;
		if ((def = DIR_GetDefaultAttribute(id)) != NULL)
			result = XP_GetString(def->resourceId);
	}

	return result;
}


const char **DIR_GetAttributeStrings (DIR_Server *server, DIR_AttributeId id)
{
	const char **result = NULL;

	/* First look in the custom attributes in case the attribute is overridden */
	XP_List *list = server->customAttributes;
	DIR_Attribute *walkList = NULL;

	while ((walkList = XP_ListNextObject(list)) != NULL)
	{
		if (walkList->id == id)
			result = (const char**)walkList->attrNames;
	}

	/* If we didn't find it, look in our own static list of attributes */
	if (!result)
	{
		static const char *array[2];
		array[0] = DIR_GetDefaultAttribute(id)->name;
		array[1] = NULL;
		result = (const char**)array;
	}
	return result;
}


const char *DIR_GetFirstAttributeString (DIR_Server *server, DIR_AttributeId id)
{
	const char **array = DIR_GetAttributeStrings (server, id);
	return array[0];
}


const char *DIR_GetFilterString (DIR_Server *server)
{
	DIR_Filter *filter = XP_ListTopObject (server->customFilters);
	if (filter)
		return filter->string;
	return NULL;
}


static DIR_Filter *DIR_LookupFilter (DIR_Server *server, const char *filter)
{
	XP_List *list = server->customFilters;
	DIR_Filter *walkFilter = NULL;

	while ((walkFilter = XP_ListNextObject(list)) != NULL)
		if (!XP_STRCASECMP(filter, walkFilter->string))
			return walkFilter;

	return NULL;
}


XP_Bool DIR_RepeatFilterForTokens (DIR_Server *server, const char *filter)
{
	const DIR_Filter *filterStruct = DIR_LookupFilter (server, filter);
	if (filterStruct)
		return (filterStruct->flags & DIR_F_REPEAT_FILTER_FOR_TOKENS) != 0;

	return kDefaultRepeatFilterForTokens;
}


XP_Bool DIR_SubstStarsForSpaces (DIR_Server *server, const char *filter)
{
	const DIR_Filter *filterStruct = DIR_LookupFilter (server, filter);
	if (filterStruct)
		return (filterStruct->flags & DIR_F_SUBST_STARS_FOR_SPACES) != 0;

	return kDefaultSubstStarsForSpaces;
}


const char *DIR_GetTokenSeparators (DIR_Server *server)
{
	return server->tokenSeps ? server->tokenSeps : kDefaultTokenSeps;
}

XP_Bool DIR_UseCustomAttribute (DIR_Server *server, DIR_AttributeId id)
{
	XP_List *list = server->customAttributes;
	DIR_Attribute *walkList = NULL;

	while ((walkList = XP_ListNextObject(list)) != NULL)
	{
		if (walkList->id == id)
			return TRUE;
	}
	return FALSE;
}


XP_Bool DIR_IsDnAttribute (DIR_Server *s, const char *attrib)
{
	if (s && s->dnAttributes)
	{
		/* Look in the server object to see if there are prefs to tell
		 * us which attributes contain DNs
		 */
		int i;
		for (i = 0; i < s->dnAttributesCount; i++)
		{
			if (!XP_STRCASECMP(attrib, s->dnAttributes[i]))
				return TRUE;
		}
	}
	else
	{
		/* We have some default guesses about what attributes 
		 * are likely to contain DNs 
		 */
		switch (XP_TO_LOWER(attrib[0]))
		{
		case 'm':
			if (!XP_STRCASECMP(attrib, "manager") || 
				!XP_STRCASECMP(attrib, "member"))
				return TRUE;
			break;
		case o:
			if (!XP_STRCASECMP(attrib, "owner"))
				return TRUE;
			break;
		case 'u':
			if (!XP_STRCASECMP(attrib, "uniquemember"))
				return TRUE;
			break;
		}
	}
	return FALSE;

}


XP_Bool DIR_IsAttributeExcludedFromHtml (DIR_Server *s, const char *attrib)
{
	if (s && s->suppressedAttributes)
	{
		/* Look in the server object to see if there are prefs to tell
		 * us which attributes shouldn't be shown in HTML
		 */
		int i;
		for (i = 0; i < s->suppressedAttributesCount; i++)
		{
			if (!XP_STRCASECMP(attrib, s->suppressedAttributes[i]))
				return TRUE;
		}
	}
	/* else don't exclude it. By default we show everything */

	return FALSE;
}


static void dir_PushStringToPrefs (DIR_Server *s, char **curVal, const char *newVal, const char *name)
{
	XP_List *servers = NULL;
	XP_List *walkList = NULL;
	DIR_Server *walkServer = NULL;
	XP_Bool found = FALSE;
	int idx = 0;

	/* Don't do anything if we already have a value and it's the same as the new one */
	if (curVal && *curVal)
	{
		if (!XP_STRCMP(*curVal, newVal))
			return;
	}

	/* Find the server in the list so we can get its index.
	 * Can't use XP_GetNumFromObject because this DIR_Server isn't guaranteed
	 * to be in the FE's list -- it may be from a copy of the FE's list
	 */
	walkList = FE_GetDirServers ();
	while (!found)
	{
		walkServer = (DIR_Server*) XP_ListNextObject (walkList);
		if (!walkServer)
			break;
		found = DIR_AreServersSame (walkServer, s);
		idx++;
	}

	if (found)
	{
		/* Build the name of the prefs string */
		char buf[512];
		char *prefRoot = PR_smprintf ("ldap_1.directory%d.", idx);

		/* Set into the prefs */
		if (prefRoot)
		{
			*curVal = XP_STRDUP(newVal);
			DIR_SetStringPref (prefRoot, name, buf, *curVal, "");

			XP_FREE(prefRoot);
		}
	}

}


void DIR_SetAuthDN (DIR_Server *s, const char *dn)
{
	dir_PushStringToPrefs (s, &(s->authDn), dn, "authDn");
}


void DIR_SetPassword (DIR_Server *s, const char *password)
{
	dir_PushStringToPrefs (s, &(s->password), password, "password");
}


XP_Bool DIR_IsEscapedAttribute (DIR_Server *s, const char *attrib)
{
	/* We're not exposing this setting in JS prefs right now, but in case we
	 * might want to in the future, leave the DIR_Server* in the prototype.
	 */

	switch (XP_TO_LOWER(attrib[0]))
	{
	case 'p':
		if (!XP_STRCASECMP(attrib, "postaladdress"))
			return TRUE;
		break;
	case 'f': 
		if (!XP_STRCASECMP(attrib, "facsimiletelephonenumber"))
			return TRUE;
		break;
	case 'o':
		if (!XP_STRCASECMP(attrib, "othermail"))
			return TRUE;
		break;
	}
	return FALSE;
}


char *DIR_Unescape (const char *src, XP_Bool makeHtml)
{
/* Borrowed from libnet\mkparse.c */
#define UNHEX(C) \
	((C >= '0' && C <= '9') ? C - '0' : \
	((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \
	((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0)))

	char *dest = NULL;
	int destLength = 0;

	int dollarCount = 0;
	int convertedLengthOfDollar = makeHtml ? 4 : 1;

	const char *tmpSrc = src;

	while (*tmpSrc)
		if (*tmpSrc++ == '$')
			dollarCount++;

	destLength = XP_STRLEN(src) + (dollarCount * convertedLengthOfDollar);
	dest = (char*) XP_ALLOC (destLength + 1);
	if (dest)
	{
		char *tmpDst = dest;
		*dest = '\0';
		tmpSrc = src;

		while (*tmpSrc)
		{
			switch (*tmpSrc)
			{
			case '$':
				/* A dollar sign is a linebreak. This is easy for HTML, but if we're converting
				 * for the Address Book or something without multiple lines, just put in a space
				 */
				if (makeHtml)
				{
					*tmpDst++ = '<';
					*tmpDst++ = 'B';
					*tmpDst++ = 'R';
					*tmpDst++ = '>';
				}
				else
					*tmpDst++ = ' ';
				break;
			case '\\': {
				/* A backslash indicates that two hex digits follow, which we're supposed to
				 * convert. The spec sez that '$', '#' and '\'' (single quote) must be encoded
				 * this way. 
				 */
				XP_Bool didEscape = FALSE;
				char c1 = *(tmpSrc + 1);
				if (c1 && (XP_IS_DIGIT(c1) || XP_IS_ALPHA(c1)))
				{
					char c2 = *(tmpSrc + 2);
					if (c2 && (XP_IS_DIGIT(c2) || XP_IS_ALPHA(c2)))
					{
						*tmpDst++ = (UNHEX(c1) << 4) | UNHEX(c2);
						tmpSrc +=2;
						didEscape = TRUE;
					}
				}
				if (!didEscape)
					*tmpDst++ = *tmpSrc;
			}
			break;
			default:
				/* Just a plain old char -- copy it over */
				*tmpDst++ = *tmpSrc;
			}
			tmpSrc++;
		}
		*tmpDst = '\0';
	}

	return dest;
}


/*****************************************************************************
 * Functions for building a secure connection to LDAP servers
 *
 * Use of PR_CALLBACK is required for Win16 because the socket API functions
 * ultimately call into MOZOCK, which has DS-resident global variables. Ick.
 */

#ifdef MOZ_LDAP

HG29989

int DIR_ValidateRootDSE (DIR_Server *server, int32 gen, int32 first, int32 last)
{
	/* Here we validate the replication info that the server has against the
	 * state of the local replica as stored in JS prefs. 
	 */

	XP_ASSERT(server && server->replInfo);
	if (!server || !server->replInfo)
		return -1;

	/* The generation of the server's DB is different than when we last
	 * saw it, which means that the first and last change number we know
	 * are totally meaningless.
	 */
	if (gen != server->replInfo->generation)
		return MK_LDAP_REPL_CANT_SYNC_REPLICA;

	/* Some changes have come and gone on the server since we last 
	 * replicated. Since we have no way to know what those changes were,
	 * we have no way to get sync'd up with the current server state
	 */
	if (first > server->replInfo->lastChangeNumber)
		return MK_LDAP_REPL_CANT_SYNC_REPLICA;

	/* We appear to have already replicated changes that the server
	 * hasn't made yet. Not likely
	 */
	if (last < server->replInfo->lastChangeNumber)
		return MK_LDAP_REPL_CANT_SYNC_REPLICA;

	return 0;
}


#define DIR_AUTO_COMPLETE_ENABLED 0x00000001
#define DIR_ENABLE_AUTH           0x00000002
#define DIR_SAVE_PASSWORD         0x00000004
#define DIR_UTF8_DISABLED         0x00000008
#define DIR_IS_SECURE             0x00000010
#define DIR_SAVE_RESULTS          0x00000020
#define DIR_EFFICIENT_WILDCARDS   0x00000040

void DIR_SetAutoCompleteEnabled (XP_List *list, DIR_Server *server, XP_Bool enabled)
{
	XP_ASSERT(server); /*list can be null*/
	if (server)
	{
		DIR_Server *tmp;

		if (enabled)
		{
			while (NULL != (tmp = XP_ListNextObject(list)))
				tmp->flags &= ~DIR_AUTO_COMPLETE_ENABLED;
			server->flags |= DIR_AUTO_COMPLETE_ENABLED;
		}
		else
			server->flags &= ~DIR_AUTO_COMPLETE_ENABLED;
	}
}

XP_Bool DIR_TestFlag (DIR_Server *server, uint32 flag)
{
	if (server)
		return 0 != (server->flags & flag);
	return FALSE;
}

void DIR_SetFlag (DIR_Server *server, uint32 flag)
{
	XP_ASSERT(server);
	if (server)
		server->flags |= flag;
}

void DIR_ClearFlag (DIR_Server *server, uint32 flag)
{
	XP_ASSERT(server);
	if (server)
		server->flags &= ~flag;
}


void DIR_ForceFlag (DIR_Server *server, uint32 flag, XP_Bool setIt)
{
	XP_ASSERT(server);
	if (server)
	{
		if (setIt)
			server->flags |= flag;
		else
			server->flags &= ~flag;
	}
}


/* Centralize this charset conversion so everyone can do the UTF8 conversion
 * in the same way. Also, if someone ever makes us do T.61 or some other silly
 * thing, we can use these bottlenecks
 */

char *DIR_ConvertToServerCharSet (DIR_Server *server, char *src, int16 srcCsid)
{
	if (server && (server->flags & DIR_UTF8_DISABLED))
		return XP_STRDUP (src);
	else
		return (char*) INTL_ConvertLineWithoutAutoDetect (srcCsid, CS_UTF8, (unsigned char*) src, XP_STRLEN(src));
}

char *DIR_ConvertFromServerCharSet (DIR_Server *server, char *src, int16 destCsid)
{
	if (server && (server->flags & DIR_UTF8_DISABLED))
		return XP_STRDUP (src);
	else
		return (char*) INTL_ConvertLineWithoutAutoDetect (CS_UTF8, destCsid, (unsigned char*) src, XP_STRLEN(src));
}


char *DIR_BuildUrl (DIR_Server *server, const char *dn, XP_Bool forAddToAB)
{
	char *url = NULL;
	char *escapedDn = NET_Escape (dn, URL_XALPHAS);
	if (escapedDn)
	{
		if (!forAddToAB && server->customDisplayUrl && server->customDisplayUrl[0])
		{
			/* Allow users to customize the URL we run when we open an LDAP entry. This
			 * is intended to appease the people who want extensive customization of the
			 * HTML we generate for LDAP. We're sidestepping the issue by allowing them
			 * to easily plug in DSGW, the LDAP-to-HTTP gateway, which already has
			 * very extensive template support.
			 */
			url = PR_smprintf (server->customDisplayUrl, escapedDn);
		}
		else
		{
			/* The default case, where we run an LDAP URL in a browser window 
			 */
			char *urlTemplate = "%s//%s/%s";
			char *urlPortTemplate = "%s//%s:%d/%s";
			char *urlScheme = NULL;
			int port = server->port;
			int standardPort = server->isSecure ? LDAPS_PORT : LDAP_PORT;

			if (server->isSecure)
				urlScheme = forAddToAB ? "addbook-ldaps:" : "ldaps:";
			else
				urlScheme = forAddToAB ? "addbook-ldap:" : "ldap:";

			if (port == standardPort)
				url = PR_smprintf (urlTemplate, urlScheme, server->serverName, escapedDn);
			else
				url = PR_smprintf (urlPortTemplate, urlScheme, server->serverName, port, escapedDn);

		}
		XP_FREE (escapedDn);
	}
	return url;
}


#endif /* MOZ_LDAP */


#endif /* #if !defined(MOZADDRSTANDALONE) */
