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/. */
6
7 #ifndef mozilla_CmdLineAndEnvUtils_h
8 #define mozilla_CmdLineAndEnvUtils_h
9
10 // NB: This code may be used outside of xul and thus must not depend on XPCOM
11
12 #if defined(MOZILLA_INTERNAL_API)
13 # include "prenv.h"
14 # include "prprf.h"
15 # include <string.h>
16 #elif defined(XP_WIN)
17 # include <stdlib.h>
18 #endif
19
20 #if defined(XP_WIN)
21 # include "mozilla/UniquePtr.h"
22 # include "mozilla/Vector.h"
23 # include "mozilla/WinHeaderOnlyUtils.h"
24
25 # include <wchar.h>
26 # include <windows.h>
27 #endif // defined(XP_WIN)
28
29 #include "mozilla/MemoryChecking.h"
30 #include "mozilla/TypedEnumBits.h"
31
32 #include <ctype.h>
33 #include <stdint.h>
34
35 #ifndef NS_NO_XPCOM
36 # include "nsIFile.h"
37 # include "mozilla/AlreadyAddRefed.h"
38 #endif
39
40 // Undo X11/X.h's definition of None
41 #undef None
42
43 namespace mozilla {
44
45 enum ArgResult {
46 ARG_NONE = 0,
47 ARG_FOUND = 1,
48 ARG_BAD = 2 // you wanted a param, but there isn't one
49 };
50
51 template <typename CharT>
RemoveArg(int & argc,CharT ** argv)52 inline void RemoveArg(int& argc, CharT** argv) {
53 do {
54 *argv = *(argv + 1);
55 ++argv;
56 } while (*argv);
57
58 --argc;
59 }
60
61 namespace internal {
62
63 template <typename FuncT, typename CharT>
strimatch(FuncT aToLowerFn,const CharT * lowerstr,const CharT * mixedstr)64 static inline bool strimatch(FuncT aToLowerFn, const CharT* lowerstr,
65 const CharT* mixedstr) {
66 while (*lowerstr) {
67 if (!*mixedstr) return false; // mixedstr is shorter
68 if (static_cast<CharT>(aToLowerFn(*mixedstr)) != *lowerstr)
69 return false; // no match
70
71 ++lowerstr;
72 ++mixedstr;
73 }
74
75 if (*mixedstr) return false; // lowerstr is shorter
76
77 return true;
78 }
79
80 } // namespace internal
81
strimatch(const char * lowerstr,const char * mixedstr)82 inline bool strimatch(const char* lowerstr, const char* mixedstr) {
83 return internal::strimatch(&tolower, lowerstr, mixedstr);
84 }
85
strimatch(const wchar_t * lowerstr,const wchar_t * mixedstr)86 inline bool strimatch(const wchar_t* lowerstr, const wchar_t* mixedstr) {
87 return internal::strimatch(&towlower, lowerstr, mixedstr);
88 }
89
90 const wchar_t kCommandLineDelimiter[] = L" \t";
91
92 enum class FlagLiteral { osint, safemode };
93
94 template <typename CharT, FlagLiteral Literal>
95 inline const CharT* GetLiteral();
96
97 #define DECLARE_FLAG_LITERAL(enum_name, literal) \
98 template <> \
99 inline const char* GetLiteral<char, FlagLiteral::enum_name>() { \
100 return literal; \
101 } \
102 \
103 template <> \
104 inline const wchar_t* GetLiteral<wchar_t, FlagLiteral::enum_name>() { \
105 return L##literal; \
106 }
107
108 DECLARE_FLAG_LITERAL(osint, "osint")
109 DECLARE_FLAG_LITERAL(safemode, "safe-mode")
110
111 enum class CheckArgFlag : uint32_t {
112 None = 0,
113 // (1 << 0) Used to be CheckOSInt
114 RemoveArg = (1 << 1) // Remove the argument from the argv array.
115 };
116
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag)117 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag)
118
119 /**
120 * Check for a commandline flag. If the flag takes a parameter, the
121 * parameter is returned in aParam. Flags may be in the form -arg or
122 * --arg (or /arg on win32).
123 *
124 * @param aArgc The argc value.
125 * @param aArgv The original argv.
126 * @param aArg the parameter to check. Must be lowercase.
127 * @param aParam if non-null, the -arg <data> will be stored in this pointer.
128 * This is *not* allocated, but rather a pointer to the argv data.
129 * @param aFlags Flags @see CheckArgFlag
130 */
131 template <typename CharT>
132 inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg,
133 const CharT** aParam = nullptr,
134 CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
135 MOZ_ASSERT(aArgv && aArg);
136
137 CharT** curarg = aArgv + 1; // skip argv[0]
138 ArgResult ar = ARG_NONE;
139
140 while (*curarg) {
141 CharT* arg = curarg[0];
142
143 if (arg[0] == '-'
144 #if defined(XP_WIN)
145 || *arg == '/'
146 #endif
147 ) {
148 ++arg;
149
150 if (*arg == '-') {
151 ++arg;
152 }
153
154 if (strimatch(aArg, arg)) {
155 if (aFlags & CheckArgFlag::RemoveArg) {
156 RemoveArg(aArgc, curarg);
157 } else {
158 ++curarg;
159 }
160
161 if (!aParam) {
162 ar = ARG_FOUND;
163 break;
164 }
165
166 if (*curarg) {
167 if (**curarg == '-'
168 #if defined(XP_WIN)
169 || **curarg == '/'
170 #endif
171 ) {
172 return ARG_BAD;
173 }
174
175 *aParam = *curarg;
176
177 if (aFlags & CheckArgFlag::RemoveArg) {
178 RemoveArg(aArgc, curarg);
179 }
180
181 ar = ARG_FOUND;
182 break;
183 }
184
185 return ARG_BAD;
186 }
187 }
188
189 ++curarg;
190 }
191
192 return ar;
193 }
194
195 template <typename CharT>
EnsureCommandlineSafe(int & aArgc,CharT ** aArgv,const CharT ** aAcceptableArgs)196 inline void EnsureCommandlineSafe(int& aArgc, CharT** aArgv,
197 const CharT** aAcceptableArgs) {
198 // We expect either no -osint, or the full commandline to be:
199 // app -osint
200 // followed by one of the arguments listed in aAcceptableArgs,
201 // followed by one parameter for that arg.
202 // If this varies, we abort to avoid abuse of other commandline handlers
203 // from apps that do a poor job escaping links they give to the OS.
204
205 const CharT* osintLit = GetLiteral<CharT, FlagLiteral::osint>();
206
207 if (CheckArg(aArgc, aArgv, osintLit, static_cast<const CharT**>(nullptr),
208 CheckArgFlag::None) == ARG_FOUND) {
209 // There should be 4 items left (app name + -osint + (acceptable arg) +
210 // param)
211 if (aArgc != 4) {
212 exit(127);
213 }
214
215 // The first should be osint.
216 CharT* arg = aArgv[1];
217 if (*arg != '-'
218 #ifdef XP_WIN
219 && *arg != '/'
220 #endif
221 ) {
222 exit(127);
223 }
224 ++arg;
225 if (*arg == '-') {
226 ++arg;
227 }
228 if (!strimatch(osintLit, arg)) {
229 exit(127);
230 }
231
232 // Now only an acceptable argument and a parameter for it should be left:
233 arg = aArgv[2];
234 if (*arg != '-'
235 #ifdef XP_WIN
236 && *arg != '/'
237 #endif
238 ) {
239 exit(127);
240 }
241 ++arg;
242 if (*arg == '-') {
243 ++arg;
244 }
245 bool haveAcceptableArg = false;
246 const CharT** acceptableArg = aAcceptableArgs;
247 while (*acceptableArg) {
248 if (strimatch(*acceptableArg, arg)) {
249 haveAcceptableArg = true;
250 break;
251 }
252 acceptableArg++;
253 }
254 if (!haveAcceptableArg) {
255 exit(127);
256 }
257 // The param that is passed afterwards shouldn't be another switch:
258 arg = aArgv[3];
259 if (*arg == '-'
260 #ifdef XP_WIN
261 || *arg == '/'
262 #endif
263 ) {
264 exit(127);
265 }
266 }
267 // Either no osint, so nothing to do, or we ensured nothing nefarious was
268 // passed.
269 }
270
271 #if defined(XP_WIN)
272
273 namespace internal {
274
275 /**
276 * Get the length that the string will take and takes into account the
277 * additional length if the string needs to be quoted and if characters need to
278 * be escaped.
279 */
ArgStrLen(const wchar_t * s)280 inline int ArgStrLen(const wchar_t* s) {
281 int backslashes = 0;
282 int i = wcslen(s);
283 bool hasDoubleQuote = wcschr(s, L'"') != nullptr;
284 // Only add doublequotes if the string contains a space or a tab
285 bool addDoubleQuotes = wcspbrk(s, kCommandLineDelimiter) != nullptr;
286
287 if (addDoubleQuotes) {
288 i += 2; // initial and final duoblequote
289 }
290
291 if (hasDoubleQuote) {
292 while (*s) {
293 if (*s == '\\') {
294 ++backslashes;
295 } else {
296 if (*s == '"') {
297 // Escape the doublequote and all backslashes preceding the
298 // doublequote
299 i += backslashes + 1;
300 }
301
302 backslashes = 0;
303 }
304
305 ++s;
306 }
307 }
308
309 return i;
310 }
311
312 /**
313 * Copy string "s" to string "d", quoting the argument as appropriate and
314 * escaping doublequotes along with any backslashes that immediately precede
315 * doublequotes.
316 * The CRT parses this to retrieve the original argc/argv that we meant,
317 * see STDARGV.C in the MSVC CRT sources.
318 *
319 * @return the end of the string
320 */
ArgToString(wchar_t * d,const wchar_t * s)321 inline wchar_t* ArgToString(wchar_t* d, const wchar_t* s) {
322 int backslashes = 0;
323 bool hasDoubleQuote = wcschr(s, L'"') != nullptr;
324 // Only add doublequotes if the string contains a space or a tab
325 bool addDoubleQuotes = wcspbrk(s, kCommandLineDelimiter) != nullptr;
326
327 if (addDoubleQuotes) {
328 *d = '"'; // initial doublequote
329 ++d;
330 }
331
332 if (hasDoubleQuote) {
333 int i;
334 while (*s) {
335 if (*s == '\\') {
336 ++backslashes;
337 } else {
338 if (*s == '"') {
339 // Escape the doublequote and all backslashes preceding the
340 // doublequote
341 for (i = 0; i <= backslashes; ++i) {
342 *d = '\\';
343 ++d;
344 }
345 }
346
347 backslashes = 0;
348 }
349
350 *d = *s;
351 ++d;
352 ++s;
353 }
354 } else {
355 wcscpy(d, s);
356 d += wcslen(s);
357 }
358
359 if (addDoubleQuotes) {
360 *d = '"'; // final doublequote
361 ++d;
362 }
363
364 return d;
365 }
366
367 } // namespace internal
368
369 /**
370 * Creates a command line from a list of arguments.
371 *
372 * @param argc Number of elements in |argv|
373 * @param argv Array of arguments
374 * @param aArgcExtra Number of elements in |aArgvExtra|
375 * @param aArgvExtra Optional array of arguments to be appended to the resulting
376 * command line after those provided by |argv|.
377 */
378 inline UniquePtr<wchar_t[]> MakeCommandLine(
379 int argc, const wchar_t* const* argv, int aArgcExtra = 0,
380 const wchar_t* const* aArgvExtra = nullptr) {
381 int i;
382 int len = 0;
383
384 // The + 1 for each argument reserves space for either a ' ' or the null
385 // terminator, depending on the position of the argument.
386 for (i = 0; i < argc; ++i) {
387 len += internal::ArgStrLen(argv[i]) + 1;
388 }
389
390 for (i = 0; i < aArgcExtra; ++i) {
391 len += internal::ArgStrLen(aArgvExtra[i]) + 1;
392 }
393
394 // Protect against callers that pass 0 arguments
395 if (len == 0) {
396 len = 1;
397 }
398
399 auto s = MakeUnique<wchar_t[]>(len);
400
401 int totalArgc = argc + aArgcExtra;
402
403 wchar_t* c = s.get();
404 for (i = 0; i < argc; ++i) {
405 c = internal::ArgToString(c, argv[i]);
406 if (i + 1 != totalArgc) {
407 *c = ' ';
408 ++c;
409 }
410 }
411
412 for (i = 0; i < aArgcExtra; ++i) {
413 c = internal::ArgToString(c, aArgvExtra[i]);
414 if (i + 1 != aArgcExtra) {
415 *c = ' ';
416 ++c;
417 }
418 }
419
420 *c = '\0';
421
422 return s;
423 }
424
SetArgv0ToFullBinaryPath(wchar_t * aArgv[])425 inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
426 if (!aArgv) {
427 return false;
428 }
429
430 UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath());
431 if (!newArgv_0) {
432 return false;
433 }
434
435 // We intentionally leak newArgv_0 into argv[0]
436 aArgv[0] = newArgv_0.release();
437 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]);
438 return true;
439 }
440
441 # if defined(MOZILLA_INTERNAL_API)
442 // This class converts a command line string into an array of the arguments.
443 // It's basically the opposite of MakeCommandLine. However, the behavior is
444 // different from ::CommandLineToArgvW in several ways, such as escaping a
445 // backslash or quoting an argument containing whitespaces. This satisfies
446 // the examples at:
447 // https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
448 // https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
449 template <typename T>
450 class CommandLineParserWin final {
451 int mArgc;
452 T** mArgv;
453
Release()454 void Release() {
455 if (mArgv) {
456 while (mArgc) {
457 delete[] mArgv[--mArgc];
458 }
459 delete[] mArgv;
460 mArgv = nullptr;
461 }
462 }
463
464 public:
CommandLineParserWin()465 CommandLineParserWin() : mArgc(0), mArgv(nullptr) {}
~CommandLineParserWin()466 ~CommandLineParserWin() { Release(); }
467
468 CommandLineParserWin(const CommandLineParserWin&) = delete;
469 CommandLineParserWin(CommandLineParserWin&&) = delete;
470 CommandLineParserWin& operator=(const CommandLineParserWin&) = delete;
471 CommandLineParserWin& operator=(CommandLineParserWin&&) = delete;
472
Argc()473 int Argc() const { return mArgc; }
Argv()474 const T* const* Argv() const { return mArgv; }
475
476 // Returns the number of characters handled
HandleCommandLine(const nsTSubstring<T> & aCmdLineString)477 int HandleCommandLine(const nsTSubstring<T>& aCmdLineString) {
478 Release();
479
480 if (aCmdLineString.IsEmpty()) {
481 return 0;
482 }
483
484 int justCounting = 1;
485 // Flags, etc.
486 int init = 1;
487 int between, quoted, bSlashCount;
488 const T* p;
489 const T* const pEnd = aCmdLineString.EndReading();
490 nsTAutoString<T> arg;
491
492 // We loop if we've not finished the second pass through.
493 while (1) {
494 // Initialize if required.
495 if (init) {
496 p = aCmdLineString.BeginReading();
497 between = 1;
498 mArgc = quoted = bSlashCount = 0;
499
500 init = 0;
501 }
502
503 const T charCurr = (p < pEnd) ? *p : 0;
504 const T charNext = (p + 1 < pEnd) ? *(p + 1) : 0;
505
506 if (between) {
507 // We are traversing whitespace between args.
508 // Check for start of next arg.
509 if (charCurr != 0 && !wcschr(kCommandLineDelimiter, charCurr)) {
510 // Start of another arg.
511 between = 0;
512 arg.Truncate();
513 switch (charCurr) {
514 case '\\':
515 // Count the backslash.
516 bSlashCount = 1;
517 break;
518 case '"':
519 // Remember we're inside quotes.
520 quoted = 1;
521 break;
522 default:
523 // Add character to arg.
524 arg += charCurr;
525 break;
526 }
527 } else {
528 // Another space between args, ignore it.
529 }
530 } else {
531 // We are processing the contents of an argument.
532 // Check for whitespace or end.
533 if (charCurr == 0 ||
534 (!quoted && wcschr(kCommandLineDelimiter, charCurr))) {
535 // Process pending backslashes (interpret them
536 // literally since they're not followed by a ").
537 while (bSlashCount) {
538 arg += '\\';
539 bSlashCount--;
540 }
541 // End current arg.
542 if (!justCounting) {
543 mArgv[mArgc] = new T[arg.Length() + 1];
544 memcpy(mArgv[mArgc], arg.get(), (arg.Length() + 1) * sizeof(T));
545 }
546 mArgc++;
547 // We're now between args.
548 between = 1;
549 } else {
550 // Still inside argument, process the character.
551 switch (charCurr) {
552 case '"':
553 // First, digest preceding backslashes (if any).
554 while (bSlashCount > 1) {
555 // Put one backsplash in arg for each pair.
556 arg += '\\';
557 bSlashCount -= 2;
558 }
559 if (bSlashCount) {
560 // Quote is literal.
561 arg += '"';
562 bSlashCount = 0;
563 } else {
564 // Quote starts or ends a quoted section.
565 if (quoted) {
566 // Check for special case of consecutive double
567 // quotes inside a quoted section.
568 if (charNext == '"') {
569 // This implies a literal double-quote. Fake that
570 // out by causing next double-quote to look as
571 // if it was preceded by a backslash.
572 bSlashCount = 1;
573 } else {
574 quoted = 0;
575 }
576 } else {
577 quoted = 1;
578 }
579 }
580 break;
581 case '\\':
582 // Add to count.
583 bSlashCount++;
584 break;
585 default:
586 // Accept any preceding backslashes literally.
587 while (bSlashCount) {
588 arg += '\\';
589 bSlashCount--;
590 }
591 // Just add next char to the current arg.
592 arg += charCurr;
593 break;
594 }
595 }
596 }
597
598 // Check for end of input.
599 if (charCurr) {
600 // Go to next character.
601 p++;
602 } else {
603 // If on first pass, go on to second.
604 if (justCounting) {
605 // Allocate argv array.
606 mArgv = new T*[mArgc];
607
608 // Start second pass
609 justCounting = 0;
610 init = 1;
611 } else {
612 // Quit.
613 break;
614 }
615 }
616 }
617
618 return p - aCmdLineString.BeginReading();
619 }
620 };
621 # endif // defined(MOZILLA_INTERNAL_API)
622
623 #endif // defined(XP_WIN)
624
625 // SaveToEnv and EnvHasValue are only available on Windows or when
626 // MOZILLA_INTERNAL_API is defined
627 #if defined(MOZILLA_INTERNAL_API) || defined(XP_WIN)
628
629 // Save literal putenv string to environment variable.
SaveToEnv(const char * aEnvString)630 MOZ_NEVER_INLINE inline void SaveToEnv(const char* aEnvString) {
631 # if defined(MOZILLA_INTERNAL_API)
632 char* expr = strdup(aEnvString);
633 if (expr) {
634 PR_SetEnv(expr);
635 }
636
637 // We intentionally leak |expr| here since it is required by PR_SetEnv.
638 MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr);
639 # elif defined(XP_WIN)
640 // This is the same as the NSPR implementation
641 // (Note that we don't need to do a strdup for this case; the CRT makes a
642 // copy)
643 _putenv(aEnvString);
644 # endif
645 }
646
EnvHasValue(const char * aVarName)647 inline bool EnvHasValue(const char* aVarName) {
648 # if defined(MOZILLA_INTERNAL_API)
649 const char* val = PR_GetEnv(aVarName);
650 return val && *val;
651 # elif defined(XP_WIN)
652 // This is the same as the NSPR implementation
653 const char* val = getenv(aVarName);
654 return val && *val;
655 # endif
656 }
657
658 #endif // end windows/internal_api-only definitions
659
660 #ifndef NS_NO_XPCOM
661 already_AddRefed<nsIFile> GetFileFromEnv(const char* name);
662 #endif
663
664 } // namespace mozilla
665
666 #endif // mozilla_CmdLineAndEnvUtils_h
667