Bug 1925425: Only consider -allow-content-analysis command line flag in nightly and...
[gecko.git] / toolkit / components / commandlines / nsCommandLine.cpp
blob68023089f44ee13f9bc11552a52364b74fd2ef9d
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"
13 #include "nsIFile.h"
14 #include "nsISimpleEnumerator.h"
15 #include "mozilla/SimpleEnumerator.h"
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"
25 #ifdef MOZ_WIDGET_COCOA
26 # include <CoreFoundation/CoreFoundation.h>
27 # include "nsILocalFileMac.h"
28 #elif defined(XP_WIN)
29 # include <windows.h>
30 # include <shlobj.h>
31 #endif
33 #ifdef DEBUG_bsmedberg
34 # define DEBUG_COMMANDLINE
35 #endif
37 #define NS_COMMANDLINE_CID \
38 { \
39 0x23bcc750, 0xdc20, 0x460b, { \
40 0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 \
41 } \
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)
52 NS_IMETHODIMP
53 nsCommandLine::GetLength(int32_t* aResult) {
54 *aResult = int32_t(mArgs.Length());
55 return NS_OK;
58 NS_IMETHODIMP
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];
64 return NS_OK;
67 NS_IMETHODIMP
68 nsCommandLine::FindFlag(const nsAString& aFlag, bool aCaseSensitive,
69 int32_t* aResult) {
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)) {
80 *aResult = f;
81 return NS_OK;
86 *aResult = -1;
87 return NS_OK;
90 NS_IMETHODIMP
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);
97 return NS_OK;
100 NS_IMETHODIMP
101 nsCommandLine::HandleFlag(const nsAString& aFlag, bool aCaseSensitive,
102 bool* aResult) {
103 nsresult rv;
105 int32_t found;
106 rv = FindFlag(aFlag, aCaseSensitive, &found);
107 NS_ENSURE_SUCCESS(rv, rv);
109 if (found == -1) {
110 *aResult = false;
111 return NS_OK;
114 *aResult = true;
115 RemoveArguments(found, found);
117 return NS_OK;
120 NS_IMETHODIMP
121 nsCommandLine::HandleFlagWithParam(const nsAString& aFlag, bool aCaseSensitive,
122 nsAString& aResult) {
123 nsresult rv;
125 int32_t found;
126 rv = FindFlag(aFlag, aCaseSensitive, &found);
127 NS_ENSURE_SUCCESS(rv, rv);
129 if (found == -1) {
130 aResult.SetIsVoid(true);
131 return NS_OK;
134 if (found == int32_t(mArgs.Length()) - 1) {
135 return NS_ERROR_INVALID_ARG;
138 ++found;
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;
146 aResult = param;
149 RemoveArguments(found - 1, found);
151 return NS_OK;
154 NS_IMETHODIMP
155 nsCommandLine::GetState(uint32_t* aResult) {
156 *aResult = mState;
157 return NS_OK;
160 NS_IMETHODIMP
161 nsCommandLine::GetPreventDefault(bool* aResult) {
162 *aResult = mPreventDefault;
163 return NS_OK;
166 NS_IMETHODIMP
167 nsCommandLine::SetPreventDefault(bool aValue) {
168 mPreventDefault = aValue;
169 return NS_OK;
172 NS_IMETHODIMP
173 nsCommandLine::GetWorkingDirectory(nsIFile** aResult) {
174 NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
176 NS_ADDREF(*aResult = mWorkingDir);
177 return NS_OK;
180 NS_IMETHODIMP
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,
185 #if defined(XP_UNIX)
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);
190 if (NS_FAILED(rv)) {
191 return rv;
194 lf.forget(aResult);
195 return NS_OK;
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))) {
204 lf.forget(aResult);
205 return NS_OK;
207 #endif
208 // If that fails
209 return ResolveRelativeFile(aArgument, aResult);
212 nsresult nsCommandLine::ResolveRelativeFile(const nsAString& aArgument,
213 nsIFile** aResult) {
214 if (!mWorkingDir) {
215 *aResult = nullptr;
216 return NS_OK;
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.
223 nsresult rv;
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);
233 CFURLRef baseurl;
234 rv = lfm->GetCFURL(&baseurl);
235 NS_ENSURE_SUCCESS(rv, rv);
237 nsAutoCString path;
238 NS_CopyUnicodeToNative(aArgument, path);
240 CFURLRef newurl = CFURLCreateFromFileSystemRepresentationRelativeToBase(
241 nullptr, (const UInt8*)path.get(), path.Length(), true, baseurl);
243 CFRelease(baseurl);
245 rv = newfile->InitWithCFURL(newurl);
246 CFRelease(newurl);
247 if (NS_FAILED(rv)) return rv;
249 newfile.forget(aResult);
250 return NS_OK;
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);
262 newpath.Append('/');
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;
271 lf.forget(aResult);
272 return NS_OK;
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;
295 lf.forget(aResult);
296 return NS_OK;
298 #else
299 # error Need platform-specific logic here.
300 #endif
303 NS_IMETHODIMP
304 nsCommandLine::ResolveURI(const nsAString& aArgument, nsIURI** aResult) {
305 nsresult rv;
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;
314 if (mWorkingDir) {
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)) {
321 lf->Normalize();
322 nsAutoCString url;
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,
333 aResult);
336 void nsCommandLine::appendArg(const char* arg) {
337 #ifdef DEBUG_COMMANDLINE
338 printf("Adding XP arg: %s\n", arg);
339 #endif
341 nsAutoString warg;
342 #ifdef XP_WIN
343 CopyUTF8toUTF16(nsDependentCString(arg), warg);
344 #else
345 NS_CopyNativeToUnicode(nsDependentCString(arg), warg);
346 #endif
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);
363 NS_IMETHODIMP
364 nsCommandLine::Init(int32_t argc, const char* const* argv, nsIFile* aWorkingDir,
365 uint32_t aState) {
366 NS_ENSURE_ARG_MAX(aState, 2);
368 int32_t i;
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);
378 #endif
379 #if defined(XP_WIN)
380 if (*curarg == '/') {
381 char* dup = strdup(curarg);
382 if (!dup) return NS_ERROR_OUT_OF_MEMORY;
384 *dup = '-';
385 char* colon = strchr(dup, ':');
386 if (colon) {
387 *colon = '\0';
388 appendArg(dup);
389 appendArg(colon + 1);
390 } else {
391 appendArg(dup);
393 free(dup);
394 continue;
396 #endif
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, '=');
404 if (eq) {
405 *eq = '\0';
406 appendArg(dup);
407 appendArg(eq + 1);
408 } else {
409 appendArg(dup);
411 free(dup);
412 continue;
415 appendArg(curarg);
418 mState = aState;
420 return NS_OK;
423 template <typename... T>
424 static void LogConsoleMessage(const char16_t* fmt, T... args) {
425 nsString msg;
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,
434 void* aClosure) {
435 nsresult rv;
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()));
451 if (!clh) {
452 nsCString entry;
453 categoryEntry->GetEntry(entry);
455 LogConsoleMessage(
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());
459 continue;
462 rv = (aCallback)(clh, this, aClosure);
463 if (rv == NS_ERROR_ABORT) break;
465 rv = NS_OK;
468 return rv;
471 nsresult nsCommandLine::EnumerateValidators(
472 EnumerateValidatorsCallback aCallback, void* aClosure) {
473 nsresult rv;
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()));
489 if (!clv) continue;
491 rv = (aCallback)(clv, this, aClosure);
492 if (rv == NS_ERROR_ABORT) break;
494 rv = NS_OK;
497 return rv;
500 static nsresult EnumValidate(nsICommandLineValidator* aValidator,
501 nsICommandLine* aThis, void*) {
502 return aValidator->Validate(aThis);
505 static nsresult EnumRun(nsICommandLineHandler* aHandler, nsICommandLine* aThis,
506 void*) {
507 return aHandler->Handle(aThis);
510 NS_IMETHODIMP
511 nsCommandLine::Run() {
512 nsresult rv;
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;
520 return NS_OK;
523 static nsresult EnumHelp(nsICommandLineHandler* aHandler, nsICommandLine* aThis,
524 void* aClosure) {
525 nsresult rv;
527 nsCString text;
528 rv = aHandler->GetHelpInfo(text);
529 if (NS_SUCCEEDED(rv)) {
530 NS_ASSERTION(
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);
538 return NS_OK;
541 NS_IMETHODIMP
542 nsCommandLine::GetHelpText(nsACString& aResult) {
543 EnumerateHandlers(EnumHelp, &aResult);
545 return NS_OK;