Bug 1841035 [wpt PR 40807] - LoAF: reduce memory usage, a=testonly
[gecko.git] / toolkit / xre / CmdLineAndEnvUtils.h
blobcad34d2503ac1ee008d2e7fbee06c83f8fa94d5e
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)
13 # include "prenv.h"
14 # include "prprf.h"
15 # include <string.h>
16 #endif
18 #if defined(XP_WIN)
19 # include "mozilla/UniquePtr.h"
20 # include "mozilla/Vector.h"
21 # include "mozilla/WinHeaderOnlyUtils.h"
23 # include <wchar.h>
24 # include <windows.h>
25 #endif // defined(XP_WIN)
27 #include "mozilla/Maybe.h"
28 #include "mozilla/MemoryChecking.h"
29 #include "mozilla/TypedEnumBits.h"
31 #include <ctype.h>
32 #include <stdint.h>
33 #include <stdlib.h>
35 #ifndef NS_NO_XPCOM
36 # include "nsIFile.h"
37 # include "mozilla/AlreadyAddRefed.h"
38 #endif
40 // Undo X11/X.h's definition of None
41 #undef None
43 namespace mozilla {
45 enum ArgResult {
46 ARG_NONE = 0,
47 ARG_FOUND = 1,
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) {
53 do {
54 *argv = *(argv + 1);
55 ++argv;
56 } while (*argv);
58 --argc;
61 namespace internal {
63 #if 'a' == '\x61'
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
76 // locales.
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;
87 #else
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.
91 #endif
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) {
97 while (*lowerstr) {
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
102 // and simplicity.
103 if (!isValidOptionCharacter(*lowerstr)) return false;
105 if (toLowercase(toNarrow(*mixedstr)) != *lowerstr) {
106 return false; // no match
109 ++lowerstr;
110 ++mixedstr;
113 if (*mixedstr) return false; // lowerstr is shorter
115 return true;
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) {
122 if (!str) {
123 return Nothing();
125 if (*str == '-') {
126 str++;
127 if (*str == '-') {
128 str++;
130 return Some(str);
132 #ifdef XP_WIN
133 if (*str == '/') {
134 return Some(str + 1);
136 #endif
137 return Nothing();
140 } // namespace internal
142 using internal::strimatch;
144 const wchar_t kCommandLineDelimiter[] = L" \t";
146 enum class CheckArgFlag : uint32_t {
147 None = 0,
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;
176 while (*curarg) {
177 if (const auto arg = ReadAsOption(*curarg)) {
178 if (strimatch(aArg, arg.value())) {
179 if (aFlags & CheckArgFlag::RemoveArg) {
180 RemoveArg(aArgc, curarg);
181 } else {
182 ++curarg;
185 if (!aParam) {
186 ar = ARG_FOUND;
187 break;
190 if (*curarg) {
191 if (ReadAsOption(*curarg)) {
192 return ARG_BAD;
195 *aParam = *curarg;
197 if (aFlags & CheckArgFlag::RemoveArg) {
198 RemoveArg(aArgc, curarg);
201 ar = ARG_FOUND;
202 break;
205 return ARG_BAD;
209 ++curarg;
212 return ar;
215 template <typename CharT>
216 inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const char* aArg,
217 std::nullptr_t,
218 CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
219 return CheckArg<CharT>(aArgc, aArgv, aArg,
220 static_cast<const CharT**>(nullptr), aFlags);
223 namespace internal {
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)) {
233 return true;
236 return false;
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
258 // satisfied.
259 if (CheckArg(aArgc, aArgv, osintLit, nullptr, CheckArgFlag::None) !=
260 ARG_FOUND) {
261 return true;
264 // There should be at least 4 items present:
265 // <app name> -osint <required param> <arg>.
266 if (aArgc < 4) {
267 return false;
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())) {
274 return false;
276 // Following this is any number of optional parameters, terminated by a
277 // required parameter.
278 int pos = 2;
279 while (true) {
280 if (pos >= aArgc) return false;
282 auto const arg = ReadAsOption(aArgv[pos]);
283 if (!arg) return false;
285 if (MatchesAnyOf(arg.value(), optionalParams)) {
286 ++pos;
287 continue;
290 if (MatchesAnyOf(arg.value(), requiredParams)) {
291 ++pos;
292 break;
295 return false;
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])) {
302 return false;
305 // Nothing ill-formed was passed.
306 return true;
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
314 // entry.
315 template <typename CharT, typename ReqContainerT>
316 inline bool EnsureCommandlineSafeImpl(int aArgc, CharT** aArgv,
317 ReqContainerT const& requiredParams,
318 std::nullptr_t _ = nullptr) {
319 struct {
320 inline const char** begin() const { return nullptr; }
321 inline const char** end() const { return nullptr; }
322 } emptyContainer;
323 return EnsureCommandlineSafeImpl(aArgc, aArgv, requiredParams,
324 emptyContainer);
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,
334 optionalParams)) {
335 exit(127);
339 #if defined(XP_WIN)
340 namespace internal {
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
344 * be escaped.
346 inline int ArgStrLen(const wchar_t* s) {
347 int backslashes = 0;
348 int i = wcslen(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) {
358 while (*s) {
359 if (*s == '\\') {
360 ++backslashes;
361 } else {
362 if (*s == '"') {
363 // Escape the doublequote and all backslashes preceding the
364 // doublequote
365 i += backslashes + 1;
368 backslashes = 0;
371 ++s;
375 return i;
379 * Copy string "s" to string "d", quoting the argument as appropriate and
380 * escaping doublequotes along with any backslashes that immediately precede
381 * doublequotes.
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) {
388 int backslashes = 0;
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
395 ++d;
398 if (hasDoubleQuote) {
399 int i;
400 while (*s) {
401 if (*s == '\\') {
402 ++backslashes;
403 } else {
404 if (*s == '"') {
405 // Escape the doublequote and all backslashes preceding the
406 // doublequote
407 for (i = 0; i <= backslashes; ++i) {
408 *d = '\\';
409 ++d;
413 backslashes = 0;
416 *d = *s;
417 ++d;
418 ++s;
420 } else {
421 wcscpy(d, s);
422 d += wcslen(s);
425 if (addDoubleQuotes) {
426 *d = '"'; // final doublequote
427 ++d;
430 return d;
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) {
447 int i;
448 int len = 0;
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
461 if (len == 0) {
462 len = 1;
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) {
473 *c = ' ';
474 ++c;
478 for (i = 0; i < aArgcExtra; ++i) {
479 c = internal::ArgToString(c, aArgvExtra[i]);
480 if (i + 1 != aArgcExtra) {
481 *c = ' ';
482 ++c;
486 *c = '\0';
488 return s;
491 inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
492 if (!aArgv) {
493 return false;
496 UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath());
497 if (!newArgv_0) {
498 return false;
501 // We intentionally leak newArgv_0 into argv[0]
502 aArgv[0] = newArgv_0.release();
503 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]);
504 return true;
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
512 // the examples at:
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 {
517 int mArgc;
518 T** mArgv;
520 void Release() {
521 if (mArgv) {
522 while (mArgc) {
523 delete[] mArgv[--mArgc];
525 delete[] mArgv;
526 mArgv = nullptr;
530 public:
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) {
544 Release();
546 if (aCmdLineString.IsEmpty()) {
547 return 0;
550 int justCounting = 1;
551 // Flags, etc.
552 int init = 1;
553 int between, quoted, bSlashCount;
554 const T* p;
555 const T* const pEnd = aCmdLineString.EndReading();
556 nsTAutoString<T> arg;
558 // We loop if we've not finished the second pass through.
559 while (1) {
560 // Initialize if required.
561 if (init) {
562 p = aCmdLineString.BeginReading();
563 between = 1;
564 mArgc = quoted = bSlashCount = 0;
566 init = 0;
569 const T charCurr = (p < pEnd) ? *p : 0;
570 const T charNext = (p + 1 < pEnd) ? *(p + 1) : 0;
572 if (between) {
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.
577 between = 0;
578 arg.Truncate();
579 switch (charCurr) {
580 case '\\':
581 // Count the backslash.
582 bSlashCount = 1;
583 break;
584 case '"':
585 // Remember we're inside quotes.
586 quoted = 1;
587 break;
588 default:
589 // Add character to arg.
590 arg += charCurr;
591 break;
593 } else {
594 // Another space between args, ignore it.
596 } else {
597 // We are processing the contents of an argument.
598 // Check for whitespace or end.
599 if (charCurr == 0 ||
600 (!quoted && wcschr(kCommandLineDelimiter, charCurr))) {
601 // Process pending backslashes (interpret them
602 // literally since they're not followed by a ").
603 while (bSlashCount) {
604 arg += '\\';
605 bSlashCount--;
607 // End current arg.
608 if (!justCounting) {
609 mArgv[mArgc] = new T[arg.Length() + 1];
610 memcpy(mArgv[mArgc], arg.get(), (arg.Length() + 1) * sizeof(T));
612 mArgc++;
613 // We're now between args.
614 between = 1;
615 } else {
616 // Still inside argument, process the character.
617 switch (charCurr) {
618 case '"':
619 // First, digest preceding backslashes (if any).
620 while (bSlashCount > 1) {
621 // Put one backsplash in arg for each pair.
622 arg += '\\';
623 bSlashCount -= 2;
625 if (bSlashCount) {
626 // Quote is literal.
627 arg += '"';
628 bSlashCount = 0;
629 } else {
630 // Quote starts or ends a quoted section.
631 if (quoted) {
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.
638 bSlashCount = 1;
639 } else {
640 quoted = 0;
642 } else {
643 quoted = 1;
646 break;
647 case '\\':
648 // Add to count.
649 bSlashCount++;
650 break;
651 default:
652 // Accept any preceding backslashes literally.
653 while (bSlashCount) {
654 arg += '\\';
655 bSlashCount--;
657 // Just add next char to the current arg.
658 arg += charCurr;
659 break;
664 // Check for end of input.
665 if (charCurr) {
666 // Go to next character.
667 p++;
668 } else {
669 // If on first pass, go on to second.
670 if (justCounting) {
671 // Allocate argv array.
672 mArgv = new T*[mArgc];
674 // Start second pass
675 justCounting = 0;
676 init = 1;
677 } else {
678 // Quit.
679 break;
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);
699 if (expr) {
700 PR_SetEnv(expr);
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
708 // copy)
709 _putenv(aEnvString);
710 # endif
713 inline bool EnvHasValue(const char* aVarName) {
714 # if defined(MOZILLA_INTERNAL_API)
715 const char* val = PR_GetEnv(aVarName);
716 return val && *val;
717 # elif defined(XP_WIN)
718 // This is the same as the NSPR implementation
719 const char* val = getenv(aVarName);
720 return val && *val;
721 # endif
724 #endif // end windows/internal_api-only definitions
726 #ifndef NS_NO_XPCOM
727 already_AddRefed<nsIFile> GetFileFromEnv(const char* name);
728 #endif
730 } // namespace mozilla
732 #endif // mozilla_CmdLineAndEnvUtils_h