1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/command_line.h"
11
12 #include <ostream>
13 #include <string_view>
14
15 #include "base/check_op.h"
16 #include "base/containers/contains.h"
17 #include "base/containers/span.h"
18 #include "base/debug/debugging_buildflags.h"
19 #include "base/files/file_path.h"
20 #include "base/logging.h"
21 #include "base/notreached.h"
22 #include "base/numerics/checked_math.h"
23 #include "base/ranges/algorithm.h"
24 #include "base/strings/strcat.h"
25 #include "base/strings/string_split.h"
26 #include "base/strings/string_tokenizer.h"
27 #include "base/strings/string_util.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "build/build_config.h"
30
31 #if BUILDFLAG(IS_WIN)
32 #include <windows.h>
33
34 #include <shellapi.h>
35
36 #include "base/strings/string_util_win.h"
37 #endif // BUILDFLAG(IS_WIN)
38
39 namespace base {
40
41 CommandLine* CommandLine::current_process_commandline_ = nullptr;
42
43 namespace {
44
45 DuplicateSwitchHandler* g_duplicate_switch_handler = nullptr;
46
47 constexpr CommandLine::CharType kSwitchTerminator[] = FILE_PATH_LITERAL("--");
48 constexpr CommandLine::CharType kSwitchValueSeparator[] =
49 FILE_PATH_LITERAL("=");
50
51 // Since we use a lazy match, make sure that longer versions (like "--") are
52 // listed before shorter versions (like "-") of similar prefixes.
53 #if BUILDFLAG(IS_WIN)
54 // By putting slash last, we can control whether it is treaded as a switch
55 // value by changing the value of switch_prefix_count to be one less than
56 // the array size.
57 constexpr CommandLine::StringViewType kSwitchPrefixes[] = {L"--", L"-", L"/"};
58 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
59 // Unixes don't use slash as a switch.
60 constexpr CommandLine::StringViewType kSwitchPrefixes[] = {"--", "-"};
61 #endif
62 size_t switch_prefix_count = std::size(kSwitchPrefixes);
63
64 #if BUILDFLAG(IS_WIN)
65 // Switch string that specifies the single argument to the command line.
66 // If present, everything after this switch is interpreted as a single
67 // argument regardless of whitespace, quotes, etc. Used for launches from the
68 // Windows shell, which may have arguments with unencoded quotes that could
69 // otherwise unexpectedly be split into multiple arguments
70 // (https://crbug.com/937179).
71 constexpr CommandLine::CharType kSingleArgument[] =
72 FILE_PATH_LITERAL("single-argument");
73 #endif // BUILDFLAG(IS_WIN)
74
GetSwitchPrefixLength(CommandLine::StringViewType string)75 size_t GetSwitchPrefixLength(CommandLine::StringViewType string) {
76 for (size_t i = 0; i < switch_prefix_count; ++i) {
77 CommandLine::StringType prefix(kSwitchPrefixes[i]);
78 if (string.substr(0, prefix.length()) == prefix)
79 return prefix.length();
80 }
81 return 0;
82 }
83
84 // Fills in |switch_string| and |switch_value| if |string| is a switch.
85 // This will preserve the input switch prefix in the output |switch_string|.
IsSwitch(const CommandLine::StringType & string,CommandLine::StringType * switch_string,CommandLine::StringType * switch_value)86 bool IsSwitch(const CommandLine::StringType& string,
87 CommandLine::StringType* switch_string,
88 CommandLine::StringType* switch_value) {
89 switch_string->clear();
90 switch_value->clear();
91 size_t prefix_length = GetSwitchPrefixLength(string);
92 if (prefix_length == 0 || prefix_length == string.length())
93 return false;
94
95 const size_t equals_position = string.find(kSwitchValueSeparator);
96 *switch_string = string.substr(0, equals_position);
97 if (equals_position != CommandLine::StringType::npos)
98 *switch_value = string.substr(equals_position + 1);
99 return true;
100 }
101
102 // Returns true iff |string| represents a switch with key
103 // |switch_key_without_prefix|, regardless of value.
IsSwitchWithKey(CommandLine::StringViewType string,CommandLine::StringViewType switch_key_without_prefix)104 bool IsSwitchWithKey(CommandLine::StringViewType string,
105 CommandLine::StringViewType switch_key_without_prefix) {
106 size_t prefix_length = GetSwitchPrefixLength(string);
107 if (prefix_length == 0 || prefix_length == string.length())
108 return false;
109
110 const size_t equals_position = string.find(kSwitchValueSeparator);
111 return string.substr(prefix_length, equals_position - prefix_length) ==
112 switch_key_without_prefix;
113 }
114
115 #if BUILDFLAG(IS_WIN)
116 // Quotes a string as necessary for CommandLineToArgvW compatibility *on
117 // Windows*.
118 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx#parsing-c-command-line-arguments.
QuoteForCommandLineToArgvWInternal(const std::wstring & arg,bool allow_unsafe_insert_sequences)119 std::wstring QuoteForCommandLineToArgvWInternal(
120 const std::wstring& arg,
121 bool allow_unsafe_insert_sequences) {
122 // Ensures that GetCommandLineString isn't used to generate command-line
123 // strings for the Windows shell by checking for Windows insert sequences like
124 // "%1". GetCommandLineStringForShell should be used instead to get a string
125 // with the correct placeholder format for the shell.
126 DCHECK(arg.size() != 2 || arg[0] != L'%' || allow_unsafe_insert_sequences);
127
128 constexpr wchar_t kQuotableCharacters[] = L" \t\\\"";
129 if (arg.find_first_of(kQuotableCharacters) == std::wstring::npos) {
130 return arg;
131 }
132
133 std::wstring out(1, L'"');
134 for (size_t i = 0; i < arg.size(); ++i) {
135 if (arg[i] == L'\\') {
136 // Finds the extent of this run of backslashes.
137 size_t end = i + 1;
138 while (end < arg.size() && arg[end] == L'\\') {
139 ++end;
140 }
141
142 const size_t backslash_count = end - i;
143
144 // Backslashes are escaped only if the run is followed by a double quote.
145 // Since we also will end the string with a double quote, we escape for
146 // either a double quote or the end of the string.
147 const size_t backslash_multiplier =
148 (end == arg.size() || arg[end] == L'"') ? 2 : 1;
149
150 out.append(std::wstring(backslash_count * backslash_multiplier, L'\\'));
151
152 // Advances `i` to one before `end` to balance `++i` in loop.
153 i = end - 1;
154 } else if (arg[i] == L'"') {
155 out.append(LR"(\")");
156 } else {
157 out.push_back(arg[i]);
158 }
159 }
160
161 out.push_back(L'"');
162
163 return out;
164 }
165 #endif // BUILDFLAG(IS_WIN)
166
167 } // namespace
168
169 // static
SetDuplicateSwitchHandler(std::unique_ptr<DuplicateSwitchHandler> new_duplicate_switch_handler)170 void CommandLine::SetDuplicateSwitchHandler(
171 std::unique_ptr<DuplicateSwitchHandler> new_duplicate_switch_handler) {
172 delete g_duplicate_switch_handler;
173 g_duplicate_switch_handler = new_duplicate_switch_handler.release();
174 }
175
CommandLine(NoProgram no_program)176 CommandLine::CommandLine(NoProgram no_program) : argv_(1), begin_args_(1) {}
177
CommandLine(const FilePath & program)178 CommandLine::CommandLine(const FilePath& program)
179 : argv_(1),
180 begin_args_(1) {
181 SetProgram(program);
182 }
183
CommandLine(int argc,const CommandLine::CharType * const * argv)184 CommandLine::CommandLine(int argc, const CommandLine::CharType* const* argv)
185 : argv_(1), begin_args_(1) {
186 InitFromArgv(argc, argv);
187 }
188
CommandLine(const StringVector & argv)189 CommandLine::CommandLine(const StringVector& argv)
190 : argv_(1),
191 begin_args_(1) {
192 InitFromArgv(argv);
193 }
194
195 CommandLine::CommandLine(const CommandLine& other) = default;
CommandLine(CommandLine && other)196 CommandLine::CommandLine(CommandLine&& other) noexcept
197 :
198 #if BUILDFLAG(IS_WIN)
199 raw_command_line_string_(
200 std::exchange(other.raw_command_line_string_, StringViewType())),
201 has_single_argument_switch_(
202 std::exchange(other.has_single_argument_switch_, false)),
203 #endif // BUILDFLAG(IS_WIN)
204 argv_(std::exchange(other.argv_, StringVector(1))),
205 switches_(std::move(other.switches_)),
206 begin_args_(std::exchange(other.begin_args_, 1)) {
207 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
208 other.sequence_checker_.Detach();
209 #endif
210 }
211 CommandLine& CommandLine::operator=(const CommandLine& other) = default;
operator =(CommandLine && other)212 CommandLine& CommandLine::operator=(CommandLine&& other) noexcept {
213 #if BUILDFLAG(IS_WIN)
214 raw_command_line_string_ =
215 std::exchange(other.raw_command_line_string_, StringViewType());
216 has_single_argument_switch_ =
217 std::exchange(other.has_single_argument_switch_, false);
218 #endif // BUILDFLAG(IS_WIN)
219 argv_ = std::exchange(other.argv_, StringVector(1));
220 switches_ = std::move(other.switches_);
221 begin_args_ = std::exchange(other.begin_args_, 1);
222 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
223 other.sequence_checker_.Detach();
224 #endif
225 return *this;
226 }
227 CommandLine::~CommandLine() = default;
228
229 #if BUILDFLAG(IS_WIN)
230 // static
set_slash_is_not_a_switch()231 void CommandLine::set_slash_is_not_a_switch() {
232 // The last switch prefix should be slash, so adjust the size to skip it.
233 static_assert(base::span(kSwitchPrefixes).back() == L"/",
234 "Error: Last switch prefix is not a slash.");
235 switch_prefix_count = std::size(kSwitchPrefixes) - 1;
236 }
237
238 // static
InitUsingArgvForTesting(int argc,const char * const * argv)239 void CommandLine::InitUsingArgvForTesting(int argc, const char* const* argv) {
240 DCHECK(!current_process_commandline_);
241 current_process_commandline_ = new CommandLine(NO_PROGRAM);
242 // On Windows we need to convert the command line arguments to std::wstring.
243 CommandLine::StringVector argv_vector;
244 for (int i = 0; i < argc; ++i)
245 argv_vector.push_back(UTF8ToWide(argv[i]));
246 current_process_commandline_->InitFromArgv(argv_vector);
247 }
248 #endif // BUILDFLAG(IS_WIN)
249
250 // static
Init(int argc,const char * const * argv)251 bool CommandLine::Init(int argc, const char* const* argv) {
252 if (current_process_commandline_) {
253 // If this is intentional, Reset() must be called first. If we are using
254 // the shared build mode, we have to share a single object across multiple
255 // shared libraries.
256 return false;
257 }
258
259 current_process_commandline_ = new CommandLine(NO_PROGRAM);
260 #if BUILDFLAG(IS_WIN)
261 current_process_commandline_->ParseFromString(::GetCommandLineW());
262 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
263 current_process_commandline_->InitFromArgv(argc, argv);
264 #else
265 #error Unsupported platform
266 #endif
267
268 return true;
269 }
270
271 // static
Reset()272 void CommandLine::Reset() {
273 DCHECK(current_process_commandline_);
274 delete current_process_commandline_;
275 current_process_commandline_ = nullptr;
276 }
277
278 // static
ForCurrentProcess()279 CommandLine* CommandLine::ForCurrentProcess() {
280 DCHECK(current_process_commandline_);
281 return current_process_commandline_;
282 }
283
284 // static
InitializedForCurrentProcess()285 bool CommandLine::InitializedForCurrentProcess() {
286 return !!current_process_commandline_;
287 }
288
289 // static
FromArgvWithoutProgram(const StringVector & argv)290 CommandLine CommandLine::FromArgvWithoutProgram(const StringVector& argv) {
291 CommandLine cmd(NO_PROGRAM);
292 cmd.AppendSwitchesAndArguments(argv);
293 return cmd;
294 }
295
296 #if BUILDFLAG(IS_WIN)
297 // static
FromString(StringViewType command_line)298 CommandLine CommandLine::FromString(StringViewType command_line) {
299 CommandLine cmd(NO_PROGRAM);
300 cmd.ParseFromString(command_line);
301 return cmd;
302 }
303 #endif // BUILDFLAG(IS_WIN)
304
InitFromArgv(int argc,const CommandLine::CharType * const * argv)305 void CommandLine::InitFromArgv(int argc,
306 const CommandLine::CharType* const* argv) {
307 StringVector new_argv;
308 for (int i = 0; i < argc; ++i)
309 new_argv.push_back(argv[i]);
310 InitFromArgv(new_argv);
311 }
312
InitFromArgv(const StringVector & argv)313 void CommandLine::InitFromArgv(const StringVector& argv) {
314 argv_ = StringVector(1);
315 switches_.clear();
316 begin_args_ = 1;
317 SetProgram(argv.empty() ? FilePath() : FilePath(argv[0]));
318 if (!argv.empty()) {
319 AppendSwitchesAndArguments(span(argv).subspan<1>());
320 }
321 }
322
GetProgram() const323 FilePath CommandLine::GetProgram() const {
324 return FilePath(argv_[0]);
325 }
326
SetProgram(const FilePath & program)327 void CommandLine::SetProgram(const FilePath& program) {
328 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
329 sequence_checker_.Check();
330 #endif
331 #if BUILDFLAG(IS_WIN)
332 argv_[0] = StringType(TrimWhitespace(program.value(), TRIM_ALL));
333 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
334 TrimWhitespaceASCII(program.value(), TRIM_ALL, &argv_[0]);
335 #else
336 #error Unsupported platform
337 #endif
338 }
339
HasSwitch(std::string_view switch_string) const340 bool CommandLine::HasSwitch(std::string_view switch_string) const {
341 DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
342 return Contains(switches_, switch_string);
343 }
344
HasSwitch(const char switch_constant[]) const345 bool CommandLine::HasSwitch(const char switch_constant[]) const {
346 return HasSwitch(std::string_view(switch_constant));
347 }
348
GetSwitchValueASCII(std::string_view switch_string) const349 std::string CommandLine::GetSwitchValueASCII(
350 std::string_view switch_string) const {
351 StringType value = GetSwitchValueNative(switch_string);
352 #if BUILDFLAG(IS_WIN)
353 if (!IsStringASCII(base::AsStringPiece16(value))) {
354 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
355 if (!IsStringASCII(value)) {
356 #endif
357 DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII.";
358 return std::string();
359 }
360 #if BUILDFLAG(IS_WIN)
361 return WideToUTF8(value);
362 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
363 return value;
364 #endif
365 }
366
367 FilePath CommandLine::GetSwitchValuePath(std::string_view switch_string) const {
368 return FilePath(GetSwitchValueNative(switch_string));
369 }
370
371 CommandLine::StringType CommandLine::GetSwitchValueNative(
372 std::string_view switch_string) const {
373 DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
374 auto result = switches_.find(switch_string);
375 return result == switches_.end() ? StringType() : result->second;
376 }
377
378 void CommandLine::AppendSwitch(std::string_view switch_string) {
379 AppendSwitchNative(switch_string, StringType());
380 }
381
382 void CommandLine::AppendSwitchPath(std::string_view switch_string,
383 const FilePath& path) {
384 AppendSwitchNative(switch_string, path.value());
385 }
386
387 void CommandLine::AppendSwitchNative(std::string_view switch_string,
388 CommandLine::StringViewType value) {
389 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
390 sequence_checker_.Check();
391 #endif
392 #if BUILDFLAG(IS_WIN)
393 const std::string switch_key = ToLowerASCII(switch_string);
394 StringType combined_switch_string(UTF8ToWide(switch_key));
395 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
396 std::string_view switch_key = switch_string;
397 StringType combined_switch_string(switch_key);
398 #endif
399 size_t prefix_length = GetSwitchPrefixLength(combined_switch_string);
400 auto key = switch_key.substr(prefix_length);
401 if (g_duplicate_switch_handler) {
402 g_duplicate_switch_handler->ResolveDuplicate(key, value,
403 switches_[std::string(key)]);
404 } else {
405 switches_[std::string(key)] = StringType(value);
406 }
407
408 // Preserve existing switch prefixes in |argv_|; only append one if necessary.
409 if (prefix_length == 0) {
410 combined_switch_string.insert(0, kSwitchPrefixes[0].data(),
411 kSwitchPrefixes[0].size());
412 }
413 if (!value.empty())
414 base::StrAppend(&combined_switch_string, {kSwitchValueSeparator, value});
415 // Append the switch and update the switches/arguments divider |begin_args_|.
416 argv_.insert(argv_.begin() + begin_args_, combined_switch_string);
417 begin_args_ = (CheckedNumeric(begin_args_) + 1).ValueOrDie();
418 }
419
420 void CommandLine::AppendSwitchASCII(std::string_view switch_string,
421 std::string_view value_string) {
422 #if BUILDFLAG(IS_WIN)
423 AppendSwitchNative(switch_string, UTF8ToWide(value_string));
424 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
425 AppendSwitchNative(switch_string, value_string);
426 #else
427 #error Unsupported platform
428 #endif
429 }
430
431 void CommandLine::RemoveSwitch(std::string_view switch_key_without_prefix) {
432 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
433 sequence_checker_.Check();
434 #endif
435 #if BUILDFLAG(IS_WIN)
436 StringType switch_key_native = UTF8ToWide(switch_key_without_prefix);
437 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
438 StringType switch_key_native(switch_key_without_prefix);
439 #endif
440
441 DCHECK_EQ(ToLowerASCII(switch_key_without_prefix), switch_key_without_prefix);
442 DCHECK_EQ(0u, GetSwitchPrefixLength(switch_key_native));
443 auto it = switches_.find(switch_key_without_prefix);
444 if (it == switches_.end())
445 return;
446 switches_.erase(it);
447 // Also erase from the switches section of |argv_| and update |begin_args_|
448 // accordingly.
449 // Switches in |argv_| have indices [1, begin_args_).
450 auto argv_switches_begin = argv_.begin() + 1;
451 auto argv_switches_end = argv_.begin() + begin_args_;
452 DCHECK(argv_switches_begin <= argv_switches_end);
453 DCHECK(argv_switches_end <= argv_.end());
454 auto expell = std::remove_if(argv_switches_begin, argv_switches_end,
455 [&switch_key_native](const StringType& arg) {
456 return IsSwitchWithKey(arg, switch_key_native);
457 });
458 if (expell == argv_switches_end) {
459 NOTREACHED();
460 }
461 begin_args_ -= argv_switches_end - expell;
462 argv_.erase(expell, argv_switches_end);
463 }
464
465 void CommandLine::CopySwitchesFrom(const CommandLine& source,
466 span<const char* const> switches) {
467 for (const char* entry : switches) {
468 if (source.HasSwitch(entry)) {
469 AppendSwitchNative(entry, source.GetSwitchValueNative(entry));
470 }
471 }
472 }
473
474 CommandLine::StringVector CommandLine::GetArgs() const {
475 // Gather all arguments after the last switch (may include kSwitchTerminator).
476 StringVector args(argv_.begin() + begin_args_, argv_.end());
477 // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?)
478 auto switch_terminator = ranges::find(args, kSwitchTerminator);
479 if (switch_terminator != args.end())
480 args.erase(switch_terminator);
481 return args;
482 }
483
484 void CommandLine::AppendArg(std::string_view value) {
485 #if BUILDFLAG(IS_WIN)
486 DCHECK(IsStringUTF8(value));
487 AppendArgNative(UTF8ToWide(value));
488 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
489 AppendArgNative(value);
490 #else
491 #error Unsupported platform
492 #endif
493 }
494
495 void CommandLine::AppendArgPath(const FilePath& path) {
496 AppendArgNative(path.value());
497 }
498
499 void CommandLine::AppendArgNative(StringViewType value) {
500 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
501 sequence_checker_.Check();
502 #endif
503 argv_.push_back(StringType(value));
504 }
505
506 void CommandLine::AppendArguments(const CommandLine& other,
507 bool include_program) {
508 if (include_program)
509 SetProgram(other.GetProgram());
510 if (!other.argv().empty()) {
511 AppendSwitchesAndArguments(span(other.argv()).subspan<1>());
512 }
513 }
514
515 void CommandLine::PrependWrapper(StringViewType wrapper) {
516 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
517 sequence_checker_.Check();
518 #endif
519 if (wrapper.empty())
520 return;
521 // Split the wrapper command based on whitespace (with quoting).
522 // StringViewType does not currently work directly with StringTokenizerT.
523 using CommandLineTokenizer =
524 StringTokenizerT<StringType, StringType::const_iterator>;
525 StringType wrapper_string(wrapper);
526 CommandLineTokenizer tokenizer(wrapper_string, FILE_PATH_LITERAL(" "));
527 tokenizer.set_quote_chars(FILE_PATH_LITERAL("'\""));
528 std::vector<StringType> wrapper_argv;
529 while (std::optional<StringViewType> token = tokenizer.GetNextTokenView()) {
530 wrapper_argv.emplace_back(token.value());
531 }
532
533 // Prepend the wrapper and update the switches/arguments |begin_args_|.
534 argv_.insert(argv_.begin(), wrapper_argv.begin(), wrapper_argv.end());
535 begin_args_ += wrapper_argv.size();
536 }
537
538 #if BUILDFLAG(IS_WIN)
539 void CommandLine::ParseFromString(StringViewType command_line) {
540 command_line = TrimWhitespace(command_line, TRIM_ALL);
541 if (command_line.empty())
542 return;
543 raw_command_line_string_ = command_line;
544
545 int num_args = 0;
546 wchar_t** args = NULL;
547 // When calling CommandLineToArgvW, use the apiset if available.
548 // Doing so will bypass loading shell32.dll on Windows.
549 HMODULE downlevel_shell32_dll =
550 ::LoadLibraryEx(L"api-ms-win-downlevel-shell32-l1-1-0.dll", nullptr,
551 LOAD_LIBRARY_SEARCH_SYSTEM32);
552 if (downlevel_shell32_dll) {
553 auto command_line_to_argv_w_proc =
554 reinterpret_cast<decltype(::CommandLineToArgvW)*>(
555 ::GetProcAddress(downlevel_shell32_dll, "CommandLineToArgvW"));
556 if (command_line_to_argv_w_proc)
557 args = command_line_to_argv_w_proc(command_line.data(), &num_args);
558 } else {
559 // Since the apiset is not available, allow the delayload of shell32.dll
560 // to take place.
561 args = ::CommandLineToArgvW(command_line.data(), &num_args);
562 }
563
564 DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: "
565 << command_line;
566 StringVector argv(args, args + num_args);
567 InitFromArgv(argv);
568 raw_command_line_string_ = StringViewType();
569 LocalFree(args);
570
571 if (downlevel_shell32_dll)
572 ::FreeLibrary(downlevel_shell32_dll);
573 }
574
575 #endif // BUILDFLAG(IS_WIN)
576
577 void CommandLine::AppendSwitchesAndArguments(span<const StringType> argv) {
578 bool parse_switches = true;
579 #if BUILDFLAG(IS_WIN)
580 const bool is_parsed_from_string = !raw_command_line_string_.empty();
581 #endif
582 for (StringType arg : argv) {
583 #if BUILDFLAG(IS_WIN)
584 arg = CommandLine::StringType(TrimWhitespace(arg, TRIM_ALL));
585 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
586 TrimWhitespaceASCII(arg, TRIM_ALL, &arg);
587 #endif
588
589 CommandLine::StringType switch_string;
590 CommandLine::StringType switch_value;
591 parse_switches &= (arg != kSwitchTerminator);
592 if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
593 #if BUILDFLAG(IS_WIN)
594 if (is_parsed_from_string &&
595 IsSwitchWithKey(switch_string, kSingleArgument)) {
596 ParseAsSingleArgument(switch_string);
597 return;
598 }
599 AppendSwitchNative(WideToUTF8(switch_string), switch_value);
600 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
601 AppendSwitchNative(switch_string, switch_value);
602 #else
603 #error Unsupported platform
604 #endif
605 } else {
606 AppendArgNative(arg);
607 }
608 }
609 }
610
611 CommandLine::StringType CommandLine::GetArgumentsStringInternal(
612 bool allow_unsafe_insert_sequences) const {
613 StringType params;
614 // Append switches and arguments.
615 bool parse_switches = true;
616 #if BUILDFLAG(IS_WIN)
617 bool appended_single_argument_switch = false;
618 #endif
619
620 for (size_t i = 1; i < argv_.size(); ++i) {
621 StringType arg = argv_[i];
622 StringType switch_string;
623 StringType switch_value;
624 parse_switches &= arg != kSwitchTerminator;
625 if (i > 1)
626 params.append(FILE_PATH_LITERAL(" "));
627 if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
628 params.append(switch_string);
629 if (!switch_value.empty()) {
630 #if BUILDFLAG(IS_WIN)
631 switch_value = QuoteForCommandLineToArgvWInternal(
632 switch_value, allow_unsafe_insert_sequences);
633 #endif
634 params.append(kSwitchValueSeparator + switch_value);
635 }
636 } else {
637 #if BUILDFLAG(IS_WIN)
638 if (has_single_argument_switch_) {
639 // Check that we don't have multiple arguments when
640 // `has_single_argument_switch_` is true.
641 DCHECK(!appended_single_argument_switch);
642 appended_single_argument_switch = true;
643 params.append(base::StrCat(
644 {kSwitchPrefixes[0], kSingleArgument, FILE_PATH_LITERAL(" ")}));
645 } else {
646 arg = QuoteForCommandLineToArgvWInternal(arg,
647 allow_unsafe_insert_sequences);
648 }
649 #endif
650 params.append(arg);
651 }
652 }
653 return params;
654 }
655
656 CommandLine::StringType CommandLine::GetCommandLineString() const {
657 StringType string(argv_[0]);
658 #if BUILDFLAG(IS_WIN)
659 string = QuoteForCommandLineToArgvWInternal(
660 string,
661 /*allow_unsafe_insert_sequences=*/false);
662 #endif
663 StringType params(GetArgumentsString());
664 if (!params.empty()) {
665 string.append(FILE_PATH_LITERAL(" "));
666 string.append(params);
667 }
668 return string;
669 }
670
671 #if BUILDFLAG(IS_WIN)
672 // static
673 std::wstring CommandLine::QuoteForCommandLineToArgvW(const std::wstring& arg) {
674 return QuoteForCommandLineToArgvWInternal(
675 arg, /*allow_unsafe_insert_sequences=*/false);
676 }
677
678 // NOTE: this function is used to set Chrome's open command in the registry
679 // during update. Any change to the syntax must be compatible with the prior
680 // version (i.e., any new syntax must be understood by older browsers expecting
681 // the old syntax, and the new browser must still handle the old syntax), as
682 // old versions are likely to persist, e.g., immediately after background
683 // update, when parsing command lines for other channels, when uninstalling web
684 // applications installed using the old syntax, etc.
685 CommandLine::StringType CommandLine::GetCommandLineStringForShell() const {
686 DCHECK(GetArgs().empty());
687 StringType command_line_string = GetCommandLineString();
688 return command_line_string + FILE_PATH_LITERAL(" ") +
689 StringType(kSwitchPrefixes[0]) + kSingleArgument +
690 FILE_PATH_LITERAL(" %1");
691 }
692
693 CommandLine::StringType
694 CommandLine::GetCommandLineStringWithUnsafeInsertSequences() const {
695 StringType string(argv_[0]);
696 string = QuoteForCommandLineToArgvWInternal(
697 string,
698 /*allow_unsafe_insert_sequences=*/true);
699 StringType params(
700 GetArgumentsStringInternal(/*allow_unsafe_insert_sequences=*/true));
701 if (!params.empty()) {
702 string.append(FILE_PATH_LITERAL(" "));
703 string.append(params);
704 }
705 return string;
706 }
707 #endif // BUILDFLAG(IS_WIN)
708
709 CommandLine::StringType CommandLine::GetArgumentsString() const {
710 return GetArgumentsStringInternal(/*allow_unsafe_insert_sequences=*/false);
711 }
712
713 #if BUILDFLAG(IS_WIN)
714 void CommandLine::ParseAsSingleArgument(
715 const CommandLine::StringType& single_arg_switch) {
716 DCHECK(!raw_command_line_string_.empty());
717
718 // Remove any previously parsed arguments.
719 argv_.resize(static_cast<size_t>(begin_args_));
720
721 // Locate "--single-argument" in the process's raw command line. Results are
722 // unpredictable if "--single-argument" appears as part of a previous
723 // argument or switch.
724 const size_t single_arg_switch_position =
725 raw_command_line_string_.find(single_arg_switch);
726 DCHECK_NE(single_arg_switch_position, StringType::npos);
727
728 // Append the portion of the raw command line that starts one character past
729 // "--single-argument" as the one and only argument, or return if no
730 // argument is present.
731 const size_t arg_position =
732 single_arg_switch_position + single_arg_switch.length() + 1;
733 if (arg_position >= raw_command_line_string_.length())
734 return;
735 has_single_argument_switch_ = true;
736 const StringViewType arg = raw_command_line_string_.substr(arg_position);
737 if (!arg.empty()) {
738 AppendArgNative(arg);
739 }
740 }
741 #endif // BUILDFLAG(IS_WIN)
742
743 void CommandLine::DetachFromCurrentSequence() {
744 #if BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
745 sequence_checker_.Detach();
746 #endif // BUILDFLAG(ENABLE_COMMANDLINE_SEQUENCE_CHECKS)
747 }
748
749 } // namespace base
750