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