/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is The Browser Profile Migrator.
 *
 * The Initial Developer of the Original Code is Ben Goodger.
 * Portions created by the Initial Developer are Copyright (C) 2004
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Ben Goodger <ben@bengoodger.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsBrowserProfileMigratorUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIPrefService.h"
#include "nsISupportsPrimitives.h"
#include "nsNetUtil.h"
#include "nsFirefoxProfileMigrator.h"
#include "nsIProfileMigrator.h"
#include "nsIRDFService.h"
#include "nsIExtensionManager.h"
#include "nsILoginInfo.h"
#include "nsILoginManager.h"
#include "nsIPrefLocalizedString.h"
#include "nsVoidArray.h"
#include "nsINavBookmarksService.h"
#include "nsToolkitCompsCID.h"

#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"

#include "flockIIndexer.h"

#include "nsINIParser.h"

#ifdef XP_WIN32
typedef void *HKEY;
#include "nsIWindowsRegKey.h"
#endif

#define MIGRATION_BUNDLE "chrome://browser/locale/migration/migration.properties"

class FolderMap {
public:
  FolderMap() { mHead = 0; };
  ~FolderMap();
  void Insert(PRInt64 aFromFolder, PRInt64 aToFolder);
  PRInt64 Find(PRInt64 aFromFolder);
private:
  struct fmEntry {
    struct fmEntry * next;
    PRInt64 from;
    PRInt64 to;
  };
  struct fmEntry * mHead;
};

void FolderMap::Insert(PRInt64 aFromFolder, PRInt64 aToFolder) {
  if (!Find(aFromFolder)) {
    struct fmEntry * newEntry = new struct fmEntry;
    if (newEntry) {
      newEntry->next = mHead;
      newEntry->from = aFromFolder;
      newEntry->to = aToFolder;
      mHead = newEntry;
    }
  }
}

PRInt64 FolderMap::Find(PRInt64 aFromFolder) {
  struct fmEntry * curr = mHead;
  while (curr) {
    if (curr->from == aFromFolder) {
      return curr->to;
    }
    curr = curr->next;
  }
  return 0;
}

FolderMap::~FolderMap() {
  struct fmEntry * temp;
  while (mHead) {
    temp = mHead->next;
    delete mHead;
    mHead = temp;
  }
}

///////////////////////////////////////////////////////////////////////////////
// nsFirefoxProfileMigrator

#define FILE_NAME_COOKIES_FF3     NS_LITERAL_STRING("cookies.sqlite")
#define FILE_NAME_COOKIES         NS_LITERAL_STRING("cookies.txt")
#define FILE_NAME_HISTORY_FF3     NS_LITERAL_STRING("history.sqlite")
#define FILE_NAME_HISTORY         NS_LITERAL_STRING("history.dat")
#define FILE_NAME_FORMHISTORY_FF3 NS_LITERAL_STRING("formhistory.sqlite")
#define FILE_NAME_FORMHISTORY     NS_LITERAL_STRING("formhistory.dat")
#define FILE_NAME_DOWNLOADS_FF3   NS_LITERAL_STRING("downloads.sqlite")
#define FILE_NAME_DOWNLOADS       NS_LITERAL_STRING("downloads.rdf")
#define FILE_NAME_BOOKMARKS_FF3   NS_LITERAL_STRING("places.sqlite")
#define FILE_NAME_BOOKMARKS       NS_LITERAL_STRING("bookmarks.html")
#define FILE_NAME_SITEPERM_FF3    NS_LITERAL_STRING("permissions.sqlite")
#define FILE_NAME_SITEPERM        NS_LITERAL_STRING("hostperm.1")
#define FILE_NAME_SITEPERM_FF1_5  NS_LITERAL_STRING("cookperm.txt")
#define FILE_NAME_CERT8DB         NS_LITERAL_STRING("cert8.db")
#define FILE_NAME_KEY3DB          NS_LITERAL_STRING("key3.db")
#define FILE_NAME_SECMODDB        NS_LITERAL_STRING("secmod.db")
#define FILE_NAME_LOCALSTORE      NS_LITERAL_STRING("localstore.rdf")
#define FILE_NAME_MIMETYPES       NS_LITERAL_STRING("mimeTypes.rdf")
#define FILE_NAME_PREFS           NS_LITERAL_STRING("prefs.js")
#define FILE_NAME_USER_PREFS      NS_LITERAL_STRING("user.js")
#define FILE_NAME_USERCHROME      NS_LITERAL_STRING("userChrome.css")
#define FILE_NAME_USERCONTENT     NS_LITERAL_STRING("userContent.css")
#define DIR_NAME_CHROME           NS_LITERAL_STRING("chrome")
#define DIR_NAME_EXTENSIONS       NS_LITERAL_STRING("extensions")

#define PREF_BROWSER_IMPORT_BOOKMARKS "browser.places.importBookmarksHTML"
#define LOGIN_INFO                    "@mozilla.org/login-manager/loginInfo;1"

#define NETSCAPE8_FILENAME            "netscape.exe"

NS_IMPL_ISUPPORTS1(nsFirefoxProfileMigrator, nsIBrowserProfileMigrator)

nsFirefoxProfileMigrator::nsFirefoxProfileMigrator()
{
  mObserverService = do_GetService("@mozilla.org/observer-service;1");
}

nsFirefoxProfileMigrator::~nsFirefoxProfileMigrator()
{
}

PRBool
nsFirefoxProfileMigrator::SourceFileExists(const nsAString& aSourceFileName)
{
  nsCOMPtr<nsIFile> sourceFile;
  mSourceProfile->Clone(getter_AddRefs(sourceFile));
  sourceFile->Append(aSourceFileName);
  PRBool exists = PR_FALSE;
  sourceFile->Exists(&exists);
  return exists;
 }

///////////////////////////////////////////////////////////////////////////////
// nsIBrowserProfileMigrator

NS_IMETHODIMP
nsFirefoxProfileMigrator::Migrate(PRUint16 aItems,
                                  nsIProfileStartup* aStartup,
                                  const PRUnichar* aProfile)
{
  nsresult rv = NS_OK;
  PRBool aReplace = aStartup ? PR_TRUE : PR_FALSE;

  if (!mTargetProfile) {
    GetProfilePath(aStartup, mTargetProfile);
    if (!mTargetProfile) {
      return NS_ERROR_FAILURE;
    }
  }

  if (!mSourceProfile) {
    GetSourceProfile(aProfile);
  }

  NOTIFY_OBSERVERS(MIGRATION_STARTED, nsnull);

  // Settings are now migrated properly (only needed on first run)
  COPY_DATA(CopyPreferences,  aReplace, nsIBrowserProfileMigrator::SETTINGS);
  // Will merge Cookies on subsequent imports.
  COPY_DATA(CopyCookies,     aReplace, nsIBrowserProfileMigrator::COOKIES);      
  COPY_DATA(CopyHistory,     aReplace, nsIBrowserProfileMigrator::HISTORY);
  COPY_DATA(CopyFormHistory, aReplace, nsIBrowserProfileMigrator::FORMDATA);
  COPY_DATA(CopyOtherData,   aReplace, nsIBrowserProfileMigrator::OTHERDATA);

  if (aReplace) {
    // Try copying FF3 sqlite bookmarks
    CopyFile(FILE_NAME_BOOKMARKS_FF3, FILE_NAME_BOOKMARKS_FF3);
    if (aItems & nsIBrowserProfileMigrator::SETTINGS || 
        aItems & nsIBrowserProfileMigrator::COOKIES || 
        aItems & nsIBrowserProfileMigrator::PASSWORDS ||
        !aItems) {
      // Permissions (Images, Cookies, Popups)
      rv |= CopyFile(FILE_NAME_SITEPERM_FF3, FILE_NAME_SITEPERM_FF3);
      rv |= CopyFile(FILE_NAME_SITEPERM, FILE_NAME_SITEPERM);
      rv |= CopyFile(FILE_NAME_SITEPERM_FF1_5, FILE_NAME_SITEPERM_FF1_5);
    }
  }

  // Enable access to ProfD for dependent services (ie favorites)
  if (aStartup) {
    rv = aStartup->DoStartup();
    NS_ENSURE_SUCCESS(rv, rv);
  }

  COPY_DATA(CopyPasswords,   aReplace, nsIBrowserProfileMigrator::PASSWORDS);
  COPY_DATA(CopyBookmarks,   aReplace, nsIBrowserProfileMigrator::BOOKMARKS);

  // tell indexer to rebuild index for new history and favorites
  nsCOMPtr<flockIIndexer> indexer(do_GetService("@flock.com/indexer;1", &rv));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = indexer->RebuildIndex();
  if (NS_SUCCEEDED(rv)) {
    // Added in an extra import for the yahoo toolbar.
    MigrateYahooToolbar();

    NOTIFY_OBSERVERS(MIGRATION_ENDED, nsnull);
  }
  return rv;
}

