1 /*
2 * ConfigProfile.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include <boost/algorithm/string.hpp>
17 #include <boost/property_tree/ini_parser.hpp>
18 #include <boost/property_tree/ptree.hpp>
19
20 #include <core/FileSerializer.hpp>
21
22 #include <core/ConfigProfile.hpp>
23
24 namespace rstudio {
25 namespace core {
26
27 using namespace boost::property_tree;
28
validateParam(const std::string & paramName,const std::string & paramValue) const29 Error ConfigProfile::validateParam(const std::string& paramName,
30 const std::string& paramValue) const
31 {
32 ValidatorMap::const_iterator iter = validators_.find(paramName);
33
34 if (iter == validators_.end())
35 return Success();
36
37 const ValidatorFunc& validator = iter->second;
38
39 std::string errorMessage;
40 if (!validator(paramValue, &errorMessage))
41 {
42 return systemError(boost::system::errc::protocol_error,
43 "Param " + paramName + " is not valid: " + errorMessage,
44 ERROR_LOCATION);
45 }
46
47 return Success();
48 }
49
load(const FilePath & filePath)50 Error ConfigProfile::load(const FilePath& filePath)
51 {
52 std::string contents;
53
54 Error error = readStringFromFile(filePath, &contents);
55 if (error)
56 return error;
57
58 error = parseString(contents);
59 if (error)
60 {
61 std::string description = error.getProperty("description");
62 description += " in file " + filePath.getAbsolutePath();
63 error.addOrUpdateProperty("description", description);
64 }
65
66 return error;
67 }
68
parseString(const std::string & profileStr)69 Error ConfigProfile::parseString(const std::string& profileStr)
70 {
71 std::istringstream stream{profileStr};
72
73 // parse the profile (ini syntax)
74 ptree profileTree;
75 try
76 {
77 ini_parser::read_ini(stream, profileTree);
78 }
79 catch (const ini_parser_error& error)
80 {
81 return systemError(boost::system::errc::protocol_error,
82 "Could not parse config profile: " + error.message(),
83 ERROR_LOCATION);
84 }
85
86 // build section overrides
87 std::vector<LevelValues> levels;
88 for (const ptree::value_type& child : profileTree)
89 {
90 boost::optional<Level> matchingLevel;
91 for (Level level : sections_)
92 {
93 // if the section level starts with the defined section name
94 // from a prior call to addSections, we have a matching section level
95 if (boost::algorithm::starts_with(child.first, level.second))
96 {
97 matchingLevel = level;
98 break;
99 }
100 }
101
102 if (!matchingLevel)
103 {
104 // no matching section level - it was not specified and is thus
105 // an erroneous section
106 return systemError(boost::system::errc::protocol_error,
107 "Invalid config section " + child.first,
108 ERROR_LOCATION);
109 }
110
111 std::map<std::string, std::string> values;
112 for (const ptree::value_type& val : child.second)
113 {
114 // check to see if the parameter within the section has been registered
115 const std::string& paramName = val.first;
116
117 DefaultParamValuesMap::const_iterator defaultIter = defaultValues_.find(paramName);
118 if (defaultIter == defaultValues_.end())
119 {
120 // we require every param to have a default value / be registered so error out here
121 return systemError(boost::system::errc::protocol_error,
122 "Unknown param " + paramName + " specified",
123 ERROR_LOCATION);
124 }
125
126 std::string paramValue = val.second.get_value<std::string>();
127
128 Error error = validateParam(paramName, paramValue);
129 if (error)
130 return error;
131
132 values[paramName] = paramValue;
133 }
134
135 std::string levelValue = child.first.substr(matchingLevel.get().second.size());
136 levels.push_back({{matchingLevel.get().first, levelValue}, values});
137 }
138
139 // stable sort the levels in ascending level number
140 // this will preserve the order of same-level section overrides
141 std::stable_sort(
142 levels.begin(),
143 levels.end(),
144 [](const LevelValues& a, const LevelValues& b) { return a.first.first < b.first.first; });
145
146 // assign the temporary variable levels to the class member
147 // this assures that we can safely call this method multiple times, preserving the last
148 // good configuration in case we error out above due to invalid configuration
149 levels_ = levels;
150
151 return Success();
152 }
153
getLevelNames(uint32_t level) const154 std::vector<std::string> ConfigProfile::getLevelNames(uint32_t level) const
155 {
156 std::vector<std::string> levelNames;
157
158 for (const LevelValues& value : levels_)
159 {
160 if (value.first.first == level)
161 levelNames.push_back(value.first.second);
162 }
163
164 return levelNames;
165 }
166
167 } // core
168 } // namespace rstudio
169