mailnews/addrbook/src/nsDirPrefs.cpp
author Geoff Lankow <geoff@darktrojan.net>
Wed, 28 Aug 2019 12:32:06 +1200
changeset 79099 aa8107b682ba6fd44e9b91e49ac8846d84bf5abb
parent 79031 fc23924872465f081c982627b664258eed5537ec
child 80140 7d63ed41d20c46fbbc7bce5ed33b30d7a15e924b
permissions -rw-r--r--
Bug 1576525 - Use JS address book provider for the default address books in new profiles; r=mkmelin

/* -*- 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 "nsString.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 RefPtr<DirPrefObserver> prefObserver;

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

      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.
   */
  prefObserver = nullptr;

  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, kMDBAddressBook);
    else if (dirType == LDAPDirectory)
      DIR_SetFileName(&server->fileName, kMainLdapAddressBook);
    else if (dirType == JSDirectory)
      DIR_SetFileName(&server->fileName, kJSAddressBook);

    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 && server->dirType != JSDirectory &&
        strcmp(server->fileName, "abook.mab") &&
        strcmp(server->fileName, "history.mab")) {
      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
 */

class MOZ_STACK_CLASS PrefArrayMemberComparator {
 public:
  explicit PrefArrayMemberComparator(uint32_t aOffset) : mOffset(aOffset) {}

  // begin the comparison at |mOffset| chars into the string -
  // this avoids comparing the "ldap_2.servers." portion of every element,
  // which will always remain the same.
  bool Equals(const nsCString &aFirst, const nsCString &aSecond) const {
    return Substring(aFirst, mOffset) == Substring(aSecond, mOffset);
  }

  bool LessThan(const nsCString &aFirst, const nsCString &aSecond) const {
    return Substring(aFirst, mOffset) < Substring(aSecond, mOffset);
  }

 private:
  uint32_t mOffset;
};

static nsresult dir_GetChildList(const nsCString &aBranch,
                                 nsTArray<nsCString> &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(), 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 (auto &child : aChildList) {
    // 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
    // terminate the string at the first '.' after our branchname.
    int32_t dotPos = child.FindChar('.', branchLen);
    if (dotPos != kNotFound) {
      child.Truncate(dotPos);
    }
  }

  if (aChildList.Length() > 1) {
    // sort the list, in preparation for duplicate entry removal
    PrefArrayMemberComparator comparator(branchLen);
    aChildList.Sort(comparator);

    // 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 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 < aChildList.Length(); ++next) {
      // check if the elements are equal or unique
      if (comparator.Equals(aChildList[cur], aChildList[next])) {
        ;  // equal - just increment the next element ptr
      } 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
    aChildList.SetLength(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(), 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(), 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));

  nsCString value;
  if (!wvalue.IsEmpty()) {
    value = 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.Truncate();
  }

  return moz_xstrdup(value.get());
}

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 == JSDirectory))
      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 == PABDirectory) {
        DIR_SetFileName(&(server->fileName), kMDBAddressBook);
      } else 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;
    nsTArray<nsCString> children;
    /* we need to verify that this pref string name is unique */
    prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME ".%s", leafName);
    isUnique = false;
    nsresult rv = dir_GetChildList(
        NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."), 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 (auto &child : children) {
          if (child.EqualsIgnoreCase(
                  prefName)) { /* are they the same branch? */
            isUnique = false;
            break;
          }
        }
        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 */
    }   /* 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;
  switch (server->dirType) {
    case PABDirectory:
    case MAPIDirectory:
      s = kMDBDirectoryRoot;
      break;
    case JSDirectory:
      s = kJSDirectoryRoot;
      break;
    default:
#if defined(MOZ_LDAP_XPCOM)
      s = kLDAPDirectoryRoot;
#else
      // Fallback to the all directory root in the non-ldap enabled case.
      s = kAllDirectoryRoot;
#endif
      break;
  }
  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;

  nsTArray<nsCString> children;

  rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."),
                        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 (auto &child : children) {
    DIR_Server *server;

    server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server));
    if (server) {
      DIR_InitServer(server);
      server->prefName = strdup(child.get());
      DIR_GetPrefsForOneServer(server);
      if (server->description && server->description[0] &&
          ((server->dirType == PABDirectory ||
            server->dirType == MAPIDirectory ||
            server->dirType == JSDirectory ||
            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);
      }
    }
  }

  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(), 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(), nsDependentCString(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(), userPref))) {
      if (value && (defaultValue ? PL_strcasecmp(value, defaultValue)
                                 : value != defaultValue))
        rv = pPref->SetCharPref(prefLocation.get(), nsDependentCString(value));
      else
        rv = pPref->ClearUserPref(prefLocation.get());
    } else {
      if (value && (defaultValue ? PL_strcasecmp(value, defaultValue)
                                 : value != defaultValue))
        rv = pPref->SetCharPref(prefLocation.get(), nsDependentCString(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);
  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(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(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);
  }
}