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