• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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