NS_IMETHODIMP
nsFirefoxProfileMigrator::GetMigrateData(const PRUnichar* aProfile, 
                                         PRBool aReplace, 
                                         PRUint16* aResult)
{
  *aResult = 0;
  if (!mSourceProfile) {
    GetSourceProfile(aProfile);
    if (!mSourceProfile)
      return NS_ERROR_FILE_NOT_FOUND;
  }

  /*
  Struct values and semantics:

  PRUnichar* fileName : Name of file on disk, to check whether source exists.
  PRUint32 sourceFlag : The bit to set in aResult if element should be migrated
  PRBool replaceOnly  : If TRUE, omit element unless aReplace is also TRUE.
  */
  MigrationData data[] = { 
                           { ToNewUnicode(FILE_NAME_PREFS),
                             nsIBrowserProfileMigrator::SETTINGS,
                             PR_TRUE },
                           { ToNewUnicode(FILE_NAME_USER_PREFS),
                             nsIBrowserProfileMigrator::SETTINGS,
                             PR_TRUE },
                           { ToNewUnicode(FILE_NAME_COOKIES_FF3),
                             nsIBrowserProfileMigrator::COOKIES,
                             PR_TRUE }, // TODO: Implement merge from sqlite
                           { ToNewUnicode(FILE_NAME_COOKIES),
                             nsIBrowserProfileMigrator::COOKIES,
                             SourceFileExists(FILE_NAME_COOKIES_FF3) },
                           { ToNewUnicode(FILE_NAME_HISTORY_FF3),
                             nsIBrowserProfileMigrator::HISTORY,
                             PR_TRUE }, // TODO: Implement merge from sqlite
                           { ToNewUnicode(FILE_NAME_HISTORY),
                             nsIBrowserProfileMigrator::HISTORY,
                             PR_TRUE }, // TODO: Implement merge.
                           { ToNewUnicode(FILE_NAME_BOOKMARKS),
                             nsIBrowserProfileMigrator::BOOKMARKS,
                             PR_FALSE },
                           { ToNewUnicode(FILE_NAME_FORMHISTORY_FF3),
                             nsIBrowserProfileMigrator::FORMDATA,
                             PR_TRUE }, // TODO: Implement merge from sqlite
                           { ToNewUnicode(FILE_NAME_FORMHISTORY),
                             nsIBrowserProfileMigrator::FORMDATA,
                             PR_TRUE },   // can't use MorkReader to merge
                           { ToNewUnicode(FILE_NAME_MIMETYPES),
                             nsIBrowserProfileMigrator::OTHERDATA,
                             PR_TRUE },
                           { ToNewUnicode(FILE_NAME_USERCHROME),
                             nsIBrowserProfileMigrator::OTHERDATA,
                             PR_TRUE },
                           { ToNewUnicode(FILE_NAME_USERCONTENT),
                             nsIBrowserProfileMigrator::OTHERDATA,
                             PR_TRUE },
                           { ToNewUnicode(FILE_NAME_DOWNLOADS_FF3),
                             nsIBrowserProfileMigrator::OTHERDATA,
                             PR_TRUE } };

  // Frees file name strings allocated above.
  GetMigrateDataFromArray(data, sizeof(data)/sizeof(MigrationData), 
                          aReplace, mSourceProfile, aResult);

  if (!aReplace) {
    nsAutoString fileName;
    GetAppFileName(fileName);
    // can't merge passwords from FF and NS9
    if (!fileName.EqualsLiteral(NETSCAPE8_FILENAME)) {
      return NS_OK;
    }
  }

  // Now locate the passwords and save the file name so we don't have to
  // get it again later.
  if (SetSourceSignonFileName(aReplace)) {
    *aResult |= nsIBrowserProfileMigrator::PASSWORDS;
  }
  return NS_OK;
}

PRBool nsFirefoxProfileMigrator::SetSourceSignonFileName(PRBool aReplace)
{
  mSourceSignonFileName.AssignLiteral("signons3.txt");
  if (SourceFileExists(mSourceSignonFileName)) {
    return PR_TRUE;
  }
  mSourceSignonFileName.AssignLiteral("signons2.txt");
  if (SourceFileExists(mSourceSignonFileName)) {
    return PR_TRUE;
  }
  // At last try FF 1.5
  nsCString signonsFileName;
  GetSignonFileName(aReplace, getter_Copies(signonsFileName));
  mSourceSignonFileName = NS_ConvertUTF8toUTF16(signonsFileName);
  return SourceFileExists(mSourceSignonFileName);
}

