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