Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/commandlines/nsCommandLine.cpp
Line
Count
Source (jump to first uncovered line)
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 "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 "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
  { 0x23bcc750, 0xdc20, 0x460b, { 0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 } }
43
44
using mozilla::SimpleEnumerator;
45
46
nsCommandLine::nsCommandLine() :
47
  mState(STATE_INITIAL_LAUNCH),
48
  mPreventDefault(false)
49
0
{
50
0
51
0
}
52
53
54
NS_IMPL_CLASSINFO(nsCommandLine, nullptr, 0, NS_COMMANDLINE_CID)
55
NS_IMPL_ISUPPORTS_CI(nsCommandLine,
56
                     nsICommandLine,
57
                     nsICommandLineRunner)
58
59
NS_IMETHODIMP
60
nsCommandLine::GetLength(int32_t *aResult)
61
0
{
62
0
  *aResult = int32_t(mArgs.Length());
63
0
  return NS_OK;
64
0
}
65
66
NS_IMETHODIMP
67
nsCommandLine::GetArgument(int32_t aIndex, nsAString& aResult)
68
0
{
69
0
  NS_ENSURE_ARG_MIN(aIndex, 0);
70
0
  NS_ENSURE_ARG_MAX(aIndex, int32_t(mArgs.Length() - 1));
71
0
72
0
  aResult = mArgs[aIndex];
73
0
  return NS_OK;
74
0
}
75
76
NS_IMETHODIMP
77
nsCommandLine::FindFlag(const nsAString& aFlag, bool aCaseSensitive, int32_t *aResult)
78
0
{
79
0
  NS_ENSURE_ARG(!aFlag.IsEmpty());
80
0
81
0
  nsDefaultStringComparator caseCmp;
82
0
  nsCaseInsensitiveStringComparator caseICmp;
83
0
  nsStringComparator& c = aCaseSensitive ?
84
0
    static_cast<nsStringComparator&>(caseCmp) :
85
0
    static_cast<nsStringComparator&>(caseICmp);
86
0
87
0
  for (uint32_t f = 0; f < mArgs.Length(); f++) {
88
0
    const nsString &arg = mArgs[f];
89
0
90
0
    if (arg.Length() >= 2 && arg.First() == char16_t('-')) {
91
0
      if (aFlag.Equals(Substring(arg, 1), c)) {
92
0
        *aResult = f;
93
0
        return NS_OK;
94
0
      }
95
0
    }
96
0
  }
97
0
98
0
  *aResult = -1;
99
0
  return NS_OK;
100
0
}
101
102
NS_IMETHODIMP
103
nsCommandLine::RemoveArguments(int32_t aStart, int32_t aEnd)
104
0
{
105
0
  NS_ENSURE_ARG_MIN(aStart, 0);
106
0
  NS_ENSURE_ARG_MAX(uint32_t(aEnd) + 1, mArgs.Length());
107
0
108
0
  for (int32_t i = aEnd; i >= aStart; --i) {
109
0
    mArgs.RemoveElementAt(i);
110
0
  }
111
0
112
0
  return NS_OK;
113
0
}
114
115
NS_IMETHODIMP
116
nsCommandLine::HandleFlag(const nsAString& aFlag, bool aCaseSensitive,
117
                          bool *aResult)
118
0
{
119
0
  nsresult rv;
120
0
121
0
  int32_t found;
122
0
  rv = FindFlag(aFlag, aCaseSensitive, &found);
123
0
  NS_ENSURE_SUCCESS(rv, rv);
124
0
125
0
  if (found == -1) {
126
0
    *aResult = false;
127
0
    return NS_OK;
128
0
  }
129
0
130
0
  *aResult = true;
131
0
  RemoveArguments(found, found);
132
0
133
0
  return NS_OK;
134
0
}
135
136
NS_IMETHODIMP
137
nsCommandLine::HandleFlagWithParam(const nsAString& aFlag, bool aCaseSensitive,
138
                                   nsAString& aResult)
