| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/system/name_value_pairs_parser.h" |
| |
| #include <stddef.h> |
| #include <unistd.h> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/process/launch.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| |
| namespace chromeos { // NOLINT |
| namespace system { |
| |
| namespace { |
| |
| // The name of the stable device secret VPD key. |
| const char kStableDeviceSecretDoNotShare[] = |
| "stable_device_secret_DO_NOT_SHARE"; |
| |
| // Runs a tool and capture its standard output into |output|. Returns false |
| // if the tool cannot be run. |
| bool GetToolOutput(int argc, const char* argv[], std::string* output) { |
| DCHECK_GE(argc, 1); |
| |
| if (!base::PathExists(base::FilePath(argv[0]))) { |
| LOG(WARNING) << "Tool for statistics not found: " << argv[0]; |
| return false; |
| } |
| |
| std::vector<std::string> args; |
| for (int argn = 0; argn < argc; ++argn) |
| args.push_back(argv[argn]); |
| if (!base::GetAppOutput(args, output)) { |
| LOG(WARNING) << "Error executing " << argv[0]; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Assigns a non quoted version of |input| to |unquoted|, and returns |
| // whether |input| was actually quoted or not. |
| bool GetUnquotedString(const std::string& input, std::string* unquoted) { |
| const size_t input_size = input.size(); |
| |
| if (input_size >= 2 && input[0] == '"' && input[input_size - 1] == '"') |
| unquoted->assign(input, 1, input_size - 2); |
| else |
| unquoted->assign(input); |
| |
| return unquoted->size() != input_size; |
| } |
| |
| // Assigns a non commented version of |input| to |uncommented|. Whitespace |
| // before the comment is trimmed. |
| void GetUncommentedString(const std::string& input, std::string* uncommented) { |
| const size_t comment_pos = input.find('#'); |
| if (comment_pos == std::string::npos) { |
| uncommented->assign(input); |
| } else { |
| uncommented->assign(input, 0, comment_pos); |
| base::TrimWhitespaceASCII(*uncommented, base::TRIM_TRAILING, uncommented); |
| } |
| } |
| |
| // Parse a name from |input|, validating that it is in |format|, and assign it |
| // to |name|. |
| bool ParseName(const std::string& input, |
| system::NameValuePairsFormat format, |
| std::string* name) { |
| bool parsed_ok = false; |
| switch (format) { |
| case NameValuePairsFormat::kVpdDump: |
| // The name must be quoted, and we unquote it. |
| parsed_ok = GetUnquotedString(input, name); |
| break; |
| case NameValuePairsFormat::kMachineInfo: |
| // The name may be quoted, and we unquote it. |
| GetUnquotedString(input, name); |
| parsed_ok = true; |
| break; |
| case NameValuePairsFormat::kCrossystem: |
| // We trim all ASCII whitespace and the name then must not be quoted. |
| base::TrimWhitespaceASCII(input, base::TRIM_ALL, name); |
| parsed_ok = !GetUnquotedString(*name, name); |
| break; |
| } |
| |
| // Names must not be empty in addition to having parsed successfully. |
| return parsed_ok && !name->empty(); |
| } |
| |
| // Parse a value from |input|, validating that it is in |format|, and assign it |
| // to |name|. |
| bool ParseValue(const std::string& input, |
| system::NameValuePairsFormat format, |
| std::string* value) { |
| if (format == NameValuePairsFormat::kCrossystem) { |
| // The crossystem format allows for comments, remove them. |
| GetUncommentedString(input, value); |
| // We trim all ASCII whitespace and preserve the rest as is. |
| base::TrimWhitespaceASCII(*value, base::TRIM_ALL, value); |
| return true; |
| } else { |
| // The value must be quoted, and we unquote it. |
| return GetUnquotedString(input, value); |
| } |
| } |
| |
| // Return a string for logging a value that does not expose data that should |
| // not be visible in the logs. |
| std::string GetLoggingStringForValue(const std::string& name, |
| const std::string& value) { |
| // TODO(crbug.com/1250037): Delete special casing of secret after fixing. |
| if (name == kStableDeviceSecretDoNotShare) |
| return "value edited out for security"; |
| return "value: " + value; |
| } |
| |
| const char* GetNameValuePairsFormatName(NameValuePairsFormat format) { |
| switch (format) { |
| case NameValuePairsFormat::kVpdDump: |
| return "VPD dump"; |
| case NameValuePairsFormat::kMachineInfo: |
| return "machine info"; |
| case NameValuePairsFormat::kCrossystem: |
| return "crossystem"; |
| } |
| return "unknown"; |
| } |
| |
| } // namespace |
| |
| NameValuePairsParser::NameValuePairsParser(NameValueMap* map) |
| : map_(map) { |
| } |
| |
| bool NameValuePairsParser::ParseNameValuePairsFromFile( |
| const base::FilePath& file_path, |
| NameValuePairsFormat format) { |
| std::string file_contents; |
| if (base::ReadFileToString(file_path, &file_contents)) { |
| return ParseNameValuePairs(file_contents, format, |
| /*debug_source=*/file_path.value()); |
| } else { |
| if (base::SysInfo::IsRunningOnChromeOS()) |
| VLOG(1) << "Statistics file not present: " << file_path.value(); |
| return false; |
| } |
| } |
| |
| bool NameValuePairsParser::ParseNameValuePairsFromTool( |
| int argc, |
| const char* argv[], |
| NameValuePairsFormat format) { |
| DCHECK_GE(argc, 1); |
| |
| std::string output_string; |
| if (!GetToolOutput(argc, argv, &output_string)) |
| return false; |
| |
| return ParseNameValuePairs(output_string, format, /*debug_source=*/argv[0]); |
| } |
| |
| void NameValuePairsParser::DeletePairsWithValue(const std::string& value) { |
| auto it = map_->begin(); |
| while (it != map_->end()) { |
| if (it->second == value) { |
| it = map_->erase(it); |
| } else { |
| it++; |
| } |
| } |
| } |
| |
| void NameValuePairsParser::AddNameValuePair(const std::string& name, |
| const std::string& value) { |
| const auto it = map_->find(name); |
| if (it == map_->end()) { |
| (*map_)[name] = value; |
| VLOG(1) << "Name: " << name << ", " |
| << GetLoggingStringForValue(name, value); |
| } else if (it->second != value) { |
| LOG(WARNING) << "Name: " << name << " already has " |
| << GetLoggingStringForValue(name, it->second) |
| << ", ignoring new " << GetLoggingStringForValue(name, value); |
| } |
| } |
| |
| bool NameValuePairsParser::ParseNameValuePairs( |
| const std::string& input, |
| NameValuePairsFormat format, |
| const std::string& debug_source) { |
| bool all_valid = true; |
| |
| // We use StringPairs to parse pairs since this is the class that is also |
| // used to parse machine-info for server backed state keys in Chromium OS. |
| base::StringPairs pairs; |
| // This gives us somewhat more lenient parsing than strictly respecting the |
| // formats we want. For example, whitespace will be removed around the equal |
| // sign regardless of format. That's okay as we care more about consistency |
| // with the server backed state keys parser than the strictness of the format, |
| // and we still preserve our ability to handle the quoted values as we wish. |
| base::SplitStringIntoKeyValuePairs(input, '=', '\n', &pairs); |
| |
| for (const auto& pair : pairs) { |
| std::string name; |
| if (!ParseName(pair.first, format, &name)) { |
| LOG(WARNING) << "Could not parse " << GetNameValuePairsFormatName(format) |
| << " name-value name from: " << pair.first; |
| all_valid = false; |
| continue; |
| } |
| |
| std::string value; |
| if (!ParseValue(pair.second, format, &value)) { |
| LOG(WARNING) << "Could not parse " << GetNameValuePairsFormatName(format) |
| << " name-value value from: " << pair.second; |
| all_valid = false; |
| continue; |
| } |
| |
| // TODO(crbug.com/1250037): Delete logging after the bug is fixed. |
| LOG_IF(WARNING, name == kStableDeviceSecretDoNotShare) |
| << "Statistic " << kStableDeviceSecretDoNotShare << " exposed in " |
| << debug_source; |
| |
| AddNameValuePair(name, value); |
| } |
| |
| return all_valid; |
| } |
| |
| } // namespace system |
| } // namespace chromeos |