NS_IMETHODIMP
nsFirefoxProfileMigrator::GetSourceExists(PRBool* aResult)
{
  nsCOMPtr<nsISupportsArray> profiles;
  GetSourceProfiles(getter_AddRefs(profiles));

  if (profiles) { 
    PRUint32 count;
    profiles->Count(&count);
    *aResult = count > 0;
  } else {
    *aResult = PR_FALSE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsFirefoxProfileMigrator::GetSourceHasMultipleProfiles(PRBool* aResult)
{
  nsCOMPtr<nsISupportsArray> profiles;
  GetSourceProfiles(getter_AddRefs(profiles));

  if (profiles) {
    PRUint32 count;
    profiles->Count(&count);
    *aResult = count > 1;
  } else {
    *aResult = PR_FALSE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsFirefoxProfileMigrator::GetSourceProfiles(nsISupportsArray** aResult)
{
  if (!mProfileNames && !mProfileLocations) {
    mProfileNames = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
    mProfileLocations = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
    NS_ENSURE_TRUE(mProfileNames && mProfileLocations, NS_ERROR_UNEXPECTED);

    // Fills mProfileNames and mProfileLocations
    FillProfileDataFromRegistry();
  }
  NS_IF_ADDREF(*aResult = mProfileNames);
  return NS_OK;
}

NS_IMETHODIMP
nsFirefoxProfileMigrator::GetSourceHomePageURL(nsACString& aResult)
{
  nsresult rv;
  nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
  psvc->ResetPrefs();

  nsCOMPtr<nsIFile> sourcePrefsFile;
  mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile));
  sourcePrefsFile->Append(FILE_NAME_PREFS);
  psvc->ReadUserPrefs(sourcePrefsFile);

  static const char kHomePage[] = "browser.startup.homepage";
  nsCString homePage;
  nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc));
  rv = branch->GetCharPref(kHomePage, getter_Copies(homePage));
  NS_ENSURE_SUCCESS(rv, rv);
  
  if (homePage.EqualsLiteral("resource:/browserconfig.properties")) {
    nsAutoString nativePath, fileName;

    rv = GetAppFileName(fileName);
    NS_ENSURE_SUCCESS(rv, rv);

#ifdef XP_WIN32
    // The preference for browser.startup.homepage may be a resource
    // ie: resource:///browserconfig.properties
    // We need to load up browserconfig.properties and read in the line with
    // "browser.startup.homepage=..."
    // First we need the application install location (since that is where
    // browserconfig.properties is located)
    nsAutoString appPathKey;
    appPathKey.AssignLiteral("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
    appPathKey.Append(fileName);

    nsCOMPtr<nsIWindowsRegKey> regKey;
    regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1");
    rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, appPathKey,
                      nsIWindowsRegKey::ACCESS_READ);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = regKey->ReadStringValue(NS_LITERAL_STRING("Path"), nativePath);
    NS_ENSURE_SUCCESS(rv, rv);

    nativePath.AppendLiteral("\\browserconfig.properties");
    nativePath.Insert(NS_LITERAL_STRING("file://"), 0);

#elif defined(XP_MACOSX)
    nsAutoString appName(fileName);
    appName.AppendLiteral(".app");

    // To find the application directory associated with this profile, we
    // look in two places:
    // /Applications/<appname>/Contents/MacOS
    // ~/Desktop/<appname>/Contents/MacOS
    nsCOMPtr<nsIProperties> fileLocator;
    fileLocator = do_GetService("@mozilla.org/file/directory_service;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsILocalFile> appDir;
    rv = fileLocator->Get(NS_OSX_LOCAL_APPLICATIONS_DIR,
                          NS_GET_IID(nsILocalFile),
                          getter_AddRefs(appDir));
    NS_ENSURE_SUCCESS(rv, rv);

    appDir->Append(appName);

    PRBool exists = PR_FALSE;
    appDir->Exists(&exists);
    if (!exists) {
      rv = fileLocator->Get(NS_OSX_USER_DESKTOP_DIR,
                            NS_GET_IID(nsILocalFile),
                            getter_AddRefs(appDir));
      NS_ENSURE_SUCCESS(rv, rv);

      appDir->Append(appName);

      appDir->Exists(&exists);
      if (!exists) {
        nsAutoString volumePath;

        volumePath.AppendLiteral("/Volumes/");
        volumePath.Append(fileName);
        volumePath.AppendLiteral("/");
        volumePath.Append(appName);

        appDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
        NS_ENSURE_SUCCESS(rv, rv);

        appDir->InitWithPath(volumePath);
      }
    }

    appDir->GetPath(nativePath);
    nativePath.AppendLiteral("/Contents/MacOS/browserconfig.properties");
    nativePath.Insert(NS_LITERAL_STRING("file://"), 0);
#else
    aResult.Assign(homePage);
    return NS_OK;
#endif
    // Use the StringBundleService to parse the browserconfig.properties file
    nsCOMPtr<nsIStringBundleService> bundleService;
    bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIStringBundle> entities;
    rv = bundleService->CreateBundle(NS_ConvertUTF16toUTF8(nativePath).get(),
                                     getter_AddRefs(entities));
    NS_ENSURE_SUCCESS(rv, rv);
    nsString value;
    rv = entities->GetStringFromName(NS_ConvertUTF8toUTF16(kHomePage).get(),
                                     getter_Copies(value));
    NS_ENSURE_SUCCESS(rv, rv);
    CopyUTF16toUTF8(nsAutoString(value), aResult);
  } else {
    aResult.Assign(homePage);
  }
  
  return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
// nsFirefoxProfileMigrator

nsresult
nsFirefoxProfileMigrator::GetSourceProfile(const PRUnichar* aProfile)
{
  PRUint32 count;
  mProfileNames->Count(&count);
  for (PRUint32 i = 0; i < count; ++i) {
    nsCOMPtr<nsISupportsString> str;
    mProfileNames->QueryElementAt(i, NS_GET_IID(nsISupportsString),
                                  getter_AddRefs(str));
    nsString profileName;
    str->GetData(profileName);
    if (profileName.Equals(aProfile)) {
      mProfileLocations->QueryElementAt(i, NS_GET_IID(nsILocalFile),
                                        getter_AddRefs(mSourceProfile));
      break;
    }
  }

  return NS_OK;
}

nsresult nsFirefoxProfileMigrator::AppendNativeProfilePath(nsILocalFile *aFile)
{
#ifdef XP_WIN
  aFile->Append(NS_LITERAL_STRING("Mozilla"));
  aFile->Append(NS_LITERAL_STRING("Firefox"));
#elif defined(XP_MACOSX)
  aFile->Append(NS_LITERAL_STRING("Application Support"));
  aFile->Append(NS_LITERAL_STRING("Firefox"));
#elif defined(XP_UNIX)
  aFile->Append(NS_LITERAL_STRING(".mozilla"));
  aFile->Append(NS_LITERAL_STRING("firefox"));
#endif
  return NS_OK;
}

nsresult
nsFirefoxProfileMigrator::FillProfileDataFromRegistry()
{
  nsCOMPtr<nsIProperties> fileLocator;
  fileLocator = do_GetService("@mozilla.org/file/directory_service;1");
  nsCOMPtr<nsILocalFile> firefoxRegistry, profileRoot;
  char *APPDATADIR;
#ifdef XP_WIN
  APPDATADIR = NS_WIN_APPDATA_DIR;
#elif defined(XP_MACOSX)
  APPDATADIR = NS_MAC_USER_LIB_DIR;
#elif defined(XP_UNIX)
  APPDATADIR = NS_UNIX_HOME_DIR;
#endif
  fileLocator->Get(APPDATADIR,
                   NS_GET_IID(nsILocalFile),
                   getter_AddRefs(firefoxRegistry));
  fileLocator->Get(APPDATADIR,
                   NS_GET_IID(nsILocalFile),
                   getter_AddRefs(profileRoot));

  AppendNativeProfilePath(profileRoot);
  AppendNativeProfilePath(firefoxRegistry);

  firefoxRegistry->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
  nsINIParser parser;
  nsresult rv = parser.Init(firefoxRegistry);
  NS_ENSURE_SUCCESS(rv, rv);

  unsigned int c = 0;
  nsCAutoString buffer;
  for (c = 0; PR_TRUE; ++c) {
    nsCAutoString profileID("Profile");
    profileID.AppendInt(c);

    rv = parser.GetString(profileID.get(), "IsRelative", buffer);
    if (NS_FAILED(rv)) {
      break;
    }

    PRBool isRelative = buffer.EqualsLiteral("1");

    nsCAutoString filePath, profileName;

    rv = parser.GetString(profileID.get(), "Path", filePath);
    if (NS_FAILED(rv)) {
      NS_ERROR("Malformed profiles.ini: Path= not found");
      continue;
    }

    rv = parser.GetString(profileID.get(), "Name", profileName);
    if (NS_FAILED(rv)) {
      NS_ERROR("Malformed profiles.ini: Name= not found");
      continue;
    }

    nsCOMPtr<nsILocalFile> firefoxProfileDir;
    rv = NS_NewNativeLocalFile(EmptyCString(), PR_TRUE,
                                getter_AddRefs(firefoxProfileDir));
    NS_ENSURE_SUCCESS(rv, rv);
    if (isRelative) {
      rv = firefoxProfileDir->SetRelativeDescriptor(profileRoot, filePath);
    } else {
      rv = firefoxProfileDir->SetPersistentDescriptor(filePath);
    }
    NS_ENSURE_SUCCESS(rv, rv);
    PRBool exists;
    firefoxProfileDir->Exists(&exists);

    if (NS_SUCCEEDED(rv) && exists) {
      nsCOMPtr<nsISupportsString> profNameString;
      profNameString = do_CreateInstance("@mozilla.org/supports-string;1");
      profNameString->SetData(NS_ConvertUTF8toUTF16(profileName));
      mProfileNames->AppendElement(profNameString);

      mProfileLocations->AppendElement(firefoxProfileDir);
    }
  }
  return NS_OK;
}

#define F(a) nsFirefoxProfileMigrator::a

#define MAKEPREFTRANSFORM(pref, newpref, getmethod, setmethod) \
  { pref, newpref, F(Get##getmethod), F(Set##setmethod), PR_FALSE, { -1 } }

#define MAKESAMETYPEPREFTRANSFORM(pref, method) \
  { pref, 0, F(Get##method), F(Set##method), PR_FALSE, { -1 } }


static 
nsFirefoxProfileMigrator::PrefTransform gTransforms[] = {
  MAKESAMETYPEPREFTRANSFORM("signon.SignonFileName",                   String),
  MAKESAMETYPEPREFTRANSFORM("signon.SignonFileName2",                  String),
  MAKESAMETYPEPREFTRANSFORM("signon.SignonFileName3",                  String),
  MAKESAMETYPEPREFTRANSFORM("browser.history_expire_days",             Int),
  MAKESAMETYPEPREFTRANSFORM("browser.tabs.autoHide",                   Bool),
  MAKESAMETYPEPREFTRANSFORM("browser.tabs.loadInBackground",           Bool),
  MAKESAMETYPEPREFTRANSFORM("browser.enable_automatic_image_resizing", Bool),
  MAKESAMETYPEPREFTRANSFORM("network.cookie.warnAboutCookies",         Bool),
  MAKESAMETYPEPREFTRANSFORM("network.cookie.lifetime.enabled",         Bool),
  MAKESAMETYPEPREFTRANSFORM("network.cookie.lifetime.behavior",        Int),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_open_during_load",            Bool),
  MAKESAMETYPEPREFTRANSFORM("signon.rememberSignons",                  Bool),
  MAKESAMETYPEPREFTRANSFORM("security.enable_ssl2",                    Bool),
  MAKESAMETYPEPREFTRANSFORM("security.enable_ssl3",                    Bool),
  MAKESAMETYPEPREFTRANSFORM("security.enable_tls",                     Bool),
  MAKESAMETYPEPREFTRANSFORM("security.warn_entering_secure",           Bool),
  MAKESAMETYPEPREFTRANSFORM("security.warn_entering_weak",             Bool),
  MAKESAMETYPEPREFTRANSFORM("security.warn_leaving_secure",            Bool),
  MAKESAMETYPEPREFTRANSFORM("security.warn_submit_insecure",           Bool),
  MAKESAMETYPEPREFTRANSFORM("security.warn_viewing_mixed",             Bool),
  MAKESAMETYPEPREFTRANSFORM("security.default_personal_cert",          String),
  MAKESAMETYPEPREFTRANSFORM("security.OSCP.enabled",                   Int),
  MAKESAMETYPEPREFTRANSFORM("security.OSCP.signingCA",                 String),
  MAKESAMETYPEPREFTRANSFORM("security.OSCP.URL",                       String),
  MAKESAMETYPEPREFTRANSFORM("security.enable_java",                    Bool),
  MAKESAMETYPEPREFTRANSFORM("javascript.enabled",                      Bool),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_window_move_resize",          Bool),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_window_flip",                 Bool),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_window_open_feature.status",  Bool),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_window_status_change",        Bool),
  MAKESAMETYPEPREFTRANSFORM("dom.disable_image_src_set",               Bool),
  MAKESAMETYPEPREFTRANSFORM("accessibility.typeaheadfind.autostart",   Bool),
  MAKESAMETYPEPREFTRANSFORM("accessibility.typeaheadfind.linksonly",   Bool),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.type",                      Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.http",                      String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.http_port",                 Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp",                       String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp_port",                  Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl",                       String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl_port",                  Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.socks",                     String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.socks_port",                Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.gopher",                    String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.gopher_port",               Int),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.no_proxies_on",             String),
  MAKESAMETYPEPREFTRANSFORM("network.proxy.autoconfig_url",            String),
  MAKESAMETYPEPREFTRANSFORM("browser.display.foreground_color",        String),
  MAKESAMETYPEPREFTRANSFORM("browser.display.background_color",        String),
  MAKESAMETYPEPREFTRANSFORM("browser.anchor_color",                    String),
  MAKESAMETYPEPREFTRANSFORM("browser.visited_color",                   String),
  MAKESAMETYPEPREFTRANSFORM("browser.underline_anchors",               Bool),
  MAKESAMETYPEPREFTRANSFORM("browser.display.use_system_colors",       Bool),
  MAKESAMETYPEPREFTRANSFORM("browser.display.use_document_colors",     Bool),
  MAKESAMETYPEPREFTRANSFORM("browser.display.use_document_fonts",      Bool),
  MAKESAMETYPEPREFTRANSFORM("intl.charset.default",                    String),
  MAKESAMETYPEPREFTRANSFORM("intl.accept_languages",                   String),
  MAKESAMETYPEPREFTRANSFORM("intl.accept_charsets",                    String),

  MAKEPREFTRANSFORM("network.image.imageBehavior",      0, Int, Image),
  MAKEPREFTRANSFORM("network.cookie.cookieBehavior",    0, Int, Cookie),
  MAKEPREFTRANSFORM("browser.downloadmanager.behavior", 0, Int, DownloadManager),

  MAKEPREFTRANSFORM("wallet.captureForms", "formfill.enabled", Bool, Bool)
};

nsresult 
nsFirefoxProfileMigrator::SetImage(void* aTransform, nsIPrefBranch* aBranch)
{
  PrefTransform* xform = (PrefTransform*)aTransform;

  if (!xform->prefHasValue) {
    return NS_OK;
  }
  return aBranch->SetIntPref("network.image.imageBehavior",
                             xform->intValue == 1 ? 0 : xform->intValue);
}

nsresult 
nsFirefoxProfileMigrator::SetCookie(void* aTransform, nsIPrefBranch* aBranch)
{
  PrefTransform* xform = (PrefTransform*)aTransform;
  if (!xform->prefHasValue) {
    return NS_OK;
  }
  return aBranch->SetIntPref("network.cookie.cookieBehavior",
                              xform->intValue == 3 ? 0 : xform->intValue);
}

nsresult nsFirefoxProfileMigrator::SetDownloadManager(void* aTransform,
                                                      nsIPrefBranch* aBranch)
{
  PrefTransform* xform = (PrefTransform*)aTransform;
  nsresult rv = NS_OK;
  
  if (xform->prefHasValue) {
    // Firefox's download manager uses a single pref to control behavior:
    // 0 - show download manager window
    // 1 - show individual progress dialogs
    // 2 - show nothing
    //
    // Firebird has only a download manager window, but it can behave like a
    // progress dialog, thus:
    // 0 || 1  -> show downloads window when a download starts
    // 2       -> don't show anything when a download starts
    // 1       -> close the downloads window as if it were a progress window
    //            when downloads complete.
    rv |= aBranch->SetBoolPref("browser.download.manager.showWhenStarting",
                               xform->intValue != 2);
    rv |= aBranch->SetBoolPref("browser.download.manager.closeWhenDone",
                               xform->intValue == 1);
  }
  return NS_OK;
}

nsresult
nsFirefoxProfileMigrator::TransformPreferences(
    const nsAString& aSourcePrefFileName, const nsAString& aTargetPrefFileName)
{
  PrefTransform* transform;
  PrefTransform* end = gTransforms + sizeof(gTransforms)/sizeof(PrefTransform);

  // Load the source pref file
  nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
  psvc->ResetPrefs();

  nsCOMPtr<nsIFile> sourcePrefsFile;
  mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile));
  sourcePrefsFile->Append(aSourcePrefFileName);
  psvc->ReadUserPrefs(sourcePrefsFile);

  nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc));
  for (transform = gTransforms; transform < end; ++transform) {
    transform->prefGetterFunc(transform, branch);
  }

  nsVoidArray* fontPrefs = new nsVoidArray();
  if (!fontPrefs) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  ReadFontsBranch(psvc, fontPrefs);

  // Now that we have all the pref data in memory, load the target pref file,
  // and write it back out
  psvc->ResetPrefs();
  for (transform = gTransforms; transform < end; ++transform) {
    transform->prefSetterFunc(transform, branch);
  }

  WriteFontsBranch(psvc, fontPrefs);
  delete fontPrefs;
  fontPrefs = nsnull;

  nsCOMPtr<nsIFile> targetPrefsFile;
  mTargetProfile->Clone(getter_AddRefs(targetPrefsFile));
  targetPrefsFile->Append(aTargetPrefFileName);
  psvc->SavePrefFile(targetPrefsFile);

  psvc->ResetPrefs();
  psvc->ReadUserPrefs(nsnull);

  return NS_OK;
}