139
0
{
140
0
  nsresult rv;
141
0
142
0
  int32_t found;
143
0
  rv = FindFlag(aFlag, aCaseSensitive, &found);
144
0
  NS_ENSURE_SUCCESS(rv, rv);
145
0
146
0
  if (found == -1) {
147
0
    aResult.SetIsVoid(true);
148
0
    return NS_OK;
149
0
  }
150
0
151
0
  if (found == int32_t(mArgs.Length()) - 1) {
152
0
    return NS_ERROR_INVALID_ARG;
153
0
  }
154
0
155
0
  ++found;
156
0
157
0
  { // scope for validity of |param|, which RemoveArguments call invalidates
158
0
    const nsString& param = mArgs[found];
159
0
    if (!param.IsEmpty() && param.First() == '-') {
160
0
      return NS_ERROR_INVALID_ARG;
161
0
    }
162
0
163
0
    aResult = param;
164
0
  }
165
0
166
0
  RemoveArguments(found - 1, found);
167
0
168
0
  return NS_OK;
169
0
}
170
171
NS_IMETHODIMP
172
nsCommandLine::GetState(uint32_t *aResult)
173
0
{
174
0
  *aResult = mState;
175
0
  return NS_OK;
176
0
}
177
178
NS_IMETHODIMP
179
nsCommandLine::GetPreventDefault(bool *aResult)
180
0
{
181
0
  *aResult = mPreventDefault;
182
0
  return NS_OK;
183
0
}
184
185
NS_IMETHODIMP
186
nsCommandLine::SetPreventDefault(bool aValue)
187
0
{
188
0
  mPreventDefault = aValue;
189
0
  return NS_OK;
190
0
}
191
192
NS_IMETHODIMP
193
nsCommandLine::GetWorkingDirectory(nsIFile* *aResult)
194
0
{
195
0
  NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
196
0
197
0
  NS_ADDREF(*aResult = mWorkingDir);
198
0
  return NS_OK;
199
0
}
200
201
NS_IMETHODIMP
202
nsCommandLine::GetWindowContext(nsIDOMWindow* *aResult)
203
0
{
204
0
  NS_IF_ADDREF(*aResult = mWindowContext);
205
0
  return NS_OK;
206
0
}
207
208
NS_IMETHODIMP
209
nsCommandLine::SetWindowContext(nsIDOMWindow* aValue)
210
0
{
211
0
  mWindowContext = aValue;
212
0
  return NS_OK;
213
0
}
214
215
NS_IMETHODIMP
216
nsCommandLine::ResolveFile(const nsAString& aArgument, nsIFile* *aResult)
217
0
{
218
0
  NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
219
0
220
0
  // This is some seriously screwed-up code. nsIFile.appendRelativeNativePath
221
0
  // explicitly does not accept .. or . path parts, but that is exactly what we
222
0
  // need here. So we hack around it.
223
0
224
0
  nsresult rv;
225
0
226
#if defined(MOZ_WIDGET_COCOA)
227
  nsCOMPtr<nsILocalFileMac> lfm (do_QueryInterface(mWorkingDir));
228
  NS_ENSURE_TRUE(lfm, NS_ERROR_NO_INTERFACE);
229
230
  nsCOMPtr<nsILocalFileMac> newfile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
231
  NS_ENSURE_TRUE(newfile, NS_ERROR_OUT_OF_MEMORY);
232
233
  CFURLRef baseurl;
234
  rv = lfm->GetCFURL(&baseurl);
235
  NS_ENSURE_SUCCESS(rv, rv);
236
237
  nsAutoCString path;
238
  NS_CopyUnicodeToNative(aArgument, path);
239
240
  CFURLRef newurl =
241
    CFURLCreateFromFileSystemRepresentationRelativeToBase(nullptr, (const UInt8*) path.get(),
242
                                                          path.Length(),
243
                                                          true, baseurl);
244
245
  CFRelease(baseurl);
246
247
  rv = newfile->InitWithCFURL(newurl);
248
  CFRelease(newurl);
249
  if (NS_FAILED(rv)) return rv;
250
251
  newfile.forget(aResult);
252
  return NS_OK;
253
254
#elif defined(XP_UNIX)
255
0
  nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
256
0
  NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
257
0
258
0
  if (aArgument.First() == '/') {
259
0
    // absolute path
260
0
    rv = lf->InitWithPath(aArgument);
261
0
    if (NS_FAILED(rv)) return rv;
262
0
263
0
    NS_ADDREF(*aResult = lf);
264
0
    return NS_OK;
265
0
  }
266
0
267
0
  nsAutoCString nativeArg;
268
0
  NS_CopyUnicodeToNative(aArgument, nativeArg);
269
0
270
0
  nsAutoCString newpath;
271
0
  mWorkingDir->GetNativePath(newpath);
272
0
273
0
  newpath.Append('/');
274
0
  newpath.Append(nativeArg);
275
0
276
0
  rv = lf->InitWithNativePath(newpath);
277
0
  if (NS_FAILED(rv)) return rv;
278
0
279
0
  rv = lf->Normalize();
280
0
  if (NS_FAILED(rv)) return rv;
281
0
282
0
  lf.forget(aResult);
283
0
  return NS_OK;
284
0
285
#elif defined(XP_WIN32)
286
  nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
287
  NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
288
289
  rv = lf->InitWithPath(aArgument);
290
  if (NS_FAILED(rv)) {
291
    // If it's a relative path, the Init is *going* to fail. We use string magic and
292
    // win32 _fullpath. Note that paths of the form "\Relative\To\CurDrive" are
293
    // going to fail, and I haven't figured out a way to work around this without
294
    // the PathCombine() function, which is not available in plain win95/nt4
295
296
    nsAutoString fullPath;
297
    mWorkingDir->GetPath(fullPath);
298
299
    fullPath.Append('\\');
300
    fullPath.Append(aArgument);
301
302
    WCHAR pathBuf[MAX_PATH];
303
    if (!_wfullpath(pathBuf, fullPath.get(), MAX_PATH))
304
      return NS_ERROR_FAILURE;
305
306
    rv = lf->InitWithPath(nsDependentString(pathBuf));
307
    if (NS_FAILED(rv)) return rv;
308
  }
309
  lf.forget(aResult);
310
  return NS_OK;
311
312
#else
313
#error Need platform-specific logic here.
314
#endif
315
}
316
317
NS_IMETHODIMP
318
nsCommandLine::ResolveURI(const nsAString& aArgument, nsIURI* *aResult)
319
0
{
320
0
  nsresult rv;
321
0
322
0
  // First, we try to init the argument as an absolute file path. If this doesn't
323
0
  // work, it is an absolute or relative URI.
324
0
325
0
  nsCOMPtr<nsIIOService> io = do_GetIOService();
326
0
  NS_ENSURE_TRUE(io, NS_ERROR_OUT_OF_MEMORY);
327
0
328
0
  nsCOMPtr<nsIURI> workingDirURI;
329
0
  if (mWorkingDir) {
330
0
    io->NewFileURI(mWorkingDir, getter_AddRefs(workingDirURI));
331
0
  }
332
0
333
0
  nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
334
0
  rv = lf->InitWithPath(aArgument);
335
0
  if (NS_SUCCEEDED(rv)) {
336
0
    lf->Normalize();
337
0
    nsAutoCString url;
338
0
    // Try to resolve the url for .url files.
339
0
    rv = resolveShortcutURL(lf, url);
340
0
    if (NS_SUCCEEDED(rv) && !url.IsEmpty()) {
341
0
      return io->NewURI(url,
342
0
                        nullptr,
343
0
                        workingDirURI,
344
0
                        aResult);
345
0
    }
346
0
347
0
    return io->NewFileURI(lf, aResult);
348
0
  }
349
0
350
0
  return io->NewURI(NS_ConvertUTF16toUTF8(aArgument),
351
0
                    nullptr,
352
0
                    workingDirURI,
353
0
                    aResult);
354
0
}
355
356
void
357
nsCommandLine::appendArg(const char* arg)
358
0
{
359
#ifdef DEBUG_COMMANDLINE
360
  printf("Adding XP arg: %s\n", arg);
361
#endif
362
363
0
  nsAutoString warg;
364
#ifdef XP_WIN
365
  CopyUTF8toUTF16(nsDependentCString(arg), warg);
366
#else
367
  NS_CopyNativeToUnicode(nsDependentCString(arg), warg);
368
0
#endif
369
0
370
0
  mArgs.AppendElement(warg);
371
0
}
372
373
nsresult
374
nsCommandLine::resolveShortcutURL(nsIFile* aFile, nsACString& outURL)
375
0
{
376
0
  nsCOMPtr<nsIFileProtocolHandler> fph;
377
0
  nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
378
0
  if (NS_FAILED(rv))
379
0
    return rv;
380
0
381
0
  nsCOMPtr<nsIURI> uri;
382
0
  rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
383
0
  if (NS_FAILED(rv))
384
0
    return rv;
385
0
386
0
  return uri->GetSpec(outURL);
387
0
}
388
389
NS_IMETHODIMP
390
nsCommandLine::Init(int32_t argc, const char* const* argv, nsIFile* aWorkingDir,
391
                    uint32_t aState)
