Commit 7d6942f6 authored by Kathleen Brade's avatar Kathleen Brade Committed by Alex Catarineu
Browse files

Bug 14631: Improve profile access error messages.

Instead of always reporting that the profile is locked, display specific
messages for "access denied" and "read-only file system".

To allow for localization, get profile-related error strings from Torbutton.
Use app display name ("Tor Browser") in profile-related error alerts.
parent 90891242
......@@ -12,6 +12,11 @@ restartMessageUnlocker=%S is already running, but is not responding. The old %S
restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.
# LOCALIZATION NOTE (profileProblemTitle, profileReadOnly, profileReadOnlyMac, profileAccessDenied): Messages displayed when the browser profile cannot be accessed or written to. %S is the application name.
profileProblemTitle=%S Profile Problem
profileReadOnly=You cannot run %S from a read-only file system. Please copy %S to another location before trying to use it.
profileReadOnlyMac=You cannot run %S from a read-only file system. Please copy %S to your Desktop or Applications folder before trying to use it.
profileAccessDenied=%S does not have permission to access the profile. Please adjust your file system permissions and try again.
# Profile manager
# LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder.
profileTooltip=Profile: ‘%S’ - Path: ‘%S’
......
......@@ -1160,9 +1160,10 @@ nsToolkitProfileService::SelectStartupProfile(
}
bool wasDefault;
ProfileStatus profileStatus;
nsresult rv =
SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
aProfile, aDidCreate, &wasDefault);
aProfile, aDidCreate, &wasDefault, profileStatus);
// Since we were called outside of the normal startup path complete any
// startup tasks.
......@@ -1195,7 +1196,8 @@ nsToolkitProfileService::SelectStartupProfile(
nsresult nsToolkitProfileService::SelectStartupProfile(
int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
bool* aWasDefaultSelection) {
bool* aWasDefaultSelection, ProfileStatus& aProfileStatus) {
aProfileStatus = PROFILE_STATUS_OK;
if (mStartupProfileSelected) {
return NS_ERROR_ALREADY_INITIALIZED;
}
......@@ -1289,6 +1291,13 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
NS_ENSURE_SUCCESS(rv, rv);
aProfileStatus = CheckProfileWriteAccess(lf);
if (PROFILE_STATUS_OK != aProfileStatus) {
NS_ADDREF(*aRootDir = lf);
NS_ADDREF(*aLocalDir = lf);
return NS_ERROR_FAILURE;
}
// Make sure that the profile path exists and it's a directory.
bool exists;
rv = lf->Exists(&exists);
......@@ -2078,3 +2087,47 @@ nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
# error Platform-specific logic needed here.
#endif
}
// Check for write permission to the profile directory by trying to create a
// new file (after ensuring that no file with the same name exists).
ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
nsIFile* aProfileDir) {
#if defined(XP_UNIX)
constexpr auto writeTestFileName = u".parentwritetest"_ns;
#else
constexpr auto writeTestFileName = u"parent.writetest"_ns;
#endif
nsCOMPtr<nsIFile> writeTestFile;
nsresult rv = aProfileDir->Clone(getter_AddRefs(writeTestFile));
if (NS_SUCCEEDED(rv)) rv = writeTestFile->Append(writeTestFileName);
if (NS_SUCCEEDED(rv)) {
bool doesExist = false;
rv = writeTestFile->Exists(&doesExist);
if (NS_SUCCEEDED(rv) && doesExist) rv = writeTestFile->Remove(true);
}
if (NS_SUCCEEDED(rv)) {
rv = writeTestFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
(void)writeTestFile->Remove(true);
}
ProfileStatus status =
NS_SUCCEEDED(rv) ? PROFILE_STATUS_OK : PROFILE_STATUS_OTHER_ERROR;
if (NS_ERROR_FILE_ACCESS_DENIED == rv)
status = PROFILE_STATUS_ACCESS_DENIED;
else if (NS_ERROR_FILE_READ_ONLY == rv)
status = PROFILE_STATUS_READ_ONLY;
return status;
}
ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
nsIToolkitProfile* aProfile) {
nsCOMPtr<nsIFile> profileDir;
nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
if (NS_FAILED(rv)) return PROFILE_STATUS_OTHER_ERROR;
return CheckProfileWriteAccess(profileDir);
}
......@@ -15,6 +15,14 @@
#include "nsProfileLock.h"
#include "nsINIParser.h"
enum ProfileStatus {
PROFILE_STATUS_OK,
PROFILE_STATUS_ACCESS_DENIED,
PROFILE_STATUS_READ_ONLY,
PROFILE_STATUS_IS_LOCKED,
PROFILE_STATUS_OTHER_ERROR
};
class nsToolkitProfile final
: public nsIToolkitProfile,
public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
......@@ -79,10 +87,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting,
nsIFile** aRootDir, nsIFile** aLocalDir,
nsIToolkitProfile** aProfile, bool* aDidCreate,
bool* aWasDefaultSelection);
bool* aWasDefaultSelection,
ProfileStatus& aProfileStatus);
nsresult CreateResetProfile(nsIToolkitProfile** aNewProfile);
nsresult ApplyResetProfile(nsIToolkitProfile* aOldProfile);
void CompleteStartup();
static ProfileStatus CheckProfileWriteAccess(nsIToolkitProfile* aProfile);
static ProfileStatus CheckProfileWriteAccess(nsIFile* aProfileDir);
private:
friend class nsToolkitProfile;
......
......@@ -1818,6 +1818,91 @@ nsresult LaunchChild(bool aBlankCommandLine) {
return NS_ERROR_LAUNCHED_CHILD_PROCESS;
}
static nsresult GetOverrideStringBundleForLocale(nsIStringBundleService* aSBS,
const char* aTorbuttonURI,
const char* aLocale,
nsIStringBundle** aResult) {
NS_ENSURE_ARG(aSBS);
NS_ENSURE_ARG(aTorbuttonURI);
NS_ENSURE_ARG(aLocale);
NS_ENSURE_ARG(aResult);
const char* kFormatStr =
"jar:%s!/chrome/torbutton/locale/%s/torbutton.properties";
nsPrintfCString strBundleURL(kFormatStr, aTorbuttonURI, aLocale);
nsresult rv = aSBS->CreateBundle(strBundleURL.get(), aResult);
NS_ENSURE_SUCCESS(rv, rv);
// To ensure that we have a valid string bundle, try to retrieve a string
// that we know exists.
nsAutoString val;
rv = (*aResult)->GetStringFromName("profileProblemTitle", val);
if (!NS_SUCCEEDED(rv)) *aResult = nullptr; // No good. Discard it.
return rv;
}
static void GetOverrideStringBundle(nsIStringBundleService* aSBS,
nsIStringBundle** aResult) {
if (!aSBS || !aResult) return;
*aResult = nullptr;
// Build Torbutton file URI string by starting from GREDir.
RefPtr<nsXREDirProvider> dirProvider = nsXREDirProvider::GetSingleton();
if (!dirProvider) return;
nsCOMPtr<nsIFile> greDir = dirProvider->GetGREDir();
if (!greDir) return;
// Create file URI, extract as string, and append omni.ja relative path.
nsCOMPtr<nsIURI> uri;
nsAutoCString uriString;
if (NS_FAILED(NS_NewFileURI(getter_AddRefs(uri), greDir)) ||
NS_FAILED(uri->GetSpec(uriString))) {
return;
}
uriString.Append("omni.ja");
nsAutoCString userAgentLocale;
if (!NS_SUCCEEDED(
Preferences::GetCString("intl.locale.requested", userAgentLocale))) {
return;
}
nsresult rv = GetOverrideStringBundleForLocale(
aSBS, uriString.get(), userAgentLocale.get(), aResult);
if (NS_FAILED(rv)) {
// Try again using base locale, e.g., "en" vs. "en-US".
int16_t offset = userAgentLocale.FindChar('-', 1);
if (offset > 0) {
nsAutoCString shortLocale(Substring(userAgentLocale, 0, offset));
rv = GetOverrideStringBundleForLocale(aSBS, uriString.get(),
shortLocale.get(), aResult);
}
}
}
static nsresult GetFormattedString(nsIStringBundle* aOverrideBundle,
nsIStringBundle* aMainBundle,
const char* aName,
const nsTArray<nsString>& aParams,
nsAString& aResult) {
NS_ENSURE_ARG(aName);
nsresult rv = NS_ERROR_FAILURE;
if (aOverrideBundle) {
rv = aOverrideBundle->FormatStringFromName(aName, aParams, aResult);
}
// If string was not found in override bundle, use main (browser) bundle.
if (NS_FAILED(rv) && aMainBundle)
rv = aMainBundle->FormatStringFromName(aName, aParams, aResult);
return rv;
}
static const char kProfileProperties[] =
"chrome://mozapps/locale/profile/profileSelection.properties";
......@@ -1866,7 +1951,7 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
NS_ConvertUTF8toUTF16 appName(gAppData->name);
NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME);
AutoTArray<nsString, 2> params = {appName, appName};
// profileMissing
......@@ -1888,11 +1973,12 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
}
}
static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
nsIFile* aProfileLocalDir,
nsIProfileUnlocker* aUnlocker,
nsINativeAppSupport* aNative,
nsIProfileLock** aResult) {
static ReturnAbortOnError ProfileErrorDialog(nsIFile* aProfileDir,
nsIFile* aProfileLocalDir,
ProfileStatus aStatus,
nsIProfileUnlocker* aUnlocker,
nsINativeAppSupport* aNative,
nsIProfileLock** aResult) {
nsresult rv;
bool exists;
......@@ -1920,24 +2006,39 @@ static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
NS_ConvertUTF8toUTF16 appName(gAppData->name);
nsCOMPtr<nsIStringBundle> overrideSB;
GetOverrideStringBundle(sbs, getter_AddRefs(overrideSB));
NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME);
AutoTArray<nsString, 3> params = {appName, appName, appName};
nsAutoString killMessage;
#ifndef XP_MACOSX
rv = sb->FormatStringFromName(
aUnlocker ? "restartMessageUnlocker" : "restartMessageNoUnlocker2",
params, killMessage);
static const char kRestartUnlocker[] = "restartMessageUnlocker";
static const char kRestartNoUnlocker[] = "restartMessageNoUnlocker2";
static const char kReadOnly[] = "profileReadOnly";
#else
rv = sb->FormatStringFromName(
aUnlocker ? "restartMessageUnlockerMac" : "restartMessageNoUnlockerMac",
params, killMessage);
#endif
static const char kRestartUnlocker[] = "restartMessageUnlockerMac";
static const char kRestartNoUnlocker[] = "restartMessageNoUnlockerMac";
static const char kReadOnly[] = "profileReadOnlyMac";
#endif
static const char kAccessDenied[] = "profileAccessDenied";
const char* errorKey = aUnlocker ? kRestartUnlocker : kRestartNoUnlocker;
if (PROFILE_STATUS_READ_ONLY == aStatus)
errorKey = kReadOnly;
else if (PROFILE_STATUS_ACCESS_DENIED == aStatus)
errorKey = kAccessDenied;
rv = GetFormattedString(overrideSB, sb, errorKey, params, killMessage);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
const char* titleKey = ((PROFILE_STATUS_READ_ONLY == aStatus) ||
(PROFILE_STATUS_ACCESS_DENIED == aStatus))
? "profileProblemTitle"
: "restartTitle";
params.SetLength(1);
nsAutoString killTitle;
rv = sb->FormatStringFromName("restartTitle", params, killTitle);
rv = sb->FormatStringFromName(titleKey, params, killTitle);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
if (gfxPlatform::IsHeadless()) {
......@@ -2096,6 +2197,13 @@ static nsCOMPtr<nsIToolkitProfile> gResetOldProfile;
static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
nsIFile* aLocalDir, nsIToolkitProfile* aProfile,
nsIProfileLock** aResult) {
ProfileStatus status =
(aProfile ? nsToolkitProfileService::CheckProfileWriteAccess(aProfile)
: nsToolkitProfileService::CheckProfileWriteAccess(aRootDir));
if (PROFILE_STATUS_OK != status)
return ProfileErrorDialog(aRootDir, aLocalDir, status, nullptr, aNative,
aResult);
// If you close Firefox and very quickly reopen it, the old Firefox may
// still be closing down. Rather than immediately showing the
// "Firefox is running but is not responding" message, we spend a few
......@@ -2122,7 +2230,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
} while (TimeStamp::Now() - start <
TimeDuration::FromSeconds(kLockRetrySeconds));
return ProfileLockedDialog(aRootDir, aLocalDir, unlocker, aNative, aResult);
return ProfileErrorDialog(aRootDir, aLocalDir, PROFILE_STATUS_IS_LOCKED,
unlocker, aNative, aResult);
}
// Pick a profile. We need to end up with a profile root dir, local dir and
......@@ -2137,7 +2246,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
nsINativeAppSupport* aNative, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
bool* aWasDefaultSelection) {
bool* aWasDefaultSelection,
nsIProfileLock** aResult) {
StartupTimeline::Record(StartupTimeline::SELECT_PROFILE);
nsresult rv;
......@@ -2183,9 +2293,14 @@ static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
// Ask the profile manager to select the profile directories to use.
bool didCreate = false;
rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset,
aRootDir, aLocalDir, aProfile,
&didCreate, aWasDefaultSelection);
ProfileStatus profileStatus = PROFILE_STATUS_OK;
rv = aProfileSvc->SelectStartupProfile(
&gArgc, gArgv, gDoProfileReset, aRootDir, aLocalDir, aProfile, &didCreate,
aWasDefaultSelection, profileStatus);
if (PROFILE_STATUS_OK != profileStatus) {
return ProfileErrorDialog(*aRootDir, *aLocalDir, profileStatus, nullptr,
aNative, aResult);
}
if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) {
return ShowProfileManager(aProfileSvc, aNative);
......@@ -4013,7 +4128,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
nsCOMPtr<nsIToolkitProfile> profile;
rv = SelectProfile(mProfileSvc, mNativeApp, getter_AddRefs(mProfD),
getter_AddRefs(mProfLD), getter_AddRefs(profile),
&wasDefaultSelection);
&wasDefaultSelection, getter_AddRefs(mProfileLock));
if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) {
*aExitFlag = true;
return 0;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment