From d6661abc54d044315f1afe49ebaa5f0ecd3df9d4 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Thu, 19 Apr 2018 17:22:41 -0600 Subject: [PATCH] Bug 1457999: Part 1 - Move command line argument utils and safe mode checks into a common header file; r=jimm --HG-- extra : rebase_source : 368d45d026ddc658ba732b6f58fcd976c48ff7d4 --- toolkit/xre/CmdLineAndEnvUtils.h | 385 ++++++++++++++++++ toolkit/xre/PolicyChecks.h | 46 +++ toolkit/xre/SafeMode.h | 90 ++++ toolkit/xre/moz.build | 8 +- toolkit/xre/nsAppRunner.cpp | 258 ++---------- toolkit/xre/nsWindowsRestart.cpp | 139 +------ .../test/win/TestXREMakeCommandLineWin.cpp | 14 +- 7 files changed, 566 insertions(+), 374 deletions(-) create mode 100644 toolkit/xre/CmdLineAndEnvUtils.h create mode 100644 toolkit/xre/PolicyChecks.h create mode 100644 toolkit/xre/SafeMode.h diff --git a/toolkit/xre/CmdLineAndEnvUtils.h b/toolkit/xre/CmdLineAndEnvUtils.h new file mode 100644 index 000000000000..0c059b4a0fd8 --- /dev/null +++ b/toolkit/xre/CmdLineAndEnvUtils.h @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CmdLineAndEnvUtils_h +#define mozilla_CmdLineAndEnvUtils_h + +// NB: This code may be used outside of xul and thus must not depend on XPCOM + +#if defined(MOZILLA_INTERNAL_API) +#include "prenv.h" +#include +#elif defined(XP_WIN) +#include +#endif + +#if defined(XP_WIN) +#include "mozilla/Move.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +#include +#endif // defined(XP_WIN) + +#include "mozilla/TypedEnumBits.h" + +#include + +namespace mozilla { + +enum ArgResult { + ARG_NONE = 0, + ARG_FOUND = 1, + ARG_BAD = 2 // you wanted a param, but there isn't one +}; + +template +inline void +RemoveArg(int& argc, CharT **argv) +{ + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --argc; +} + +namespace internal { + +template +inline bool +strimatch(FuncT aToLowerFn, const CharT* lowerstr, const CharT* mixedstr) +{ + while(*lowerstr) { + if (!*mixedstr) return false; // mixedstr is shorter + if (aToLowerFn(*mixedstr) != *lowerstr) return false; // no match + + ++lowerstr; + ++mixedstr; + } + + if (*mixedstr) return false; // lowerstr is shorter + + return true; +} + +} // namespace internal + +inline bool +strimatch(const char* lowerstr, const char* mixedstr) +{ + return internal::strimatch(&tolower, lowerstr, mixedstr); +} + +inline bool +strimatch(const wchar_t* lowerstr, const wchar_t* mixedstr) +{ + return internal::strimatch(&towlower, lowerstr, mixedstr); +} + +enum class FlagLiteral +{ + osint, + safemode +}; + +template +inline const CharT* GetLiteral(); + +#define DECLARE_FLAG_LITERAL(enum_name, literal) \ + template <> inline \ + const char* GetLiteral() \ + { \ + return literal; \ + } \ + \ + template <> inline \ + const wchar_t* GetLiteral() \ + { \ + return L##literal; \ + } + +DECLARE_FLAG_LITERAL(osint, "osint") +DECLARE_FLAG_LITERAL(safemode, "safe-mode") + +enum class CheckArgFlag : uint32_t +{ + None = 0, + CheckOSInt = (1 << 0), // Retrun ARG_BAD if osint arg is also present. + RemoveArg = (1 << 1) // Remove the argument from the argv array. +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag) + +/** + * Check for a commandline flag. If the flag takes a parameter, the + * parameter is returned in aParam. Flags may be in the form -arg or + * --arg (or /arg on win32). + * + * @param aArgc The argc value. + * @param aArgv The original argv. + * @param aArg the parameter to check. Must be lowercase. + * @param aParam if non-null, the -arg will be stored in this pointer. + * This is *not* allocated, but rather a pointer to the argv data. + * @param aFlags Flags @see CheckArgFlag + */ +template +inline ArgResult +CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg, const CharT **aParam, + CheckArgFlag aFlags) +{ + MOZ_ASSERT(aArgv && aArg); + + CharT **curarg = aArgv + 1; // skip argv[0] + ArgResult ar = ARG_NONE; + + while (*curarg) { + CharT *arg = curarg[0]; + + if (arg[0] == '-' +#if defined(XP_WIN) + || *arg == '/' +#endif + ) { + ++arg; + + if (*arg == '-') { + ++arg; + } + + if (strimatch(aArg, arg)) { + if (aFlags & CheckArgFlag::RemoveArg) { + RemoveArg(aArgc, curarg); + } else { + ++curarg; + } + + if (!aParam) { + ar = ARG_FOUND; + break; + } + + if (*curarg) { + if (**curarg == '-' +#if defined(XP_WIN) + || **curarg == '/' +#endif + ) { + return ARG_BAD; + } + + *aParam = *curarg; + + if (aFlags & CheckArgFlag::RemoveArg) { + RemoveArg(aArgc, curarg); + } + + ar = ARG_FOUND; + break; + } + + return ARG_BAD; + } + } + + ++curarg; + } + + if ((aFlags & CheckArgFlag::CheckOSInt) && ar == ARG_FOUND) { + ArgResult arOSInt = CheckArg(aArgc, aArgv, + GetLiteral(), + reinterpret_cast(nullptr), + CheckArgFlag::None); + if (arOSInt == ARG_FOUND) { + ar = ARG_BAD; +#if defined(MOZILLA_INTERNAL_API) + PR_fprintf(PR_STDERR, "Error: argument --osint is invalid\n"); +#endif // defined(MOZILLA_INTERNAL_API) + } + } + + return ar; +} + +#if defined(XP_WIN) + +namespace internal { + +/** + * Get the length that the string will take and takes into account the + * additional length if the string needs to be quoted and if characters need to + * be escaped. + */ +inline int +ArgStrLen(const wchar_t *s) +{ + int backslashes = 0; + int i = wcslen(s); + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) { + i += 2; // initial and final duoblequote + } + + if (hasDoubleQuote) { + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the doublequote + i += backslashes + 1; + } + + backslashes = 0; + } + + ++s; + } + } + + return i; +} + +/** + * Copy string "s" to string "d", quoting the argument as appropriate and + * escaping doublequotes along with any backslashes that immediately precede + * doublequotes. + * The CRT parses this to retrieve the original argc/argv that we meant, + * see STDARGV.C in the MSVC CRT sources. + * + * @return the end of the string + */ +inline wchar_t* +ArgToString(wchar_t *d, const wchar_t *s) +{ + int backslashes = 0; + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) { + *d = '"'; // initial doublequote + ++d; + } + + if (hasDoubleQuote) { + int i; + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the doublequote + for (i = 0; i <= backslashes; ++i) { + *d = '\\'; + ++d; + } + } + + backslashes = 0; + } + + *d = *s; + ++d; ++s; + } + } else { + wcscpy(d, s); + d += wcslen(s); + } + + if (addDoubleQuotes) { + *d = '"'; // final doublequote + ++d; + } + + return d; +} + +} // namespace internal + +/** + * Creates a command line from a list of arguments. + */ +inline UniquePtr +MakeCommandLine(int argc, wchar_t **argv) +{ + int i; + int len = 0; + + // The + 1 of the last argument handles the allocation for null termination + for (i = 0; i < argc; ++i) { + len += internal::ArgStrLen(argv[i]) + 1; + } + + // Protect against callers that pass 0 arguments + if (len == 0) { + len = 1; + } + + auto s = MakeUnique(len); + if (!s) { + return nullptr; + } + + wchar_t *c = s.get(); + for (i = 0; i < argc; ++i) { + c = internal::ArgToString(c, argv[i]); + if (i + 1 != argc) { + *c = ' '; + ++c; + } + } + + *c = '\0'; + + return Move(s); +} + +#endif // defined(XP_WIN) + +// Save literal putenv string to environment variable. +inline void +SaveToEnv(const char *aEnvString) +{ +#if defined(MOZILLA_INTERNAL_API) + char *expr = strdup(aEnvString); + if (expr) { + PR_SetEnv(expr); + } + + // We intentionally leak |expr| here since it is required by PR_SetEnv. + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr); +#elif defined(XP_WIN) + // This is the same as the NSPR implementation + // (Note that we don't need to do a strdup for this case; the CRT makes a copy) + _putenv(aEnvString); +#else +#error "Not implemented for this configuration" +#endif +} + +inline bool +EnvHasValue(const char* aVarName) +{ +#if defined(MOZILLA_INTERNAL_API) + const char* val = PR_GetEnv(aVarName); + return val && *val; +#elif defined(XP_WIN) + // This is the same as the NSPR implementation + const char* val = getenv(aVarName); + return val && *val; +#else +#error "Not implemented for this configuration" +#endif +} + +} // namespace mozilla + +#endif // mozilla_CmdLineAndEnvUtils_h diff --git a/toolkit/xre/PolicyChecks.h b/toolkit/xre/PolicyChecks.h new file mode 100644 index 000000000000..5a93153c1ea6 --- /dev/null +++ b/toolkit/xre/PolicyChecks.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PolicyChecks_h +#define mozilla_PolicyChecks_h + +#if defined(XP_WIN) + +#include + +// NB: This code must be able to run apart from XPCOM + +namespace mozilla { + +inline bool +PolicyHasRegValue(HKEY aKey, LPCWSTR aName, DWORD* aValue) +{ + DWORD len = sizeof(DWORD); + LONG ret = ::RegGetValueW(aKey, L"SOFTWARE\\Policies\\Mozilla\\Firefox", aName, + RRF_RT_DWORD, nullptr, aValue, &len); + return ret == ERROR_SUCCESS; +} + +inline bool +PolicyCheckBoolean(LPCWSTR aPolicyName) +{ + DWORD value; + if (PolicyHasRegValue(HKEY_LOCAL_MACHINE, aPolicyName, &value)) { + return value == 1; + } + + if (PolicyHasRegValue(HKEY_CURRENT_USER, aPolicyName, &value)) { + return value == 1; + } + + return false; +} + +} // namespace mozilla + +#endif // defined(XP_WIN) + +#endif // mozilla_PolicyChecks_h diff --git a/toolkit/xre/SafeMode.h b/toolkit/xre/SafeMode.h new file mode 100644 index 000000000000..875bd8974002 --- /dev/null +++ b/toolkit/xre/SafeMode.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SafeMode_h +#define mozilla_SafeMode_h + +// NB: This code must be able to run apart from XPCOM + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/Maybe.h" + +#if defined(XP_WIN) +#include "mozilla/PolicyChecks.h" +#include +#endif // defined(XP_WIN) + +namespace mozilla { + +enum class SafeModeFlag : uint32_t +{ + None = 0, + Unset = (1 << 0) +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SafeModeFlag) + +template +inline Maybe +IsSafeModeRequested(int& aArgc, CharT* aArgv[], + const SafeModeFlag aFlags = SafeModeFlag::Unset) +{ + CheckArgFlag checkArgFlags = CheckArgFlag::CheckOSInt; + if (aFlags & SafeModeFlag::Unset) { + checkArgFlags |= CheckArgFlag::RemoveArg; + } + + ArgResult ar = CheckArg(aArgc, aArgv, + GetLiteral(), + reinterpret_cast(nullptr), + checkArgFlags); + if (ar == ARG_BAD) { + return Nothing(); + } + + bool result = ar == ARG_FOUND; + +#if defined(XP_WIN) + // If the shift key is pressed and the ctrl and / or alt keys are not pressed + // during startup, start in safe mode. GetKeyState returns a short and the high + // order bit will be 1 if the key is pressed. By masking the returned short + // with 0x8000 the result will be 0 if the key is not pressed and non-zero + // otherwise. + if ((GetKeyState(VK_SHIFT) & 0x8000) && + !(GetKeyState(VK_CONTROL) & 0x8000) && + !(GetKeyState(VK_MENU) & 0x8000) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { + result = true; + } + + if (result && PolicyCheckBoolean(L"DisableSafeMode")) { + result = false; + } +#endif // defined(XP_WIN) + +#if defined(XP_MACOSX) + if ((GetCurrentEventKeyModifiers() & optionKey) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { + result = true; + } +#endif // defined(XP_MACOSX) + + // The Safe Mode Policy should not be enforced for the env var case + // (used by updater and crash-recovery). + if (EnvHasValue("MOZ_SAFE_MODE_RESTART")) { + result = true; + if (aFlags & SafeModeFlag::Unset) { + // unset the env variable + SaveToEnv("MOZ_SAFE_MODE_RESTART="); + } + } + + return Some(result); +} + +} // namespace mozilla + +#endif // mozilla_SafeMode_h diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build index b7bf67ac25e0..431b98917e62 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -30,13 +30,19 @@ EXPORTS += [ 'nsIAppStartupNotifier.h', ] -EXPORTS.mozilla += ['AutoSQLiteLifetime.h', 'Bootstrap.h'] +EXPORTS.mozilla += [ + 'AutoSQLiteLifetime.h', + 'Bootstrap.h', + 'CmdLineAndEnvUtils.h', + 'SafeMode.h', +] if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']: EXPORTS += ['EventTracer.h'] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': EXPORTS.mozilla += [ + 'PolicyChecks.h', 'WinDllServices.h', ] SOURCES += [ diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 9b33cc51fb46..c6f2ba2986ed 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -10,6 +10,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/ChaosMode.h" +#include "mozilla/CmdLineAndEnvUtils.h" #include "mozilla/IOInterposer.h" #include "mozilla/Likely.h" #include "mozilla/MemoryChecking.h" @@ -232,6 +233,7 @@ #endif #include "mozilla/mozalloc_oom.h" +#include "SafeMode.h" extern uint32_t gRestartMode; extern void InstallSignalHandlers(const char *ProgramName); @@ -306,25 +308,6 @@ using mozilla::dom::ContentParent; using mozilla::dom::ContentChild; using mozilla::intl::LocaleService; -// Save literal putenv string to environment variable. -static void -SaveToEnv(const char *putenv) -{ - char *expr = strdup(putenv); - if (expr) - PR_SetEnv(expr); - // We intentionally leak |expr| here since it is required by PR_SetEnv. - MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr); -} - -// Tests that an environment variable exists and has a value -static bool -EnvHasValue(const char *name) -{ - const char *val = PR_GetEnv(name); - return (val && *val); -} - // Save the given word to the specified environment variable. static void SaveWordToEnv(const char *name, const nsACString & word) @@ -400,22 +383,6 @@ SaveFileToEnvIfUnset(const char *name, nsIFile *file) SaveFileToEnv(name, file); } -static bool -strimatch(const char* lowerstr, const char* mixedstr) -{ - while(*lowerstr) { - if (!*mixedstr) return false; // mixedstr is shorter - if (tolower(*mixedstr) != *lowerstr) return false; // no match - - ++lowerstr; - ++mixedstr; - } - - if (*mixedstr) return false; // lowerstr is shorter - - return true; -} - static bool gIsExpectedExit = false; void MozExpectedExit() { @@ -482,94 +449,22 @@ enum RemoteResult { REMOTE_ARG_BAD = 2 }; -enum ArgResult { - ARG_NONE = 0, - ARG_FOUND = 1, - ARG_BAD = 2 // you wanted a param, but there isn't one -}; - -static void RemoveArg(char **argv) -{ - do { - *argv = *(argv + 1); - ++argv; - } while (*argv); - - --gArgc; -} - /** * Check for a commandline flag. If the flag takes a parameter, the * parameter is returned in aParam. Flags may be in the form -arg or * --arg (or /arg on win32). * * @param aArg the parameter to check. Must be lowercase. - * @param aCheckOSInt if true returns ARG_BAD if the osint argument is present - * when aArg is also present. * @param aParam if non-null, the -arg will be stored in this pointer. * This is *not* allocated, but rather a pointer to the argv data. - * @param aRemArg if true, the argument is removed from the gArgv array. + * @param aFlags flags @see CheckArgFlag */ static ArgResult -CheckArg(const char* aArg, bool aCheckOSInt = false, const char **aParam = nullptr, bool aRemArg = true) +CheckArg(const char* aArg, const char** aParam = nullptr, + CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { MOZ_ASSERT(gArgv, "gArgv must be initialized before CheckArg()"); - - char **curarg = gArgv + 1; // skip argv[0] - ArgResult ar = ARG_NONE; - - while (*curarg) { - char *arg = curarg[0]; - - if (arg[0] == '-' -#if defined(XP_WIN) - || *arg == '/' -#endif - ) { - ++arg; - if (*arg == '-') - ++arg; - - if (strimatch(aArg, arg)) { - if (aRemArg) - RemoveArg(curarg); - else - ++curarg; - if (!aParam) { - ar = ARG_FOUND; - break; - } - - if (*curarg) { - if (**curarg == '-' -#if defined(XP_WIN) - || **curarg == '/' -#endif - ) - return ARG_BAD; - - *aParam = *curarg; - if (aRemArg) - RemoveArg(curarg); - ar = ARG_FOUND; - break; - } - return ARG_BAD; - } - } - - ++curarg; - } - - if (aCheckOSInt && ar == ARG_FOUND) { - ArgResult arOSInt = CheckArg("osint"); - if (arOSInt == ARG_FOUND) { - ar = ARG_BAD; - PR_fprintf(PR_STDERR, "Error: argument --osint is invalid\n"); - } - } - - return ar; + return CheckArg(gArgc, gArgv, aArg, aParam, aFlags); } /** @@ -582,38 +477,7 @@ CheckArg(const char* aArg, bool aCheckOSInt = false, const char **aParam = nullp static ArgResult CheckArgExists(const char* aArg) { - char **curarg = gArgv + 1; // skip argv[0] - while (*curarg) { - char *arg = curarg[0]; - - if (arg[0] == '-' -#if defined(XP_WIN) - || *arg == '/' -#endif - ) { - ++arg; - if (*arg == '-') - ++arg; - - char delimiter = '='; -#if defined(XP_WIN) - delimiter = ':'; -#endif - int i; - for (i = 0; arg[i] && arg[i] != delimiter; i++) {} - char tmp = arg[i]; - arg[i] = '\0'; - bool found = strimatch(aArg, arg); - arg[i] = tmp; - if (found) { - return ARG_FOUND; - } - } - - ++curarg; - } - - return ARG_NONE; + return CheckArg(aArg, nullptr, CheckArgFlag::None); } #if defined(XP_WIN) @@ -1799,14 +1663,14 @@ ParseRemoteCommandLine(nsCString& program, { ArgResult ar; - ar = CheckArg("p", false, profile, false); + ar = CheckArg("p", profile, CheckArgFlag::None); if (ar == ARG_BAD) { // Leave it to the normal command line handling to handle this situation. return REMOTE_NOT_FOUND; } const char *temp = nullptr; - ar = CheckArg("a", true, &temp); + ar = CheckArg("a", &temp, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument -a requires an application name\n"); return REMOTE_ARG_BAD; @@ -1815,7 +1679,7 @@ ParseRemoteCommandLine(nsCString& program, program.Assign(temp); } - ar = CheckArg("u", true, username); + ar = CheckArg("u", username, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument -u requires a username\n"); return REMOTE_ARG_BAD; @@ -2322,7 +2186,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n *aResult = nullptr; *aStartOffline = false; - ar = CheckArg("offline", true); + ar = CheckArg("offline", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --offline is invalid when argument --osint is specified\n"); return NS_ERROR_FAILURE; @@ -2344,7 +2208,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n } // reset-profile and migration args need to be checked before any profiles are chosen below. - ar = CheckArg("reset-profile", true); + ar = CheckArg("reset-profile", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --reset-profile is invalid when argument --osint is specified\n"); return NS_ERROR_FAILURE; @@ -2353,7 +2217,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n gDoProfileReset = true; } - ar = CheckArg("migration", true); + ar = CheckArg("migration", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --migration is invalid when argument --osint is specified\n"); return NS_ERROR_FAILURE; @@ -2380,8 +2244,8 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n // Clear out flags that we handled (or should have handled!) last startup. const char *dummy; - CheckArg("p", false, &dummy); - CheckArg("profile", false, &dummy); + CheckArg("p", &dummy); + CheckArg("profile", &dummy); CheckArg("profilemanager"); if (gDoProfileReset) { @@ -2410,7 +2274,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n return NS_LockProfilePath(lf, localDir, nullptr, aResult); } - ar = CheckArg("profile", true, &arg); + ar = CheckArg("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; @@ -2444,7 +2308,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n return ProfileLockedDialog(lf, lf, unlocker, aNative, aResult); } - ar = CheckArg("createprofile", true, &arg); + ar = CheckArg("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; @@ -2500,7 +2364,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n rv = aProfileSvc->GetProfileCount(&count); NS_ENSURE_SUCCESS(rv, rv); - ar = CheckArg("p", false, &arg); + ar = CheckArg("p", &arg); if (ar == ARG_BAD) { ar = CheckArg("osint"); if (ar == ARG_FOUND) { @@ -2565,7 +2429,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n } } - ar = CheckArg("profilemanager", true); + ar = CheckArg("profilemanager", 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; @@ -3161,33 +3025,6 @@ public: #endif }; -#ifdef XP_WIN -namespace { - -bool PolicyHasRegValue(HKEY aKey, LPCWSTR aName, DWORD* aValue) -{ - DWORD len = sizeof(DWORD); - LONG ret = ::RegGetValueW(aKey, L"SOFTWARE\\Policies\\Mozilla\\Firefox", aName, - RRF_RT_DWORD, nullptr, aValue, &len); - return ret == ERROR_SUCCESS; -} - -bool SafeModeBlockedByPolicy() -{ - LPCTSTR policyName = L"DisableSafeMode"; - DWORD value; - if (PolicyHasRegValue(HKEY_LOCAL_MACHINE, policyName, &value)) { - return value == 1; - } - if (PolicyHasRegValue(HKEY_CURRENT_USER, policyName, &value)) { - return value == 1; - } - return false; -} - -} // anonymous namespace -#endif // XP_WIN - #if defined(XP_UNIX) && !defined(ANDROID) static SmprintfPointer FormatUid(uid_t aId) @@ -3366,7 +3203,7 @@ XREMain::XRE_mainInit(bool* aExitFlag) // Check for application.ini overrides const char* override = nullptr; - ar = CheckArg("override", true, &override); + ar = CheckArg("override", &override, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { Output(true, "Incorrect number of arguments passed to --override"); return 1; @@ -3586,49 +3423,12 @@ XREMain::XRE_mainInit(bool* aExitFlag) gRestartArgv[gRestartArgc] = nullptr; - - ar = CheckArg("safe-mode", true); - if (ar == ARG_BAD) { - PR_fprintf(PR_STDERR, "Error: argument --safe-mode is invalid when argument --osint is specified\n"); + Maybe safeModeRequested = IsSafeModeRequested(gArgc, gArgv); + if (!safeModeRequested) { return 1; } - if (ar == ARG_FOUND) { - gSafeMode = true; - } -#ifdef XP_WIN - // If the shift key is pressed and the ctrl and / or alt keys are not pressed - // during startup start in safe mode. GetKeyState returns a short and the high - // order bit will be 1 if the key is pressed. By masking the returned short - // with 0x8000 the result will be 0 if the key is not pressed and non-zero - // otherwise. - if ((GetKeyState(VK_SHIFT) & 0x8000) && - !(GetKeyState(VK_CONTROL) & 0x8000) && - !(GetKeyState(VK_MENU) & 0x8000) && - !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { - gSafeMode = true; - } -#endif - -#ifdef XP_MACOSX - if ((GetCurrentEventKeyModifiers() & optionKey) && - !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) - gSafeMode = true; -#endif - -#ifdef XP_WIN -if (gSafeMode && SafeModeBlockedByPolicy()) { - gSafeMode = false; - } -#endif - - // The Safe Mode Policy should not be enforced for the env var case - // (used by updater and crash-recovery). - if (EnvHasValue("MOZ_SAFE_MODE_RESTART")) { - gSafeMode = true; - // unset the env variable - SaveToEnv("MOZ_SAFE_MODE_RESTART="); - } + gSafeMode = safeModeRequested.value(); #ifdef XP_WIN { @@ -3681,7 +3481,7 @@ if (gSafeMode && SafeModeBlockedByPolicy()) { // Handle --no-remote and --new-instance command line arguments. Setup // the environment to better accommodate other components and various // restart scenarios. - ar = CheckArg("no-remote", true); + ar = CheckArg("no-remote", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --no-remote is invalid when argument --osint is specified\n"); return 1; @@ -3690,7 +3490,7 @@ if (gSafeMode && SafeModeBlockedByPolicy()) { SaveToEnv("MOZ_NO_REMOTE=1"); } - ar = CheckArg("new-instance", true); + ar = CheckArg("new-instance", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --new-instance is invalid when argument --osint is specified\n"); return 1; @@ -3717,7 +3517,7 @@ if (gSafeMode && SafeModeBlockedByPolicy()) { NS_ENSURE_SUCCESS(rv, 1); // Check for --register, which registers chrome and then exits immediately. - ar = CheckArg("register", true); + ar = CheckArg("register", nullptr, CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --register is invalid when argument --osint is specified\n"); return 1; @@ -4253,7 +4053,7 @@ XREMain::XRE_mainStartup(bool* aExitFlag) // to make sure that the maintenance service successfully launches the // callback application. const char *logFile = nullptr; - if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) { + if (ARG_FOUND == CheckArg("dump-args", &logFile)) { FILE* logFP = fopen(logFile, "wb"); if (logFP) { for (int i = 1; i < gRestartArgc; ++i) { @@ -5108,7 +4908,7 @@ XRE_InitCommandLine(int aArgc, char* aArgv[]) #endif const char *path = nullptr; - ArgResult ar = CheckArg("greomni", false, &path); + ArgResult ar = CheckArg("greomni", &path); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --greomni requires a path argument\n"); return NS_ERROR_FAILURE; @@ -5124,7 +4924,7 @@ XRE_InitCommandLine(int aArgc, char* aArgv[]) return rv; } - ar = CheckArg("appomni", false, &path); + ar = CheckArg("appomni", &path); if (ar == ARG_BAD) { PR_fprintf(PR_STDERR, "Error: argument --appomni requires a path argument\n"); return NS_ERROR_FAILURE; diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp index 11a654f34c92..7fa3b0de7e66 100644 --- a/toolkit/xre/nsWindowsRestart.cpp +++ b/toolkit/xre/nsWindowsRestart.cpp @@ -11,6 +11,7 @@ #define nsWindowsRestart_cpp #endif +#include "mozilla/CmdLineAndEnvUtils.h" #include "nsUTF8Utils.h" #include @@ -19,135 +20,6 @@ #include #pragma comment(lib, "userenv.lib") -/** - * Get the length that the string will take and takes into account the - * additional length if the string needs to be quoted and if characters need to - * be escaped. - */ -static int ArgStrLen(const wchar_t *s) -{ - int backslashes = 0; - int i = wcslen(s); - BOOL hasDoubleQuote = wcschr(s, L'"') != nullptr; - // Only add doublequotes if the string contains a space or a tab - BOOL addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; - - if (addDoubleQuotes) { - i += 2; // initial and final duoblequote - } - - if (hasDoubleQuote) { - while (*s) { - if (*s == '\\') { - ++backslashes; - } else { - if (*s == '"') { - // Escape the doublequote and all backslashes preceding the doublequote - i += backslashes + 1; - } - - backslashes = 0; - } - - ++s; - } - } - - return i; -} - -/** - * Copy string "s" to string "d", quoting the argument as appropriate and - * escaping doublequotes along with any backslashes that immediately precede - * doublequotes. - * The CRT parses this to retrieve the original argc/argv that we meant, - * see STDARGV.C in the MSVC CRT sources. - * - * @return the end of the string - */ -static wchar_t* ArgToString(wchar_t *d, const wchar_t *s) -{ - int backslashes = 0; - BOOL hasDoubleQuote = wcschr(s, L'"') != nullptr; - // Only add doublequotes if the string contains a space or a tab - BOOL addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; - - if (addDoubleQuotes) { - *d = '"'; // initial doublequote - ++d; - } - - if (hasDoubleQuote) { - int i; - while (*s) { - if (*s == '\\') { - ++backslashes; - } else { - if (*s == '"') { - // Escape the doublequote and all backslashes preceding the doublequote - for (i = 0; i <= backslashes; ++i) { - *d = '\\'; - ++d; - } - } - - backslashes = 0; - } - - *d = *s; - ++d; ++s; - } - } else { - wcscpy(d, s); - d += wcslen(s); - } - - if (addDoubleQuotes) { - *d = '"'; // final doublequote - ++d; - } - - return d; -} - -/** - * Creates a command line from a list of arguments. The returned - * string is allocated with "malloc" and should be "free"d. - * - * argv is UTF8 - */ -wchar_t* -MakeCommandLine(int argc, wchar_t **argv) -{ - int i; - int len = 0; - - // The + 1 of the last argument handles the allocation for null termination - for (i = 0; i < argc; ++i) - len += ArgStrLen(argv[i]) + 1; - - // Protect against callers that pass 0 arguments - if (len == 0) - len = 1; - - wchar_t *s = (wchar_t*) malloc(len * sizeof(wchar_t)); - if (!s) - return nullptr; - - wchar_t *c = s; - for (i = 0; i < argc; ++i) { - c = ArgToString(c, argv[i]); - if (i + 1 != argc) { - *c = ' '; - ++c; - } - } - - *c = '\0'; - - return s; -} - /** * Convert UTF8 to UTF16 without using the normal XPCOM goop, which we * can't link to updater.exe. @@ -222,10 +94,9 @@ WinLaunchChild(const wchar_t *exePath, HANDLE userToken, HANDLE *hProcess) { - wchar_t *cl; BOOL ok; - cl = MakeCommandLine(argc, argv); + mozilla::UniquePtr cl(mozilla::MakeCommandLine(argc, argv)); if (!cl) { return FALSE; } @@ -237,7 +108,7 @@ WinLaunchChild(const wchar_t *exePath, if (userToken == nullptr) { ok = CreateProcessW(exePath, - cl, + cl.get(), nullptr, // no special security attributes nullptr, // no special thread attributes FALSE, // don't inherit filehandles @@ -256,7 +127,7 @@ WinLaunchChild(const wchar_t *exePath, ok = CreateProcessAsUserW(userToken, exePath, - cl, + cl.get(), nullptr, // no special security attributes nullptr, // no special thread attributes FALSE, // don't inherit filehandles @@ -294,7 +165,5 @@ WinLaunchChild(const wchar_t *exePath, LocalFree(lpMsgBuf); } - free(cl); - return ok; } diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp index 00d786aa2bfc..d0c13335a2e8 100644 --- a/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp +++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp @@ -69,10 +69,10 @@ verifyCmdLineCreation(wchar_t *inCmdLine, wcscat(inCmdLineNew, inCmdLine); LPWSTR *inArgv = CommandLineToArgvW(inCmdLineNew, &inArgc); - wchar_t *outCmdLine = MakeCommandLine(inArgc - 1, inArgv + 1); - wchar_t *outCmdLineNew = (wchar_t *) malloc((wcslen(DUMMY_ARG1) + wcslen(outCmdLine) + 1) * sizeof(wchar_t)); + auto outCmdLine = mozilla::MakeCommandLine(inArgc - 1, inArgv + 1); + wchar_t *outCmdLineNew = (wchar_t *) malloc((wcslen(DUMMY_ARG1) + wcslen(outCmdLine.get()) + 1) * sizeof(wchar_t)); wcscpy(outCmdLineNew, DUMMY_ARG1); - wcscat(outCmdLineNew, outCmdLine); + wcscat(outCmdLineNew, outCmdLine.get()); LPWSTR *outArgv = CommandLineToArgvW(outCmdLineNew, &outArgc); if (VERBOSE) { @@ -80,7 +80,7 @@ verifyCmdLineCreation(wchar_t *inCmdLine, wprintf(L"Verbose Output\n"); wprintf(L"--------------\n"); wprintf(L"Input command line : >%s<\n", inCmdLine); - wprintf(L"MakeComandLine output: >%s<\n", outCmdLine); + wprintf(L"MakeComandLine output: >%s<\n", outCmdLine.get()); wprintf(L"Expected command line: >%s<\n", compareCmdLine); wprintf(L"input argc : %d\n", inArgc - 1); @@ -107,7 +107,6 @@ verifyCmdLineCreation(wchar_t *inCmdLine, LocalFree(outArgv); free(inCmdLineNew); free(outCmdLineNew); - free(outCmdLine); return rv; } @@ -123,12 +122,11 @@ verifyCmdLineCreation(wchar_t *inCmdLine, LocalFree(outArgv); free(inCmdLineNew); free(outCmdLineNew); - free(outCmdLine); return rv; } } - isEqual = (wcscmp(outCmdLine, compareCmdLine) == 0); + isEqual = (wcscmp(outCmdLine.get(), compareCmdLine) == 0); if (!isEqual) { wprintf(L"TEST-%s-FAIL | %s | Command Line Comparison (check %2d)\n", passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); @@ -139,7 +137,6 @@ verifyCmdLineCreation(wchar_t *inCmdLine, LocalFree(outArgv); free(inCmdLineNew); free(outCmdLineNew); - free(outCmdLine); return rv; } @@ -156,7 +153,6 @@ verifyCmdLineCreation(wchar_t *inCmdLine, LocalFree(outArgv); free(inCmdLineNew); free(outCmdLineNew); - free(outCmdLine); return rv; }