diff options
Diffstat (limited to 'mailnews/addrbook/src/nsDirPrefs.cpp')
-rw-r--r-- | mailnews/addrbook/src/nsDirPrefs.cpp | 1452 |
1 files changed, 1452 insertions, 0 deletions
diff --git a/mailnews/addrbook/src/nsDirPrefs.cpp b/mailnews/addrbook/src/nsDirPrefs.cpp new file mode 100644 index 000000000..5e4c046b2 --- /dev/null +++ b/mailnews/addrbook/src/nsDirPrefs.cpp @@ -0,0 +1,1452 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* directory server preferences (used to be dirprefs.c in 4.x) */ + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsDirPrefs.h" +#include "nsIPrefLocalizedString.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsIAddrDatabase.h" +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "nsIFile.h" +#include "nsWeakReference.h" +#include "nsIAbMDBDirectory.h" +#if defined(MOZ_LDAP_XPCOM) +#include "nsIAbLDAPDirectory.h" +#endif +#include "prmem.h" +#include "prprf.h" +#include "plstr.h" +#include "nsQuickSort.h" +#include "nsComponentManagerUtils.h" +#include "msgCore.h" +#include "nsStringGlue.h" + +#include <ctype.h> + +/***************************************************************************** + * Private definitions + */ + +/* Default settings for site-configurable prefs */ +#define kDefaultPosition 1 +static bool dir_IsServerDeleted(DIR_Server * server); + +static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue); +static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue); +static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf); + +static char * dir_ConvertDescriptionToPrefName(DIR_Server * server); + +void DIR_SetFileName(char** filename, const char* leafName); +static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue); +static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref); +static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname); +static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList); + +static char *dir_CreateServerPrefName(DIR_Server *server); +static void DIR_GetPrefsForOneServer(DIR_Server *server); + +static void DIR_InitServer(DIR_Server *server, DirectoryType dirType = (DirectoryType)0); +static DIR_PrefId DIR_AtomizePrefName(const char *prefname); + +const int32_t DIR_POS_APPEND = -1; +const int32_t DIR_POS_DELETE = -2; +static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position); + +/* These two routines should be called to initialize and save + * directory preferences from the XP Java Script preferences + */ +static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list); +static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList); + +static int32_t dir_UserId = 0; +nsTArray<DIR_Server*> *dir_ServerList = nullptr; + +/***************************************************************************** + * Functions for creating the new back end managed DIR_Server list. + */ +class DirPrefObserver final : public nsSupportsWeakReference, + public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~DirPrefObserver() {} +}; + +NS_IMPL_ISUPPORTS(DirPrefObserver, nsISupportsWeakReference, nsIObserver) + +NS_IMETHODIMP DirPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + nsCOMPtr<nsIPrefBranch> prefBranch(do_QueryInterface(aSubject)); + nsCString strPrefName; + strPrefName.Assign(NS_ConvertUTF16toUTF8(aData)); + const char *prefname = strPrefName.get(); + + DIR_PrefId id = DIR_AtomizePrefName(prefname); + + // Just get out if we get nothing here - we don't need to do anything + if (id == idNone) + return NS_OK; + + /* Check to see if the server is in the unified server list. + */ + DIR_Server *server = dir_MatchServerPrefToServer(dir_ServerList, prefname); + if (server) + { + /* If the server is in the process of being saved, just ignore this + * change. The DIR_Server structure is not really changing. + */ + if (server->savingServer) + return NS_OK; + + /* If the pref that changed is the position, read it in. If the new + * position is zero, remove the server from the list. + */ + if (id == idPosition) + { + int32_t position; + + /* We must not do anything if the new position is the same as the + * position in the DIR_Server. This avoids recursion in cases + * where we are deleting the server. + */ + prefBranch->GetIntPref(prefname, &position); + if (position != server->position) + { + server->position = position; + if (dir_IsServerDeleted(server)) + DIR_SetServerPosition(dir_ServerList, server, DIR_POS_DELETE); + } + } + + if (id == idDescription) { + // Ensure the local copy of the description is kept up to date. + PR_FREEIF(server->description); + server->description = DIR_GetLocalizedStringPref(prefname, nullptr); + } + } + /* If the server is not in the unified list, we may need to add it. Servers + * are only added when the position, serverName and description are valid. + */ + else if (id == idPosition || id == idType || id == idDescription) + { + dir_ValidateAndAddNewServer(dir_ServerList, prefname); + } + + return NS_OK; +} + +// A pointer to the pref observer +static DirPrefObserver *prefObserver = nullptr; + +static nsresult DIR_GetDirServers() +{ + nsresult rv = NS_OK; + + if (!dir_ServerList) + { + /* we need to build the DIR_Server list */ + rv = DIR_GetServerPreferences(&dir_ServerList); + + /* Register the preference call back if necessary. */ + if (NS_SUCCEEDED(rv) && !prefObserver) + { + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + prefObserver = new DirPrefObserver(); + + if (!prefObserver) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(prefObserver); + + pbi->AddObserver(PREF_LDAP_SERVER_TREE_NAME, prefObserver, true); + } + } + return rv; +} + +nsTArray<DIR_Server*>* DIR_GetDirectories() +{ + if (!dir_ServerList) + DIR_GetDirServers(); + return dir_ServerList; +} + +DIR_Server* DIR_GetServerFromList(const char* prefName) +{ + DIR_Server* result = nullptr; + + if (!dir_ServerList) + DIR_GetDirServers(); + + if (dir_ServerList) + { + int32_t count = dir_ServerList->Length(); + int32_t i; + for (i = 0; i < count; ++i) + { + DIR_Server *server = dir_ServerList->ElementAt(i); + + if (server && strcmp(server->prefName, prefName) == 0) + { + result = server; + break; + } + } + } + return result; +} + +static nsresult SavePrefsFile() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + return pPref->SavePrefFile(nullptr); +} + +nsresult DIR_ShutDown() /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */ +{ + nsresult rv = SavePrefsFile(); + NS_ENSURE_SUCCESS(rv, rv); + + DIR_DeleteServerList(dir_ServerList); + dir_ServerList = nullptr; + + /* unregister the preference call back, if necessary. + * we need to do this as DIR_Shutdown() is called when switching profiles + * when using turbo. (see nsAbDirectoryDataSource::Observe()) + * When switching profiles, prefs get unloaded and then re-loaded + * we don't want our callback to get called for all that. + * We'll reset our callback the first time DIR_GetDirServers() is called + * after we've switched profiles. + */ + NS_IF_RELEASE(prefObserver); + + return NS_OK; +} + +nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir) +{ + if (dir_ServerList) + { + int32_t count = dir_ServerList->Length(); + int32_t i; + for (i = 0; i < count; i++) + { + DIR_Server* server = dir_ServerList->ElementAt(i); + if (server == pServer) + { + *hasDir = true; + return NS_OK; + } + } + } + *hasDir = false; + return NS_OK; +} + +nsresult DIR_AddNewAddressBook(const nsAString &dirName, + const nsACString &fileName, + const nsACString &uri, + DirectoryType dirType, + const nsACString &prefName, + DIR_Server** pServer) +{ + DIR_Server * server = (DIR_Server *) PR_Malloc(sizeof(DIR_Server)); + if (!server) + return NS_ERROR_OUT_OF_MEMORY; + + DIR_InitServer(server, dirType); + if (!dir_ServerList) + DIR_GetDirServers(); + if (dir_ServerList) + { + server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName)); + server->position = kDefaultPosition; // don't set position so alphabetic sort will happen. + + if (!fileName.IsEmpty()) + server->fileName = ToNewCString(fileName); + else if (dirType == PABDirectory) + DIR_SetFileName(&server->fileName, kPersonalAddressbook); + else if (dirType == LDAPDirectory) + DIR_SetFileName(&server->fileName, kMainLdapAddressBook); + + if (dirType != PABDirectory) { + if (!uri.IsEmpty()) + server->uri = ToNewCString(uri); + } + + if (!prefName.IsEmpty()) + server->prefName = ToNewCString(prefName); + + dir_ServerList->AppendElement(server); + + DIR_SavePrefsForOneServer(server); + + *pServer = server; + + // save new address book into pref file + return SavePrefsFile(); + } + return NS_ERROR_FAILURE; +} + +/***************************************************************************** + * Functions for creating DIR_Servers + */ +static void DIR_InitServer(DIR_Server *server, DirectoryType dirType) +{ + if (!server) { + NS_WARNING("DIR_InitServer: server parameter not initialized"); + return; + } + + memset(server, 0, sizeof(DIR_Server)); + server->position = kDefaultPosition; + server->uri = nullptr; + server->savingServer = false; + server->dirType = dirType; +} + +/* Function for setting the position of a server. Can be used to append, + * delete, or move a server in a server list. + * + * The third parameter specifies the new position the server is to occupy. + * The resulting position may differ depending on the lock state of the + * given server and other servers in the list. The following special values + * are supported: + * DIR_POS_APPEND - Appends the server to the end of the list. If the server + * is already in the list, does nothing. + * DIR_POS_DELETE - Deletes the given server from the list. Note that this + * does not cause the server structure to be freed. + * + * Returns true if the server list was re-sorted. + */ +static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position) + { + NS_ENSURE_TRUE(wholeList, false); + + int32_t i, count, num; + bool resort = false; + DIR_Server *s=nullptr; + + switch (position) { + case DIR_POS_APPEND: + /* Do nothing if the request is to append a server that is already + * in the list. + */ + count = wholeList->Length(); + for (i= 0; i < count; i++) + { + if ((s = wholeList->ElementAt(i)) != nullptr) + if (s == server) + return false; + } + /* In general, if there are any servers already in the list, set the + * position to the position of the last server plus one. If there + * are none, set it to position 1. + */ + if (count > 0) + { + s = wholeList->ElementAt(count - 1); + server->position = s->position + 1; + } + else + server->position = 1; + + wholeList->AppendElement(server); + break; + + case DIR_POS_DELETE: + /* Remove the prefs corresponding to the given server. If the prefName + * value is nullptr, the server has never been saved and there are no + * prefs to remove. + */ + if (server->prefName) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return false; + + pPref->DeleteBranch(server->prefName); + + // mark the server as deleted by setting its position to 0 + DIR_SetIntPref(server->prefName, "position", 0, -1); + } + + /* If the server is in the server list, remove it. + */ + num = wholeList->IndexOf(server); + if (num >= 0) + { + /* The list does not need to be re-sorted if the server is the + * last one in the list. + */ + count = wholeList->Length(); + if (num == count - 1) + { + wholeList->RemoveElementAt(num); + } + else + { + resort = true; + wholeList->RemoveElement(server); + } + } + break; + + default: + /* See if the server is already in the list. + */ + count = wholeList->Length(); + for (i= 0; i < count; i++) + { + if ((s = wholeList->ElementAt(i)) != nullptr) + if (s == server) + break; + } + + /* If the server is not in the list, add it to the beginning and re-sort. + */ + if (s == nullptr) + { + server->position = position; + wholeList->AppendElement(server); + resort = true; + } + + /* Don't re-sort if the server is already in the requested position. + */ + else if (server->position != position) + { + server->position = position; + wholeList->RemoveElement(server); + wholeList->AppendElement(server); + resort = true; + } + break; + } + + /* Make sure our position changes get saved back to prefs + */ + DIR_SaveServerPreferences(wholeList); + + return resort; +} + +/***************************************************************************** + * DIR_Server Callback Notification Functions + */ + +/* dir_matchServerPrefToServer + * + * This function finds the DIR_Server in the unified DIR_Server list to which + * the given preference string belongs. + */ +static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref) +{ + DIR_Server *server; + + int32_t count = wholeList->Length(); + int32_t i; + for (i = 0; i < count; i++) + { + if ((server = wholeList->ElementAt(i)) != nullptr) + { + if (server->prefName && PL_strstr(pref, server->prefName) == pref) + { + char c = pref[PL_strlen(server->prefName)]; + if (c == 0 || c == '.') + return server; + } + } + } + return nullptr; +} + +/* dir_ValidateAndAddNewServer + * + * This function verifies that the position, serverName and description values + * are set for the given prefName. If they are then it adds the server to the + * unified server list. + */ +static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname) +{ + bool rc = false; + + const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.'); + if (endname) + { + char *prefname = (char *)PR_Malloc(endname - fullprefname + 1); + if (prefname) + { + int32_t dirType; + char *t1 = nullptr, *t2 = nullptr; + + PL_strncpyz(prefname, fullprefname, endname - fullprefname + 1); + + dirType = DIR_GetIntPref(prefname, "dirType", -1); + if (dirType != -1 && + DIR_GetIntPref(prefname, "position", 0) != 0 && + (t1 = DIR_GetLocalizedStringPref(prefname, "description")) != nullptr) + { + if (dirType == PABDirectory || + (t2 = DIR_GetStringPref(prefname, "serverName", nullptr)) != nullptr) + { + DIR_Server *server = (DIR_Server *)PR_Malloc(sizeof(DIR_Server)); + if (server) + { + DIR_InitServer(server, (DirectoryType)dirType); + server->prefName = prefname; + DIR_GetPrefsForOneServer(server); + DIR_SetServerPosition(wholeList, server, server->position); + rc = true; + } + PR_FREEIF(t2); + } + PR_Free(t1); + } + else + PR_Free(prefname); + } + } + + return rc; +} + +static DIR_PrefId DIR_AtomizePrefName(const char *prefname) +{ + if (!prefname) + return idNone; + + DIR_PrefId rc = idNone; + + /* Skip the "ldap_2.servers.<server-name>." portion of the string. + */ + if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname) + { + prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.'); + if (!prefname) + return idNone; + else + prefname = prefname + 1; + } + + switch (prefname[0]) { + case 'd': + switch (prefname[1]) { + case 'e': /* description */ + rc = idDescription; + break; + case 'i': /* dirType */ + rc = idType; + break; + } + break; + + case 'f': + rc = idFileName; + break; + + case 'p': + switch (prefname[1]) { + case 'o': + switch (prefname[2]) { + case 's': /* position */ + rc = idPosition; + break; + } + break; + } + break; + + case 'u': /* uri */ + rc = idUri; + break; + } + + return rc; +} + +/***************************************************************************** + * Functions for destroying DIR_Servers + */ + +/* this function determines if the passed in server is no longer part of the of + the global server list. */ +static bool dir_IsServerDeleted(DIR_Server * server) +{ + return (server && server->position == 0); +} + +/* when the back end manages the server list, deleting a server just decrements its ref count, + in the old world, we actually delete the server */ +static void DIR_DeleteServer(DIR_Server *server) +{ + if (server) + { + /* when destroying the server check its clear flag to see if things need cleared */ +#ifdef XP_FileRemove + if (DIR_TestFlag(server, DIR_CLEAR_SERVER)) + { + if (server->fileName) + XP_FileRemove (server->fileName, xpAddrBookNew); + } +#endif /* XP_FileRemove */ + PR_Free(server->prefName); + PR_Free(server->description); + PR_Free(server->fileName); + PR_Free(server->uri); + PR_Free(server); + } +} + +nsresult DIR_DeleteServerFromList(DIR_Server *server) +{ + if (!server) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> dbPath; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + + if (NS_SUCCEEDED(rv)) + { + // close the database, as long as it isn't the special ones + // (personal addressbook and collected addressbook) + // which can never be deleted. There was a bug where we would slap in + // "abook.mab" as the file name for LDAP directories, which would cause a crash + // on delete of LDAP directories. this is just extra protection. + if (server->fileName && + strcmp(server->fileName, kPersonalAddressbook) && + strcmp(server->fileName, kCollectedAddressbook)) + { + nsCOMPtr<nsIAddrDatabase> database; + + rv = dbPath->AppendNative(nsDependentCString(server->fileName)); + NS_ENSURE_SUCCESS(rv, rv); + + // close file before delete it + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv) && addrDBFactory) + rv = addrDBFactory->Open(dbPath, false, true, getter_AddRefs(database)); + if (database) /* database exists */ + { + database->ForceClosed(); + rv = dbPath->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + nsTArray<DIR_Server*> *dirList = DIR_GetDirectories(); + DIR_SetServerPosition(dirList, server, DIR_POS_DELETE); + DIR_DeleteServer(server); + + return SavePrefsFile(); + } + + return NS_ERROR_NULL_POINTER; +} + +static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList) +{ + if (wholeList) + { + DIR_Server *server = nullptr; + + /* TBD: Send notifications? */ + int32_t count = wholeList->Length(); + int32_t i; + for (i = count - 1; i >=0; i--) + { + server = wholeList->ElementAt(i); + if (server != nullptr) + DIR_DeleteServer(server); + } + delete wholeList; + } +} + +/***************************************************************************** + * Functions for managing JavaScript prefs for the DIR_Servers + */ + +static int +comparePrefArrayMembers(const void* aElement1, const void* aElement2, void* aData) +{ + const char* element1 = *static_cast<const char* const *>(aElement1); + const char* element2 = *static_cast<const char* const *>(aElement2); + const uint32_t offset = *((const uint32_t*)aData); + + // begin the comparison at |offset| chars into the string - + // this avoids comparing the "ldap_2.servers." portion of every element, + // which will always remain the same. + return strcmp(element1 + offset, element2 + offset); +} + +static nsresult dir_GetChildList(const nsCString &aBranch, + uint32_t *aCount, char ***aChildList) +{ + uint32_t branchLen = aBranch.Length(); + + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefBranch) { + return NS_ERROR_FAILURE; + } + + nsresult rv = prefBranch->GetChildList(aBranch.get(), aCount, aChildList); + if (NS_FAILED(rv)) { + return rv; + } + + // traverse the list, and truncate all the descendant strings to just + // one branch level below the root branch. + for (uint32_t i = *aCount; i--; ) { + // The prefname we passed to GetChildList was of the form + // "ldap_2.servers." and we are returned the descendants + // in the form of "ldap_2.servers.servername.foo" + // But we want the prefbranch of the servername, so + // write a NUL character in to terminate the string early. + char *endToken = strchr((*aChildList)[i] + branchLen, '.'); + if (endToken) + *endToken = '\0'; + } + + if (*aCount > 1) { + // sort the list, in preparation for duplicate entry removal + NS_QuickSort(*aChildList, *aCount, sizeof(char*), comparePrefArrayMembers, &branchLen); + + // traverse the list and remove duplicate entries. + // we use two positions in the list; the current entry and the next + // entry; and perform a bunch of in-place ptr moves. so |cur| points + // to the last unique entry, and |next| points to some (possibly much + // later) entry to test, at any given point. we know we have >= 2 + // elements in the list here, so we just init the two counters sensibly + // to begin with. + uint32_t cur = 0; + for (uint32_t next = 1; next < *aCount; ++next) { + // check if the elements are equal or unique + if (!comparePrefArrayMembers(&((*aChildList)[cur]), &((*aChildList)[next]), &branchLen)) { + // equal - just free & increment the next element ptr + + free((*aChildList)[next]); + } else { + // cur & next are unique, so we need to shift the element. + // ++cur will point to the next free location in the + // reduced array (it's okay if that's == next) + (*aChildList)[++cur] = (*aChildList)[next]; + } + } + + // update the unique element count + *aCount = cur + 1; + } + + return NS_OK; +} + +static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return nullptr; + + nsCString value; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(value)))) + { + /* unfortunately, there may be some prefs out there which look like this */ + if (value.EqualsLiteral("(null)")) + { + if (defaultValue) + value = defaultValue; + else + value.Truncate(); + } + + if (value.IsEmpty()) + { + rv = pPref->GetCharPref(prefLocation.get(), getter_Copies(value)); + } + } + else + value = defaultValue; + + return ToNewCString(value); +} + +/** + * Get localized unicode string pref from properties file, convert into an UTF8 string + * since address book prefs store as UTF8 strings. So far there are 2 default + * prefs stored in addressbook.properties. + * "ldap_2.servers.pab.description" + * "ldap_2.servers.history.description" + */ +static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return nullptr; + + nsAutoCString prefLocation(prefRoot); + if (prefLeaf) { + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + } + + nsString wvalue; + nsCOMPtr<nsIPrefLocalizedString> locStr; + + rv = pPref->GetComplexValue(prefLocation.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr)); + if (NS_SUCCEEDED(rv)) + rv = locStr->ToString(getter_Copies(wvalue)); + + char *value = nullptr; + if (!wvalue.IsEmpty()) + { + value = ToNewCString(NS_ConvertUTF16toUTF8(wvalue)); + } + else + { + // In TB 2 only some prefs had chrome:// URIs. We had code in place that would + // only get the localized string pref for the particular address books that + // were built-in. + // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set, + // non-locked pref value if it is a chrome:// URI and will get the string + // value at that chrome URI. This breaks extensions/autoconfig that want to + // set default pref values and allow users to change directory names. + // + // Now we have to support this, and so if for whatever reason we fail to get + // the localized version, then we try and get the non-localized version + // instead. If the string value is empty, then we'll just get the empty value + // back here. + rv = pPref->GetCharPref(prefLocation.get(), &value); + if (NS_FAILED(rv)) + value = nullptr; + } + + return value; +} + +static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return defaultValue; + + int32_t value; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_FAILED(pPref->GetIntPref(prefLocation.get(), &value))) + value = defaultValue; + + return value; +} + +/* This will convert from the old preference that was a path and filename */ +/* to a just a filename */ +static void DIR_ConvertServerFileName(DIR_Server* pServer) +{ + char* leafName = pServer->fileName; + char* newLeafName = nullptr; +#if defined(XP_WIN) + /* jefft -- bug 73349 This is to allow users share same address book. + * It only works if the user specify a full path filename. + */ +#ifdef XP_FileIsFullPath + if (! XP_FileIsFullPath(leafName)) + newLeafName = XP_STRRCHR (leafName, '\\'); +#endif /* XP_FileIsFullPath */ +#else + newLeafName = strrchr(leafName, '/'); +#endif + pServer->fileName = newLeafName ? strdup(newLeafName + 1) : strdup(leafName); + if (leafName) PR_Free(leafName); +} + +/* This will generate a correct filename and then remove the path. + * Note: we are assuming that the default name is in the native + * filesystem charset. The filename will be returned as a UTF8 + * string. + */ +void DIR_SetFileName(char** fileName, const char* defaultName) +{ + if (!fileName) + return; + + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> dbPath; + + *fileName = nullptr; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + if (NS_SUCCEEDED(rv)) + { + rv = dbPath->AppendNative(nsDependentCString(defaultName)); + if (NS_SUCCEEDED(rv)) + { + rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664); + + nsAutoString realFileName; + rv = dbPath->GetLeafName(realFileName); + + if (NS_SUCCEEDED(rv)) + *fileName = ToNewUTF8String(realFileName); + } + } +} + +/**************************************************************** +Helper function used to generate a file name from the description +of a directory. Caller must free returned string. +An extension is not applied +*****************************************************************/ + +static char * dir_ConvertDescriptionToPrefName(DIR_Server * server) +{ +#define MAX_PREF_NAME_SIZE 25 + char * fileName = nullptr; + char fileNameBuf[MAX_PREF_NAME_SIZE]; + int32_t srcIndex = 0; + int32_t destIndex = 0; + int32_t numSrcBytes = 0; + const char * descr = nullptr; + if (server && server->description) + { + descr = server->description; + numSrcBytes = PL_strlen(descr); + while (srcIndex < numSrcBytes && destIndex < MAX_PREF_NAME_SIZE-1) + { + if (IS_DIGIT(descr[srcIndex]) || IS_ALPHA(descr[srcIndex])) + { + fileNameBuf[destIndex] = descr[srcIndex]; + destIndex++; + } + + srcIndex++; + } + + fileNameBuf[destIndex] = '\0'; /* zero out the last character */ + } + + if (destIndex) /* have at least one character in the file name? */ + fileName = strdup(fileNameBuf); + + return fileName; +} + + +void DIR_SetServerFileName(DIR_Server *server) +{ + char * tempName = nullptr; + const char * prefName = nullptr; + uint32_t numHeaderBytes = 0; + + if (server && (!server->fileName || !(*server->fileName)) ) + { + PR_FREEIF(server->fileName); // might be one byte empty string. + /* make sure we have a pref name...*/ + if (!server->prefName || !*server->prefName) + server->prefName = dir_CreateServerPrefName(server); + + /* set default personal address book file name*/ + if ((server->position == 1) && (server->dirType == PABDirectory)) + server->fileName = strdup(kPersonalAddressbook); + else + { + /* now use the pref name as the file name since we know the pref name + will be unique */ + prefName = server->prefName; + if (prefName && *prefName) + { + /* extract just the pref name part and not the ldap tree name portion from the string */ + numHeaderBytes = PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1; /* + 1 for the '.' b4 the name */ + if (PL_strlen(prefName) > numHeaderBytes) + tempName = strdup(prefName + numHeaderBytes); + + if (tempName) + { + server->fileName = PR_smprintf("%s%s", tempName, kABFileName_CurrentSuffix); + PR_Free(tempName); + } + } + } + + if (!server->fileName || !*server->fileName) /* when all else has failed, generate a default name */ + { + if (server->dirType == LDAPDirectory) + DIR_SetFileName(&(server->fileName), kMainLdapAddressBook); /* generates file name with an ldap prefix */ + else + DIR_SetFileName(&(server->fileName), kPersonalAddressbook); + } + } +} + +static char *dir_CreateServerPrefName (DIR_Server *server) +{ + /* we are going to try to be smart in how we generate our server + pref name. We'll try to convert the description into a pref name + and then verify that it is unique. If it is unique then use it... */ + char * leafName = dir_ConvertDescriptionToPrefName(server); + char * prefName = nullptr; + bool isUnique = false; + + if (!leafName || !*leafName) + { + // we need to handle this in case the description has no alphanumeric chars + // it's very common for cjk users + leafName = strdup("_nonascii"); + } + + if (leafName) + { + int32_t uniqueIDCnt = 0; + char **children = nullptr; + /* we need to verify that this pref string name is unique */ + prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s", leafName); + isUnique = false; + uint32_t prefCount; + nsresult rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."), + &prefCount, &children); + if (NS_SUCCEEDED(rv)) + { + while (!isUnique && prefName) + { + isUnique = true; /* now flip the logic and assume we are unique until we find a match */ + for (uint32_t i = 0; i < prefCount && isUnique; ++i) + { + if (!PL_strcasecmp(children[i], prefName)) /* are they the same branch? */ + isUnique = false; + } + if (!isUnique) /* then try generating a new pref name and try again */ + { + PR_smprintf_free(prefName); + prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s_%d", leafName, ++uniqueIDCnt); + } + } /* if we have a list of pref Names */ + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children); + } /* while we don't have a unique name */ + + // fallback to "user_directory_N" form if we failed to verify + if (!isUnique && prefName) + { + PR_smprintf_free(prefName); + prefName = nullptr; + } + + PR_Free(leafName); + + } /* if leafName */ + + if (!prefName) /* last resort if we still don't have a pref name is to use user_directory string */ + return PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".user_directory_%d", ++dir_UserId); + else + return prefName; +} + +static void DIR_GetPrefsForOneServer(DIR_Server *server) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + char *prefstring = server->prefName; + + // this call fills in tempstring with the position pref, and + // we then check to see if it's locked. + server->position = DIR_GetIntPref (prefstring, "position", kDefaultPosition); + + // For default address books, this will get the name from the chrome + // file referenced, for other address books it'll just retrieve it from prefs + // as normal. + server->description = DIR_GetLocalizedStringPref(prefstring, "description"); + + server->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", LDAPDirectory); + + server->fileName = DIR_GetStringPref (prefstring, "filename", ""); + // if we don't have a file name try and get one + if (!server->fileName || !*(server->fileName)) + DIR_SetServerFileName (server); + if (server->fileName && *server->fileName) + DIR_ConvertServerFileName(server); + + // the string "s" is the default uri ( <scheme> + "://" + <filename> ) + nsCString s((server->dirType == PABDirectory || server->dirType == MAPIDirectory) ? +#if defined(MOZ_LDAP_XPCOM) + kMDBDirectoryRoot : kLDAPDirectoryRoot); +#else + // Fallback to the all directory root in the non-ldap enabled case. + kMDBDirectoryRoot : kAllDirectoryRoot); +#endif + s.Append (server->fileName); + server->uri = DIR_GetStringPref (prefstring, "uri", s.get ()); +} + +static nsresult dir_GetPrefs(nsTArray<DIR_Server*> **list) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + (*list) = new nsTArray<DIR_Server*>(); + if (!(*list)) + return NS_ERROR_OUT_OF_MEMORY; + + char **children; + uint32_t prefCount; + + rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."), + &prefCount, &children); + if (NS_FAILED(rv)) + return rv; + + /* TBD: Temporary code to read broken "ldap" preferences tree. + * Remove line with if statement after M10. + */ + if (dir_UserId == 0) + pPref->GetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", &dir_UserId); + + for (uint32_t i = 0; i < prefCount; ++i) + { + DIR_Server *server; + + server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server)); + if (server) + { + DIR_InitServer(server); + server->prefName = strdup(children[i]); + DIR_GetPrefsForOneServer(server); + if (server->description && server->description[0] && + ((server->dirType == PABDirectory || + server->dirType == MAPIDirectory || + server->dirType == FixedQueryLDAPDirectory || // this one might go away + server->dirType == LDAPDirectory))) + { + if (!dir_IsServerDeleted(server)) + { + (*list)->AppendElement(server); + } + else + DIR_DeleteServer(server); + } + else + { + DIR_DeleteServer(server); + } + } + } + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children); + + return NS_OK; +} + +// I don't think we care about locked positions, etc. +void DIR_SortServersByPosition(nsTArray<DIR_Server*> *serverList) +{ + int i, j; + DIR_Server *server; + + int count = serverList->Length(); + for (i = 0; i < count - 1; i++) + { + for (j = i + 1; j < count; j++) + { + if (serverList->ElementAt(j)->position < serverList->ElementAt(i)->position) + { + server = serverList->ElementAt(i); + serverList->ReplaceElementAt(i, serverList->ElementAt(j)); + serverList->ReplaceElementAt(j, server); + } + } + } +} + +static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list) +{ + nsresult err; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &err)); + if (NS_FAILED(err)) + return err; + + int32_t version = -1; + nsTArray<DIR_Server*> *newList = nullptr; + + /* Update the ldap list version and see if there are old prefs to migrate. */ + err = pPref->GetIntPref(PREF_LDAP_VERSION_NAME, &version); + NS_ENSURE_SUCCESS(err, err); + + /* Find the new-style "ldap_2.servers" tree in prefs */ + err = dir_GetPrefs(&newList); + + if (version < kCurrentListVersion) + { + pPref->SetIntPref(PREF_LDAP_VERSION_NAME, kCurrentListVersion); + } + + DIR_SortServersByPosition(newList); + + *list = newList; + + return err; +} + +static void DIR_SetStringPref(const char *prefRoot, const char *prefLeaf, const char *value, const char *defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCString defaultPref; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(defaultPref)))) + { + /* If there's a default pref, just set ours in and let libpref worry + * about potential defaults in all.js + */ + if (value) /* added this check to make sure we have a value before we try to set it..*/ + rv = pPref->SetCharPref (prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + 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. + */ + nsCString userPref; + if (NS_SUCCEEDED(pPref->GetCharPref (prefLocation.get(), getter_Copies(userPref)))) + { + if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue)) + rv = pPref->SetCharPref (prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + else + { + if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue)) + rv = pPref->SetCharPref (prefLocation.get(), value); + } + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetStringPref"); +} + +static void DIR_SetLocalizedStringPref +(const char *prefRoot, const char *prefLeaf, const char *value) +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return; + + nsAutoCString prefLocation(prefRoot); + prefLocation.Append('.'); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefSvc->GetBranch(prefLocation.get(), getter_AddRefs(prefBranch)); + if (NS_FAILED(rv)) + return; + + nsString wvalue; + nsCOMPtr<nsIPrefLocalizedString> newStr( + do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + { + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not createInstance in DIR_SetLocalizedStringPref"); + return; + } + + NS_ConvertUTF8toUTF16 newValue(value); + + rv = newStr->SetData(newValue.get()); + if (NS_FAILED(rv)) + { + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref data in DIR_SetLocalizedStringPref"); + return; + } + nsCOMPtr<nsIPrefLocalizedString> locStr; + if (NS_SUCCEEDED(prefBranch->GetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(locStr)))) + { + nsString data; + locStr->GetData(getter_Copies(data)); + + // Only set the pref if the data values aren't the same (i.e. don't change + // unnecessarily, but also, don't change in the case that its a chrome + // string pointing to the value we want to set the pref to). + if (newValue != data) + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + } + else { + // No value set, but check the default pref branch (i.e. user may have + // cleared the pref) + nsCOMPtr<nsIPrefBranch> dPB; + rv = prefSvc->GetDefaultBranch(prefLocation.get(), + getter_AddRefs(dPB)); + + if (NS_SUCCEEDED(dPB->GetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(locStr)))) + { + // Default branch has a value + nsString data; + locStr->GetData(getter_Copies(data)); + + if (newValue != data) + // If the vales aren't the same, set the data on the main pref branch + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + else + // Else if they are, kill the user pref + rv = prefBranch->ClearUserPref(prefLeaf); + } + else + // No values set anywhere, so just set the pref + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetLocalizedStringPref"); +} + + +static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + int32_t defaultPref; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &defaultPref))) + { + /* solve the problem where reordering user prefs must override default prefs */ + rv = pPref->SetIntPref(prefLocation.get(), value); + } + else + { + int32_t userPref; + if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &userPref))) + { + if (value != defaultValue) + rv = pPref->SetIntPref(prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + else + { + if (value != defaultValue) + rv = pPref->SetIntPref(prefLocation.get(), value); + } + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetIntPref"); +} + +void DIR_SavePrefsForOneServer(DIR_Server *server) +{ + if (!server) + return; + + char *prefstring; + + if (server->prefName == nullptr) + server->prefName = dir_CreateServerPrefName(server); + prefstring = server->prefName; + + server->savingServer = true; + + DIR_SetIntPref (prefstring, "position", server->position, kDefaultPosition); + + // Only save the non-default address book name + DIR_SetLocalizedStringPref(prefstring, "description", server->description); + + DIR_SetStringPref(prefstring, "filename", server->fileName, ""); + DIR_SetIntPref(prefstring, "dirType", server->dirType, LDAPDirectory); + + if (server->dirType != PABDirectory) + DIR_SetStringPref(prefstring, "uri", server->uri, ""); + + server->savingServer = false; +} + +static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList) +{ + if (wholeList) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + NS_WARNING("DIR_SaveServerPreferences: Failed to get the pref service\n"); + return; + } + + int32_t i; + int32_t count = wholeList->Length(); + DIR_Server *server; + + for (i = 0; i < count; i++) + { + server = wholeList->ElementAt(i); + if (server) + DIR_SavePrefsForOneServer(server); + } + pPref->SetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", dir_UserId); + } +} |