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 "nsCommandLine.h"
7 #include "nsComponentManagerUtils.h"
8 #include "nsICategoryManager.h"
9 #include "nsICommandLineHandler.h"
10 #include "nsICommandLineValidator.h"
11 #include "nsIConsoleService.h"
12 #include "nsIClassInfoImpl.h"
14 #include "nsISimpleEnumerator.h"
15 #include "mozilla/SimpleEnumerator.h"
17 #include "nsNativeCharsetUtils.h"
18 #include "nsNetUtil.h"
19 #include "nsIFileProtocolHandler.h"
21 #include "nsUnicharUtils.h"
22 #include "nsTextFormatter.h"
23 #include "nsXPCOMCID.h"
25 #ifdef MOZ_WIDGET_COCOA
26 # include <CoreFoundation/CoreFoundation.h>
27 # include "nsILocalFileMac.h"
33 #ifdef DEBUG_bsmedberg
34 # define DEBUG_COMMANDLINE
37 #define NS_COMMANDLINE_CID \
39 0x23bcc750, 0xdc20, 0x460b, { \
40 0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 \
44 using mozilla::SimpleEnumerator
;
46 nsCommandLine::nsCommandLine()
47 : mState(STATE_INITIAL_LAUNCH
), mPreventDefault(false) {}
49 NS_IMPL_CLASSINFO(nsCommandLine
, nullptr, 0, NS_COMMANDLINE_CID
)
50 NS_IMPL_ISUPPORTS_CI(nsCommandLine
, nsICommandLine
, nsICommandLineRunner
)
53 nsCommandLine::GetLength(int32_t* aResult
) {
54 *aResult
= int32_t(mArgs
.Length());
59 nsCommandLine::GetArgument(int32_t aIndex
, nsAString
& aResult
) {
60 NS_ENSURE_ARG_MIN(aIndex
, 0);
61 NS_ENSURE_ARG_MAX(aIndex
, int32_t(mArgs
.Length() - 1));
63 aResult
= mArgs
[aIndex
];
68 nsCommandLine::FindFlag(const nsAString
& aFlag
, bool aCaseSensitive
,
70 NS_ENSURE_ARG(!aFlag
.IsEmpty());
72 auto c
= aCaseSensitive
? nsTDefaultStringComparator
<char16_t
>
73 : nsCaseInsensitiveStringComparator
;
75 for (uint32_t f
= 0; f
< mArgs
.Length(); f
++) {
76 const nsString
& arg
= mArgs
[f
];
78 if (arg
.Length() >= 2 && arg
.First() == char16_t('-')) {
79 if (aFlag
.Equals(Substring(arg
, 1), c
)) {
91 nsCommandLine::RemoveArguments(int32_t aStart
, int32_t aEnd
) {
92 NS_ENSURE_ARG_MIN(aStart
, 0);
93 NS_ENSURE_ARG_MAX(uint32_t(aEnd
) + 1, mArgs
.Length());
95 mArgs
.RemoveElementsRange(mArgs
.begin() + aStart
, mArgs
.begin() + aEnd
+ 1);
101 nsCommandLine::HandleFlag(const nsAString
& aFlag
, bool aCaseSensitive
,
106 rv
= FindFlag(aFlag
, aCaseSensitive
, &found
);
107 NS_ENSURE_SUCCESS(rv
, rv
);
115 RemoveArguments(found
, found
);
121 nsCommandLine::HandleFlagWithParam(const nsAString
& aFlag
, bool aCaseSensitive
,
122 nsAString
& aResult
) {
126 rv
= FindFlag(aFlag
, aCaseSensitive
, &found
);
127 NS_ENSURE_SUCCESS(rv
, rv
);
130 aResult
.SetIsVoid(true);
134 if (found
== int32_t(mArgs
.Length()) - 1) {
135 return NS_ERROR_INVALID_ARG
;
140 { // scope for validity of |param|, which RemoveArguments call invalidates
141 const nsString
& param
= mArgs
[found
];
142 if (!param
.IsEmpty() && param
.First() == '-') {
143 return NS_ERROR_INVALID_ARG
;
149 RemoveArguments(found
- 1, found
);
155 nsCommandLine::GetState(uint32_t* aResult
) {
161 nsCommandLine::GetPreventDefault(bool* aResult
) {
162 *aResult
= mPreventDefault
;
167 nsCommandLine::SetPreventDefault(bool aValue
) {
168 mPreventDefault
= aValue
;
173 nsCommandLine::GetWorkingDirectory(nsIFile
** aResult
) {
174 NS_ENSURE_TRUE(mWorkingDir
, NS_ERROR_NOT_INITIALIZED
);
176 NS_ADDREF(*aResult
= mWorkingDir
);
181 nsCommandLine::ResolveFile(const nsAString
& aArgument
, nsIFile
** aResult
) {
182 // First try to resolve as an absolute path if we can.
183 // This will work even if we have no mWorkingDir, which happens if e.g.
184 // the dir from which we were started was deleted before we started,
186 if (aArgument
.First() == '/') {
187 nsCOMPtr
<nsIFile
> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
188 NS_ENSURE_TRUE(lf
, NS_ERROR_OUT_OF_MEMORY
);
189 nsresult rv
= lf
->InitWithPath(aArgument
);
197 #elif defined(XP_WIN)
198 nsCOMPtr
<nsIFile
> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
199 NS_ENSURE_TRUE(lf
, NS_ERROR_OUT_OF_MEMORY
);
201 // Just try creating the file with the absolute path; if it fails,
202 // we'll keep going and try it as a relative path.
203 if (NS_SUCCEEDED(lf
->InitWithPath(aArgument
))) {
209 return ResolveRelativeFile(aArgument
, aResult
);
212 nsresult
nsCommandLine::ResolveRelativeFile(const nsAString
& aArgument
,
219 // This is some seriously screwed-up code. nsIFile.appendRelativeNativePath
220 // explicitly does not accept .. or . path parts, but that is exactly what we
221 // need here. So we hack around it.
225 #if defined(MOZ_WIDGET_COCOA)
226 nsCOMPtr
<nsILocalFileMac
> lfm(do_QueryInterface(mWorkingDir
));
227 NS_ENSURE_TRUE(lfm
, NS_ERROR_NO_INTERFACE
);
229 nsCOMPtr
<nsILocalFileMac
> newfile(
230 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
231 NS_ENSURE_TRUE(newfile
, NS_ERROR_OUT_OF_MEMORY
);
234 rv
= lfm
->GetCFURL(&baseurl
);
235 NS_ENSURE_SUCCESS(rv
, rv
);
238 NS_CopyUnicodeToNative(aArgument
, path
);
240 CFURLRef newurl
= CFURLCreateFromFileSystemRepresentationRelativeToBase(
241 nullptr, (const UInt8
*)path
.get(), path
.Length(), true, baseurl
);
245 rv
= newfile
->InitWithCFURL(newurl
);
247 if (NS_FAILED(rv
)) return rv
;
249 newfile
.forget(aResult
);
252 #elif defined(XP_UNIX)
253 nsCOMPtr
<nsIFile
> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
254 NS_ENSURE_TRUE(lf
, NS_ERROR_OUT_OF_MEMORY
);
256 nsAutoCString nativeArg
;
257 NS_CopyUnicodeToNative(aArgument
, nativeArg
);
259 nsAutoCString newpath
;
260 mWorkingDir
->GetNativePath(newpath
);
263 newpath
.Append(nativeArg
);
265 rv
= lf
->InitWithNativePath(newpath
);
266 if (NS_FAILED(rv
)) return rv
;
268 rv
= lf
->Normalize();
269 if (NS_FAILED(rv
)) return rv
;
274 #elif defined(XP_WIN)
275 nsCOMPtr
<nsIFile
> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
276 NS_ENSURE_TRUE(lf
, NS_ERROR_OUT_OF_MEMORY
);
278 // This is a relative path. We use string magic
279 // and win32 _fullpath. Note that paths of the form "\Relative\To\CurDrive"
280 // are going to fail, and I haven't figured out a way to work around this
281 // without the PathCombine() function, which is not available before
282 // Windows 8; see https://bugzilla.mozilla.org/show_bug.cgi?id=1672814
284 nsAutoString fullPath
;
285 mWorkingDir
->GetPath(fullPath
);
287 fullPath
.Append('\\');
288 fullPath
.Append(aArgument
);
290 WCHAR pathBuf
[MAX_PATH
];
291 if (!_wfullpath(pathBuf
, fullPath
.get(), MAX_PATH
)) return NS_ERROR_FAILURE
;
293 rv
= lf
->InitWithPath(nsDependentString(pathBuf
));
294 if (NS_FAILED(rv
)) return rv
;
299 # error Need platform-specific logic here.
304 nsCommandLine::ResolveURI(const nsAString
& aArgument
, nsIURI
** aResult
) {
307 // First, we try to init the argument as an absolute file path. If this
308 // doesn't work, it is an absolute or relative URI.
310 nsCOMPtr
<nsIIOService
> io
= do_GetIOService();
311 NS_ENSURE_TRUE(io
, NS_ERROR_OUT_OF_MEMORY
);
313 nsCOMPtr
<nsIURI
> workingDirURI
;
315 io
->NewFileURI(mWorkingDir
, getter_AddRefs(workingDirURI
));
318 nsCOMPtr
<nsIFile
> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
));
319 rv
= lf
->InitWithPath(aArgument
);
320 if (NS_SUCCEEDED(rv
)) {
323 // Try to resolve the url for .url files.
324 rv
= resolveShortcutURL(lf
, url
);
325 if (NS_SUCCEEDED(rv
) && !url
.IsEmpty()) {
326 return io
->NewURI(url
, nullptr, workingDirURI
, aResult
);
329 return io
->NewFileURI(lf
, aResult
);
332 return io
->NewURI(NS_ConvertUTF16toUTF8(aArgument
), nullptr, workingDirURI
,
336 void nsCommandLine::appendArg(const char* arg
) {
337 #ifdef DEBUG_COMMANDLINE
338 printf("Adding XP arg: %s\n", arg
);
343 CopyUTF8toUTF16(nsDependentCString(arg
), warg
);
345 NS_CopyNativeToUnicode(nsDependentCString(arg
), warg
);
348 mArgs
.AppendElement(warg
);
351 nsresult
nsCommandLine::resolveShortcutURL(nsIFile
* aFile
, nsACString
& outURL
) {
352 nsCOMPtr
<nsIFileProtocolHandler
> fph
;
353 nsresult rv
= NS_GetFileProtocolHandler(getter_AddRefs(fph
));
354 if (NS_FAILED(rv
)) return rv
;
356 nsCOMPtr
<nsIURI
> uri
;
357 rv
= fph
->ReadURLFile(aFile
, getter_AddRefs(uri
));
358 if (NS_FAILED(rv
)) return rv
;
360 return uri
->GetSpec(outURL
);
364 nsCommandLine::Init(int32_t argc
, const char* const* argv
, nsIFile
* aWorkingDir
,
366 NS_ENSURE_ARG_MAX(aState
, 2);
370 mWorkingDir
= aWorkingDir
;
372 // skip argv[0], we don't want it
373 for (i
= 1; i
< argc
; ++i
) {
374 const char* curarg
= argv
[i
];
376 #ifdef DEBUG_COMMANDLINE
377 printf("Testing native arg %i: '%s'\n", i
, curarg
);
380 if (*curarg
== '/') {
381 char* dup
= strdup(curarg
);
382 if (!dup
) return NS_ERROR_OUT_OF_MEMORY
;
385 char* colon
= strchr(dup
, ':');
389 appendArg(colon
+ 1);
397 if (*curarg
== '-') {
398 if (*(curarg
+ 1) == '-') ++curarg
;
400 char* dup
= strdup(curarg
);
401 if (!dup
) return NS_ERROR_OUT_OF_MEMORY
;
403 char* eq
= strchr(dup
, '=');
423 template <typename
... T
>
424 static void LogConsoleMessage(const char16_t
* fmt
, T
... args
) {
426 nsTextFormatter::ssprintf(msg
, fmt
, args
...);
428 nsCOMPtr
<nsIConsoleService
> cs
=
429 do_GetService("@mozilla.org/consoleservice;1");
430 if (cs
) cs
->LogStringMessage(msg
.get());
433 nsresult
nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback
,
437 nsCOMPtr
<nsICategoryManager
> catman(
438 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
));
439 NS_ENSURE_TRUE(catman
, NS_ERROR_UNEXPECTED
);
441 nsCOMPtr
<nsISimpleEnumerator
> entenum
;
442 rv
= catman
->EnumerateCategory("command-line-handler",
443 getter_AddRefs(entenum
));
444 NS_ENSURE_SUCCESS(rv
, rv
);
446 for (auto& categoryEntry
: SimpleEnumerator
<nsICategoryEntry
>(entenum
)) {
447 nsAutoCString contractID
;
448 categoryEntry
->GetValue(contractID
);
450 nsCOMPtr
<nsICommandLineHandler
> clh(do_GetService(contractID
.get()));
453 categoryEntry
->GetEntry(entry
);
456 u
"Contract ID '%s' was registered as a command line handler for "
457 u
"entry '%s', but could not be created.",
458 contractID
.get(), entry
.get());
462 rv
= (aCallback
)(clh
, this, aClosure
);
463 if (rv
== NS_ERROR_ABORT
) break;
471 nsresult
nsCommandLine::EnumerateValidators(
472 EnumerateValidatorsCallback aCallback
, void* aClosure
) {
475 nsCOMPtr
<nsICategoryManager
> catman(
476 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
));
477 NS_ENSURE_TRUE(catman
, NS_ERROR_UNEXPECTED
);
479 nsCOMPtr
<nsISimpleEnumerator
> entenum
;
480 rv
= catman
->EnumerateCategory("command-line-validator",
481 getter_AddRefs(entenum
));
482 NS_ENSURE_SUCCESS(rv
, rv
);
484 for (auto& categoryEntry
: SimpleEnumerator
<nsICategoryEntry
>(entenum
)) {
485 nsAutoCString contractID
;
486 categoryEntry
->GetValue(contractID
);
488 nsCOMPtr
<nsICommandLineValidator
> clv(do_GetService(contractID
.get()));
491 rv
= (aCallback
)(clv
, this, aClosure
);
492 if (rv
== NS_ERROR_ABORT
) break;
500 static nsresult
EnumValidate(nsICommandLineValidator
* aValidator
,
501 nsICommandLine
* aThis
, void*) {
502 return aValidator
->Validate(aThis
);
505 static nsresult
EnumRun(nsICommandLineHandler
* aHandler
, nsICommandLine
* aThis
,
507 return aHandler
->Handle(aThis
);
511 nsCommandLine::Run() {
514 rv
= EnumerateValidators(EnumValidate
, nullptr);
515 if (rv
== NS_ERROR_ABORT
) return rv
;
517 rv
= EnumerateHandlers(EnumRun
, nullptr);
518 if (rv
== NS_ERROR_ABORT
) return rv
;
523 static nsresult
EnumHelp(nsICommandLineHandler
* aHandler
, nsICommandLine
* aThis
,
528 rv
= aHandler
->GetHelpInfo(text
);
529 if (NS_SUCCEEDED(rv
)) {
531 text
.Length() == 0 || text
.Last() == '\n',
532 "Help text from command line handlers should end in a newline.");
534 nsACString
* totalText
= reinterpret_cast<nsACString
*>(aClosure
);
535 totalText
->Append(text
);
542 nsCommandLine::GetHelpText(nsACString
& aResult
) {
543 EnumerateHandlers(EnumHelp
, &aResult
);