struct FontPref {
  char*         prefName;
  PRInt32       type;
  union {
    char*       stringValue;
    PRInt32     intValue;
    PRBool      boolValue;
    PRUnichar*  wstringValue;
  };
};

void nsFirefoxProfileMigrator::ReadFontsBranch(nsIPrefService* aPrefService, 
                                               nsVoidArray* aPrefs)
{
  // Enumerate the branch
  nsCOMPtr<nsIPrefBranch> branch;
  aPrefService->GetBranch("font.", getter_AddRefs(branch));

  PRUint32 count;
  char** prefs = nsnull;
  nsresult rv = branch->GetChildList("", &count, &prefs);
  if (NS_FAILED(rv)) return;

  for (PRUint32 i = 0; i < count; ++i) {
    // Save each pref's value into an array
    char* currPref = prefs[i];
    PRInt32 type;
    branch->GetPrefType(currPref, &type);
    FontPref* pref = new FontPref;
    pref->prefName = currPref;
    pref->type = type;
    switch (type) {
    case nsIPrefBranch::PREF_STRING:
      rv = branch->GetCharPref(currPref, &pref->stringValue);
      break;
    case nsIPrefBranch::PREF_BOOL:
      rv = branch->GetBoolPref(currPref, &pref->boolValue);
      break;
    case nsIPrefBranch::PREF_INT:
      rv = branch->GetIntPref(currPref, &pref->intValue);
      break;
    case nsIPrefBranch::PREF_INVALID:
      {
        nsCOMPtr<nsIPrefLocalizedString> str;
        rv = branch->GetComplexValue(currPref, 
                                     NS_GET_IID(nsIPrefLocalizedString), 
                                     getter_AddRefs(str));
        if (NS_SUCCEEDED(rv) && str) {
          str->ToString(&pref->wstringValue);
        }
      }
      break;
    }

    if (NS_SUCCEEDED(rv)) {
      aPrefs->AppendElement((void*)pref);
    }
  }
}

