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