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 enum class FlagLiteral { osint, safemode };
91 
92 template <typename CharT, FlagLiteral Literal>
93 inline const CharT* GetLiteral();
94 
95 #define DECLARE_FLAG_LITERAL(enum_name, literal)                        \
96   template <>                                                           \
97   inline const char* GetLiteral<char, FlagLiteral::enum_name>() {       \
98     return literal;                                                     \
99   }                                                                     \
100                                                                         \
101   template <>                                                           \
102   inline const wchar_t* GetLiteral<wchar_t, FlagLiteral::enum_name>() { \
103     return L##literal;                                                  \
104   }
105 
106 DECLARE_FLAG_LITERAL(osint, "osint")
107 DECLARE_FLAG_LITERAL(safemode, "safe-mode")
108 
109 enum class CheckArgFlag : uint32_t {
110   None = 0,
111   // (1 << 0) Used to be CheckOSInt
112   RemoveArg = (1 << 1)  // Remove the argument from the argv array.
113 };
114 
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag)115 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag)
116 
117 /**
118  * Check for a commandline flag. If the flag takes a parameter, the
119  * parameter is returned in aParam. Flags may be in the form -arg or
120  * --arg (or /arg on win32).
121  *
122  * @param aArgc The argc value.
123  * @param aArgv The original argv.
124  * @param aArg the parameter to check. Must be lowercase.
125  * @param aParam if non-null, the -arg <data> will be stored in this pointer.
126  *        This is *not* allocated, but rather a pointer to the argv data.
127  * @param aFlags Flags @see CheckArgFlag
128  */
129 template <typename CharT>
130 inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg,
131                           const CharT** aParam = nullptr,
132                           CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
133   MOZ_ASSERT(aArgv && aArg);
134 
135   CharT** curarg = aArgv + 1;  // skip argv[0]
136   ArgResult ar = ARG_NONE;
137 
138   while (*curarg) {
139     CharT* arg = curarg[0];
140 
141     if (arg[0] == '-'
142 #if defined(XP_WIN)
143         || *arg == '/'
144 #endif
145     ) {
146       ++arg;
147 
148       if (*arg == '-') {
149         ++arg;
150       }
151 
152       if (strimatch(aArg, arg)) {
153         if (aFlags & CheckArgFlag::RemoveArg) {
154           RemoveArg(aArgc, curarg);
155         } else {
156           ++curarg;
157         }
158 
159         if (!aParam) {
160           ar = ARG_FOUND;
161           break;
162         }
163 
164         if (*curarg) {
165           if (**curarg == '-'
166 #if defined(XP_WIN)
167               || **curarg == '/'
168 #endif
169           ) {
170             return ARG_BAD;
171           }
172 
173           *aParam = *curarg;
174 
175           if (aFlags & CheckArgFlag::RemoveArg) {
176             RemoveArg(aArgc, curarg);
177           }
178 
179           ar = ARG_FOUND;
180           break;
181         }
182 
183         return ARG_BAD;
184       }
185     }
186 
187     ++curarg;
188   }
189 
190   return ar;
191 }
192 
193 template <typename CharT>
EnsureCommandlineSafe(int & aArgc,CharT ** aArgv,const CharT ** aAcceptableArgs)194 inline void EnsureCommandlineSafe(int& aArgc, CharT** aArgv,
195                                   const CharT** aAcceptableArgs) {
196   // We expect either no -osint, or the full commandline to be:
197   // app -osint
198   // followed by one of the arguments listed in aAcceptableArgs,
199   // followed by one parameter for that arg.
200   // If this varies, we abort to avoid abuse of other commandline handlers
201   // from apps that do a poor job escaping links they give to the OS.
202 
203   const CharT* osintLit = GetLiteral<CharT, FlagLiteral::osint>();
204 
205   if (CheckArg(aArgc, aArgv, osintLit, static_cast<const CharT**>(nullptr),
206                CheckArgFlag::None) == ARG_FOUND) {
207     // There should be 4 items left (app name + -osint + (acceptable arg) +
208     // param)
209     if (aArgc != 4) {
210       exit(127);
211     }
212 
213     // The first should be osint.
214     CharT* arg = aArgv[1];
215     if (*arg != '-'
216 #ifdef XP_WIN
217         && *arg != '/'
218 #endif
219     ) {
220       exit(127);
221     }
222     ++arg;
223     if (*arg == '-') {
224       ++arg;
225     }
226     if (!strimatch(osintLit, arg)) {
227       exit(127);
228     }
229     // Strip it:
230     RemoveArg(aArgc, aArgv + 1);
231 
232     // Now only an acceptable argument and a parameter for it should be left:
233     arg = aArgv[1];
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[2];
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, L" \t") != 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, L" \t") != 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   if (!s) {
401     return s;
402   }
403 
404   int totalArgc = argc + aArgcExtra;
405 
406   wchar_t* c = s.get();
407   for (i = 0; i < argc; ++i) {
408     c = internal::ArgToString(c, argv[i]);
409     if (i + 1 != totalArgc) {
410       *c = ' ';
411       ++c;
412     }
413   }
414 
415   for (i = 0; i < aArgcExtra; ++i) {
416     c = internal::ArgToString(c, aArgvExtra[i]);
417     if (i + 1 != aArgcExtra) {
418       *c = ' ';
419       ++c;
420     }
421   }
422 
423   *c = '\0';
424 
425   return s;
426 }
427 
SetArgv0ToFullBinaryPath(wchar_t * aArgv[])428 inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
429   if (!aArgv) {
430     return false;
431   }
432 
433   UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath());
434   if (!newArgv_0) {
435     return false;
436   }
437 
438   // We intentionally leak newArgv_0 into argv[0]
439   aArgv[0] = newArgv_0.release();
440   MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]);
441   return true;
442 }
443 
444 #endif  // defined(XP_WIN)
445 
446 // SaveToEnv and EnvHasValue are only available on Windows or when
447 // MOZILLA_INTERNAL_API is defined
448 #if defined(MOZILLA_INTERNAL_API) || defined(XP_WIN)
449 
450 // Save literal putenv string to environment variable.
SaveToEnv(const char * aEnvString)451 MOZ_NEVER_INLINE inline void SaveToEnv(const char* aEnvString) {
452 #  if defined(MOZILLA_INTERNAL_API)
453   char* expr = strdup(aEnvString);
454   if (expr) {
455     PR_SetEnv(expr);
456   }
457 
458   // We intentionally leak |expr| here since it is required by PR_SetEnv.
459   MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr);
460 #  elif defined(XP_WIN)
461   // This is the same as the NSPR implementation
462   // (Note that we don't need to do a strdup for this case; the CRT makes a
463   // copy)
464   _putenv(aEnvString);
465 #  endif
466 }
467 
EnvHasValue(const char * aVarName)468 inline bool EnvHasValue(const char* aVarName) {
469 #  if defined(MOZILLA_INTERNAL_API)
470   const char* val = PR_GetEnv(aVarName);
471   return val && *val;
472 #  elif defined(XP_WIN)
473   // This is the same as the NSPR implementation
474   const char* val = getenv(aVarName);
475   return val && *val;
476 #  endif
477 }
478 
479 #endif  // end windows/internal_api-only definitions
480 
481 #ifndef NS_NO_XPCOM
482 already_AddRefed<nsIFile> GetFileFromEnv(const char* name);
483 #endif
484 
485 }  // namespace mozilla
486 
487 #endif  // mozilla_CmdLineAndEnvUtils_h
488