void
nsFirefoxProfileMigrator::WriteFontsBranch(nsIPrefService* aPrefService,
                                           nsVoidArray* aPrefs)
{
  nsresult rv;

  // Enumerate the branch
  nsCOMPtr<nsIPrefBranch> branch;
  aPrefService->GetBranch("font.", getter_AddRefs(branch));

  PRUint32 count = aPrefs->Count();
  for (PRUint32 i = 0; i < count; ++i) {
    FontPref* pref = (FontPref*)aPrefs->ElementAt(i);
    switch (pref->type) {
    case nsIPrefBranch::PREF_STRING:
      rv = branch->SetCharPref(pref->prefName, pref->stringValue);
      NS_Free(pref->stringValue);
      pref->stringValue = nsnull;
      break;
    case nsIPrefBranch::PREF_BOOL:
      rv = branch->SetBoolPref(pref->prefName, pref->boolValue);
      break;
    case nsIPrefBranch::PREF_INT:
      rv = branch->SetIntPref(pref->prefName, pref->intValue);
      break;
    case nsIPrefBranch::PREF_INVALID:
      nsCOMPtr<nsIPrefLocalizedString> pls;
      pls = do_CreateInstance("@mozilla.org/pref-localizedstring;1");
      pls->SetData(pref->wstringValue);
      rv = branch->SetComplexValue(pref->prefName, 
                                   NS_GET_IID(nsIPrefLocalizedString),
                                   pls);
      NS_Free(pref->wstringValue);
      pref->wstringValue = nsnull;
      break;
    }
    NS_Free(pref->prefName);
    pref->prefName = nsnull;
    delete pref;
  }
  aPrefs->Clear();
}

nsresult
nsFirefoxProfileMigrator::CopyPreferences(PRBool aReplace)
{
  nsresult rv = NS_OK;
  if (aReplace) {
    // Prefs files
    rv |= TransformPreferences(FILE_NAME_PREFS, FILE_NAME_PREFS);
    rv |= CopyFile(FILE_NAME_USER_PREFS, FILE_NAME_USER_PREFS);

    // Security Stuff
    rv |= CopyFile(FILE_NAME_CERT8DB, FILE_NAME_CERT8DB);
    rv |= CopyFile(FILE_NAME_SECMODDB, FILE_NAME_SECMODDB);

    nsAutoString fileName;
    rv |= GetAppFileName(fileName);
    if (!fileName.EqualsLiteral(NETSCAPE8_FILENAME)) {
      // for Netscape 8, the file containing the master password crashes
      // the application when importing passwords. We don't need anyway.
      rv |= CopyFile(FILE_NAME_KEY3DB, FILE_NAME_KEY3DB);
    }

    // User MIME Type overrides
    rv |= CopyFile(FILE_NAME_MIMETYPES, FILE_NAME_MIMETYPES);

    rv |= CopyUserStyleSheets();
  }
  return rv;
}

nsresult 
nsFirefoxProfileMigrator::CopyUserStyleSheets()
{
  nsresult rv = NS_OK;

  nsCOMPtr<nsIFile> sourceUserContent;
  mSourceProfile->Clone(getter_AddRefs(sourceUserContent));
  sourceUserContent->Append(DIR_NAME_CHROME);
  sourceUserContent->Append(FILE_NAME_USERCONTENT);

  PRBool exists = PR_FALSE;
  sourceUserContent->Exists(&exists);
  if (exists) {
    nsCOMPtr<nsIFile> targetUserContent;
    mTargetProfile->Clone(getter_AddRefs(targetUserContent));
    targetUserContent->Append(DIR_NAME_CHROME);
    nsCOMPtr<nsIFile> targetChromeDir;
    targetUserContent->Clone(getter_AddRefs(targetChromeDir));
    targetUserContent->Append(FILE_NAME_USERCONTENT);

    targetUserContent->Exists(&exists);
    if (exists) {
      targetUserContent->Remove(PR_FALSE);
    }
    rv |= sourceUserContent->CopyTo(targetChromeDir, FILE_NAME_USERCONTENT);
  }

  nsCOMPtr<nsIFile> sourceUserChrome;
  mSourceProfile->Clone(getter_AddRefs(sourceUserChrome));
  sourceUserChrome->Append(DIR_NAME_CHROME);
  sourceUserChrome->Append(FILE_NAME_USERCHROME);

  sourceUserChrome->Exists(&exists);
  if (exists) {
    nsCOMPtr<nsIFile> targetUserChrome;
    mTargetProfile->Clone(getter_AddRefs(targetUserChrome));
    targetUserChrome->Append(DIR_NAME_CHROME);
    nsCOMPtr<nsIFile> targetChromeDir;
    targetUserChrome->Clone(getter_AddRefs(targetChromeDir));
    targetUserChrome->Append(FILE_NAME_USERCHROME);

    targetUserChrome->Exists(&exists);
    if (exists) {
      targetUserChrome->Remove(PR_FALSE);
    }
    rv |= sourceUserChrome->CopyTo(targetChromeDir, FILE_NAME_USERCHROME);
  }

  return rv;
}

nsresult
nsFirefoxProfileMigrator::CopyCookies(PRBool aReplace)
{
  nsresult rv;
  if (aReplace) {
    rv = CopyFile(FILE_NAME_COOKIES_FF3, FILE_NAME_COOKIES_FF3);
    rv = CopyFile(FILE_NAME_COOKIES, FILE_NAME_COOKIES);
  } else {
    nsCOMPtr<nsIFile> sourceCookiesFile;
    mSourceProfile->Clone(getter_AddRefs(sourceCookiesFile));
    sourceCookiesFile->Append(FILE_NAME_COOKIES);
    rv = ImportNetscapeCookies(sourceCookiesFile);
  }
  return rv;
}

nsresult
nsFirefoxProfileMigrator::CopyHistory(PRBool aReplace)
{
  nsresult rv = NS_OK;

  if (aReplace) {
    // First import so just copy the history since we have none yet
    rv = CopyFile(FILE_NAME_HISTORY_FF3, FILE_NAME_HISTORY_FF3);
    rv = CopyFile(FILE_NAME_HISTORY, FILE_NAME_HISTORY);
  } // else TODO: Read and append browsing history.

  return rv;
}

