blob: d311c81ac9ce645e49ff920479d426b5e87edaed [file] [log] [blame]
// 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