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