[tor-commits] [tor-browser/tor-browser-88.0.1-10.0-1] Bug 14631: Improve profile access error messages.

sysrqb at torproject.org sysrqb at torproject.org
Tue May 4 20:28:37 UTC 2021


commit 2f15abbb0f2d772b0c7f9ff7c3a697390ce3813b
Author: Kathy Brade <brade at pearlcrescent.com>
Date:   Tue Feb 24 13:50:23 2015 -0500

    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.
---
 .../mozapps/profile/profileSelection.properties    |   5 +
 toolkit/profile/nsToolkitProfileService.cpp        |  57 +++++++-
 toolkit/profile/nsToolkitProfileService.h          |  13 +-
 toolkit/xre/nsAppRunner.cpp                        | 157 ++++++++++++++++++---
 4 files changed, 208 insertions(+), 24 deletions(-)

diff --git a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
index d326083202b2..aa38bda24347 100644
--- a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
@@ -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’
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
index b80291179219..90eb336cb3d2 100644
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -1159,9 +1159,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.
@@ -1194,7 +1195,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;
   }
@@ -1288,6 +1290,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);
@@ -2081,3 +2090,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);
+}
diff --git a/toolkit/profile/nsToolkitProfileService.h b/toolkit/profile/nsToolkitProfileService.h
index 6b68c019ca52..aa92b9a27b64 100644
--- a/toolkit/profile/nsToolkitProfileService.h
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -16,6 +16,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>> {
@@ -80,10 +88,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;
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index 761c5975f5b5..414c33f18147 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2280,6 +2280,91 @@ nsresult LaunchChild(bool aBlankCommandLine, bool aTryExec) {
   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";
 
@@ -2345,7 +2430,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
@@ -2367,11 +2452,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;
@@ -2399,24 +2485,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);
 
 #ifdef MOZ_BACKGROUNDTASKS
@@ -2576,6 +2677,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
@@ -2602,7 +2710,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
@@ -2617,7 +2726,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;
@@ -2663,9 +2773,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);
@@ -4560,7 +4675,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;





More information about the tor-commits mailing list