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