/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */

#include "mozilla/ArrayUtils.h"
#include "mozilla/UniquePtr.h"

#include <stdio.h>
#include <stdlib.h>
#include <prprf.h>
#include <prtime.h>
#include "nsProfileLock.h"

#ifdef XP_WIN
#include <windows.h>
#include <shlobj.h>
#endif
#ifdef XP_UNIX
#include <unistd.h>
#endif

#include "nsIToolkitProfileService.h"
#include "nsIToolkitProfile.h"
#include "nsIFactory.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"

#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#include "nsILocalFileMac.h"
#endif

#include "nsAppDirectoryServiceDefs.h"
#include "nsXULAppAPI.h"

#include "nsINIParser.h"
#include "nsXREDirProvider.h"
#include "nsAppRunner.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsNativeCharsetUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Sprintf.h"

using namespace mozilla;

class nsToolkitProfile final : public nsIToolkitProfile
{
public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSITOOLKITPROFILE

    friend class nsToolkitProfileService;
    RefPtr<nsToolkitProfile> mNext;
    nsToolkitProfile          *mPrev;

private:
    ~nsToolkitProfile() { }

    nsToolkitProfile(const nsACString& aName,
                     nsIFile* aRootDir,
                     nsIFile* aLocalDir,
                     nsToolkitProfile* aPrev,
                     bool aForExternalApp);

    friend class nsToolkitProfileLock;

    nsCString                  mName;
    nsCOMPtr<nsIFile>          mRootDir;
    nsCOMPtr<nsIFile>          mLocalDir;
    nsIProfileLock*            mLock;
    bool                       mForExternalApp;
};

class nsToolkitProfileLock final : public nsIProfileLock
{
public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIPROFILELOCK

    nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker);
    nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
                  nsIProfileUnlocker* *aUnlocker);

    nsToolkitProfileLock() { }

private:
    ~nsToolkitProfileLock();

    RefPtr<nsToolkitProfile> mProfile;
    nsCOMPtr<nsIFile> mDirectory;
    nsCOMPtr<nsIFile> mLocalDirectory;

    nsProfileLock mLock;
};

class nsToolkitProfileFactory final : public nsIFactory
{
    ~nsToolkitProfileFactory() {}
public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIFACTORY
};

class nsToolkitProfileService final : public nsIToolkitProfileService
{
public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSITOOLKITPROFILESERVICE

private:
    friend class nsToolkitProfile;
    friend class nsToolkitProfileFactory;
    friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);

    nsToolkitProfileService() :
        mDirty(false),
        mStartWithLast(true),
        mStartOffline(false)
    {
        gService = this;
    }
    ~nsToolkitProfileService()
    {
        gService = nullptr;
    }

    nsresult Init();

    nsresult CreateTimesInternal(nsIFile *profileDir);

    nsresult CreateProfileInternal(nsIFile* aRootDir,
                                   const nsACString& aName,
                                   const nsACString* aProfileName,
                                   const nsACString* aAppName,
                                   const nsACString* aVendorName,
                                   bool aForExternalApp,
                                   nsIToolkitProfile** aResult);

    RefPtr<nsToolkitProfile>  mFirst;
    nsCOMPtr<nsIToolkitProfile> mChosen;
    nsCOMPtr<nsIToolkitProfile> mDefault;
    nsCOMPtr<nsIFile>           mAppData;
    nsCOMPtr<nsIFile>           mTempData;
    nsCOMPtr<nsIFile>           mListFile;
    bool mDirty;
    bool mStartWithLast;
    bool mStartOffline;

    static nsToolkitProfileService *gService;

    class ProfileEnumerator final : public nsISimpleEnumerator
    {
    public:
        NS_DECL_ISUPPORTS
        NS_DECL_NSISIMPLEENUMERATOR

        explicit ProfileEnumerator(nsToolkitProfile *first)
          { mCurrent = first; }
    private:
        ~ProfileEnumerator() { }
        RefPtr<nsToolkitProfile> mCurrent;
    };
};

