1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
9 #include "base/containers/contains.h"
10 #include "base/containers/span.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/notreached.h"
14 #include "base/ranges/algorithm.h"
15 #include "base/stl_util.h"
16 #include "base/strings/strcat.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_split.h"
19 #include "base/strings/string_tokenizer.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "build/build_config.h"
28 #include "base/strings/string_util_win.h"
29 #endif // defined(OS_WIN)
33 CommandLine* CommandLine::current_process_commandline_ = nullptr;
37 constexpr CommandLine::CharType kSwitchTerminator[] = FILE_PATH_LITERAL("--");
38 constexpr CommandLine::CharType kSwitchValueSeparator[] =
39 FILE_PATH_LITERAL("=");
41 // Since we use a lazy match, make sure that longer versions (like "--") are
42 // listed before shorter versions (like "-") of similar prefixes.
44 // By putting slash last, we can control whether it is treaded as a switch
45 // value by changing the value of switch_prefix_count to be one less than
47 constexpr CommandLine::StringPieceType kSwitchPrefixes[] = {L"--", L"-", L"/"};
48 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
49 // Unixes don't use slash as a switch.
50 constexpr CommandLine::StringPieceType kSwitchPrefixes[] = {"--", "-"};
52 size_t switch_prefix_count = base::size(kSwitchPrefixes);
55 // Switch string that specifies the single argument to the command line.
56 // If present, everything after this switch is interpreted as a single
57 // argument regardless of whitespace, quotes, etc. Used for launches from the
58 // Windows shell, which may have arguments with unencoded quotes that could
59 // otherwise unexpectedly be split into multiple arguments
60 // (https://crbug.com/937179).
61 constexpr CommandLine::CharType kSingleArgument[] =
62 FILE_PATH_LITERAL("single-argument");
63 #endif // defined(OS_WIN)
65 size_t GetSwitchPrefixLength(CommandLine::StringPieceType string) {
66 for (size_t i = 0; i < switch_prefix_count; ++i) {
67 CommandLine::StringType prefix(kSwitchPrefixes[i]);
68 if (string.substr(0, prefix.length()) == prefix)
69 return prefix.length();
74 // Fills in |switch_string| and |switch_value| if |string| is a switch.
75 // This will preserve the input switch prefix in the output |switch_string|.
76 bool IsSwitch(const CommandLine::StringType& string,
77 CommandLine::StringType* switch_string,
78 CommandLine::StringType* switch_value) {
79 switch_string->clear();
80 switch_value->clear();
81 size_t prefix_length = GetSwitchPrefixLength(string);
82 if (prefix_length == 0 || prefix_length == string.length())
85 const size_t equals_position = string.find(kSwitchValueSeparator);
86 *switch_string = string.substr(0, equals_position);
87 if (equals_position != CommandLine::StringType::npos)
88 *switch_value = string.substr(equals_position + 1);
92 // Returns true iff |string| represents a switch with key
93 // |switch_key_without_prefix|, regardless of value.
94 bool IsSwitchWithKey(CommandLine::StringPieceType string,
95 CommandLine::StringPieceType switch_key_without_prefix) {
96 size_t prefix_length = GetSwitchPrefixLength(string);
97 if (prefix_length == 0 || prefix_length == string.length())
100 const size_t equals_position = string.find(kSwitchValueSeparator);
101 return string.substr(prefix_length, equals_position - prefix_length) ==
102 switch_key_without_prefix;
106 // Quote a string as necessary for CommandLineToArgvW compatibility *on
108 std::wstring QuoteForCommandLineToArgvW(const std::wstring& arg,
109 bool allow_unsafe_insert_sequences) {
110 // Ensure that GetCommandLineString isn't used to generate command-line
111 // strings for the Windows shell by checking for Windows insert sequences like
112 // "%1". GetCommandLineStringForShell should be used instead to get a string
113 // with the correct placeholder format for the shell.
114 DCHECK(arg.size() != 2 || arg[0] != L'%' || allow_unsafe_insert_sequences);
116 // We follow the quoting rules of CommandLineToArgvW.
117 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
118 std::wstring quotable_chars(L" \\\"");
119 if (arg.find_first_of(quotable_chars) == std::wstring::npos) {
120 // No quoting necessary.
126 for (size_t i = 0; i < arg.size(); ++i) {
127 if (arg[i] == '\\') {
128 // Find the extent of this run of backslashes.
129 size_t start = i, end = start + 1;
130 for (; end < arg.size() && arg[end] == '\\'; ++end) {}
131 size_t backslash_count = end - start;
133 // Backslashes are escapes only if the run is followed by a double quote.
134 // Since we also will end the string with a double quote, we escape for
135 // either a double quote or the end of the string.
136 if (end == arg.size() || arg[end] == '"') {
137 // To quote, we need to output 2x as many backslashes.
138 backslash_count *= 2;
140 for (size_t j = 0; j < backslash_count; ++j)
143 // Advance i to one before the end to balance i++ in loop.
145 } else if (arg[i] == '"') {
149 out.push_back(arg[i]);
156 #endif // defined(OS_WIN)
160 CommandLine::CommandLine(NoProgram no_program)
165 CommandLine::CommandLine(const FilePath& program)
171 CommandLine::CommandLine(int argc, const CommandLine::CharType* const* argv)
174 InitFromArgv(argc, argv);
177 CommandLine::CommandLine(const StringVector& argv)
183 CommandLine::CommandLine(const CommandLine& other) = default;
185 CommandLine& CommandLine::operator=(const CommandLine& other) = default;
187 CommandLine::~CommandLine() = default;
191 void CommandLine::set_slash_is_not_a_switch() {
192 // The last switch prefix should be slash, so adjust the size to skip it.
193 static_assert(base::make_span(kSwitchPrefixes).back() == L"/",
194 "Error: Last switch prefix is not a slash.");
195 switch_prefix_count = base::size(kSwitchPrefixes) - 1;
199 void CommandLine::InitUsingArgvForTesting(int argc, const char* const* argv) {
200 DCHECK(!current_process_commandline_);
201 current_process_commandline_ = new CommandLine(NO_PROGRAM);
202 // On Windows we need to convert the command line arguments to std::wstring.
203 CommandLine::StringVector argv_vector;
204 for (int i = 0; i < argc; ++i)
205 argv_vector.push_back(UTF8ToWide(argv[i]));
206 current_process_commandline_->InitFromArgv(argv_vector);
208 #endif // defined(OS_WIN)
211 bool CommandLine::Init(int argc, const char* const* argv) {
212 if (current_process_commandline_) {
213 // If this is intentional, Reset() must be called first. If we are using
214 // the shared build mode, we have to share a single object across multiple
219 current_process_commandline_ = new CommandLine(NO_PROGRAM);
221 current_process_commandline_->ParseFromString(::GetCommandLineW());
222 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
223 current_process_commandline_->InitFromArgv(argc, argv);
225 #error Unsupported platform
232 void CommandLine::Reset() {
233 DCHECK(current_process_commandline_);
234 delete current_process_commandline_;
235 current_process_commandline_ = nullptr;
239 CommandLine* CommandLine::ForCurrentProcess() {
240 DCHECK(current_process_commandline_);
241 return current_process_commandline_;
245 bool CommandLine::InitializedForCurrentProcess() {
246 return !!current_process_commandline_;
251 CommandLine CommandLine::FromString(StringPieceType command_line) {
252 CommandLine cmd(NO_PROGRAM);
253 cmd.ParseFromString(command_line);
256 #endif // defined(OS_WIN)
258 void CommandLine::InitFromArgv(int argc,
259 const CommandLine::CharType* const* argv) {
260 StringVector new_argv;
261 for (int i = 0; i < argc; ++i)
262 new_argv.push_back(argv[i]);
263 InitFromArgv(new_argv);
266 void CommandLine::InitFromArgv(const StringVector& argv) {
267 argv_ = StringVector(1);
270 SetProgram(argv.empty() ? FilePath() : FilePath(argv[0]));
271 AppendSwitchesAndArguments(argv);
274 FilePath CommandLine::GetProgram() const {
275 return FilePath(argv_[0]);
278 void CommandLine::SetProgram(const FilePath& program) {
280 argv_[0] = StringType(TrimWhitespace(program.value(), TRIM_ALL));
281 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
282 TrimWhitespaceASCII(program.value(), TRIM_ALL, &argv_[0]);
284 #error Unsupported platform
288 bool CommandLine::HasSwitch(StringPiece switch_string) const {
289 DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
290 return Contains(switches_, switch_string);
293 bool CommandLine::HasSwitch(const char switch_constant[]) const {
294 return HasSwitch(StringPiece(switch_constant));
297 std::string CommandLine::GetSwitchValueASCII(StringPiece switch_string) const {
298 StringType value = GetSwitchValueNative(switch_string);
300 if (!IsStringASCII(base::AsStringPiece16(value))) {
301 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
302 if (!IsStringASCII(value)) {
304 DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII.";
305 return std::string();
308 return WideToUTF8(value);
309 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
314 FilePath CommandLine::GetSwitchValuePath(StringPiece switch_string) const {
315 return FilePath(GetSwitchValueNative(switch_string));
318 CommandLine::StringType CommandLine::GetSwitchValueNative(
319 StringPiece switch_string) const {
320 DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
321 auto result = switches_.find(switch_string);
322 return result == switches_.end() ? StringType() : result->second;
325 void CommandLine::AppendSwitch(StringPiece switch_string) {
326 AppendSwitchNative(switch_string, StringType());
329 void CommandLine::AppendSwitchPath(StringPiece switch_string,
330 const FilePath& path) {
331 AppendSwitchNative(switch_string, path.value());
334 void CommandLine::AppendSwitchNative(StringPiece switch_string,
335 CommandLine::StringPieceType value) {
337 const std::string switch_key = ToLowerASCII(switch_string);
338 StringType combined_switch_string(UTF8ToWide(switch_key));
339 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
340 StringPiece switch_key = switch_string;
341 StringType combined_switch_string(switch_key);
343 size_t prefix_length = GetSwitchPrefixLength(combined_switch_string);
344 base::InsertOrAssign(switches_, std::string(switch_key.substr(prefix_length)),
346 // Preserve existing switch prefixes in |argv_|; only append one if necessary.
347 if (prefix_length == 0) {
348 combined_switch_string.insert(0, kSwitchPrefixes[0].data(),
349 kSwitchPrefixes[0].size());
352 base::StrAppend(&combined_switch_string, {kSwitchValueSeparator, value});
353 // Append the switch and update the switches/arguments divider |begin_args_|.
354 argv_.insert(argv_.begin() + begin_args_++, combined_switch_string);
357 void CommandLine::AppendSwitchASCII(StringPiece switch_string,
358 StringPiece value_string) {
360 AppendSwitchNative(switch_string, UTF8ToWide(value_string));
361 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
362 AppendSwitchNative(switch_string, value_string);
364 #error Unsupported platform
368 void CommandLine::RemoveSwitch(base::StringPiece switch_key_without_prefix) {
370 StringType switch_key_native = UTF8ToWide(switch_key_without_prefix);
371 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
372 StringType switch_key_native(switch_key_without_prefix);
375 DCHECK_EQ(ToLowerASCII(switch_key_without_prefix), switch_key_without_prefix);
376 DCHECK_EQ(0u, GetSwitchPrefixLength(switch_key_native));
377 auto it = switches_.find(switch_key_without_prefix);
378 if (it == switches_.end())
381 // Also erase from the switches section of |argv_| and update |begin_args_|
383 // Switches in |argv_| have indices [1, begin_args_).
384 auto argv_switches_begin = argv_.begin() + 1;
385 auto argv_switches_end = argv_.begin() + begin_args_;
386 DCHECK(argv_switches_begin <= argv_switches_end);
387 DCHECK(argv_switches_end <= argv_.end());
388 auto expell = std::remove_if(argv_switches_begin, argv_switches_end,
389 [&switch_key_native](const StringType& arg) {
390 return IsSwitchWithKey(arg, switch_key_native);
392 if (expell == argv_switches_end) {
396 begin_args_ -= argv_switches_end - expell;
397 argv_.erase(expell, argv_switches_end);
400 void CommandLine::CopySwitchesFrom(const CommandLine& source,
401 const char* const switches[],
403 for (size_t i = 0; i < count; ++i) {
404 if (source.HasSwitch(switches[i]))
405 AppendSwitchNative(switches[i], source.GetSwitchValueNative(switches[i]));
409 CommandLine::StringVector CommandLine::GetArgs() const {
410 // Gather all arguments after the last switch (may include kSwitchTerminator).
411 StringVector args(argv_.begin() + begin_args_, argv_.end());
412 // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?)
413 auto switch_terminator = ranges::find(args, kSwitchTerminator);
414 if (switch_terminator != args.end())
415 args.erase(switch_terminator);
419 void CommandLine::AppendArg(StringPiece value) {
421 DCHECK(IsStringUTF8(value));
422 AppendArgNative(UTF8ToWide(value));
423 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
424 AppendArgNative(value);
426 #error Unsupported platform
430 void CommandLine::AppendArgPath(const FilePath& path) {
431 AppendArgNative(path.value());
434 void CommandLine::AppendArgNative(StringPieceType value) {
435 argv_.push_back(StringType(value));
438 void CommandLine::AppendArguments(const CommandLine& other,
439 bool include_program) {
441 SetProgram(other.GetProgram());
442 AppendSwitchesAndArguments(other.argv());
445 void CommandLine::PrependWrapper(StringPieceType wrapper) {
448 // Split the wrapper command based on whitespace (with quoting).
449 // StringPieceType does not currently work directly with StringTokenizerT.
450 using CommandLineTokenizer =
451 StringTokenizerT<StringType, StringType::const_iterator>;
452 StringType wrapper_string(wrapper);
453 CommandLineTokenizer tokenizer(wrapper_string, FILE_PATH_LITERAL(" "));
454 tokenizer.set_quote_chars(FILE_PATH_LITERAL("'\""));
455 std::vector<StringType> wrapper_argv;
456 while (tokenizer.GetNext())
457 wrapper_argv.emplace_back(tokenizer.token());
459 // Prepend the wrapper and update the switches/arguments |begin_args_|.
460 argv_.insert(argv_.begin(), wrapper_argv.begin(), wrapper_argv.end());
461 begin_args_ += wrapper_argv.size();
465 void CommandLine::ParseFromString(StringPieceType command_line) {
466 command_line = TrimWhitespace(command_line, TRIM_ALL);
467 if (command_line.empty())
469 raw_command_line_string_ = command_line;
472 wchar_t** args = NULL;
473 // When calling CommandLineToArgvW, use the apiset if available.
474 // Doing so will bypass loading shell32.dll on Win8+.
475 HMODULE downlevel_shell32_dll =
476 ::LoadLibraryEx(L"api-ms-win-downlevel-shell32-l1-1-0.dll", nullptr,
477 LOAD_LIBRARY_SEARCH_SYSTEM32);
478 if (downlevel_shell32_dll) {
479 auto command_line_to_argv_w_proc =
480 reinterpret_cast<decltype(::CommandLineToArgvW)*>(
481 ::GetProcAddress(downlevel_shell32_dll, "CommandLineToArgvW"));
482 if (command_line_to_argv_w_proc)
483 args = command_line_to_argv_w_proc(command_line.data(), &num_args);
485 // Since the apiset is not available, allow the delayload of shell32.dll
487 args = ::CommandLineToArgvW(command_line.data(), &num_args);
490 DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: "
492 StringVector argv(args, args + num_args);
494 raw_command_line_string_ = StringPieceType();
497 if (downlevel_shell32_dll)
498 ::FreeLibrary(downlevel_shell32_dll);
500 #endif // defined(OS_WIN)
502 void CommandLine::AppendSwitchesAndArguments(
503 const CommandLine::StringVector& argv) {
504 bool parse_switches = true;
506 const bool is_parsed_from_string = !raw_command_line_string_.empty();
508 for (size_t i = 1; i < argv.size(); ++i) {
509 CommandLine::StringType arg = argv[i];
511 arg = CommandLine::StringType(TrimWhitespace(arg, TRIM_ALL));
512 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
513 TrimWhitespaceASCII(arg, TRIM_ALL, &arg);
516 CommandLine::StringType switch_string;
517 CommandLine::StringType switch_value;
518 parse_switches &= (arg != kSwitchTerminator);
519 if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
521 if (is_parsed_from_string &&
522 IsSwitchWithKey(switch_string, kSingleArgument)) {
523 ParseAsSingleArgument(switch_string);
526 AppendSwitchNative(WideToUTF8(switch_string), switch_value);
527 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
528 AppendSwitchNative(switch_string, switch_value);
530 #error Unsupported platform
533 AppendArgNative(arg);
538 CommandLine::StringType CommandLine::GetArgumentsStringInternal(
539 bool allow_unsafe_insert_sequences) const {
541 // Append switches and arguments.
542 bool parse_switches = true;
543 for (size_t i = 1; i < argv_.size(); ++i) {
544 StringType arg = argv_[i];
545 StringType switch_string;
546 StringType switch_value;
547 parse_switches &= arg != kSwitchTerminator;
549 params.append(FILE_PATH_LITERAL(" "));
550 if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
551 params.append(switch_string);
552 if (!switch_value.empty()) {
554 switch_value = QuoteForCommandLineToArgvW(
555 switch_value, allow_unsafe_insert_sequences);
557 params.append(kSwitchValueSeparator + switch_value);
561 arg = QuoteForCommandLineToArgvW(arg, allow_unsafe_insert_sequences);
569 CommandLine::StringType CommandLine::GetCommandLineString() const {
570 StringType string(argv_[0]);
572 string = QuoteForCommandLineToArgvW(string,
573 /*allow_unsafe_insert_sequences=*/false);
575 StringType params(GetArgumentsString());
576 if (!params.empty()) {
577 string.append(FILE_PATH_LITERAL(" "));
578 string.append(params);
584 // NOTE: this function is used to set Chrome's open command in the registry
585 // during update. Any change to the syntax must be compatible with the prior
586 // version (i.e., any new syntax must be understood by older browsers expecting
587 // the old syntax, and the new browser must still handle the old syntax), as
588 // old versions are likely to persist, e.g., immediately after background
589 // update, when parsing command lines for other channels, when uninstalling web
590 // applications installed using the old syntax, etc.
591 CommandLine::StringType CommandLine::GetCommandLineStringForShell() const {
592 DCHECK(GetArgs().empty());
593 StringType command_line_string = GetCommandLineString();
594 return command_line_string + FILE_PATH_LITERAL(" ") +
595 StringType(kSwitchPrefixes[0]) + kSingleArgument +
596 FILE_PATH_LITERAL(" %1");
599 CommandLine::StringType
600 CommandLine::GetCommandLineStringWithUnsafeInsertSequences() const {
601 StringType string(argv_[0]);
602 string = QuoteForCommandLineToArgvW(string,
603 /*allow_unsafe_insert_sequences=*/true);
605 GetArgumentsStringInternal(/*allow_unsafe_insert_sequences=*/true));
606 if (!params.empty()) {
607 string.append(FILE_PATH_LITERAL(" "));
608 string.append(params);
612 #endif // defined(OS_WIN)
614 CommandLine::StringType CommandLine::GetArgumentsString() const {
615 return GetArgumentsStringInternal(/*allow_unsafe_insert_sequences=*/false);
619 void CommandLine::ParseAsSingleArgument(
620 const CommandLine::StringType& single_arg_switch) {
621 DCHECK(!raw_command_line_string_.empty());
623 // Remove any previously parsed arguments.
624 argv_.resize(begin_args_);
626 // Locate "--single-argument" in the process's raw command line. Results are
627 // unpredictable if "--single-argument" appears as part of a previous
628 // argument or switch.
629 const size_t single_arg_switch_position =
630 raw_command_line_string_.find(single_arg_switch);
631 DCHECK_NE(single_arg_switch_position, StringType::npos);
633 // Append the portion of the raw command line that starts one character past
634 // "--single-argument" as the one and only argument, or return if no
635 // argument is present.
636 const size_t arg_position =
637 single_arg_switch_position + single_arg_switch.length() + 1;
638 if (arg_position >= raw_command_line_string_.length())
640 const StringPieceType arg = raw_command_line_string_.substr(arg_position);
642 AppendArgNative(arg);
645 #endif // defined(OS_WIN)