392
0
{
393
0
  NS_ENSURE_ARG_MAX(aState, 2);
394
0
395
0
  int32_t i;
396
0
397
0
  mWorkingDir = aWorkingDir;
398
0
399
0
  // skip argv[0], we don't want it
400
0
  for (i = 1; i < argc; ++i) {
401
0
    const char* curarg = argv[i];
402
0
403
#ifdef DEBUG_COMMANDLINE
404
    printf("Testing native arg %i: '%s'\n", i, curarg);
405
#endif
406
#if defined(XP_WIN)
407
    if (*curarg == '/') {
408
      char* dup = PL_strdup(curarg);
409
      if (!dup) return NS_ERROR_OUT_OF_MEMORY;
410
411
      *dup = '-';
412
      char* colon = PL_strchr(dup, ':');
413
      if (colon) {
414
        *colon = '\0';
415
        appendArg(dup);
416
        appendArg(colon+1);
417
      } else {
418
        appendArg(dup);
419
      }
420
      PL_strfree(dup);
421
      continue;
422
    }
423
#endif
424
0
    if (*curarg == '-') {
425
0
      if (*(curarg+1) == '-') ++curarg;
426
0
427
0
      char* dup = PL_strdup(curarg);
428
0
      if (!dup) return NS_ERROR_OUT_OF_MEMORY;
429
0
430
0
      char* eq = PL_strchr(dup, '=');
431
0
      if (eq) {
432
0
        *eq = '\0';
433
0
        appendArg(dup);
434
0
        appendArg(eq + 1);
435
0
      } else {
436
0
        appendArg(dup);
437
0
      }
438
0
      PL_strfree(dup);
439
0
      continue;
440
0
    }
441
0
442
0
    appendArg(curarg);
443
0
  }
444
0
445
0
  mState = aState;
446
0
447
0
  return NS_OK;
448
0
}
449
450
template<typename ...T>
451
static void
452
LogConsoleMessage(const char16_t* fmt, T... args)
453
0
{
454
0
  nsString msg;
455
0
  nsTextFormatter::ssprintf(msg, fmt, args...);
456
0
457
0
  nsCOMPtr<nsIConsoleService> cs = do_GetService("@mozilla.org/consoleservice;1");
458
0
  if (cs)
459
0
    cs->LogStringMessage(msg.get());
460
0
}
461
462
nsresult
463
nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback, void *aClosure)
464
0
{
465
0
  nsresult rv;
466
0
467
0
  nsCOMPtr<nsICategoryManager> catman
468
0
    (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
469
0
  NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
470
0
471
0
  nsCOMPtr<nsISimpleEnumerator> entenum;
472
0
  rv = catman->EnumerateCategory("command-line-handler",
473
0
                                 getter_AddRefs(entenum));
474
0
  NS_ENSURE_SUCCESS(rv, rv);
475
0
476
0
  for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) {
477
0
    nsAutoCString contractID;
478
0
    categoryEntry->GetValue(contractID);
479
0
480
0
    nsCOMPtr<nsICommandLineHandler> clh(do_GetService(contractID.get()));
481
0
    if (!clh) {
482
0
      nsCString entry;
483
0
      categoryEntry->GetEntry(entry);
484
0
485
0
      LogConsoleMessage(u"Contract ID '%s' was registered as a command line handler for entry '%s', but could not be created.",
486
0
                        contractID.get(), entry.get());
487
0
      continue;
488
0
    }
489
0
490
0
    rv = (aCallback)(clh, this, aClosure);
491
0
    if (rv == NS_ERROR_ABORT)
492
0
      break;
493
0
494
0
    rv = NS_OK;
495
0
  }
496
0
497
0
  return rv;
498
0
}
499
500
nsresult
501
nsCommandLine::EnumerateValidators(EnumerateValidatorsCallback aCallback, void *aClosure)
502
0
{
503
0
  nsresult rv;
504
0
505
0
  nsCOMPtr<nsICategoryManager> catman
506
0
    (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
507
0
  NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
508
0
509
0
  nsCOMPtr<nsISimpleEnumerator> entenum;
510
0
  rv = catman->EnumerateCategory("command-line-validator",
511
0
                                 getter_AddRefs(entenum));
512
0
  NS_ENSURE_SUCCESS(rv, rv);
513
0
514
0
  for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) {
515
0
    nsAutoCString contractID;
516
0
    categoryEntry->GetValue(contractID);
517
0
518
0
    nsCOMPtr<nsICommandLineValidator> clv(do_GetService(contractID.get()));
519
0
    if (!clv)
520
0
      continue;
521
0
522
0
    rv = (aCallback)(clv, this, aClosure);
523
0
    if (rv == NS_ERROR_ABORT)
524
0
      break;
525
0
526
0
    rv = NS_OK;
527
0
  }
528
0
529
0
  return rv;
530
0
}
531
532
static nsresult
533
EnumValidate(nsICommandLineValidator* aValidator, nsICommandLine* aThis, void*)
534
0
{
535
0
  return aValidator->Validate(aThis);
536
0
}
537
538
static nsresult
539
EnumRun(nsICommandLineHandler* aHandler, nsICommandLine* aThis, void*)
540
0
{
541
0
  return aHandler->Handle(aThis);
542
0
}
543
544
NS_IMETHODIMP
545
nsCommandLine::Run()
546
0
{
547
0
  nsresult rv;
548
0
549
0
  rv = EnumerateValidators(EnumValidate, nullptr);
550
0
  if (rv == NS_ERROR_ABORT)
551
0
    return rv;
552
0
553
0
  rv = EnumerateHandlers(EnumRun, nullptr);
554
0
  if (rv == NS_ERROR_ABORT)
555
0
    return rv;
556
0
557
0
  return NS_OK;
558
0
}
559
560
static nsresult
561
EnumHelp(nsICommandLineHandler* aHandler, nsICommandLine* aThis, void* aClosure)
562
0
{
563
0
  nsresult rv;
564
0
565
0
  nsCString text;
566
0
  rv = aHandler->GetHelpInfo(text);
567
0
  if (NS_SUCCEEDED(rv)) {
568
0
    NS_ASSERTION(text.Length() == 0 || text.Last() == '\n',
569
0
                 "Help text from command line handlers should end in a newline.");
570
0
571
0
    nsACString* totalText = reinterpret_cast<nsACString*>(aClosure);
572
0
    totalText->Append(text);
573
0
  }
574
0
575
0
  return NS_OK;
576
0
}
577
578
NS_IMETHODIMP
579
nsCommandLine::GetHelpText(nsACString& aResult)
580
0
{
581
0
  EnumerateHandlers(EnumHelp, &aResult);
582
0
583
0
  return NS_OK;
584
0
}