/* -*- 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 #endif #if defined(XP_WIN) # include "mozilla/UniquePtr.h" # include "mozilla/Vector.h" # include "mozilla/WinHeaderOnlyUtils.h" # include # include #endif // defined(XP_WIN) #include "mozilla/Maybe.h" #include "mozilla/MemoryChecking.h" #include "mozilla/TypedEnumBits.h" #include #include #include #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 inline void RemoveArg(int& argc, CharT** argv) { do { *argv = *(argv + 1); ++argv; } while (*argv); --argc; } namespace internal { #if 'a' == '\x61' // Valid option characters must have the same representation in every locale // (which is true for most of ASCII, barring \x5C and \x7E). static inline constexpr bool isValidOptionCharacter(char c) { // We specifically avoid the use of `islower` here; it's locale-dependent, and // may return true for non-ASCII values in some locales. return ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || c == '-'; }; // Convert uppercase to lowercase, locale-insensitively. static inline constexpr char toLowercase(char c) { // We specifically avoid the use of `tolower` here; it's locale-dependent, and // may output ASCII values for non-ASCII input (or vice versa) in some // locales. return ('A' <= c && c <= 'Z') ? char(c | ' ') : c; }; // Convert a CharT to a char, ensuring that no CharT is mapped to any valid // option character except the unique CharT naturally corresponding thereto. template static inline constexpr char toNarrow(CharT c) { // confirmed to compile down to nothing when `CharT` is `char` return (c & static_cast(0xff)) == c ? c : 0xff; }; #else // The target system's character set isn't even ASCII-compatible. If you're // porting Gecko to such a platform, you'll have to implement these yourself. # error Character conversion functions not implemented for this platform. #endif // Case-insensitively compare a string taken from the command-line (`mixedstr`) // to the text of some known command-line option (`lowerstr`). template static inline bool strimatch(const char* lowerstr, const CharT* mixedstr) { while (*lowerstr) { if (!*mixedstr) return false; // mixedstr is shorter // Non-ASCII strings may compare incorrectly depending on the user's locale. // Some ASCII-safe characters are also dispermitted for semantic reasons // and simplicity. if (!isValidOptionCharacter(*lowerstr)) return false; if (toLowercase(toNarrow(*mixedstr)) != *lowerstr) { return false; // no match } ++lowerstr; ++mixedstr; } if (*mixedstr) return false; // lowerstr is shorter return true; } // Given a command-line argument, return Nothing if it isn't structurally a // command-line option, and Some() if it is. template mozilla::Maybe ReadAsOption(const CharT* str) { if (!str) { return Nothing(); } if (*str == '-') { str++; if (*str == '-') { str++; } return Some(str); } #ifdef XP_WIN if (*str == '/') { return Some(str + 1); } #endif return Nothing(); } } // namespace internal using internal::strimatch; const wchar_t kCommandLineDelimiter[] = L" \t"; 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 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 char* aArg, const CharT** aParam = nullptr, CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { using internal::ReadAsOption; MOZ_ASSERT(aArgv && aArg); CharT** curarg = aArgv + 1; // skip argv[0] ArgResult ar = ARG_NONE; while (*curarg) { if (const auto arg = ReadAsOption(*curarg)) { if (strimatch(aArg, arg.value())) { if (aFlags & CheckArgFlag::RemoveArg) { RemoveArg(aArgc, curarg); } else { ++curarg; } if (!aParam) { ar = ARG_FOUND; break; } if (*curarg) { if (ReadAsOption(*curarg)) { return ARG_BAD; } *aParam = *curarg; if (aFlags & CheckArgFlag::RemoveArg) { RemoveArg(aArgc, curarg); } ar = ARG_FOUND; break; } return ARG_BAD; } } ++curarg; } return ar; } template inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const char* aArg, std::nullptr_t, CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { return CheckArg(aArgc, aArgv, aArg, static_cast(nullptr), aFlags); } namespace internal { // template // constexpr bool IsStringRange = // std::convertible_to, const char *>; template // requires IsStringRange static bool MatchesAnyOf(CharT const* unknown, ListT const& known) { for (const char* k : known) { if (strimatch(k, unknown)) { return true; } } return false; } template // requires IsStringRange && IsStringRange inline bool EnsureCommandlineSafeImpl(int aArgc, CharT** aArgv, ReqContainerT const& requiredParams, OptContainerT const& optionalParams) { // We expect either no -osint, or the full commandline to be: // // app -osint [...] // // Otherwise, we abort to avoid abuse of other command-line handlers from apps // that do a poor job escaping links they give to the OS. // // Note that the above implies that optional parameters do not themselves take // arguments. This is a security feature, to prevent the possible injection of // additional parameters via such arguments. (See, e.g., bug 384384.) static constexpr const char* osintLit = "osint"; // If "-osint" (or the equivalent) is not present, then this is trivially // satisfied. if (CheckArg(aArgc, aArgv, osintLit, nullptr, CheckArgFlag::None) != ARG_FOUND) { return true; } // There should be at least 4 items present: // -osint . if (aArgc < 4) { return false; } // The first parameter must be osint. const auto arg1 = ReadAsOption(aArgv[1]); if (!arg1) return false; if (!strimatch(osintLit, arg1.value())) { return false; } // Following this is any number of optional parameters, terminated by a // required parameter. int pos = 2; while (true) { if (pos >= aArgc) return false; auto const arg = ReadAsOption(aArgv[pos]); if (!arg) return false; if (MatchesAnyOf(arg.value(), optionalParams)) { ++pos; continue; } if (MatchesAnyOf(arg.value(), requiredParams)) { ++pos; break; } return false; } // There must be one argument remaining... if (pos + 1 != aArgc) return false; // ... which must not be another option. if (ReadAsOption(aArgv[pos])) { return false; } // Nothing ill-formed was passed. return true; } // C (and so C++) disallows empty arrays. Rather than require callers to jump // through hoops to specify an empty optional-argument list, allow either its // omission or its specification as `nullptr`, and do the hoop-jumping here. // // No such facility is provided for requiredParams, which must have at least one // entry. template inline bool EnsureCommandlineSafeImpl(int aArgc, CharT** aArgv, ReqContainerT const& requiredParams, std::nullptr_t _ = nullptr) { struct { inline const char** begin() const { return nullptr; } inline const char** end() const { return nullptr; } } emptyContainer; return EnsureCommandlineSafeImpl(aArgc, aArgv, requiredParams, emptyContainer); } } // namespace internal template inline void EnsureCommandlineSafe( int aArgc, CharT** aArgv, ReqContainerT const& requiredParams, OptContainerT const& optionalParams = nullptr) { if (!internal::EnsureCommandlineSafeImpl(aArgc, aArgv, requiredParams, optionalParams)) { exit(127); } } #if defined(XP_WIN) namespace internal { /** * Attempt to copy the string `s` (considered as a command-line argument) into * the buffer `d` with all necessary escaping and quoting. Returns the number of * characters written. * * If `d` is NULL, doesn't actually write anything to `d`, and merely returns * the number of characters that _would_ have been written. * * (This moderately-awkward conflation ensures that the pre-allocation counting * step and post-allocation copying step use the same algorithm.) */ inline size_t CopyArgImpl_(wchar_t* d, const wchar_t* s) { size_t len = 0; bool const actuallyCopy = d != nullptr; auto const appendChar = [&](wchar_t c) { if (actuallyCopy) { *d++ = c; } len++; }; bool hasDoubleQuote = wcschr(s, L'"') != nullptr; // Only add doublequotes if... bool addDoubleQuotes = // ... the string is empty, or... *s == '\0' || // ... the string contains a space or a tab. wcspbrk(s, kCommandLineDelimiter) != nullptr; if (addDoubleQuotes) { appendChar('"'); } if (hasDoubleQuote) { size_t backslashes = 0; while (*s) { if (*s == '\\') { ++backslashes; } else { if (*s == '"') { // Escape the doublequote and all backslashes preceding the // doublequote for (size_t i = 0; i <= backslashes; ++i) { appendChar('\\'); } } backslashes = 0; } appendChar(*s); ++s; } } else { // optimization: just blit auto const src_len = wcslen(s); if (actuallyCopy) { ::wcscpy(d, s); d += src_len; } len += src_len; } if (addDoubleQuotes) { appendChar('"'); } return len; } /** * Compute the space required for the serialized form of this argument. Includes * any additional space needed for quotes and backslash-escapes. */ inline size_t ArgStrLen(const wchar_t* s) { return CopyArgImpl_(nullptr, s); } /** * 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) { return d + CopyArgImpl_(d, s); } } // 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 MakeCommandLine( int argc, const wchar_t* const* argv, int aArgcExtra = 0, const wchar_t* const* aArgvExtra = nullptr) { int i; size_t 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(len); 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 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. This satisfies // the examples at: // https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines // https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85) template 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; } // Returns the number of characters handled int HandleCommandLine(const nsTSubstring& aCmdLineString) { Release(); if (aCmdLineString.IsEmpty()) { return 0; } int justCounting = 1; // Flags, etc. int init = 1; int between, quoted, bSlashCount; const T* p; const T* const pEnd = aCmdLineString.EndReading(); nsTAutoString arg; // We loop if we've not finished the second pass through. while (1) { // Initialize if required. if (init) { p = aCmdLineString.BeginReading(); between = 1; mArgc = quoted = bSlashCount = 0; init = 0; } const T charCurr = (p < pEnd) ? *p : 0; const T charNext = (p + 1 < pEnd) ? *(p + 1) : 0; if (between) { // We are traversing whitespace between args. // Check for start of next arg. if (charCurr != 0 && !wcschr(kCommandLineDelimiter, charCurr)) { // Start of another arg. between = 0; arg.Truncate(); switch (charCurr) { case '\\': // Count the backslash. bSlashCount = 1; break; case '"': // Remember we're inside quotes. quoted = 1; break; default: // Add character to arg. arg += charCurr; break; } } else { // Another space between args, ignore it. } } else { // We are processing the contents of an argument. // Check for whitespace or end. if (charCurr == 0 || (!quoted && wcschr(kCommandLineDelimiter, charCurr))) { // 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 (charCurr) { 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 (charNext == '"') { // 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 += charCurr; break; } } } // Check for end of input. if (charCurr) { // 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; } } } return p - aCmdLineString.BeginReading(); } }; # 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 GetFileFromEnv(const char* name); #endif } // namespace mozilla #endif // mozilla_CmdLineAndEnvUtils_h