nsToolkitProfile::nsToolkitProfile(const nsACString& aName,
                                   nsIFile* aRootDir,
                                   nsIFile* aLocalDir,
                                   nsToolkitProfile* aPrev,
                                   bool aForExternalApp) :
    mPrev(aPrev),
    mName(aName),
    mRootDir(aRootDir),
    mLocalDir(aLocalDir),
    mLock(nullptr),
    mForExternalApp(aForExternalApp)
{
    NS_ASSERTION(aRootDir, "No file!");

    if (!aForExternalApp) {
        if (aPrev) {
            aPrev->mNext = this;
        } else {
            nsToolkitProfileService::gService->mFirst = this;
        }
    }
}

NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)

NS_IMETHODIMP
nsToolkitProfile::GetRootDir(nsIFile* *aResult)
{
    NS_ADDREF(*aResult = mRootDir);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfile::GetLocalDir(nsIFile* *aResult)
{
    NS_ADDREF(*aResult = mLocalDir);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfile::GetName(nsACString& aResult)
{
    aResult = mName;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfile::SetName(const nsACString& aName)
{
    NS_ASSERTION(nsToolkitProfileService::gService,
                 "Where did my service go?");
    NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);

    mName = aName;
    nsToolkitProfileService::gService->mDirty = true;

    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfile::Remove(bool removeFiles)
{
    NS_ASSERTION(nsToolkitProfileService::gService,
                 "Whoa, my service is gone.");

    NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);

    if (mLock)
        return NS_ERROR_FILE_IS_LOCKED;

    if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
        return NS_ERROR_NOT_INITIALIZED;

    if (removeFiles) {
        bool equals;
        nsresult rv = mRootDir->Equals(mLocalDir, &equals);
        if (NS_FAILED(rv))
            return rv;

        // The root dir might contain the temp dir, so remove
        // the temp dir first.
        if (!equals)
            mLocalDir->Remove(true);

        mRootDir->Remove(true);
    }

    if (mPrev)
        mPrev->mNext = mNext;
    else
        nsToolkitProfileService::gService->mFirst = mNext;

    if (mNext)
        mNext->mPrev = mPrev;

    mPrev = nullptr;
    mNext = nullptr;

    if (nsToolkitProfileService::gService->mChosen == this)
        nsToolkitProfileService::gService->mChosen = nullptr;

    nsToolkitProfileService::gService->mDirty = true;

    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfile::Lock(nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
{
    if (mLock) {
        NS_ADDREF(*aResult = mLock);
        return NS_OK;
    }

    RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
    if (!lock) return NS_ERROR_OUT_OF_MEMORY;

    nsresult rv = lock->Init(this, aUnlocker);
    if (NS_FAILED(rv)) return rv;

    NS_ADDREF(*aResult = lock);
    return NS_OK;
}

NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)

nsresult
nsToolkitProfileLock::Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker)
{
    nsresult rv;
    rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
    if (NS_SUCCEEDED(rv))
        mProfile = aProfile;

    return rv;
}

nsresult
nsToolkitProfileLock::Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
                           nsIProfileUnlocker* *aUnlocker)
{
    nsresult rv;

    rv = mLock.Lock(aDirectory, aUnlocker);

    if (NS_SUCCEEDED(rv)) {
        mDirectory = aDirectory;
        mLocalDirectory = aLocalDirectory;
    }

    return rv;
}

NS_IMETHODIMP
nsToolkitProfileLock::GetDirectory(nsIFile* *aResult)
{
    if (!mDirectory) {
        NS_ERROR("Not initialized, or unlocked!");
        return NS_ERROR_NOT_INITIALIZED;
    }

    NS_ADDREF(*aResult = mDirectory);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileLock::GetLocalDirectory(nsIFile* *aResult)
{
    if (!mLocalDirectory) {
        NS_ERROR("Not initialized, or unlocked!");
        return NS_ERROR_NOT_INITIALIZED;
    }

    NS_ADDREF(*aResult = mLocalDirectory);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileLock::Unlock()
{
    if (!mDirectory) {
        NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
        return NS_ERROR_UNEXPECTED;
    }

    mLock.Unlock();

    if (mProfile) {
        mProfile->mLock = nullptr;
        mProfile = nullptr;
    }
    mDirectory = nullptr;
    mLocalDirectory = nullptr;

    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileLock::GetReplacedLockTime(PRTime *aResult)
{
    mLock.GetReplacedLockTime(aResult);
    return NS_OK;
}

nsToolkitProfileLock::~nsToolkitProfileLock()
{
    if (mDirectory) {
        Unlock();
    }
}

nsToolkitProfileService*
nsToolkitProfileService::gService = nullptr;

NS_IMPL_ISUPPORTS(nsToolkitProfileService,
                  nsIToolkitProfileService)

nsresult
nsToolkitProfileService::Init()
{
    NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
    nsresult rv;

    rv = gDirServiceProvider->GetUserAppDataDirectory(getter_AddRefs(mAppData));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = gDirServiceProvider->GetUserLocalDataDirectory(getter_AddRefs(mTempData));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mAppData->Clone(getter_AddRefs(mListFile));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
    NS_ENSURE_SUCCESS(rv, rv);

    bool exists;
    rv = mListFile->IsFile(&exists);
    if (NS_FAILED(rv) || !exists) {
        return NS_OK;
    }

    int64_t size;
    rv = mListFile->GetFileSize(&size);
    if (NS_FAILED(rv) || !size) {
        return NS_OK;
    }

    nsINIParser parser;
    rv = parser.Init(mListFile);
    // Init does not fail on parsing errors, only on OOM/really unexpected
    // conditions.
    if (NS_FAILED(rv))
        return rv;

    nsAutoCString buffer;
    rv = parser.GetString("General", "StartWithLastProfile", buffer);
    if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0"))
        mStartWithLast = false;

    nsToolkitProfile* currentProfile = nullptr;

#ifdef MOZ_DEV_EDITION
    nsCOMPtr<nsIFile> ignoreSeparateProfile;
    rv = mAppData->Clone(getter_AddRefs(ignoreSeparateProfile));
    if (NS_FAILED(rv))
        return rv;

    rv = ignoreSeparateProfile->AppendNative(NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
    if (NS_FAILED(rv))
        return rv;

    bool shouldIgnoreSeparateProfile;
    rv = ignoreSeparateProfile->Exists(&shouldIgnoreSeparateProfile);
    if (NS_FAILED(rv))
        return rv;
#endif

    unsigned int c = 0;
    bool foundAuroraDefault = false;
    for (c = 0; true; ++c) {
        nsAutoCString profileID("Profile");
        profileID.AppendInt(c);

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

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

        nsAutoCString filePath;

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

        nsAutoCString name;

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

        nsCOMPtr<nsIFile> rootDir;
        rv = NS_NewNativeLocalFile(EmptyCString(), true,
                                   getter_AddRefs(rootDir));
        NS_ENSURE_SUCCESS(rv, rv);

        if (isRelative) {
            rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
        } else {
            rv = rootDir->SetPersistentDescriptor(filePath);
        }
        if (NS_FAILED(rv)) continue;

        nsCOMPtr<nsIFile> localDir;
        if (isRelative) {
            rv = NS_NewNativeLocalFile(EmptyCString(), true,
                                       getter_AddRefs(localDir));
            NS_ENSURE_SUCCESS(rv, rv);

            rv = localDir->SetRelativeDescriptor(mTempData, filePath);
        } else {
            localDir = rootDir;
        }

        currentProfile = new nsToolkitProfile(name,
                                              rootDir, localDir,
                                              currentProfile, false);
        NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);

        rv = parser.GetString(profileID.get(), "Default", buffer);
        if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1") && !foundAuroraDefault) {
            mChosen = currentProfile;
            this->SetDefaultProfile(currentProfile);
        }
#ifdef MOZ_DEV_EDITION
        // Use the dev-edition-default profile if this is an Aurora build and
        // ignore-dev-edition-profile is not present.
        if (name.EqualsLiteral("dev-edition-default") && !shouldIgnoreSeparateProfile) {
            mChosen = currentProfile;
            foundAuroraDefault = true;
        }
#endif
    }

#ifdef MOZ_DEV_EDITION
    if (!foundAuroraDefault && !shouldIgnoreSeparateProfile) {
        // If a single profile exists, it may not be already marked as default.
        // Do it now to avoid problems when we create the dev-edition-default profile.
        if (!mChosen && mFirst && !mFirst->mNext)
            this->SetDefaultProfile(mFirst);

        // Create a default profile for aurora, if none was found.
        nsCOMPtr<nsIToolkitProfile> profile;
        rv = CreateProfile(nullptr,
                           NS_LITERAL_CSTRING("dev-edition-default"),
                           getter_AddRefs(profile));
        if (NS_FAILED(rv)) return rv;
        mChosen = profile;
        rv = Flush();
        if (NS_FAILED(rv)) return rv;
    }
#endif

    if (!mChosen && mFirst && !mFirst->mNext) // only one profile
        mChosen = mFirst;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::SetStartWithLastProfile(bool aValue)
{
    if (mStartWithLast != aValue) {
        mStartWithLast = aValue;
        mDirty = true;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetStartWithLastProfile(bool *aResult)
{
    *aResult = mStartWithLast;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetStartOffline(bool *aResult)
{
    *aResult = mStartOffline;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::SetStartOffline(bool aValue)
{
    mStartOffline = aValue;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetProfiles(nsISimpleEnumerator* *aResult)
{
    *aResult = new ProfileEnumerator(this->mFirst);
    if (!*aResult)
        return NS_ERROR_OUT_OF_MEMORY;

    NS_ADDREF(*aResult);
    return NS_OK;
}

NS_IMPL_ISUPPORTS(nsToolkitProfileService::ProfileEnumerator,
                  nsISimpleEnumerator)

NS_IMETHODIMP
nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult)
{
    *aResult = mCurrent ? true : false;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports* *aResult)
{
    if (!mCurrent) return NS_ERROR_FAILURE;

    NS_ADDREF(*aResult = mCurrent);

    mCurrent = mCurrent->mNext;
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetSelectedProfile(nsIToolkitProfile* *aResult)
{
    if (!mChosen && mFirst && !mFirst->mNext) // only one profile
        mChosen = mFirst;

    if (!mChosen) return NS_ERROR_FAILURE;

    NS_ADDREF(*aResult = mChosen);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::SetSelectedProfile(nsIToolkitProfile* aProfile)
{
    if (mChosen != aProfile) {
        mChosen = aProfile;
        mDirty = true;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile* *aResult)
{
    if (!mDefault) return NS_ERROR_FAILURE;

    NS_ADDREF(*aResult = mDefault);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile)
{
    if (mDefault != aProfile) {
        mDefault = aProfile;
        mDirty = true;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetProfileByName(const nsACString& aName,
                                          nsIToolkitProfile* *aResult)
{
    nsToolkitProfile* curP = mFirst;
    while (curP) {
        if (curP->mName.Equals(aName)) {
            NS_ADDREF(*aResult = curP);
            return NS_OK;
        }
        curP = curP->mNext;
    }

    return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsToolkitProfileService::LockProfilePath(nsIFile* aDirectory,
                                         nsIFile* aLocalDirectory,
                                         nsIProfileLock* *aResult)
{
    return NS_LockProfilePath(aDirectory, aLocalDirectory, nullptr, aResult);
}

nsresult
NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
                   nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
{
    RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
    if (!lock) return NS_ERROR_OUT_OF_MEMORY;

    nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
    if (NS_FAILED(rv)) return rv;

    lock.forget(aResult);
    return NS_OK;
}

static const char kTable[] =
    { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
      'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };

static void SaltProfileName(nsACString& aName)
{
    double fpTime = double(PR_Now());

    // use 1e-6, granularity of PR_Now() on the mac is seconds
    srand((unsigned int)(fpTime * 1e-6 + 0.5));

    char salt[9];

    int i;
    for (i = 0; i < 8; ++i)
        salt[i] = kTable[rand() % ArrayLength(kTable)];

    salt[8] = '.';

    aName.Insert(salt, 0, 9);
}

NS_IMETHODIMP
nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileName,
                                                    const nsACString& aAppName,
                                                    const nsACString& aVendorName,
                                                    nsIToolkitProfile** aResult)
{
    NS_ENSURE_STATE(!aProfileName.IsEmpty() || !aAppName.IsEmpty());
    nsCOMPtr<nsIFile> appData;
    nsresult rv =
        gDirServiceProvider->GetUserDataDirectory(getter_AddRefs(appData),
                                                  false,
                                                  &aProfileName,
                                                  &aAppName,
                                                  &aVendorName);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIFile> profilesini;
    appData->Clone(getter_AddRefs(profilesini));
    rv = profilesini->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
    NS_ENSURE_SUCCESS(rv, rv);

    bool exists = false;
    profilesini->Exists(&exists);
    NS_ENSURE_FALSE(exists, NS_ERROR_ALREADY_INITIALIZED);

    rv = CreateProfileInternal(nullptr,
                               NS_LITERAL_CSTRING("default"),
                               &aProfileName, &aAppName, &aVendorName,
                               true, aResult);
    NS_ENSURE_SUCCESS(rv, rv);
    NS_ENSURE_STATE(*aResult);

    nsCOMPtr<nsIFile> rootDir;
    (*aResult)->GetRootDir(getter_AddRefs(rootDir));
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoCString profileDir;
    rv = rootDir->GetRelativeDescriptor(appData, profileDir);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCString ini;
    ini.SetCapacity(512);
    ini.AppendLiteral("[General]\n");
    ini.AppendLiteral("StartWithLastProfile=1\n\n");

    ini.AppendLiteral("[Profile0]\n");
    ini.AppendLiteral("Name=default\n");
    ini.AppendLiteral("IsRelative=1\n");
    ini.AppendLiteral("Path=");
    ini.Append(profileDir);
    ini.Append('\n');
    ini.AppendLiteral("Default=1\n\n");

    FILE* writeFile;
    rv = profilesini->OpenANSIFileDesc("w", &writeFile);
    NS_ENSURE_SUCCESS(rv, rv);

    if (fwrite(ini.get(), sizeof(char), ini.Length(), writeFile) !=
        ini.Length()) {
        rv = NS_ERROR_UNEXPECTED;
    }
    fclose(writeFile);
    return rv;
}

NS_IMETHODIMP
nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
                                       const nsACString& aName,
                                       nsIToolkitProfile** aResult)
{
    return CreateProfileInternal(aRootDir, aName,
                                 nullptr, nullptr, nullptr, false, aResult);
}

nsresult
nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
                                               const nsACString& aName,
                                               const nsACString* aProfileName,
                                               const nsACString* aAppName,
                                               const nsACString* aVendorName,
                                               bool aForExternalApp,
                                               nsIToolkitProfile** aResult)
{
    nsresult rv = NS_ERROR_FAILURE;

    if (!aForExternalApp) {
        rv = GetProfileByName(aName, aResult);
        if (NS_SUCCEEDED(rv)) {
            return rv;
        }
    }

    nsCOMPtr<nsIFile> rootDir (aRootDir);

    nsAutoCString dirName;
    if (!rootDir) {
        rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir),
                                                         aProfileName, aAppName,
                                                         aVendorName);
        NS_ENSURE_SUCCESS(rv, rv);

        dirName = aName;
        SaltProfileName(dirName);

        if (NS_IsNativeUTF8()) {
            rootDir->AppendNative(dirName);
        } else {
            rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
        }
    }

    nsCOMPtr<nsIFile> localDir;

    bool isRelative;
    rv = mAppData->Contains(rootDir, &isRelative);
    if (NS_SUCCEEDED(rv) && isRelative) {
        nsAutoCString path;
        rv = rootDir->GetRelativeDescriptor(mAppData, path);
        NS_ENSURE_SUCCESS(rv, rv);

        rv = NS_NewNativeLocalFile(EmptyCString(), true,
                                   getter_AddRefs(localDir));
        NS_ENSURE_SUCCESS(rv, rv);

        rv = localDir->SetRelativeDescriptor(mTempData, path);
    } else {
        localDir = rootDir;
    }

    bool exists;
    rv = rootDir->Exists(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (exists) {
        rv = rootDir->IsDirectory(&exists);
        NS_ENSURE_SUCCESS(rv, rv);

        if (!exists)
            return NS_ERROR_FILE_NOT_DIRECTORY;
    }
    else {
        nsCOMPtr<nsIFile> profileDirParent;
        nsAutoString profileDirName;

        rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
        NS_ENSURE_SUCCESS(rv, rv);

        rv = rootDir->GetLeafName(profileDirName);
        NS_ENSURE_SUCCESS(rv, rv);

        // let's ensure that the profile directory exists.
        rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
        NS_ENSURE_SUCCESS(rv, rv);
        rv = rootDir->SetPermissions(0700);
#ifndef ANDROID
        // If the profile is on the sdcard, this will fail but its non-fatal
        NS_ENSURE_SUCCESS(rv, rv);
#endif
    }

    rv = localDir->Exists(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!exists) {
        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
        NS_ENSURE_SUCCESS(rv, rv);
    }

    // We created a new profile dir. Let's store a creation timestamp.
    // Note that this code path does not apply if the profile dir was
    // created prior to launching.
    rv = CreateTimesInternal(rootDir);
    NS_ENSURE_SUCCESS(rv, rv);

    nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst.get();
    if (last) {
        while (last->mNext)
            last = last->mNext;
    }

    nsCOMPtr<nsIToolkitProfile> profile =
        new nsToolkitProfile(aName, rootDir, localDir, last, aForExternalApp);
    if (!profile) return NS_ERROR_OUT_OF_MEMORY;

    profile.forget(aResult);
    return NS_OK;
}

nsresult
nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir)
{
    nsresult rv = NS_ERROR_FAILURE;
    nsCOMPtr<nsIFile> creationLog;
    rv = aProfileDir->Clone(getter_AddRefs(creationLog));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
    NS_ENSURE_SUCCESS(rv, rv);

    bool exists = false;
    creationLog->Exists(&exists);
    if (exists) {
      return NS_OK;
    }

    rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
    NS_ENSURE_SUCCESS(rv, rv);

    // We don't care about microsecond resolution.
    int64_t msec = PR_Now() / PR_USEC_PER_MSEC;

    // Write it out.
    PRFileDesc *writeFile;
    rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
    NS_ENSURE_SUCCESS(rv, rv);

    PR_fprintf(writeFile, "{\n\"created\": %lld\n}\n", msec);
    PR_Close(writeFile);
    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
{
    if (!mFirst)
        *aResult = 0;
    else if (! mFirst->mNext)
        *aResult = 1;
    else
        *aResult = 2;

    return NS_OK;
}

NS_IMETHODIMP
nsToolkitProfileService::Flush()
{
    // Errors during writing might cause unhappy semi-written files.
    // To avoid this, write the entire thing to a buffer, then write
    // that buffer to disk.

    nsresult rv;
    uint32_t pCount = 0;
    nsToolkitProfile *cur;

    for (cur = mFirst; cur != nullptr; cur = cur->mNext)
        ++pCount;

    uint32_t length;
    const int bufsize = 100+MAXPATHLEN*pCount;
    auto buffer = MakeUnique<char[]>(bufsize);

    char *pos = buffer.get();
    char *end = pos + bufsize;

    pos += snprintf(pos, end - pos,
                    "[General]\n"
                    "StartWithLastProfile=%s\n\n",
                    mStartWithLast ? "1" : "0");

    nsAutoCString path;
    cur = mFirst;
    pCount = 0;

    while (cur) {
        // if the profile dir is relative to appdir...
        bool isRelative;
        rv = mAppData->Contains(cur->mRootDir, &isRelative);
        if (NS_SUCCEEDED(rv) && isRelative) {
            // we use a relative descriptor
            rv = cur->mRootDir->GetRelativeDescriptor(mAppData, path);
        } else {
            // otherwise, a persistent descriptor
            rv = cur->mRootDir->GetPersistentDescriptor(path);
            NS_ENSURE_SUCCESS(rv, rv);
        }

        pos += snprintf(pos, end - pos,
                        "[Profile%u]\n"
                        "Name=%s\n"
                        "IsRelative=%s\n"
                        "Path=%s\n",
                        pCount, cur->mName.get(),
                        isRelative ? "1" : "0", path.get());

        nsCOMPtr<nsIToolkitProfile> profile;
        rv = this->GetDefaultProfile(getter_AddRefs(profile));
        if (NS_SUCCEEDED(rv) && profile == cur) {
            pos += snprintf(pos, end - pos, "Default=1\n");
        }

        pos += snprintf(pos, end - pos, "\n");

        cur = cur->mNext;
        ++pCount;
    }

    FILE* writeFile;
    rv = mListFile->OpenANSIFileDesc("w", &writeFile);
    NS_ENSURE_SUCCESS(rv, rv);

    length = pos - buffer.get();

    if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
        fclose(writeFile);
        return NS_ERROR_UNEXPECTED;
    }

    fclose(writeFile);
    return NS_OK;
}

NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)

NS_IMETHODIMP
nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
                                        void** aResult)
{
    if (aOuter)
        return NS_ERROR_NO_AGGREGATION;

    nsCOMPtr<nsIToolkitProfileService> profileService =
        nsToolkitProfileService::gService;
    if (!profileService) {
        nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
        if (NS_FAILED(rv))
            return rv;
    }
    return profileService->QueryInterface(aIID, aResult);
}

NS_IMETHODIMP
nsToolkitProfileFactory::LockFactory(bool aVal)
{
    return NS_OK;
}

nsresult
NS_NewToolkitProfileFactory(nsIFactory* *aResult)
{
    *aResult = new nsToolkitProfileFactory();
    if (!*aResult)
        return NS_ERROR_OUT_OF_MEMORY;

    NS_ADDREF(*aResult);
    return NS_OK;
}

nsresult
NS_NewToolkitProfileService(nsIToolkitProfileService* *aResult)
{
    nsToolkitProfileService* profileService = new nsToolkitProfileService();
    if (!profileService)
        return NS_ERROR_OUT_OF_MEMORY;
    nsresult rv = profileService->Init();
    if (NS_FAILED(rv)) {
        NS_ERROR("nsToolkitProfileService::Init failed!");
        delete profileService;
        return rv;
    }

    NS_ADDREF(*aResult = profileService);
    return NS_OK;
}

nsresult
XRE_GetFileFromPath(const char *aPath, nsIFile* *aResult)
{
#if defined(XP_MACOSX)
    int32_t pathLen = strlen(aPath);
    if (pathLen > MAXPATHLEN)
        return NS_ERROR_INVALID_ARG;

    CFURLRef fullPath =
        CFURLCreateFromFileSystemRepresentation(nullptr, (const UInt8 *) aPath,
                                                pathLen, true);
    if (!fullPath)
        return NS_ERROR_FAILURE;

    nsCOMPtr<nsIFile> lf;
    nsresult rv = NS_NewNativeLocalFile(EmptyCString(), true,
                                        getter_AddRefs(lf));
    if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
        if (NS_SUCCEEDED(rv)) {
            rv = lfMac->InitWithCFURL(fullPath);
            if (NS_SUCCEEDED(rv)) {
                lf.forget(aResult);
            }
        }
    }
    CFRelease(fullPath);
    return rv;

#elif defined(XP_UNIX)
    char fullPath[MAXPATHLEN];

    if (!realpath(aPath, fullPath))
        return NS_ERROR_FAILURE;

    return NS_NewNativeLocalFile(nsDependentCString(fullPath), true,
                                 aResult);
#elif defined(XP_WIN)
    WCHAR fullPath[MAXPATHLEN];

    if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
        return NS_ERROR_FAILURE;

    return NS_NewLocalFile(nsDependentString(fullPath), true,
                           aResult);

#else
#error Platform-specific logic needed here.
#endif
}