nsresult
nsFirefoxProfileMigrator::CopyFormHistory(PRBool aReplace)
{
  nsresult rv = NS_OK;
  if (aReplace) {
    // First import so just copy the history since we have none yet
    rv = CopyFile(FILE_NAME_FORMHISTORY_FF3, FILE_NAME_FORMHISTORY_FF3);
    rv = CopyFile(FILE_NAME_FORMHISTORY, FILE_NAME_FORMHISTORY);
  } else {/*
    // Check for existence of "from" form history file.
    nsCOMPtr<nsIFile> historyFile;
    mSourceProfile->Clone(getter_AddRefs(historyFile));
    historyFile->Append(FILE_NAME_FORMHISTORY_FF3);
    PRBool exists = PR_FALSE;
    historyFile->Exists(&exists);
    if (!exists) {
      return NS_OK;
    }
    // Get the "to" form history service for additions.
    nsCOMPtr<nsIFormHistory2> formHistory;
    formHistory = do_GetService("@mozilla.org/satchel/form-history;1", &rv);
    if (!formHistory) {
      return rv;
    }
    ... to be continued if Mozilla allow us to use MorkReader
    from a non MOZILLA_INTERNAL_API modules
    */
    return NS_ERROR_UNEXPECTED;
  }
  return rv;
}

nsresult
nsFirefoxProfileMigrator::CopyPasswords(PRBool aReplace)
{
  if (aReplace) {
    // Overwrite.
    return CopyFile(mSourceSignonFileName, mSourceSignonFileName);
  }
  // Can't merge because SecretDecoderRing::Decrypt fails (wrong salt key)
  return NS_ERROR_UNEXPECTED;
}

nsresult
nsFirefoxProfileMigrator::CopyBookmarks(PRBool aReplace)
{
  nsresult rv;
  nsCOMPtr<nsIFile> bookmarksFile;
  PRBool sourceIsFF3 = SourceFileExists(FILE_NAME_BOOKMARKS_FF3);
  mSourceNameKey.Truncate();
  if (aReplace) {
    // Import default Flock bookmarks
    mTargetProfile->Clone(getter_AddRefs(bookmarksFile));
    bookmarksFile->Append(FILE_NAME_BOOKMARKS);

    // Do not clear the Bookmarks if we already copied the sqlite ones
    rv = ImportBookmarksHTML(bookmarksFile,
                             PR_TRUE,
                             !sourceIsFF3,
                             EmptyString().get());
    NS_ENSURE_SUCCESS(rv, rv);

    if (sourceIsFF3) {
      // We already have copied the sqlite Bookmarks
      return rv;
    }
    // Import from FF2/1.5 or Netscape
    // We need to set this pref so that on startup
    // bg__initPlaces doesn't blow away what we are going to import
    nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
    rv = pref->SetBoolPref(PREF_BROWSER_IMPORT_BOOKMARKS, PR_FALSE);
  } else {
    // For FF3: Merge Bookmarks from places.sqlite instead of merging them
    // from bookmarks.html
    rv = GetBookmarksSourceName(mSourceNameKey);
    if (sourceIsFF3) {
      nsCOMPtr<nsINavHistoryService> history;
      history = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      return history->RunInBatchMode(this, nsnull);
    }
  }
  NS_ENSURE_SUCCESS(rv, rv);
  mSourceProfile->Clone(getter_AddRefs(bookmarksFile));
  bookmarksFile->Append(FILE_NAME_BOOKMARKS);
  return ImportBookmarksHTML(bookmarksFile, aReplace,
                             PR_FALSE, mSourceNameKey.get());
}

// nsINavHistoryBatchCallback::runBatched
nsresult
nsFirefoxProfileMigrator::RunBatched(nsISupports* aUserData)
{
  nsresult rv;

  // Get the source application name.
  nsCOMPtr<nsIStringBundleService> bundleService =
    do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIStringBundle> bundle;
  rv = bundleService->CreateBundle(MIGRATION_BUNDLE, getter_AddRefs(bundle));
  NS_ENSURE_SUCCESS(rv, rv);

  nsString sourceName;
  bundle->GetStringFromName(mSourceNameKey.get(), getter_Copies(sourceName));

  const PRUnichar* sourceNameStrings[] = { sourceName.get() };
  nsString importedBookmarksTitle;
  bundle->FormatStringFromName(NS_LITERAL_STRING("importedBookmarksFolder").get(),
                               sourceNameStrings, 1,
                               getter_Copies(importedBookmarksTitle));

  // Get the bookmarks service, and the root folder.
  nsCOMPtr<nsINavBookmarksService> bms =
    do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  PRInt64 root;
  rv = bms->GetBookmarksMenuFolder(&root);
  NS_ENSURE_SUCCESS(rv, rv);

  // Make an nsIFile for places.sqlite.
  nsCOMPtr<mozIStorageService> dbService;
  nsCOMPtr<mozIStorageConnection> dbConn;
  nsCOMPtr<nsIFile> dbFile;
  nsCOMPtr<nsIFile> dbFileTempCopy;
  rv = mSourceProfile->Clone(getter_AddRefs(dbFile));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = dbFile->Append(FILE_NAME_BOOKMARKS_FF3);
  NS_ENSURE_SUCCESS(rv, rv);
  dbService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  // From here on: Do not use NS_ENSURE_SUCCESS, so we can clean up.

  // Make a temporary copy of places.sqlite in case the DB is locked.
  rv = dbService->BackupDatabaseFile(dbFile,
                                     NS_LITERAL_STRING("places.sqlite.flocktemp"),
                                     nsnull,
                                     getter_AddRefs(dbFileTempCopy));

  // Open the places.sqlite database
  if (NS_SUCCEEDED(rv)) {
    rv = dbService->OpenDatabase(dbFileTempCopy, getter_AddRefs(dbConn));
  }

  // Create an imported bookmarks folder under the bookmarks menu.
  PRInt64 fromFxFolder = 0;
  if (NS_SUCCEEDED(rv)) {
    rv = bms->CreateFolder(root,
                           NS_ConvertUTF16toUTF8(importedBookmarksTitle),
                           nsINavBookmarksService::DEFAULT_INDEX,
                           &fromFxFolder);
  }

  nsCOMPtr<mozIStorageStatement> statement;
  if (NS_SUCCEEDED(rv)) {
    rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
        "SELECT "
  /*  0 */  "B2.id AS folder_id, "
  /*  1 */  "B1.id AS item_id, "
  /*  2 */  "B1.type AS item_type, "
  /*  3 */  "B1.title AS title, "
  /*  4 */  "P.url AS url, "
  /*  5 */  "F.url AS favicon_url, "
  /*  6 */  "F.data AS favicon_data, "
  /*  7 */  "F.mime_type AS favicon_mime_type "
          "FROM moz_bookmarks B1 "
          "LEFT JOIN moz_bookmarks B2 ON B1.parent = B2.id "
          "LEFT JOIN moz_places P ON B1.fk = P.id "
          "LEFT JOIN moz_favicons F ON P.favicon_id = F.id "
          "ORDER BY B1.parent, B1.position"
         ),
      getter_AddRefs(statement));
  }
  if (NS_SUCCEEDED(rv)) {
    // Because these are stand-ins for constants, cache them.
    PRInt64 placesRoot;
    PRInt64 bookmarksMenuFolder;
    PRInt64 toolbarFolder;
    PRInt64 tagsFolder;
    PRInt64 unfiledBookmarksFolder;
    bms->GetPlacesRoot(&placesRoot);
    bms->GetBookmarksMenuFolder(&bookmarksMenuFolder);
    bms->GetToolbarFolder(&toolbarFolder);
    bms->GetTagsFolder(&tagsFolder);
    bms->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder);

    nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIURI> uri;
    PRInt64 id;
    PRInt64 parentFolder = 0;
    PRBool hasMatch = PR_FALSE;
    FolderMap fm;

    // Iterate over the rows in the database.
    while (NS_SUCCEEDED(statement->ExecuteStep(&hasMatch)) && hasMatch) {

      // Determine the parent folder for the current item.
      PRInt64 rowFolderId;
      statement->GetInt64(0, &rowFolderId);
      if (rowFolderId == placesRoot) {
        // Do nothing with these, as Firefox maintains them.
        parentFolder = 0;
      } else if (rowFolderId == bookmarksMenuFolder) {
        // Insert these into "From Firefox"
        parentFolder = fromFxFolder;
      } else if (rowFolderId == toolbarFolder) {
        // Insert these into toolbar
        parentFolder = toolbarFolder;
      } else if (rowFolderId == tagsFolder) {
        // This is a declared tag; insert the definition (note: it's a folder).
        parentFolder = tagsFolder;
      } else if (rowFolderId == unfiledBookmarksFolder) {
        // Nothing yet.
        parentFolder = 0;
      } else {
        // "places" has no parent; skip that one row.
        if (rowFolderId) {
          parentFolder = fm.Find(rowFolderId);
        } else {
          parentFolder = 0;
        }
      }

      // Insert the current item, if it has a parent folder.
      if (parentFolder) {

        PRInt32 rowItemType;
        nsCAutoString rowTitle;
        nsCAutoString rowUrl;

        statement->GetInt32(2, &rowItemType);
        statement->GetUTF8String(3, rowTitle);

        switch (rowItemType) {

          case nsINavBookmarksService::TYPE_BOOKMARK:
            statement->GetUTF8String(4, rowUrl);
            ios->NewURI(rowUrl, nsnull, nsnull, getter_AddRefs(uri));
            // Insert the bookmark.
            rv = bms->InsertBookmark(parentFolder,
                                     uri,
                                     nsINavBookmarksService::DEFAULT_INDEX,
                                     rowTitle,
                                     &id);
            // TODO: Insert the favicon:
            //         F.url AS favicon_url
            //         F.data AS favicon_data
            //         F.mime_type AS favicon_mime_type
            // TODO: Insert the item annotation:
            //         A.content AS item_desc
            break;

          case nsINavBookmarksService::TYPE_SEPARATOR:
            rv = bms->InsertSeparator(parentFolder,
                                      nsINavBookmarksService::DEFAULT_INDEX,
                                      &id);
            break;

          case nsINavBookmarksService::TYPE_FOLDER:
            PRInt64 rowItemId;
            statement->GetInt64(1, &rowItemId);
            rv = bms->CreateFolder(parentFolder,
                                   rowTitle,
                                   nsINavBookmarksService::DEFAULT_INDEX,
                                   &id);
            fm.Insert(rowItemId, id);
            break;
        }
      }
    }
  }

  // Clean up, so we can delete the temporary copy.
  if (statement) {
    rv = statement->Finalize();
  }
  if (dbConn) {
    dbConn->Close();
  }
  dbFileTempCopy->Remove(PR_FALSE);

  return rv;
}

