| // Copyright 2018 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/tools/variable_expander.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 { |
| |
| // Expects ",n" or ",n,m" in |range|. Puts n into |start| and m into |count| if |
| // present. Returns true if |range| was well formatted and parsing the numbers |
| // succeeded. |
| bool ParseRange(base::StringPiece range, size_t* start, size_t* count) { |
| std::vector<base::StringPiece> 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; |
| } |
| |
| // Expands all occurrences of a variable with name |variable_name| to |
| // |replacement| or parts of it. This method handles the following variants: |
| // - ${variable_name} -> |replacement| |
| // - ${variable_name,pos} -> |replacement.substr(pos)| |
| // - ${variable_name,pos,count} -> |replacement.substr(pos,count)| |
| // Strictly enforces the format (up to whitespace), e.g. |
| // ${variable_name , 2 , 9 } works, but ${variable_name,2o,9e} doesn't. |
| // Returns true if no error occured. |
| bool Expand(base::StringPiece variable_name, |
| base::StringPiece replacement, |
| std::string* str) { |
| std::string token("${"); |
| variable_name.AppendToString(&token); |
| size_t token_start = 0; |
| bool no_error = true; |
| int count = 0; |
| while ((token_start = str->find(token, token_start)) != std::string::npos) { |
| // Exit if |token| is found too often. |
| if (++count > 100) { |
| LOG(ERROR) << "Maximum replacement count exceeded for " << variable_name |
| << " in string '" << *str << "'"; |
| no_error = false; |
| break; |
| } |
| |
| // Find the closing braces. |
| 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; |
| } |
| |
| // Full variable, e.g. ${machine_name} or ${machine_name,8,3}. |
| DCHECK_GE(range_end, range_start); |
| const base::StringPiece full_token = base::StringPiece(*str).substr( |
| token_start, range_end + 1 - token_start); |
| |
| // Determine if the variable defines a range, e.g. ${machine_name,8,3}. |
| size_t replacement_start = 0; |
| size_t replacement_count = std::string::npos; |
| if (range_end > range_start) { |
| const base::StringPiece range = |
| base::StringPiece(*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 base::StringPiece replacement_part = |
| replacement.substr(replacement_start, replacement_count); |
| // Don't use ReplaceSubstringsAfterOffset here, it can lead to a doubling |
| // of tokens, see VariableExpanderTest.DoesNotRecurse test. |
| base::ReplaceFirstSubstringAfterOffset(str, token_start, full_token, |
| replacement_part); |
| token_start += replacement_part.size(); |
| } |
| return no_error; |
| } |
| |
| } // namespace |
| |
| namespace chromeos { |
| |
| VariableExpander::VariableExpander() = default; |
| |
| VariableExpander::VariableExpander(std::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::DICTIONARY: { |
| for (const auto& child : value->DictItems()) |
| 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: { |
| // Nothing to do here. |
| break; |
| } |
| } |
| return no_error; |
| } |
| |
| } // namespace chromeos |