variable_expander.cc 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "chromeos/components/onc/variable_expander.h"
  5. #include <algorithm>
  6. #include "base/containers/flat_map.h"
  7. #include "base/strings/strcat.h"
  8. #include "base/strings/string_number_conversions.h"
  9. #include "base/strings/string_split.h"
  10. #include "base/strings/string_util.h"
  11. #include "base/syslog_logging.h"
  12. #include "base/values.h"
  13. namespace {
  14. // Expects ",n" or ",n,m" in |range|. Puts n into |start| and m into |count| if
  15. // present. Returns true if |range| was well formatted and parsing the numbers
  16. // succeeded.
  17. bool ParseRange(base::StringPiece range, size_t* start, size_t* count) {
  18. std::vector<base::StringPiece> parts = base::SplitStringPiece(
  19. range, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  20. DCHECK(!parts.empty());
  21. if (!parts[0].empty())
  22. return false;
  23. if (parts.size() > 1 && !base::StringToSizeT(parts[1], start))
  24. return false;
  25. if (parts.size() > 2 && !base::StringToSizeT(parts[2], count))
  26. return false;
  27. if (parts.size() > 3)
  28. return false;
  29. return true;
  30. }
  31. // Expands all occurrences of a variable with name |variable_name| to
  32. // |replacement| or parts of it. This method handles the following variants:
  33. // - ${variable_name} -> |replacement|
  34. // - ${variable_name,pos} -> |replacement.substr(pos)|
  35. // - ${variable_name,pos,count} -> |replacement.substr(pos,count)|
  36. // Strictly enforces the format (up to whitespace), e.g.
  37. // ${variable_name , 2 , 9 } works, but ${variable_name,2o,9e} doesn't.
  38. // Returns true if no error occured.
  39. bool Expand(base::StringPiece variable_name,
  40. base::StringPiece replacement,
  41. std::string* str) {
  42. std::string token = base::StrCat({"${", variable_name});
  43. size_t token_start = 0;
  44. bool no_error = true;
  45. int count = 0;
  46. while ((token_start = str->find(token, token_start)) != std::string::npos) {
  47. // Exit if |token| is found too often.
  48. if (++count > 100) {
  49. LOG(ERROR) << "Maximum replacement count exceeded for " << variable_name
  50. << " in string '" << *str << "'";
  51. no_error = false;
  52. break;
  53. }
  54. // Find the closing braces.
  55. const size_t range_start = token_start + token.size();
  56. const size_t range_end = str->find("}", range_start);
  57. if (range_end == std::string::npos) {
  58. LOG(ERROR) << "Closing braces not found for " << variable_name
  59. << " in string '" << *str << "'";
  60. ++token_start;
  61. no_error = false;
  62. continue;
  63. }
  64. // Full variable, e.g. ${machine_name} or ${machine_name,8,3}.
  65. DCHECK_GE(range_end, range_start);
  66. const base::StringPiece full_token = base::StringPiece(*str).substr(
  67. token_start, range_end + 1 - token_start);
  68. // Determine if the variable defines a range, e.g. ${machine_name,8,3}.
  69. size_t replacement_start = 0;
  70. size_t replacement_count = std::string::npos;
  71. if (range_end > range_start) {
  72. const base::StringPiece range =
  73. base::StringPiece(*str).substr(range_start, range_end - range_start);
  74. if (!ParseRange(range, &replacement_start, &replacement_count)) {
  75. LOG(ERROR) << "Invalid range definition for " << variable_name
  76. << " in string '" << *str << "'";
  77. token_start += full_token.size();
  78. no_error = false;
  79. continue;
  80. }
  81. }
  82. const base::StringPiece replacement_part = replacement.substr(
  83. std::min(replacement_start, replacement.size()), replacement_count);
  84. // Don't use ReplaceSubstringsAfterOffset here, it can lead to a doubling
  85. // of tokens, see VariableExpanderTest.DoesNotRecurse test.
  86. base::ReplaceFirstSubstringAfterOffset(str, token_start, full_token,
  87. replacement_part);
  88. token_start += replacement_part.size();
  89. }
  90. return no_error;
  91. }
  92. } // namespace
  93. namespace chromeos {
  94. VariableExpander::VariableExpander(
  95. base::flat_map<std::string, std::string> variables)
  96. : variables_(std::move(variables)) {}
  97. VariableExpander::~VariableExpander() = default;
  98. bool VariableExpander::ExpandString(std::string* str) const {
  99. bool no_error = true;
  100. for (const auto& kv : variables_)
  101. no_error &= Expand(kv.first, kv.second, str);
  102. return no_error;
  103. }
  104. bool VariableExpander::ExpandValue(base::Value* value) const {
  105. bool no_error = true;
  106. switch (value->type()) {
  107. case base::Value::Type::STRING: {
  108. std::string expanded_str = value->GetString();
  109. no_error &= ExpandString(&expanded_str);
  110. *value = base::Value(expanded_str);
  111. break;
  112. }
  113. case base::Value::Type::DICTIONARY: {
  114. for (const auto child : value->DictItems())
  115. no_error &= ExpandValue(&child.second);
  116. break;
  117. }
  118. case base::Value::Type::LIST: {
  119. for (base::Value& child : value->GetListDeprecated())
  120. no_error &= ExpandValue(&child);
  121. break;
  122. }
  123. case base::Value::Type::BOOLEAN:
  124. case base::Value::Type::INTEGER:
  125. case base::Value::Type::DOUBLE:
  126. case base::Value::Type::BINARY:
  127. case base::Value::Type::NONE: {
  128. // Nothing to do here.
  129. break;
  130. }
  131. }
  132. return no_error;
  133. }
  134. } // namespace chromeos