1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_CmdLineAndEnvUtils_h
8 #define mozilla_CmdLineAndEnvUtils_h
10 // NB: This code may be used outside of xul and thus must not depend on XPCOM
12 #if defined(MOZILLA_INTERNAL_API)
19 # include "mozilla/UniquePtr.h"
20 # include "mozilla/Vector.h"
21 # include "mozilla/WinHeaderOnlyUtils.h"
25 #endif // defined(XP_WIN)
27 #include "mozilla/Maybe.h"
28 #include "mozilla/MemoryChecking.h"
29 #include "mozilla/TypedEnumBits.h"
37 # include "mozilla/AlreadyAddRefed.h"
40 // Undo X11/X.h's definition of None
48 ARG_BAD
= 2 // you wanted a param, but there isn't one
51 template <typename CharT
>
52 inline void RemoveArg(int& argc
, CharT
** argv
) {
64 // Valid option characters must have the same representation in every locale
65 // (which is true for most of ASCII, barring \x5C and \x7E).
66 static inline constexpr bool isValidOptionCharacter(char c
) {
67 // We specifically avoid the use of `islower` here; it's locale-dependent, and
68 // may return true for non-ASCII values in some locales.
69 return ('0' <= c
&& c
<= '9') || ('a' <= c
&& c
<= 'z') || c
== '-';
72 // Convert uppercase to lowercase, locale-insensitively.
73 static inline constexpr char toLowercase(char c
) {
74 // We specifically avoid the use of `tolower` here; it's locale-dependent, and
75 // may output ASCII values for non-ASCII input (or vice versa) in some
77 return ('A' <= c
&& c
<= 'Z') ? char(c
| ' ') : c
;
80 // Convert a CharT to a char, ensuring that no CharT is mapped to any valid
81 // option character except the unique CharT naturally corresponding thereto.
82 template <typename CharT
>
83 static inline constexpr char toNarrow(CharT c
) {
84 // confirmed to compile down to nothing when `CharT` is `char`
85 return (c
& static_cast<CharT
>(0xff)) == c
? c
: 0xff;
88 // The target system's character set isn't even ASCII-compatible. If you're
89 // porting Gecko to such a platform, you'll have to implement these yourself.
90 # error Character conversion functions not implemented for this platform.
93 // Case-insensitively compare a string taken from the command-line (`mixedstr`)
94 // to the text of some known command-line option (`lowerstr`).
95 template <typename CharT
>
96 static inline bool strimatch(const char* lowerstr
, const CharT
* mixedstr
) {
98 if (!*mixedstr
) return false; // mixedstr is shorter
100 // Non-ASCII strings may compare incorrectly depending on the user's locale.
101 // Some ASCII-safe characters are also dispermitted for semantic reasons
103 if (!isValidOptionCharacter(*lowerstr
)) return false;
105 if (toLowercase(toNarrow(*mixedstr
)) != *lowerstr
) {
106 return false; // no match
113 if (*mixedstr
) return false; // lowerstr is shorter
118 // Given a command-line argument, return Nothing if it isn't structurally a
119 // command-line option, and Some(<the option text>) if it is.
120 template <typename CharT
>
121 mozilla::Maybe
<const CharT
*> ReadAsOption(const CharT
* str
) {
134 return Some(str
+ 1);
140 } // namespace internal
142 using internal::strimatch
;
144 const wchar_t kCommandLineDelimiter
[] = L
" \t";
146 enum class CheckArgFlag
: uint32_t {
148 // (1 << 0) Used to be CheckOSInt
149 RemoveArg
= (1 << 1) // Remove the argument from the argv array.
152 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag
)
155 * Check for a commandline flag. If the flag takes a parameter, the
156 * parameter is returned in aParam. Flags may be in the form -arg or
157 * --arg (or /arg on win32).
159 * @param aArgc The argc value.
160 * @param aArgv The original argv.
161 * @param aArg the parameter to check. Must be lowercase.
162 * @param aParam if non-null, the -arg <data> will be stored in this pointer.
163 * This is *not* allocated, but rather a pointer to the argv data.
164 * @param aFlags Flags @see CheckArgFlag
166 template <typename CharT
>
167 inline ArgResult
CheckArg(int& aArgc
, CharT
** aArgv
, const char* aArg
,
168 const CharT
** aParam
= nullptr,
169 CheckArgFlag aFlags
= CheckArgFlag::RemoveArg
) {
170 using internal::ReadAsOption
;
171 MOZ_ASSERT(aArgv
&& aArg
);
173 CharT
** curarg
= aArgv
+ 1; // skip argv[0]
174 ArgResult ar
= ARG_NONE
;
177 if (const auto arg
= ReadAsOption(*curarg
)) {
178 if (strimatch(aArg
, arg
.value())) {
179 if (aFlags
& CheckArgFlag::RemoveArg
) {
180 RemoveArg(aArgc
, curarg
);
191 if (ReadAsOption(*curarg
)) {
197 if (aFlags
& CheckArgFlag::RemoveArg
) {
198 RemoveArg(aArgc
, curarg
);
215 template <typename CharT
>
216 inline ArgResult
CheckArg(int& aArgc
, CharT
** aArgv
, const char* aArg
,
218 CheckArgFlag aFlags
= CheckArgFlag::RemoveArg
) {
219 return CheckArg
<CharT
>(aArgc
, aArgv
, aArg
,
220 static_cast<const CharT
**>(nullptr), aFlags
);
224 // template <typename T>
225 // constexpr bool IsStringRange =
226 // std::convertible_to<std::ranges::range_value_t<T>, const char *>;
228 template <typename CharT
, typename ListT
>
229 // requires IsStringRange<ListT>
230 static bool MatchesAnyOf(CharT
const* unknown
, ListT
const& known
) {
231 for (const char* k
: known
) {
232 if (strimatch(k
, unknown
)) {
239 template <typename CharT
, typename ReqContainerT
, typename OptContainerT
>
240 // requires IsStringRange<ReqContainerT> && IsStringRange<OptContainerT>
241 inline bool EnsureCommandlineSafeImpl(int aArgc
, CharT
** aArgv
,
242 ReqContainerT
const& requiredParams
,
243 OptContainerT
const& optionalParams
) {
244 // We expect either no -osint, or the full commandline to be:
246 // app -osint [<optional-param>...] <required-param> <required-argument>
248 // Otherwise, we abort to avoid abuse of other command-line handlers from apps
249 // that do a poor job escaping links they give to the OS.
251 // Note that the above implies that optional parameters do not themselves take
252 // arguments. This is a security feature, to prevent the possible injection of
253 // additional parameters via such arguments. (See, e.g., bug 384384.)
255 static constexpr const char* osintLit
= "osint";
257 // If "-osint" (or the equivalent) is not present, then this is trivially
259 if (CheckArg(aArgc
, aArgv
, osintLit
, nullptr, CheckArgFlag::None
) !=
264 // There should be at least 4 items present:
265 // <app name> -osint <required param> <arg>.
270 // The first parameter must be osint.
271 const auto arg1
= ReadAsOption(aArgv
[1]);
272 if (!arg1
) return false;
273 if (!strimatch(osintLit
, arg1
.value())) {
276 // Following this is any number of optional parameters, terminated by a
277 // required parameter.
280 if (pos
>= aArgc
) return false;
282 auto const arg
= ReadAsOption(aArgv
[pos
]);
283 if (!arg
) return false;
285 if (MatchesAnyOf(arg
.value(), optionalParams
)) {
290 if (MatchesAnyOf(arg
.value(), requiredParams
)) {
298 // There must be one argument remaining...
299 if (pos
+ 1 != aArgc
) return false;
300 // ... which must not be another option.
301 if (ReadAsOption(aArgv
[pos
])) {
305 // Nothing ill-formed was passed.
309 // C (and so C++) disallows empty arrays. Rather than require callers to jump
310 // through hoops to specify an empty optional-argument list, allow either its
311 // omission or its specification as `nullptr`, and do the hoop-jumping here.
313 // No such facility is provided for requiredParams, which must have at least one
315 template <typename CharT
, typename ReqContainerT
>
316 inline bool EnsureCommandlineSafeImpl(int aArgc
, CharT
** aArgv
,
317 ReqContainerT
const& requiredParams
,
318 std::nullptr_t _
= nullptr) {
320 inline const char** begin() const { return nullptr; }
321 inline const char** end() const { return nullptr; }
323 return EnsureCommandlineSafeImpl(aArgc
, aArgv
, requiredParams
,
326 } // namespace internal
328 template <typename CharT
, typename ReqContainerT
,
329 typename OptContainerT
= std::nullptr_t
>
330 inline void EnsureCommandlineSafe(
331 int aArgc
, CharT
** aArgv
, ReqContainerT
const& requiredParams
,
332 OptContainerT
const& optionalParams
= nullptr) {
333 if (!internal::EnsureCommandlineSafeImpl(aArgc
, aArgv
, requiredParams
,
342 * Get the length that the string will take and takes into account the
343 * additional length if the string needs to be quoted and if characters need to
346 inline int ArgStrLen(const wchar_t* s
) {
349 bool hasDoubleQuote
= wcschr(s
, L
'"') != nullptr;
350 // Only add doublequotes if the string contains a space or a tab
351 bool addDoubleQuotes
= wcspbrk(s
, kCommandLineDelimiter
) != nullptr;
353 if (addDoubleQuotes
) {
354 i
+= 2; // initial and final duoblequote
357 if (hasDoubleQuote
) {
363 // Escape the doublequote and all backslashes preceding the
365 i
+= backslashes
+ 1;
379 * Copy string "s" to string "d", quoting the argument as appropriate and
380 * escaping doublequotes along with any backslashes that immediately precede
382 * The CRT parses this to retrieve the original argc/argv that we meant,
383 * see STDARGV.C in the MSVC CRT sources.
385 * @return the end of the string
387 inline wchar_t* ArgToString(wchar_t* d
, const wchar_t* s
) {
389 bool hasDoubleQuote
= wcschr(s
, L
'"') != nullptr;
390 // Only add doublequotes if the string contains a space or a tab
391 bool addDoubleQuotes
= wcspbrk(s
, kCommandLineDelimiter
) != nullptr;
393 if (addDoubleQuotes
) {
394 *d
= '"'; // initial doublequote
398 if (hasDoubleQuote
) {
405 // Escape the doublequote and all backslashes preceding the
407 for (i
= 0; i
<= backslashes
; ++i
) {
425 if (addDoubleQuotes
) {
426 *d
= '"'; // final doublequote
433 } // namespace internal
436 * Creates a command line from a list of arguments.
438 * @param argc Number of elements in |argv|
439 * @param argv Array of arguments
440 * @param aArgcExtra Number of elements in |aArgvExtra|
441 * @param aArgvExtra Optional array of arguments to be appended to the resulting
442 * command line after those provided by |argv|.
444 inline UniquePtr
<wchar_t[]> MakeCommandLine(
445 int argc
, const wchar_t* const* argv
, int aArgcExtra
= 0,
446 const wchar_t* const* aArgvExtra
= nullptr) {
450 // The + 1 for each argument reserves space for either a ' ' or the null
451 // terminator, depending on the position of the argument.
452 for (i
= 0; i
< argc
; ++i
) {
453 len
+= internal::ArgStrLen(argv
[i
]) + 1;
456 for (i
= 0; i
< aArgcExtra
; ++i
) {
457 len
+= internal::ArgStrLen(aArgvExtra
[i
]) + 1;
460 // Protect against callers that pass 0 arguments
465 auto s
= MakeUnique
<wchar_t[]>(len
);
467 int totalArgc
= argc
+ aArgcExtra
;
469 wchar_t* c
= s
.get();
470 for (i
= 0; i
< argc
; ++i
) {
471 c
= internal::ArgToString(c
, argv
[i
]);
472 if (i
+ 1 != totalArgc
) {
478 for (i
= 0; i
< aArgcExtra
; ++i
) {
479 c
= internal::ArgToString(c
, aArgvExtra
[i
]);
480 if (i
+ 1 != aArgcExtra
) {
491 inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv
[]) {
496 UniquePtr
<wchar_t[]> newArgv_0(GetFullBinaryPath());
501 // We intentionally leak newArgv_0 into argv[0]
502 aArgv
[0] = newArgv_0
.release();
503 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv
[0]);
507 # if defined(MOZILLA_INTERNAL_API)
508 // This class converts a command line string into an array of the arguments.
509 // It's basically the opposite of MakeCommandLine. However, the behavior is
510 // different from ::CommandLineToArgvW in several ways, such as escaping a
511 // backslash or quoting an argument containing whitespaces. This satisfies
513 // https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
514 // https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
515 template <typename T
>
516 class CommandLineParserWin final
{
523 delete[] mArgv
[--mArgc
];
531 CommandLineParserWin() : mArgc(0), mArgv(nullptr) {}
532 ~CommandLineParserWin() { Release(); }
534 CommandLineParserWin(const CommandLineParserWin
&) = delete;
535 CommandLineParserWin(CommandLineParserWin
&&) = delete;
536 CommandLineParserWin
& operator=(const CommandLineParserWin
&) = delete;
537 CommandLineParserWin
& operator=(CommandLineParserWin
&&) = delete;
539 int Argc() const { return mArgc
; }
540 const T
* const* Argv() const { return mArgv
; }
542 // Returns the number of characters handled
543 int HandleCommandLine(const nsTSubstring
<T
>& aCmdLineString
) {
546 if (aCmdLineString
.IsEmpty()) {
550 int justCounting
= 1;
553 int between
, quoted
, bSlashCount
;
555 const T
* const pEnd
= aCmdLineString
.EndReading();
556 nsTAutoString
<T
> arg
;
558 // We loop if we've not finished the second pass through.
560 // Initialize if required.
562 p
= aCmdLineString
.BeginReading();
564 mArgc
= quoted
= bSlashCount
= 0;
569 const T charCurr
= (p
< pEnd
) ? *p
: 0;
570 const T charNext
= (p
+ 1 < pEnd
) ? *(p
+ 1) : 0;
573 // We are traversing whitespace between args.
574 // Check for start of next arg.
575 if (charCurr
!= 0 && !wcschr(kCommandLineDelimiter
, charCurr
)) {
576 // Start of another arg.
581 // Count the backslash.
585 // Remember we're inside quotes.
589 // Add character to arg.
594 // Another space between args, ignore it.
597 // We are processing the contents of an argument.
598 // Check for whitespace or end.
600 (!quoted
&& wcschr(kCommandLineDelimiter
, charCurr
))) {
601 // Process pending backslashes (interpret them
602 // literally since they're not followed by a ").
603 while (bSlashCount
) {
609 mArgv
[mArgc
] = new T
[arg
.Length() + 1];
610 memcpy(mArgv
[mArgc
], arg
.get(), (arg
.Length() + 1) * sizeof(T
));
613 // We're now between args.
616 // Still inside argument, process the character.
619 // First, digest preceding backslashes (if any).
620 while (bSlashCount
> 1) {
621 // Put one backsplash in arg for each pair.
630 // Quote starts or ends a quoted section.
632 // Check for special case of consecutive double
633 // quotes inside a quoted section.
634 if (charNext
== '"') {
635 // This implies a literal double-quote. Fake that
636 // out by causing next double-quote to look as
637 // if it was preceded by a backslash.
652 // Accept any preceding backslashes literally.
653 while (bSlashCount
) {
657 // Just add next char to the current arg.
664 // Check for end of input.
666 // Go to next character.
669 // If on first pass, go on to second.
671 // Allocate argv array.
672 mArgv
= new T
*[mArgc
];
684 return p
- aCmdLineString
.BeginReading();
687 # endif // defined(MOZILLA_INTERNAL_API)
689 #endif // defined(XP_WIN)
691 // SaveToEnv and EnvHasValue are only available on Windows or when
692 // MOZILLA_INTERNAL_API is defined
693 #if defined(MOZILLA_INTERNAL_API) || defined(XP_WIN)
695 // Save literal putenv string to environment variable.
696 MOZ_NEVER_INLINE
inline void SaveToEnv(const char* aEnvString
) {
697 # if defined(MOZILLA_INTERNAL_API)
698 char* expr
= strdup(aEnvString
);
703 // We intentionally leak |expr| here since it is required by PR_SetEnv.
704 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr
);
705 # elif defined(XP_WIN)
706 // This is the same as the NSPR implementation
707 // (Note that we don't need to do a strdup for this case; the CRT makes a
713 inline bool EnvHasValue(const char* aVarName
) {
714 # if defined(MOZILLA_INTERNAL_API)
715 const char* val
= PR_GetEnv(aVarName
);
717 # elif defined(XP_WIN)
718 // This is the same as the NSPR implementation
719 const char* val
= getenv(aVarName
);
724 #endif // end windows/internal_api-only definitions
727 already_AddRefed
<nsIFile
> GetFileFromEnv(const char* name
);
730 } // namespace mozilla
732 #endif // mozilla_CmdLineAndEnvUtils_h