nsresult
nsFirefoxProfileMigrator::CopyOtherData(PRBool aReplace)
{
  nsresult rv = NS_OK;
  if (aReplace) {
    rv |= CopyFile(FILE_NAME_DOWNLOADS_FF3, FILE_NAME_DOWNLOADS_FF3);
    rv |= CopyFile(FILE_NAME_DOWNLOADS, FILE_NAME_DOWNLOADS);
//  rv |= CopyFile(NS_LITERAL_STRING("search.rdf"),
//                 NS_LITERAL_STRING("search.rdf")); bad on osx

//  Avoid the buttonset problem ;)
//  rv |= CopyFile(FILE_NAME_LOCALSTORE, FILE_NAME_LOCALSTORE);
  }
  return rv;
}

void
nsFirefoxProfileMigrator::MigrateYahooToolbar(void) {
  nsresult rv;
  nsCOMPtr<nsIFile> sourceExtensionFolder;
  mSourceProfile->Clone(getter_AddRefs(sourceExtensionFolder));
  sourceExtensionFolder->Append(DIR_NAME_EXTENSIONS);

  // Now we have the directory of the extensions, we need to go through
  // them until we find the Yahoo Toolbar or have none left
  nsCOMPtr<nsISimpleEnumerator> entries;
  rv = sourceExtensionFolder->GetDirectoryEntries(getter_AddRefs(entries));
  if (NS_FAILED(rv)) {
    return;
  }

  nsCOMPtr<nsIRDFService> rdfs;
  rdfs = do_GetService("@mozilla.org/rdf/rdf-service;1");
  if (!rdfs) {
    return;
  }

  NS_NAMED_LITERAL_STRING(kYahooTBName, "Yahoo! Toolbar");
  NS_NAMED_LITERAL_CSTRING(kResM, "urn:mozilla:install-manifest");
  NS_NAMED_LITERAL_CSTRING(kResE, "http://www.mozilla.org/2004/em-rdf#name");
  nsCOMPtr<nsIRDFResource> iManifest, emName;
  rv = rdfs->GetResource(kResM, getter_AddRefs(iManifest));
  if (NS_FAILED(rv)) {
    return;
  }
  rv = rdfs->GetResource(kResE, getter_AddRefs(emName));
  if (NS_FAILED(rv)) {
    return;
  }

  do { 
    // Loop through the funny named directories
    PRBool hasMore = PR_FALSE;
    rv = entries->HasMoreElements(&hasMore);
    if (NS_FAILED(rv) || !hasMore) break;

    nsCOMPtr<nsIFile> supp;
    rv = entries->GetNext(getter_AddRefs(supp));
    if (NS_FAILED(rv)) break;

    nsCOMPtr<nsIFile> currFile;
    supp->Clone(getter_AddRefs(currFile));
    if (!currFile) continue;
    currFile->AppendNative(NS_LITERAL_CSTRING("install.rdf"));
    // Check the install.rdf and look for a <em:name>Yahoo! Toolbar</em:name>
    // match since this is a rdf we can load it in an rdfDatasource :)
    nsCAutoString urlSpec;
    rv = NS_GetURLSpecFromFile(currFile, urlSpec);
    if (NS_FAILED(rv)) continue;

    // Load the datasource from install.rdf
    nsCOMPtr<nsIRDFDataSource> ds;
    rv = rdfs->GetDataSourceBlocking(urlSpec.get(), getter_AddRefs(ds));
    if (NS_FAILED(rv)) continue;

    nsCOMPtr<nsIRDFNode> emNameNode;
    rv = ds->GetTarget(iManifest, emName, PR_TRUE, getter_AddRefs(emNameNode));
    if (NS_FAILED(rv)) continue;
    nsCOMPtr<nsIRDFLiteral> emNameLiteral(do_QueryInterface(emNameNode));
    if (!emNameLiteral) continue;

    // We now have the value of <em:name in emNameValue...check that it matches
    const PRUnichar *emValue;
    rv = emNameLiteral->GetValueConst(&emValue);
    if (NS_SUCCEEDED(rv) && emValue) {
      if (kYahooTBName.Equals(emValue)) {
        // Copy the Yahoo toolbar extension in our extension folder
        nsCOMPtr<nsIFile> targetExtensionFolder;
        mTargetProfile->Clone(getter_AddRefs(targetExtensionFolder));
        targetExtensionFolder->Append(DIR_NAME_EXTENSIONS);
        nsCOMPtr<nsILocalFile> currDir(do_QueryInterface(supp));
        nsAutoString dirName;
        currDir->GetLeafName(dirName);
        currDir->CopyTo(targetExtensionFolder, dirName);
        break;
      }
    }
  } while(1);
}

nsresult nsFirefoxProfileMigrator::GetBookmarksSourceName(nsAString &aName)
{
  aName.Assign(NS_LITERAL_STRING("sourceNameFirefox"));
  return NS_OK;
}

nsresult nsFirefoxProfileMigrator::GetAppFileName(nsAString &aFileName)
{
#ifdef XP_WIN
  aFileName.AssignLiteral("firefox.exe");
#elif defined(XP_MACOSX)
  aFileName.AssignLiteral("Firefox");
#elif defined(XP_UNIX)
  aFileName.AssignLiteral("firefox");
#endif
  return NS_OK;
}

