#include "chromeos/components/onc/variable_expander.h"
#include <algorithm>
#include <string_view>
#include "base/containers/flat_map.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/syslog_logging.h"
#include "base/values.h"
namespace {
bool ParseRange(std::string_view range, size_t* start, size_t* count) {
std::vector<std::string_view> parts = base::SplitStringPiece(
range, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(!parts.empty());
if (!parts[0].empty())
return false;
if (parts.size() > 1 && !base::StringToSizeT(parts[1], start))
return false;
if (parts.size() > 2 && !base::StringToSizeT(parts[2], count))
return false;
if (parts.size() > 3)
return false;
return true;
}
bool Expand(std::string_view variable_name,
std::string_view replacement,
std::string* str) {
std::string token = base::StrCat({"${", variable_name});
size_t token_start = 0;
bool no_error = true;
int count = 0;
while ((token_start = str->find(token, token_start)) != std::string::npos) {
if (++count > 100) {
LOG(ERROR) << "Maximum replacement count exceeded for " << variable_name
<< " in string '" << *str << "'";
no_error = false;
break;
}
const size_t range_start = token_start + token.size();
const size_t range_end = str->find("}", range_start);
if (range_end == std::string::npos) {
LOG(ERROR) << "Closing braces not found for " << variable_name
<< " in string '" << *str << "'";
++token_start;
no_error = false;
continue;
}
DCHECK_GE(range_end, range_start);
const std::string_view full_token =
std::string_view(*str).substr(token_start, range_end + 1 - token_start);
size_t replacement_start = 0;
size_t replacement_count = std::string::npos;
if (range_end > range_start) {
const std::string_view range =
std::string_view(*str).substr(range_start, range_end - range_start);
if (!ParseRange(range, &replacement_start, &replacement_count)) {
LOG(ERROR) << "Invalid range definition for " << variable_name
<< " in string '" << *str << "'";
token_start += full_token.size();
no_error = false;
continue;
}
}
const std::string_view replacement_part = replacement.substr(
std::min(replacement_start, replacement.size()), replacement_count);
base::ReplaceFirstSubstringAfterOffset(str, token_start, full_token,
replacement_part);
token_start += replacement_part.size();
}
return no_error;
}
}
namespace chromeos {
VariableExpander::VariableExpander(
base::flat_map<std::string, std::string> variables)
: variables_(std::move(variables)) {}
VariableExpander::~VariableExpander() = default;
bool VariableExpander::ExpandString(std::string* str) const {
bool no_error = true;
for (const auto& kv : variables_)
no_error &= Expand(kv.first, kv.second, str);
return no_error;
}
bool VariableExpander::ExpandValue(base::Value* value) const {
bool no_error = true;
switch (value->type()) {
case base::Value::Type::STRING: {
std::string expanded_str = value->GetString();
no_error &= ExpandString(&expanded_str);
*value = base::Value(expanded_str);
break;
}
case base::Value::Type::DICT: {
for (const auto child : value->GetDict()) {
no_error &= ExpandValue(&child.second);
}
break;
}
case base::Value::Type::LIST: {
for (base::Value& child : value->GetList())
no_error &= ExpandValue(&child);
break;
}
case base::Value::Type::BOOLEAN:
case base::Value::Type::INTEGER:
case base::Value::Type::DOUBLE:
case base::Value::Type::BINARY:
case base::Value::Type::NONE: {
break;
}
}
return no_error;
}
}