diff options
Diffstat (limited to 'toolkit/xre/CmdLineAndEnvUtils.h')
-rw-r--r-- | toolkit/xre/CmdLineAndEnvUtils.h | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/toolkit/xre/CmdLineAndEnvUtils.h b/toolkit/xre/CmdLineAndEnvUtils.h new file mode 100644 index 0000000000..e4120da93a --- /dev/null +++ b/toolkit/xre/CmdLineAndEnvUtils.h @@ -0,0 +1,657 @@ +/* -*- 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 "prprf.h" +# include <string.h> +#elif defined(XP_WIN) +# include <stdlib.h> +#endif + +#if defined(XP_WIN) +# include "mozilla/UniquePtr.h" +# include "mozilla/Vector.h" +# include "mozilla/WinHeaderOnlyUtils.h" + +# include <wchar.h> +# include <windows.h> +#endif // defined(XP_WIN) + +#include "mozilla/MemoryChecking.h" +#include "mozilla/TypedEnumBits.h" + +#include <ctype.h> +#include <stdint.h> + +#ifndef NS_NO_XPCOM +# include "nsIFile.h" +# include "mozilla/AlreadyAddRefed.h" +#endif + +// Undo X11/X.h's definition of None +#undef None + +namespace mozilla { + +enum ArgResult { + ARG_NONE = 0, + ARG_FOUND = 1, + ARG_BAD = 2 // you wanted a param, but there isn't one +}; + +template <typename CharT> +inline void RemoveArg(int& argc, CharT** argv) { + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --argc; +} + +namespace internal { + +template <typename FuncT, typename CharT> +static inline bool strimatch(FuncT aToLowerFn, const CharT* lowerstr, + const CharT* mixedstr) { + while (*lowerstr) { + if (!*mixedstr) return false; // mixedstr is shorter + if (static_cast<CharT>(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); +} + +const wchar_t kCommandLineDelimiter[] = L" \t"; + +enum class FlagLiteral { osint, safemode }; + +template <typename CharT, FlagLiteral Literal> +inline const CharT* GetLiteral(); + +#define DECLARE_FLAG_LITERAL(enum_name, literal) \ + template <> \ + inline const char* GetLiteral<char, FlagLiteral::enum_name>() { \ + return literal; \ + } \ + \ + template <> \ + inline const wchar_t* GetLiteral<wchar_t, FlagLiteral::enum_name>() { \ + return L##literal; \ + } + +DECLARE_FLAG_LITERAL(osint, "osint") +DECLARE_FLAG_LITERAL(safemode, "safe-mode") + +enum class CheckArgFlag : uint32_t { + None = 0, + // (1 << 0) Used to be CheckOSInt + 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 <data> will be stored in this pointer. + * This is *not* allocated, but rather a pointer to the argv data. + * @param aFlags Flags @see CheckArgFlag + */ +template <typename CharT> +inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg, + const CharT** aParam = nullptr, + CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { + 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; + } + + return ar; +} + +template <typename CharT> +inline void EnsureCommandlineSafe(int& aArgc, CharT** aArgv, + const CharT** aAcceptableArgs) { + // We expect either no -osint, or the full commandline to be: + // app -osint + // followed by one of the arguments listed in aAcceptableArgs, + // followed by one parameter for that arg. + // If this varies, we abort to avoid abuse of other commandline handlers + // from apps that do a poor job escaping links they give to the OS. + + const CharT* osintLit = GetLiteral<CharT, FlagLiteral::osint>(); + + if (CheckArg(aArgc, aArgv, osintLit, static_cast<const CharT**>(nullptr), + CheckArgFlag::None) == ARG_FOUND) { + // There should be 4 items left (app name + -osint + (acceptable arg) + + // param) + if (aArgc != 4) { + exit(127); + } + + // The first should be osint. + CharT* arg = aArgv[1]; + if (*arg != '-' +#ifdef XP_WIN + && *arg != '/' +#endif + ) { + exit(127); + } + ++arg; + if (*arg == '-') { + ++arg; + } + if (!strimatch(osintLit, arg)) { + exit(127); + } + // Strip it: + RemoveArg(aArgc, aArgv + 1); + + // Now only an acceptable argument and a parameter for it should be left: + arg = aArgv[1]; + if (*arg != '-' +#ifdef XP_WIN + && *arg != '/' +#endif + ) { + exit(127); + } + ++arg; + if (*arg == '-') { + ++arg; + } + bool haveAcceptableArg = false; + const CharT** acceptableArg = aAcceptableArgs; + while (*acceptableArg) { + if (strimatch(*acceptableArg, arg)) { + haveAcceptableArg = true; + break; + } + acceptableArg++; + } + if (!haveAcceptableArg) { + exit(127); + } + // The param that is passed afterwards shouldn't be another switch: + arg = aArgv[2]; + if (*arg == '-' +#ifdef XP_WIN + || *arg == '/' +#endif + ) { + exit(127); + } + } + // Either no osint, so nothing to do, or we ensured nothing nefarious was + // passed. +} + +#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, kCommandLineDelimiter) != 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, kCommandLineDelimiter) != 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. + * + * @param argc Number of elements in |argv| + * @param argv Array of arguments + * @param aArgcExtra Number of elements in |aArgvExtra| + * @param aArgvExtra Optional array of arguments to be appended to the resulting + * command line after those provided by |argv|. + */ +inline UniquePtr<wchar_t[]> MakeCommandLine( + int argc, const wchar_t* const* argv, int aArgcExtra = 0, + const wchar_t* const* aArgvExtra = nullptr) { + int i; + int len = 0; + + // The + 1 for each argument reserves space for either a ' ' or the null + // terminator, depending on the position of the argument. + for (i = 0; i < argc; ++i) { + len += internal::ArgStrLen(argv[i]) + 1; + } + + for (i = 0; i < aArgcExtra; ++i) { + len += internal::ArgStrLen(aArgvExtra[i]) + 1; + } + + // Protect against callers that pass 0 arguments + if (len == 0) { + len = 1; + } + + auto s = MakeUnique<wchar_t[]>(len); + if (!s) { + return s; + } + + int totalArgc = argc + aArgcExtra; + + wchar_t* c = s.get(); + for (i = 0; i < argc; ++i) { + c = internal::ArgToString(c, argv[i]); + if (i + 1 != totalArgc) { + *c = ' '; + ++c; + } + } + + for (i = 0; i < aArgcExtra; ++i) { + c = internal::ArgToString(c, aArgvExtra[i]); + if (i + 1 != aArgcExtra) { + *c = ' '; + ++c; + } + } + + *c = '\0'; + + return s; +} + +inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) { + if (!aArgv) { + return false; + } + + UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath()); + if (!newArgv_0) { + return false; + } + + // We intentionally leak newArgv_0 into argv[0] + aArgv[0] = newArgv_0.release(); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]); + return true; +} + +# if defined(MOZILLA_INTERNAL_API) +// This class converts a command line string into an array of the arguments. +// It's basically the opposite of MakeCommandLine. However, the behavior is +// different from ::CommandLineToArgvW in several ways, such as escaping a +// backslash or quoting an argument containing whitespaces. +template <typename T> +class CommandLineParserWin final { + int mArgc; + T** mArgv; + + void Release() { + if (mArgv) { + while (mArgc) { + delete[] mArgv[--mArgc]; + } + delete[] mArgv; + mArgv = nullptr; + } + } + + public: + CommandLineParserWin() : mArgc(0), mArgv(nullptr) {} + ~CommandLineParserWin() { Release(); } + + CommandLineParserWin(const CommandLineParserWin&) = delete; + CommandLineParserWin(CommandLineParserWin&&) = delete; + CommandLineParserWin& operator=(const CommandLineParserWin&) = delete; + CommandLineParserWin& operator=(CommandLineParserWin&&) = delete; + + int Argc() const { return mArgc; } + const T* const* Argv() const { return mArgv; } + + void HandleCommandLine(const T* aCmdLineString) { + Release(); + + int justCounting = 1; + // Flags, etc. + int init = 1; + int between, quoted, bSlashCount; + const T* p; + nsTAutoString<T> arg; + + // Parse command line args according to MS spec + // (see "Parsing C++ Command-Line Arguments" at + // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm). + // We loop if we've not finished the second pass through. + while (1) { + // Initialize if required. + if (init) { + p = aCmdLineString; + between = 1; + mArgc = quoted = bSlashCount = 0; + + init = 0; + } + if (between) { + // We are traversing whitespace between args. + // Check for start of next arg. + if (*p != 0 && !wcschr(kCommandLineDelimiter, *p)) { + // Start of another arg. + between = 0; + arg.Truncate(); + switch (*p) { + case '\\': + // Count the backslash. + bSlashCount = 1; + break; + case '"': + // Remember we're inside quotes. + quoted = 1; + break; + default: + // Add character to arg. + arg += *p; + break; + } + } else { + // Another space between args, ignore it. + } + } else { + // We are processing the contents of an argument. + // Check for whitespace or end. + if (*p == 0 || (!quoted && wcschr(kCommandLineDelimiter, *p))) { + // Process pending backslashes (interpret them + // literally since they're not followed by a "). + while (bSlashCount) { + arg += '\\'; + bSlashCount--; + } + // End current arg. + if (!justCounting) { + mArgv[mArgc] = new T[arg.Length() + 1]; + memcpy(mArgv[mArgc], arg.get(), (arg.Length() + 1) * sizeof(T)); + } + mArgc++; + // We're now between args. + between = 1; + } else { + // Still inside argument, process the character. + switch (*p) { + case '"': + // First, digest preceding backslashes (if any). + while (bSlashCount > 1) { + // Put one backsplash in arg for each pair. + arg += '\\'; + bSlashCount -= 2; + } + if (bSlashCount) { + // Quote is literal. + arg += '"'; + bSlashCount = 0; + } else { + // Quote starts or ends a quoted section. + if (quoted) { + // Check for special case of consecutive double + // quotes inside a quoted section. + if (*(p + 1) == '"') { + // This implies a literal double-quote. Fake that + // out by causing next double-quote to look as + // if it was preceded by a backslash. + bSlashCount = 1; + } else { + quoted = 0; + } + } else { + quoted = 1; + } + } + break; + case '\\': + // Add to count. + bSlashCount++; + break; + default: + // Accept any preceding backslashes literally. + while (bSlashCount) { + arg += '\\'; + bSlashCount--; + } + // Just add next char to the current arg. + arg += *p; + break; + } + } + } + // Check for end of input. + if (*p) { + // Go to next character. + p++; + } else { + // If on first pass, go on to second. + if (justCounting) { + // Allocate argv array. + mArgv = new T*[mArgc]; + + // Start second pass + justCounting = 0; + init = 1; + } else { + // Quit. + break; + } + } + } + } +}; +# endif // defined(MOZILLA_INTERNAL_API) + +#endif // defined(XP_WIN) + +// SaveToEnv and EnvHasValue are only available on Windows or when +// MOZILLA_INTERNAL_API is defined +#if defined(MOZILLA_INTERNAL_API) || defined(XP_WIN) + +// Save literal putenv string to environment variable. +MOZ_NEVER_INLINE 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); +# 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; +# endif +} + +#endif // end windows/internal_api-only definitions + +#ifndef NS_NO_XPCOM +already_AddRefed<nsIFile> GetFileFromEnv(const char* name); +#endif + +} // namespace mozilla + +#endif // mozilla_CmdLineAndEnvUtils_h |