michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/command_line.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "base/files/file_path.h" michael@0: #include "base/logging.h" michael@0: #include "base/strings/string_split.h" michael@0: #include "base/strings/string_util.h" michael@0: #include "base/strings/utf_string_conversions.h" michael@0: #include "build/build_config.h" michael@0: michael@0: #if defined(OS_WIN) michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: using base::FilePath; michael@0: michael@0: CommandLine* CommandLine::current_process_commandline_ = NULL; michael@0: michael@0: namespace { michael@0: const CommandLine::CharType kSwitchTerminator[] = FILE_PATH_LITERAL("--"); michael@0: const CommandLine::CharType kSwitchValueSeparator[] = FILE_PATH_LITERAL("="); michael@0: // Since we use a lazy match, make sure that longer versions (like "--") are michael@0: // listed before shorter versions (like "-") of similar prefixes. michael@0: #if defined(OS_WIN) michael@0: const CommandLine::CharType* const kSwitchPrefixes[] = {L"--", L"-", L"/"}; michael@0: #elif defined(OS_POSIX) michael@0: // Unixes don't use slash as a switch. michael@0: const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"}; michael@0: #endif michael@0: michael@0: size_t GetSwitchPrefixLength(const CommandLine::StringType& string) { michael@0: for (size_t i = 0; i < arraysize(kSwitchPrefixes); ++i) { michael@0: CommandLine::StringType prefix(kSwitchPrefixes[i]); michael@0: if (string.compare(0, prefix.length(), prefix) == 0) michael@0: return prefix.length(); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: // Fills in |switch_string| and |switch_value| if |string| is a switch. michael@0: // This will preserve the input switch prefix in the output |switch_string|. michael@0: bool IsSwitch(const CommandLine::StringType& string, michael@0: CommandLine::StringType* switch_string, michael@0: CommandLine::StringType* switch_value) { michael@0: switch_string->clear(); michael@0: switch_value->clear(); michael@0: size_t prefix_length = GetSwitchPrefixLength(string); michael@0: if (prefix_length == 0 || prefix_length == string.length()) michael@0: return false; michael@0: michael@0: const size_t equals_position = string.find(kSwitchValueSeparator); michael@0: *switch_string = string.substr(0, equals_position); michael@0: if (equals_position != CommandLine::StringType::npos) michael@0: *switch_value = string.substr(equals_position + 1); michael@0: return true; michael@0: } michael@0: michael@0: // Append switches and arguments, keeping switches before arguments. michael@0: void AppendSwitchesAndArguments(CommandLine& command_line, michael@0: const CommandLine::StringVector& argv) { michael@0: bool parse_switches = true; michael@0: for (size_t i = 1; i < argv.size(); ++i) { michael@0: CommandLine::StringType arg = argv[i]; michael@0: TrimWhitespace(arg, TRIM_ALL, &arg); michael@0: michael@0: CommandLine::StringType switch_string; michael@0: CommandLine::StringType switch_value; michael@0: parse_switches &= (arg != kSwitchTerminator); michael@0: if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) { michael@0: #if defined(OS_WIN) michael@0: command_line.AppendSwitchNative(WideToASCII(switch_string), switch_value); michael@0: #elif defined(OS_POSIX) michael@0: command_line.AppendSwitchNative(switch_string, switch_value); michael@0: #endif michael@0: } else { michael@0: command_line.AppendArgNative(arg); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Lowercase switches for backwards compatiblity *on Windows*. michael@0: std::string LowerASCIIOnWindows(const std::string& string) { michael@0: #if defined(OS_WIN) michael@0: return StringToLowerASCII(string); michael@0: #elif defined(OS_POSIX) michael@0: return string; michael@0: #endif michael@0: } michael@0: michael@0: michael@0: #if defined(OS_WIN) michael@0: // Quote a string as necessary for CommandLineToArgvW compatiblity *on Windows*. michael@0: std::wstring QuoteForCommandLineToArgvW(const std::wstring& arg) { michael@0: // We follow the quoting rules of CommandLineToArgvW. michael@0: // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx michael@0: if (arg.find_first_of(L" \\\"") == std::wstring::npos) { michael@0: // No quoting necessary. michael@0: return arg; michael@0: } michael@0: michael@0: std::wstring out; michael@0: out.push_back(L'"'); michael@0: for (size_t i = 0; i < arg.size(); ++i) { michael@0: if (arg[i] == '\\') { michael@0: // Find the extent of this run of backslashes. michael@0: size_t start = i, end = start + 1; michael@0: for (; end < arg.size() && arg[end] == '\\'; ++end) michael@0: /* empty */; michael@0: size_t backslash_count = end - start; michael@0: michael@0: // Backslashes are escapes only if the run is followed by a double quote. michael@0: // Since we also will end the string with a double quote, we escape for michael@0: // either a double quote or the end of the string. michael@0: if (end == arg.size() || arg[end] == '"') { michael@0: // To quote, we need to output 2x as many backslashes. michael@0: backslash_count *= 2; michael@0: } michael@0: for (size_t j = 0; j < backslash_count; ++j) michael@0: out.push_back('\\'); michael@0: michael@0: // Advance i to one before the end to balance i++ in loop. michael@0: i = end - 1; michael@0: } else if (arg[i] == '"') { michael@0: out.push_back('\\'); michael@0: out.push_back('"'); michael@0: } else { michael@0: out.push_back(arg[i]); michael@0: } michael@0: } michael@0: out.push_back('"'); michael@0: michael@0: return out; michael@0: } michael@0: #endif michael@0: michael@0: } // namespace michael@0: michael@0: CommandLine::CommandLine(NoProgram no_program) michael@0: : argv_(1), michael@0: begin_args_(1) { michael@0: } michael@0: michael@0: CommandLine::CommandLine(const FilePath& program) michael@0: : argv_(1), michael@0: begin_args_(1) { michael@0: SetProgram(program); michael@0: } michael@0: michael@0: CommandLine::CommandLine(int argc, const CommandLine::CharType* const* argv) michael@0: : argv_(1), michael@0: begin_args_(1) { michael@0: InitFromArgv(argc, argv); michael@0: } michael@0: michael@0: CommandLine::CommandLine(const StringVector& argv) michael@0: : argv_(1), michael@0: begin_args_(1) { michael@0: InitFromArgv(argv); michael@0: } michael@0: michael@0: CommandLine::~CommandLine() { michael@0: } michael@0: michael@0: // static michael@0: bool CommandLine::Init(int argc, const char* const* argv) { michael@0: if (current_process_commandline_) { michael@0: // If this is intentional, Reset() must be called first. If we are using michael@0: // the shared build mode, we have to share a single object across multiple michael@0: // shared libraries. michael@0: return false; michael@0: } michael@0: michael@0: current_process_commandline_ = new CommandLine(NO_PROGRAM); michael@0: #if defined(OS_WIN) michael@0: current_process_commandline_->ParseFromString(::GetCommandLineW()); michael@0: #elif defined(OS_POSIX) michael@0: current_process_commandline_->InitFromArgv(argc, argv); michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: void CommandLine::Reset() { michael@0: DCHECK(current_process_commandline_); michael@0: delete current_process_commandline_; michael@0: current_process_commandline_ = NULL; michael@0: } michael@0: michael@0: // static michael@0: CommandLine* CommandLine::ForCurrentProcess() { michael@0: DCHECK(current_process_commandline_); michael@0: return current_process_commandline_; michael@0: } michael@0: michael@0: // static michael@0: bool CommandLine::InitializedForCurrentProcess() { michael@0: return !!current_process_commandline_; michael@0: } michael@0: michael@0: #if defined(OS_WIN) michael@0: // static michael@0: CommandLine CommandLine::FromString(const std::wstring& command_line) { michael@0: CommandLine cmd(NO_PROGRAM); michael@0: cmd.ParseFromString(command_line); michael@0: return cmd; michael@0: } michael@0: #endif michael@0: michael@0: void CommandLine::InitFromArgv(int argc, michael@0: const CommandLine::CharType* const* argv) { michael@0: StringVector new_argv; michael@0: for (int i = 0; i < argc; ++i) michael@0: new_argv.push_back(argv[i]); michael@0: InitFromArgv(new_argv); michael@0: } michael@0: michael@0: void CommandLine::InitFromArgv(const StringVector& argv) { michael@0: argv_ = StringVector(1); michael@0: switches_.clear(); michael@0: begin_args_ = 1; michael@0: SetProgram(argv.empty() ? FilePath() : FilePath(argv[0])); michael@0: AppendSwitchesAndArguments(*this, argv); michael@0: } michael@0: michael@0: CommandLine::StringType CommandLine::GetCommandLineString() const { michael@0: StringType string(argv_[0]); michael@0: #if defined(OS_WIN) michael@0: string = QuoteForCommandLineToArgvW(string); michael@0: #endif michael@0: StringType params(GetArgumentsString()); michael@0: if (!params.empty()) { michael@0: string.append(StringType(FILE_PATH_LITERAL(" "))); michael@0: string.append(params); michael@0: } michael@0: return string; michael@0: } michael@0: michael@0: CommandLine::StringType CommandLine::GetArgumentsString() const { michael@0: StringType params; michael@0: // Append switches and arguments. michael@0: bool parse_switches = true; michael@0: for (size_t i = 1; i < argv_.size(); ++i) { michael@0: StringType arg = argv_[i]; michael@0: StringType switch_string; michael@0: StringType switch_value; michael@0: parse_switches &= arg != kSwitchTerminator; michael@0: if (i > 1) michael@0: params.append(StringType(FILE_PATH_LITERAL(" "))); michael@0: if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) { michael@0: params.append(switch_string); michael@0: if (!switch_value.empty()) { michael@0: #if defined(OS_WIN) michael@0: switch_value = QuoteForCommandLineToArgvW(switch_value); michael@0: #endif michael@0: params.append(kSwitchValueSeparator + switch_value); michael@0: } michael@0: } michael@0: else { michael@0: #if defined(OS_WIN) michael@0: arg = QuoteForCommandLineToArgvW(arg); michael@0: #endif michael@0: params.append(arg); michael@0: } michael@0: } michael@0: return params; michael@0: } michael@0: michael@0: FilePath CommandLine::GetProgram() const { michael@0: return FilePath(argv_[0]); michael@0: } michael@0: michael@0: void CommandLine::SetProgram(const FilePath& program) { michael@0: TrimWhitespace(program.value(), TRIM_ALL, &argv_[0]); michael@0: } michael@0: michael@0: bool CommandLine::HasSwitch(const std::string& switch_string) const { michael@0: return switches_.find(LowerASCIIOnWindows(switch_string)) != switches_.end(); michael@0: } michael@0: michael@0: std::string CommandLine::GetSwitchValueASCII( michael@0: const std::string& switch_string) const { michael@0: StringType value = GetSwitchValueNative(switch_string); michael@0: if (!IsStringASCII(value)) { michael@0: DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII."; michael@0: return std::string(); michael@0: } michael@0: #if defined(OS_WIN) michael@0: return WideToASCII(value); michael@0: #else michael@0: return value; michael@0: #endif michael@0: } michael@0: michael@0: FilePath CommandLine::GetSwitchValuePath( michael@0: const std::string& switch_string) const { michael@0: return FilePath(GetSwitchValueNative(switch_string)); michael@0: } michael@0: michael@0: CommandLine::StringType CommandLine::GetSwitchValueNative( michael@0: const std::string& switch_string) const { michael@0: SwitchMap::const_iterator result = switches_.end(); michael@0: result = switches_.find(LowerASCIIOnWindows(switch_string)); michael@0: return result == switches_.end() ? StringType() : result->second; michael@0: } michael@0: michael@0: void CommandLine::AppendSwitch(const std::string& switch_string) { michael@0: AppendSwitchNative(switch_string, StringType()); michael@0: } michael@0: michael@0: void CommandLine::AppendSwitchPath(const std::string& switch_string, michael@0: const FilePath& path) { michael@0: AppendSwitchNative(switch_string, path.value()); michael@0: } michael@0: michael@0: void CommandLine::AppendSwitchNative(const std::string& switch_string, michael@0: const CommandLine::StringType& value) { michael@0: std::string switch_key(LowerASCIIOnWindows(switch_string)); michael@0: #if defined(OS_WIN) michael@0: StringType combined_switch_string(ASCIIToWide(switch_key)); michael@0: #elif defined(OS_POSIX) michael@0: StringType combined_switch_string(switch_string); michael@0: #endif michael@0: size_t prefix_length = GetSwitchPrefixLength(combined_switch_string); michael@0: switches_[switch_key.substr(prefix_length)] = value; michael@0: // Preserve existing switch prefixes in |argv_|; only append one if necessary. michael@0: if (prefix_length == 0) michael@0: combined_switch_string = kSwitchPrefixes[0] + combined_switch_string; michael@0: if (!value.empty()) michael@0: combined_switch_string += kSwitchValueSeparator + value; michael@0: // Append the switch and update the switches/arguments divider |begin_args_|. michael@0: argv_.insert(argv_.begin() + begin_args_++, combined_switch_string); michael@0: } michael@0: michael@0: void CommandLine::AppendSwitchASCII(const std::string& switch_string, michael@0: const std::string& value_string) { michael@0: #if defined(OS_WIN) michael@0: AppendSwitchNative(switch_string, ASCIIToWide(value_string)); michael@0: #elif defined(OS_POSIX) michael@0: AppendSwitchNative(switch_string, value_string); michael@0: #endif michael@0: } michael@0: michael@0: void CommandLine::CopySwitchesFrom(const CommandLine& source, michael@0: const char* const switches[], michael@0: size_t count) { michael@0: for (size_t i = 0; i < count; ++i) { michael@0: if (source.HasSwitch(switches[i])) michael@0: AppendSwitchNative(switches[i], source.GetSwitchValueNative(switches[i])); michael@0: } michael@0: } michael@0: michael@0: CommandLine::StringVector CommandLine::GetArgs() const { michael@0: // Gather all arguments after the last switch (may include kSwitchTerminator). michael@0: StringVector args(argv_.begin() + begin_args_, argv_.end()); michael@0: // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?) michael@0: StringVector::iterator switch_terminator = michael@0: std::find(args.begin(), args.end(), kSwitchTerminator); michael@0: if (switch_terminator != args.end()) michael@0: args.erase(switch_terminator); michael@0: return args; michael@0: } michael@0: michael@0: void CommandLine::AppendArg(const std::string& value) { michael@0: #if defined(OS_WIN) michael@0: DCHECK(IsStringUTF8(value)); michael@0: AppendArgNative(UTF8ToWide(value)); michael@0: #elif defined(OS_POSIX) michael@0: AppendArgNative(value); michael@0: #endif michael@0: } michael@0: michael@0: void CommandLine::AppendArgPath(const FilePath& path) { michael@0: AppendArgNative(path.value()); michael@0: } michael@0: michael@0: void CommandLine::AppendArgNative(const CommandLine::StringType& value) { michael@0: argv_.push_back(value); michael@0: } michael@0: michael@0: void CommandLine::AppendArguments(const CommandLine& other, michael@0: bool include_program) { michael@0: if (include_program) michael@0: SetProgram(other.GetProgram()); michael@0: AppendSwitchesAndArguments(*this, other.argv()); michael@0: } michael@0: michael@0: void CommandLine::PrependWrapper(const CommandLine::StringType& wrapper) { michael@0: if (wrapper.empty()) michael@0: return; michael@0: // The wrapper may have embedded arguments (like "gdb --args"). In this case, michael@0: // we don't pretend to do anything fancy, we just split on spaces. michael@0: StringVector wrapper_argv; michael@0: base::SplitString(wrapper, FILE_PATH_LITERAL(' '), &wrapper_argv); michael@0: // Prepend the wrapper and update the switches/arguments |begin_args_|. michael@0: argv_.insert(argv_.begin(), wrapper_argv.begin(), wrapper_argv.end()); michael@0: begin_args_ += wrapper_argv.size(); michael@0: } michael@0: michael@0: #if defined(OS_WIN) michael@0: void CommandLine::ParseFromString(const std::wstring& command_line) { michael@0: std::wstring command_line_string; michael@0: TrimWhitespace(command_line, TRIM_ALL, &command_line_string); michael@0: if (command_line_string.empty()) michael@0: return; michael@0: michael@0: int num_args = 0; michael@0: wchar_t** args = NULL; michael@0: args = ::CommandLineToArgvW(command_line_string.c_str(), &num_args); michael@0: michael@0: DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: " michael@0: << command_line; michael@0: InitFromArgv(num_args, args); michael@0: LocalFree(args); michael@0: } michael@0: #endif