nsresult
nsNetscape9ProfileMigrator::AppendNativeProfilePath(nsILocalFile *aFile)
{
#ifdef XP_WIN
  aFile->Append(NS_LITERAL_STRING("Netscape"));
  aFile->Append(NS_LITERAL_STRING("Navigator"));
#elif defined(XP_MACOSX)
  aFile->Append(NS_LITERAL_STRING("Application Support"));
  aFile->Append(NS_LITERAL_STRING("Navigator"));
#elif defined(XP_UNIX)
  aFile->Append(NS_LITERAL_STRING(".netscape"));
  aFile->Append(NS_LITERAL_STRING("navigator"));
#endif
  return NS_OK;
}

nsresult nsNetscape9ProfileMigrator::GetBookmarksSourceName(nsAString &aName)
{
  aName.Assign(NS_LITERAL_STRING("sourceNameNetscape9"));
  return NS_OK;
}

nsresult nsNetscape9ProfileMigrator::GetAppFileName(nsAString &aFileName)
{
#ifdef XP_WIN
  aFileName.AssignLiteral("navigator.exe");
#elif defined(XP_MACOSX)
  aFileName.AssignLiteral("Navigator");
#elif defined(XP_UNIX)
  aFileName.AssignLiteral("navigator");
#endif
  return NS_OK;
}

#if defined(XP_WIN)
nsresult
nsNetscape8ProfileMigrator::AppendNativeProfilePath(nsILocalFile *aFile)
{
  aFile->Append(NS_LITERAL_STRING("Netscape"));
  aFile->Append(NS_LITERAL_STRING("NSB"));
  return NS_OK;
}

nsresult nsNetscape8ProfileMigrator::GetBookmarksSourceName(nsAString &aName)
{
  aName.Assign(NS_LITERAL_STRING("sourceNameNetscape8"));
  return NS_OK;
}

nsresult nsNetscape8ProfileMigrator::GetAppFileName(nsAString &aFileName)
{
  aFileName.AssignLiteral(NETSCAPE8_FILENAME);
  return NS_OK;
}

PRBool nsNetscape8ProfileMigrator::SetSourceSignonFileName(PRBool aReplace)
{
  mSourceSignonFileName.AssignLiteral("c0g1n3n5.r7u");
  return SourceFileExists(mSourceSignonFileName);
}

static const char kRealmSeparator[] = ".";
static const char kDefaultPasscard[] = "http://www.DefaultPasscard";
#define PASSCARD_VERSION_INDEX 17

nsresult nsNetscape8ProfileMigrator::CopyPasswords(PRBool aReplace)
{
  // Copy from Netscape 8 Passcards
  nsCOMPtr<nsIFile> countersignFile;
  mSourceProfile->Clone(getter_AddRefs(countersignFile));
  countersignFile->Append(mSourceSignonFileName);

  nsCOMPtr<nsIInputStream> fileStream;
  NS_NewLocalFileInputStream(getter_AddRefs(fileStream), countersignFile);
  if (!fileStream) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsCOMPtr<nsILineInputStream> lineStream = do_QueryInterface(fileStream);
  NS_ASSERTION(lineStream, "File stream is not an nsILineInputStream");

  nsAutoString hostName, buffer; // MERC JVL

  // Go through preferences
  PRBool moreData = PR_FALSE;
  nsresult rv;
  int lineIndex = 0;
  enum { STATE_REJECT, STATE_REALM, STATE_USERFIELD, STATE_USERVALUE,
         STATE_PASSFIELD, STATE_PASSVALUE, STATE_PROTECT = 9 };
  PRInt32 state = STATE_REJECT;
  PRInt32 STATE_ENDPASSCARD = 12; // for Netscape 8.0
  do {
    rv = ReadObfuscatedLine(lineStream, buffer, &moreData);
    if (NS_FAILED(rv)) {
      return NS_OK;
    }
    lineIndex++;
  } while (!buffer.Equals(NS_LITERAL_STRING("|")) && moreData);

  if (lineIndex == PASSCARD_VERSION_INDEX) {
    // This pref line is only found in Netscape 8.1
    STATE_ENDPASSCARD += 4; // 4 passcard fields added in Netscape 8.1
  }

  // Now read the passcards
  nsCOMPtr<nsILoginManager> pm(
        do_GetService("@mozilla.org/login-manager;1"));
  nsCOMPtr<nsILoginInfo> loginInfo = do_CreateInstance(LOGIN_INFO);
  PRBool isProtected;
  do {
    rv = ReadObfuscatedLine(lineStream, buffer, &moreData);
    if (NS_FAILED(rv)) {
      return NS_OK;
    }

    switch (state++) {
    case STATE_REJECT:
      if (buffer.Equals(NS_LITERAL_STRING(":"))) {
        break;
      }
      state = STATE_REJECT;
      // Fall through STATE_REALM
    case STATE_REALM: {
        // Workaround for the pb of Netscape 8 stripping www
        PRInt32 posDot = buffer.FindChar('.');
        if(posDot == -1 || buffer.FindChar('.', posDot + 1) == -1) {
          // Since there're less than 2 dots in the realm we have to guess
          // that Netscape 8 stripped www
          buffer.Insert(NS_LITERAL_STRING("www."), 0);
        }
        buffer.Insert(NS_LITERAL_STRING("http://"), 0);
        if (state == STATE_REJECT) {
          pm->SetLoginSavingEnabled(buffer, PR_FALSE);
        } else {
          hostName = buffer;
        }
      }
      break;
    case STATE_USERFIELD: {
        // Add previous entry
        if (!hostName.EqualsLiteral(kDefaultPasscard)) {
          // Weed out empty username+password entries
          nsAutoString username, password;
          loginInfo->GetUsername(username);
          loginInfo->GetPassword(password);
          if (!isProtected && (!username.IsEmpty() || !password.IsEmpty())) {
            // A blank, non-null FormSubmit URL is handled by the
            // login Manager as a wildcard.
            loginInfo->SetFormSubmitURL(NS_LITERAL_STRING(""));
            loginInfo->SetHostname(hostName);
            pm->AddLogin(loginInfo);
            loginInfo = do_CreateInstance(LOGIN_INFO);
          }
        }
        // If the line is a ., we've reached the end of this realm's entries.
        if (buffer.EqualsLiteral(kRealmSeparator)) {
          state = STATE_REALM;
          loginInfo->SetUsername(NS_LITERAL_STRING(""));
          loginInfo->SetPassword(NS_LITERAL_STRING(""));
        } else {
          loginInfo->SetUsernameField(buffer);
        }
      }
      break;
    case STATE_USERVALUE:
      loginInfo->SetUsername(buffer);
      break;
    case STATE_PASSFIELD:
      // Strip off the leading "*" character
      loginInfo->SetPasswordField(Substring(buffer, 1, buffer.Length() - 1));
      break;
    case STATE_PASSVALUE:
      loginInfo->SetPassword(buffer);
      break;
    case STATE_PROTECT:
      isProtected = buffer.EqualsLiteral("1");
      break;
    default:
      if (state == STATE_ENDPASSCARD) {
        state = STATE_USERFIELD;
      }
    }
  } while (moreData);

  fileStream->Close();
  return NS_OK;
}

#define SC_TRUST_MAGIC_NUMBER	  14
// XOR the buffer to obfuscate/unobfuscate the information
nsresult nsNetscape8ProfileMigrator::ReadObfuscatedLine(nsILineInputStream* is,
                                                        nsAString& buffer,
                                                        PRBool* moreData)
{
  nsCAutoString utf8Buffer;
  nsresult rv = is->ReadLine(utf8Buffer, moreData);
  if (NS_SUCCEEDED(rv)) {
    // Ensure that our buffer has capacity to hold the data to write
    buffer.SetLength(utf8Buffer.Length());
    wchar_t *substr_start, *substr_end;
    buffer.BeginWriting(&substr_start, &substr_end);
    for (PRUint32 i = 0; i < utf8Buffer.Length(); i++) {
      *substr_start = ( SC_TRUST_MAGIC_NUMBER ^ utf8Buffer.get()[i] );
      ++substr_start;
    }
  }
  return rv;
}

#endif
