gecko-dev/toolkit/profile/nsToolkitProfileService.cpp
Dave Townsend 961eb5b518 Bug 1518591: Make the profile service responsible for protecting against downgrading to builds before dev-edition had a separate profile. r=froydnj
Because older versions of Firefox auto-select a profile if there is only one in
the database when running dev-edition which uses its own profile we create a
default for normal channels to use. Currently the browser code is responsible
for doing this but it uses a bad heuristic for deciding when to do that. It's
much easier to do it from the profile manager when the dev-edition profile is
created.

Differential Revision: https://phabricator.services.mozilla.com/D16117

--HG--
extra : moz-landing-system : lando
2019-01-10 21:15:53 +00:00

1127 lines
31 KiB
C++

/* -*- Mode: C++; tab-width: 8; 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/. */
#include "mozilla/ArrayUtils.h"
#include "mozilla/UniquePtr.h"
#include <stdio.h>
#include <stdlib.h>
#include <prprf.h>
#include <prtime.h>
#ifdef XP_WIN
#include <windows.h>
#include <shlobj.h>
#endif
#ifdef XP_UNIX
#include <unistd.h>
#endif
#include "nsToolkitProfileService.h"
#include "CmdLineAndEnvUtils.h"
#include "nsIFile.h"
#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#include "nsILocalFileMac.h"
#endif
#include "nsAppDirectoryServiceDefs.h"
#include "nsNetCID.h"
#include "nsXULAppAPI.h"
#include "nsThreadUtils.h"
#include "nsIRunnable.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;
#define DEV_EDITION_NAME "dev-edition-default"
#define DEFAULT_NAME "default"
nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
nsIFile* aLocalDir, nsToolkitProfile* aPrev)
: mPrev(aPrev),
mName(aName),
mRootDir(aRootDir),
mLocalDir(aLocalDir),
mLock(nullptr) {
NS_ASSERTION(aRootDir, "No file!");
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?");
mName = aName;
return NS_OK;
}
nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
bool aInBackground) {
NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
if (mLock) return NS_ERROR_FILE_IS_LOCKED;
if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
return NS_ERROR_NOT_INITIALIZED;
if (aRemoveFiles) {
// Check if another instance is using this profile.
nsCOMPtr<nsIProfileLock> lock;
nsresult rv = Lock(nullptr, getter_AddRefs(lock));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> rootDir(mRootDir);
nsCOMPtr<nsIFile> localDir(mLocalDir);
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
"nsToolkitProfile::RemoveInternal", [rootDir, localDir, lock]() {
bool equals;
nsresult rv = rootDir->Equals(localDir, &equals);
// The root dir might contain the temp dir, so remove
// the temp dir first.
if (NS_SUCCEEDED(rv) && !equals) {
localDir->Remove(true);
}
// Ideally we'd unlock after deleting but since the lock is a file
// in the profile we must unlock before removing.
lock->Unlock();
rootDir->Remove(true);
});
if (aInBackground) {
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
} else {
runnable->Run();
}
}
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;
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfile::Remove(bool removeFiles) {
return RemoveInternal(removeFiles, false /* in background */);
}
NS_IMETHODIMP
nsToolkitProfile::RemoveInBackground(bool removeFiles) {
return RemoveInternal(removeFiles, true /* in background */);
}
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)
nsToolkitProfileService::nsToolkitProfileService()
: mStartupProfileSelected(false), mStartWithLast(true), mIsFirstRun(true) {
gService = this;
}
nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; }
nsresult nsToolkitProfileService::Init() {
NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
nsresult rv;
rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
NS_ENSURE_SUCCESS(rv, rv);
rv = nsXREDirProvider::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
nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
unsigned int nonDevEditionProfiles = 0;
unsigned int c = 0;
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);
NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
rv = parser.GetString(profileID.get(), "Default", buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
mDefault = currentProfile;
}
if (name.EqualsLiteral(DEV_EDITION_NAME)) {
#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 (!shouldIgnoreSeparateProfile) {
mChosen = currentProfile;
}
#endif
} else {
nonDevEditionProfiles++;
autoSelectProfile = currentProfile;
}
}
// If there is only one non-dev-edition profile then mark it as the default.
if (!mDefault && nonDevEditionProfiles == 1) {
mDefault = autoSelectProfile;
}
// Normally having no non-dev-edition builds suggests this is the first run.
mIsFirstRun = nonDevEditionProfiles == 0;
#ifdef MOZ_DEV_EDITION
if (!shouldIgnoreSeparateProfile) {
// Except when using the separate dev-edition profile, in which case not
// finding it means this is a first run.
mIsFirstRun = !mChosen;
} else {
mChosen = mDefault;
}
#else
mChosen = mDefault;
#endif
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
if (mStartWithLast != aValue) {
mStartWithLast = aValue;
}
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
*aResult = mStartWithLast;
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_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;
}
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;
}
return NS_OK;
}
/**
* An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
* See nsIToolkitProfileService.idl.
*/
NS_IMETHODIMP
nsToolkitProfileService::SelectStartupProfile(
const nsTArray<nsCString>& aArgv, bool aIsResetting, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate) {
int argc = aArgv.Length();
// Our command line handling expects argv to be null-terminated so construct
// an appropriate array.
auto argv = MakeUnique<char*[]>(argc + 1);
// Also, our command line handling removes things from the array without
// freeing them so keep track of what we've created separately.
auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
for (int i = 0; i < argc; i++) {
allocated[i].reset(ToNewCString(aArgv[i]));
argv[i] = allocated[i].get();
}
argv[argc] = nullptr;
nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir,
aLocalDir, aProfile, aDidCreate);
return rv;
}
/**
* Selects or creates a profile to use based on the profiles database, any
* environment variables and any command line arguments. Will not create
* a profile if aIsResetting is true. The profile is selected based on this
* order of preference:
* * Environment variables (set when restarting the application).
* * --profile command line argument.
* * --createprofile command line argument (this also causes the app to exit).
* * -p command line argument.
* * A new profile created if this is the first run of the application.
* * The default profile.
* aRootDir and aLocalDir are set to the data and local directories for the
* profile data. If a profile from the database was selected it will be
* returned in aProfile.
* aDidCreate will be set to true if a new profile was created.
* This function should be called once at startup and will fail if called again.
* aArgv should be an array of aArgc + 1 strings, the last element being null.
* Both aArgv and aArgc will be mutated.
*/
nsresult nsToolkitProfileService::SelectStartupProfile(
int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate) {
if (mStartupProfileSelected) {
return NS_ERROR_ALREADY_INITIALIZED;
}
mStartupProfileSelected = true;
*aDidCreate = false;
nsresult rv;
const char* arg;
nsCOMPtr<nsIToolkitProfile> profile;
// Use the profile specified in the environment variables (generally from an
// app initiated restart).
nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
if (lf) {
nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
if (!localDir) {
localDir = lf;
}
// Clear out flags that we handled (or should have handled!) last startup.
const char* dummy;
CheckArg(*aArgc, aArgv, "p", &dummy);
CheckArg(*aArgc, aArgv, "profile", &dummy);
CheckArg(*aArgc, aArgv, "profilemanager");
GetProfileByDir(lf, localDir, aProfile);
lf.forget(aRootDir);
localDir.forget(aLocalDir);
return NS_OK;
}
// Check the -profile command line argument. It accepts a single argument that
// gives the path to use for the profile.
ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg,
CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
if (ar == ARG_BAD) {
PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
return NS_ERROR_FAILURE;
}
if (ar) {
nsCOMPtr<nsIFile> lf;
rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
NS_ENSURE_SUCCESS(rv, rv);
// Make sure that the profile path exists and it's a directory.
bool exists;
rv = lf->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
NS_ENSURE_SUCCESS(rv, rv);
} else {
bool isDir;
rv = lf->IsDirectory(&isDir);
NS_ENSURE_SUCCESS(rv, rv);
if (!isDir) {
PR_fprintf(
PR_STDERR,
"Error: argument --profile requires a path to a directory\n");
return NS_ERROR_FAILURE;
}
}
// If a profile path is specified directly on the command line, then
// assume that the temp directory is the same as the given directory.
GetProfileByDir(lf, lf, aProfile);
NS_ADDREF(*aRootDir = lf);
lf.forget(aLocalDir);
return NS_OK;
}
// Check the -createprofile command line argument. It accepts a single
// argument that is either the name for the new profile or the name followed
// by the path to use.
ar = CheckArg(*aArgc, aArgv, "createprofile", &arg,
CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
if (ar == ARG_BAD) {
PR_fprintf(PR_STDERR,
"Error: argument --createprofile requires a profile name\n");
return NS_ERROR_FAILURE;
}
if (ar) {
const char* delim = strchr(arg, ' ');
if (delim) {
nsCOMPtr<nsIFile> lf;
rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
getter_AddRefs(lf));
if (NS_FAILED(rv)) {
PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
return rv;
}
// As with --profile, assume that the given path will be used for the
// main profile directory.
rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
getter_AddRefs(profile));
} else {
rv = CreateProfile(nullptr, nsDependentCString(arg),
getter_AddRefs(profile));
}
// Some pathological arguments can make it this far
if (NS_FAILED(rv)) {
PR_fprintf(PR_STDERR, "Error creating profile.\n");
return rv;
}
rv = NS_ERROR_ABORT;
Flush();
return rv;
}
// Check the -p command line argument. It either accepts a profile name and
// uses that named profile or without a name it opens the profile manager.
ar = CheckArg(*aArgc, aArgv, "p", &arg);
if (ar == ARG_BAD) {
ar = CheckArg(*aArgc, aArgv, "osint");
if (ar == ARG_FOUND) {
PR_fprintf(
PR_STDERR,
"Error: argument -p is invalid when argument --osint is specified\n");
return NS_ERROR_FAILURE;
}
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
if (ar) {
ar = CheckArg(*aArgc, aArgv, "osint");
if (ar == ARG_FOUND) {
PR_fprintf(
PR_STDERR,
"Error: argument -p is invalid when argument --osint is specified\n");
return NS_ERROR_FAILURE;
}
rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(profile));
if (NS_SUCCEEDED(rv)) {
profile->GetRootDir(aRootDir);
profile->GetLocalDir(aLocalDir);
profile.forget(aProfile);
return NS_OK;
}
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
ar = CheckArg(*aArgc, aArgv, "profilemanager", (const char**)nullptr,
CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
if (ar == ARG_BAD) {
PR_fprintf(PR_STDERR,
"Error: argument --profilemanager is invalid when argument "
"--osint is specified\n");
return NS_ERROR_FAILURE;
}
if (ar == ARG_FOUND) {
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
// If this is a first run then create a new profile.
if (mIsFirstRun) {
if (aIsResetting) {
// We don't want to create a fresh profile when we're attempting a
// profile reset so just bail out here, the calling code will handle it.
*aProfile = nullptr;
return NS_OK;
}
// create a default profile
nsresult rv = CreateProfile(nullptr, // choose a default dir for us
#ifdef MOZ_DEV_EDITION
NS_LITERAL_CSTRING(DEV_EDITION_NAME),
#else
NS_LITERAL_CSTRING(DEFAULT_NAME),
#endif
getter_AddRefs(mChosen));
if (NS_SUCCEEDED(rv)) {
#ifdef MOZ_DEV_EDITION
// If the only profile is the new dev-edition-profile then older versions
// may try to auto-select it. Create a default profile for them to use
// instead.
if (mFirst && !mFirst->mNext) {
CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
getter_AddRefs(mDefault));
}
#else
SetDefaultProfile(mChosen);
#endif
Flush();
mChosen->GetRootDir(aRootDir);
mChosen->GetLocalDir(aLocalDir);
NS_ADDREF(*aProfile = mChosen);
*aDidCreate = true;
return NS_OK;
}
}
// There are multiple profiles available.
if (!mStartWithLast) {
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
// GetSelectedProfile will auto-select the only profile if there's just one
GetSelectedProfile(getter_AddRefs(profile));
// None of the profiles was marked as default (generally only happens if the
// user modifies profiles.ini manually). Let the user choose.
if (!profile) {
return NS_ERROR_SHOW_PROFILE_MANAGER;
}
// Use the selected profile.
profile->GetRootDir(aRootDir);
profile->GetLocalDir(aLocalDir);
profile.forget(aProfile);
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;
}
/**
* Finds a profile from the database that uses the given root and local
* directories.
*/
void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
nsIFile* aLocalDir,
nsIToolkitProfile** aResult) {
nsToolkitProfile* curP = mFirst;
while (curP) {
bool equal;
nsresult rv = curP->mRootDir->Equals(aRootDir, &equal);
if (NS_SUCCEEDED(rv) && equal) {
rv = curP->mLocalDir->Equals(aLocalDir, &equal);
if (NS_SUCCEEDED(rv) && equal) {
NS_ADDREF(*aResult = curP);
return;
}
}
curP = curP->mNext;
}
}
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 void SaltProfileName(nsACString& aName) {
char salt[9];
NS_MakeRandomString(salt, 8);
salt[8] = '.';
aName.Insert(salt, 0, 9);
}
NS_IMETHODIMP
nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
const nsACString& aName,
nsIToolkitProfile** aResult) {
nsresult rv = GetProfileByName(aName, aResult);
if (NS_SUCCEEDED(rv)) {
return rv;
}
nsCOMPtr<nsIFile> rootDir(aRootDir);
nsAutoCString dirName;
if (!rootDir) {
rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
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 = mFirst.get();
if (last) {
while (last->mNext) last = last->mNext;
}
nsCOMPtr<nsIToolkitProfile> profile =
new nsToolkitProfile(aName, rootDir, localDir, last);
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\"firstUse\": null\n}\n", msec);
PR_Close(writeFile);
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
*aResult = 0;
nsToolkitProfile* profile = mFirst;
while (profile) {
(*aResult)++;
profile = profile->mNext;
}
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
}