1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "nsCommandLine.h"
6 
7 #include "nsComponentManagerUtils.h"
8 #include "nsICategoryManager.h"
9 #include "nsICommandLineHandler.h"
10 #include "nsICommandLineValidator.h"
11 #include "nsIConsoleService.h"
12 #include "nsIClassInfoImpl.h"
13 #include "nsIFile.h"
14 #include "nsISimpleEnumerator.h"
15 #include "mozilla/SimpleEnumerator.h"
16 
17 #include "nsNativeCharsetUtils.h"
18 #include "nsNetUtil.h"
19 #include "nsIFileProtocolHandler.h"
20 #include "nsIURI.h"
21 #include "nsUnicharUtils.h"
22 #include "nsTextFormatter.h"
23 #include "nsXPCOMCID.h"
24 #include "plstr.h"
25 #include "mozilla/Attributes.h"
26 
27 #ifdef MOZ_WIDGET_COCOA
28 #  include <CoreFoundation/CoreFoundation.h>
29 #  include "nsILocalFileMac.h"
30 #elif defined(XP_WIN)
31 #  include <windows.h>
32 #  include <shlobj.h>
33 #elif defined(XP_UNIX)
34 #  include <unistd.h>
35 #endif
36 
37 #ifdef DEBUG_bsmedberg
38 #  define DEBUG_COMMANDLINE
39 #endif
40 
41 #define NS_COMMANDLINE_CID                           \
42   {                                                  \
43     0x23bcc750, 0xdc20, 0x460b, {                    \
44       0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 \
45     }                                                \
46   }
47 
48 using mozilla::SimpleEnumerator;
49 
nsCommandLine()50 nsCommandLine::nsCommandLine()
51     : mState(STATE_INITIAL_LAUNCH), mPreventDefault(false) {}
52 
53 NS_IMPL_CLASSINFO(nsCommandLine, nullptr, 0, NS_COMMANDLINE_CID)
NS_IMPL_ISUPPORTS_CI(nsCommandLine,nsICommandLine,nsICommandLineRunner)54 NS_IMPL_ISUPPORTS_CI(nsCommandLine, nsICommandLine, nsICommandLineRunner)
55 
56 NS_IMETHODIMP
57 nsCommandLine::GetLength(int32_t* aResult) {
58   *aResult = int32_t(mArgs.Length());
59   return NS_OK;
60 }
61 
62 NS_IMETHODIMP
GetArgument(int32_t aIndex,nsAString & aResult)63 nsCommandLine::GetArgument(int32_t aIndex, nsAString& aResult) {
64   NS_ENSURE_ARG_MIN(aIndex, 0);
65   NS_ENSURE_ARG_MAX(aIndex, int32_t(mArgs.Length() - 1));
66 
67   aResult = mArgs[aIndex];
68   return NS_OK;
69 }
70 
71 NS_IMETHODIMP
FindFlag(const nsAString & aFlag,bool aCaseSensitive,int32_t * aResult)72 nsCommandLine::FindFlag(const nsAString& aFlag, bool aCaseSensitive,
73                         int32_t* aResult) {
74   NS_ENSURE_ARG(!aFlag.IsEmpty());
75 
76   auto c = aCaseSensitive ? nsTDefaultStringComparator<char16_t>
77                           : nsCaseInsensitiveStringComparator;
78 
79   for (uint32_t f = 0; f < mArgs.Length(); f++) {
80     const nsString& arg = mArgs[f];
81 
82     if (arg.Length() >= 2 && arg.First() == char16_t('-')) {
83       if (aFlag.Equals(Substring(arg, 1), c)) {
84         *aResult = f;
85         return NS_OK;
86       }
87     }
88   }
89 
90   *aResult = -1;
91   return NS_OK;
92 }
93 
94 NS_IMETHODIMP
RemoveArguments(int32_t aStart,int32_t aEnd)95 nsCommandLine::RemoveArguments(int32_t aStart, int32_t aEnd) {
96   NS_ENSURE_ARG_MIN(aStart, 0);
97   NS_ENSURE_ARG_MAX(uint32_t(aEnd) + 1, mArgs.Length());
98 
99   mArgs.RemoveElementsRange(mArgs.begin() + aStart, mArgs.begin() + aEnd + 1);
100 
101   return NS_OK;
102 }
103 
104 NS_IMETHODIMP
HandleFlag(const nsAString & aFlag,bool aCaseSensitive,bool * aResult)105 nsCommandLine::HandleFlag(const nsAString& aFlag, bool aCaseSensitive,
106                           bool* aResult) {
107   nsresult rv;
108 
109   int32_t found;
110   rv = FindFlag(aFlag, aCaseSensitive, &found);
111   NS_ENSURE_SUCCESS(rv, rv);
112 
113   if (found == -1) {
114     *aResult = false;
115     return NS_OK;
116   }
117 
118   *aResult = true;
119   RemoveArguments(found, found);
120 
121   return NS_OK;
122 }
123 
124 NS_IMETHODIMP
HandleFlagWithParam(const nsAString & aFlag,bool aCaseSensitive,nsAString & aResult)125 nsCommandLine::HandleFlagWithParam(const nsAString& aFlag, bool aCaseSensitive,
126                                    nsAString& aResult) {
127   nsresult rv;
128 
129   int32_t found;
130   rv = FindFlag(aFlag, aCaseSensitive, &found);
131   NS_ENSURE_SUCCESS(rv, rv);
132 
133   if (found == -1) {
134     aResult.SetIsVoid(true);
135     return NS_OK;
136   }
137 
138   if (found == int32_t(mArgs.Length()) - 1) {
139     return NS_ERROR_INVALID_ARG;
140   }
141 
142   ++found;
143 
144   {  // scope for validity of |param|, which RemoveArguments call invalidates
145     const nsString& param = mArgs[found];
146     if (!param.IsEmpty() && param.First() == '-') {
147       return NS_ERROR_INVALID_ARG;
148     }
149 
150     aResult = param;
151   }
152 
153   RemoveArguments(found - 1, found);
154 
155   return NS_OK;
156 }
157 
158 NS_IMETHODIMP
GetState(uint32_t * aResult)159 nsCommandLine::GetState(uint32_t* aResult) {
160   *aResult = mState;
161   return NS_OK;
162 }
163 
164 NS_IMETHODIMP
GetPreventDefault(bool * aResult)165 nsCommandLine::GetPreventDefault(bool* aResult) {
166   *aResult = mPreventDefault;
167   return NS_OK;
168 }
169 
170 NS_IMETHODIMP
SetPreventDefault(bool aValue)171 nsCommandLine::SetPreventDefault(bool aValue) {
172   mPreventDefault = aValue;
173   return NS_OK;
174 }
175 
176 NS_IMETHODIMP
GetWorkingDirectory(nsIFile ** aResult)177 nsCommandLine::GetWorkingDirectory(nsIFile** aResult) {
178   NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
179 
180   NS_ADDREF(*aResult = mWorkingDir);
181   return NS_OK;
182 }
183 
184 NS_IMETHODIMP
ResolveFile(const nsAString & aArgument,nsIFile ** aResult)185 nsCommandLine::ResolveFile(const nsAString& aArgument, nsIFile** aResult) {
186   // First try to resolve as an absolute path if we can.
187   // This will work even if we have no mWorkingDir, which happens if e.g.
188   // the dir from which we were started was deleted before we started,
189 #if defined(XP_UNIX)
190   if (aArgument.First() == '/') {
191     nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
192     NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
193     nsresult rv = lf->InitWithPath(aArgument);
194     if (NS_FAILED(rv)) {
195       return rv;
196     }
197 
198     lf.forget(aResult);
199     return NS_OK;
200   }
201 #elif defined(XP_WIN)
202   nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
203   NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
204 
205   // Just try creating the file with the absolute path; if it fails,
206   // we'll keep going and try it as a relative path.
207   if (NS_SUCCEEDED(lf->InitWithPath(aArgument))) {
208     lf.forget(aResult);
209     return NS_OK;
210   }
211 #endif
212   // If that fails
213   return ResolveRelativeFile(aArgument, aResult);
214 }
215 
ResolveRelativeFile(const nsAString & aArgument,nsIFile ** aResult)216 nsresult nsCommandLine::ResolveRelativeFile(const nsAString& aArgument,
217                                             nsIFile** aResult) {
218   if (!mWorkingDir) {
219     *aResult = nullptr;
220     return NS_OK;
221   }
222 
223   // This is some seriously screwed-up code. nsIFile.appendRelativeNativePath
224   // explicitly does not accept .. or . path parts, but that is exactly what we
225   // need here. So we hack around it.
226 
227   nsresult rv;
228 
229 #if defined(MOZ_WIDGET_COCOA)
230   nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(mWorkingDir));
231   NS_ENSURE_TRUE(lfm, NS_ERROR_NO_INTERFACE);
232 
233   nsCOMPtr<nsILocalFileMac> newfile(
234       do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
235   NS_ENSURE_TRUE(newfile, NS_ERROR_OUT_OF_MEMORY);
236 
237   CFURLRef baseurl;
238   rv = lfm->GetCFURL(&baseurl);
239   NS_ENSURE_SUCCESS(rv, rv);
240 
241   nsAutoCString path;
242   NS_CopyUnicodeToNative(aArgument, path);
243 
244   CFURLRef newurl = CFURLCreateFromFileSystemRepresentationRelativeToBase(
245       nullptr, (const UInt8*)path.get(), path.Length(), true, baseurl);
246 
247   CFRelease(baseurl);
248 
249   rv = newfile->InitWithCFURL(newurl);
250   CFRelease(newurl);
251   if (NS_FAILED(rv)) return rv;
252 
253   newfile.forget(aResult);
254   return NS_OK;
255 
256 #elif defined(XP_UNIX)
257   nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
258   NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
259 
260   nsAutoCString nativeArg;
261   NS_CopyUnicodeToNative(aArgument, nativeArg);
262 
263   nsAutoCString newpath;
264   mWorkingDir->GetNativePath(newpath);
265 
266   newpath.Append('/');
267   newpath.Append(nativeArg);
268 
269   rv = lf->InitWithNativePath(newpath);
270   if (NS_FAILED(rv)) return rv;
271 
272   rv = lf->Normalize();
273   if (NS_FAILED(rv)) return rv;
274 
275   lf.forget(aResult);
276   return NS_OK;
277 
278 #elif defined(XP_WIN)
279   nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
280   NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
281 
282   // This is a relative path. We use string magic
283   // and win32 _fullpath. Note that paths of the form "\Relative\To\CurDrive"
284   // are going to fail, and I haven't figured out a way to work around this
285   // without the PathCombine() function, which is not available before
286   // Windows 8; see https://bugzilla.mozilla.org/show_bug.cgi?id=1672814
287 
288   nsAutoString fullPath;
289   mWorkingDir->GetPath(fullPath);
290 
291   fullPath.Append('\\');
292   fullPath.Append(aArgument);
293 
294   WCHAR pathBuf[MAX_PATH];
295   if (!_wfullpath(pathBuf, fullPath.get(), MAX_PATH)) return NS_ERROR_FAILURE;
296 
297   rv = lf->InitWithPath(nsDependentString(pathBuf));
298   if (NS_FAILED(rv)) return rv;
299   lf.forget(aResult);
300   return NS_OK;
301 
302 #else
303 #  error Need platform-specific logic here.
304 #endif
305 }
306 
307 NS_IMETHODIMP
ResolveURI(const nsAString & aArgument,nsIURI ** aResult)308 nsCommandLine::ResolveURI(const nsAString& aArgument, nsIURI** aResult) {
309   nsresult rv;
310 
311   // First, we try to init the argument as an absolute file path. If this
312   // doesn't work, it is an absolute or relative URI.
313 
314   nsCOMPtr<nsIIOService> io = do_GetIOService();
315   NS_ENSURE_TRUE(io, NS_ERROR_OUT_OF_MEMORY);
316 
317   nsCOMPtr<nsIURI> workingDirURI;
318   if (mWorkingDir) {
319     io->NewFileURI(mWorkingDir, getter_AddRefs(workingDirURI));
320   }
321 
322   nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
323   rv = lf->InitWithPath(aArgument);
324   if (NS_SUCCEEDED(rv)) {
325     lf->Normalize();
326     nsAutoCString url;
327     // Try to resolve the url for .url files.
328     rv = resolveShortcutURL(lf, url);
329     if (NS_SUCCEEDED(rv) && !url.IsEmpty()) {
330       return io->NewURI(url, nullptr, workingDirURI, aResult);
331     }
332 
333     return io->NewFileURI(lf, aResult);
334   }
335 
336   return io->NewURI(NS_ConvertUTF16toUTF8(aArgument), nullptr, workingDirURI,
337                     aResult);
338 }
339 
appendArg(const char * arg)340 void nsCommandLine::appendArg(const char* arg) {
341 #ifdef DEBUG_COMMANDLINE
342   printf("Adding XP arg: %s\n", arg);
343 #endif
344 
345   nsAutoString warg;
346 #ifdef XP_WIN
347   CopyUTF8toUTF16(nsDependentCString(arg), warg);
348 #else
349   NS_CopyNativeToUnicode(nsDependentCString(arg), warg);
350 #endif
351 
352   mArgs.AppendElement(warg);
353 }
354 
resolveShortcutURL(nsIFile * aFile,nsACString & outURL)355 nsresult nsCommandLine::resolveShortcutURL(nsIFile* aFile, nsACString& outURL) {
356   nsCOMPtr<nsIFileProtocolHandler> fph;
357   nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
358   if (NS_FAILED(rv)) return rv;
359 
360   nsCOMPtr<nsIURI> uri;
361   rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
362   if (NS_FAILED(rv)) return rv;
363 
364   return uri->GetSpec(outURL);
365 }
366 
367 NS_IMETHODIMP
Init(int32_t argc,const char * const * argv,nsIFile * aWorkingDir,uint32_t aState)368 nsCommandLine::Init(int32_t argc, const char* const* argv, nsIFile* aWorkingDir,
369                     uint32_t aState) {
370   NS_ENSURE_ARG_MAX(aState, 2);
371 
372   int32_t i;
373 
374   mWorkingDir = aWorkingDir;
375 
376   // skip argv[0], we don't want it
377   for (i = 1; i < argc; ++i) {
378     const char* curarg = argv[i];
379 
380 #ifdef DEBUG_COMMANDLINE
381     printf("Testing native arg %i: '%s'\n", i, curarg);
382 #endif
383 #if defined(XP_WIN)
384     if (*curarg == '/') {
385       char* dup = PL_strdup(curarg);
386       if (!dup) return NS_ERROR_OUT_OF_MEMORY;
387 
388       *dup = '-';
389       char* colon = PL_strchr(dup, ':');
390       if (colon) {
391         *colon = '\0';
392         appendArg(dup);
393         appendArg(colon + 1);
394       } else {
395         appendArg(dup);
396       }
397       PL_strfree(dup);
398       continue;
399     }
400 #endif
401     if (*curarg == '-') {
402       if (*(curarg + 1) == '-') ++curarg;
403 
404       char* dup = PL_strdup(curarg);
405       if (!dup) return NS_ERROR_OUT_OF_MEMORY;
406 
407       char* eq = PL_strchr(dup, '=');
408       if (eq) {
409         *eq = '\0';
410         appendArg(dup);
411         appendArg(eq + 1);
412       } else {
413         appendArg(dup);
414       }
415       PL_strfree(dup);
416       continue;
417     }
418 
419     appendArg(curarg);
420   }
421 
422   mState = aState;
423 
424   return NS_OK;
425 }
426 
427 template <typename... T>
LogConsoleMessage(const char16_t * fmt,T...args)428 static void LogConsoleMessage(const char16_t* fmt, T... args) {
429   nsString msg;
430   nsTextFormatter::ssprintf(msg, fmt, args...);
431 
432   nsCOMPtr<nsIConsoleService> cs =
433       do_GetService("@mozilla.org/consoleservice;1");
434   if (cs) cs->LogStringMessage(msg.get());
435 }
436 
EnumerateHandlers(EnumerateHandlersCallback aCallback,void * aClosure)437 nsresult nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback,
438                                           void* aClosure) {
439   nsresult rv;
440 
441   nsCOMPtr<nsICategoryManager> catman(
442       do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
443   NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
444 
445   nsCOMPtr<nsISimpleEnumerator> entenum;
446   rv = catman->EnumerateCategory("command-line-handler",
447                                  getter_AddRefs(entenum));
448   NS_ENSURE_SUCCESS(rv, rv);
449 
450   for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) {
451     nsAutoCString contractID;
452     categoryEntry->GetValue(contractID);
453 
454     nsCOMPtr<nsICommandLineHandler> clh(do_GetService(contractID.get()));
455     if (!clh) {
456       nsCString entry;
457       categoryEntry->GetEntry(entry);
458 
459       LogConsoleMessage(
460           u"Contract ID '%s' was registered as a command line handler for "
461           u"entry '%s', but could not be created.",
462           contractID.get(), entry.get());
463       continue;
464     }
465 
466     rv = (aCallback)(clh, this, aClosure);
467     if (rv == NS_ERROR_ABORT) break;
468 
469     rv = NS_OK;
470   }
471 
472   return rv;
473 }
474 
EnumerateValidators(EnumerateValidatorsCallback aCallback,void * aClosure)475 nsresult nsCommandLine::EnumerateValidators(
476     EnumerateValidatorsCallback aCallback, void* aClosure) {
477   nsresult rv;
478 
479   nsCOMPtr<nsICategoryManager> catman(
480       do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
481   NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
482 
483   nsCOMPtr<nsISimpleEnumerator> entenum;
484   rv = catman->EnumerateCategory("command-line-validator",
485                                  getter_AddRefs(entenum));
486   NS_ENSURE_SUCCESS(rv, rv);
487 
488   for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) {
489     nsAutoCString contractID;
490     categoryEntry->GetValue(contractID);
491 
492     nsCOMPtr<nsICommandLineValidator> clv(do_GetService(contractID.get()));
493     if (!clv) continue;
494 
495     rv = (aCallback)(clv, this, aClosure);
496     if (rv == NS_ERROR_ABORT) break;
497 
498     rv = NS_OK;
499   }
500 
501   return rv;
502 }
503 
EnumValidate(nsICommandLineValidator * aValidator,nsICommandLine * aThis,void *)504 static nsresult EnumValidate(nsICommandLineValidator* aValidator,
505                              nsICommandLine* aThis, void*) {
506   return aValidator->Validate(aThis);
507 }
508 
EnumRun(nsICommandLineHandler * aHandler,nsICommandLine * aThis,void *)509 static nsresult EnumRun(nsICommandLineHandler* aHandler, nsICommandLine* aThis,
510                         void*) {
511   return aHandler->Handle(aThis);
512 }
513 
514 NS_IMETHODIMP
Run()515 nsCommandLine::Run() {
516   nsresult rv;
517 
518   rv = EnumerateValidators(EnumValidate, nullptr);
519   if (rv == NS_ERROR_ABORT) return rv;
520 
521   rv = EnumerateHandlers(EnumRun, nullptr);
522   if (rv == NS_ERROR_ABORT) return rv;
523 
524   return NS_OK;
525 }
526 
EnumHelp(nsICommandLineHandler * aHandler,nsICommandLine * aThis,void * aClosure)527 static nsresult EnumHelp(nsICommandLineHandler* aHandler, nsICommandLine* aThis,
528                          void* aClosure) {
529   nsresult rv;
530 
531   nsCString text;
532   rv = aHandler->GetHelpInfo(text);
533   if (NS_SUCCEEDED(rv)) {
534     NS_ASSERTION(
535         text.Length() == 0 || text.Last() == '\n',
536         "Help text from command line handlers should end in a newline.");
537 
538     nsACString* totalText = reinterpret_cast<nsACString*>(aClosure);
539     totalText->Append(text);
540   }
541 
542   return NS_OK;
543 }
544 
545 NS_IMETHODIMP
GetHelpText(nsACString & aResult)546 nsCommandLine::GetHelpText(nsACString& aResult) {
547   EnumerateHandlers(EnumHelp, &aResult);
548 
549   return NS_OK;
550 }
551