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