]> granicus.if.org Git - icinga2/blob - lib/icinga/macroprocessor.cpp
Rename DynamicObject/DynamicType to ConfigObject/ConfigType
[icinga2] / lib / icinga / macroprocessor.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "icinga/macroprocessor.hpp"
21 #include "icinga/macroresolver.hpp"
22 #include "icinga/customvarobject.hpp"
23 #include "base/array.hpp"
24 #include "base/objectlock.hpp"
25 #include "base/logger.hpp"
26 #include "base/context.hpp"
27 #include "base/configobject.hpp"
28 #include "base/scriptframe.hpp"
29 #include <boost/foreach.hpp>
30 #include <boost/algorithm/string/split.hpp>
31 #include <boost/algorithm/string/join.hpp>
32 #include <boost/algorithm/string/classification.hpp>
33
34 using namespace icinga;
35
36 Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
37     const CheckResult::Ptr& cr, String *missingMacro,
38     const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
39     bool useResolvedMacros)
40 {
41         Value result;
42
43         if (str.IsEmpty())
44                 return Empty;
45
46         if (str.IsScalar()) {
47                 result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
48                     resolvedMacros, useResolvedMacros);
49         } else if (str.IsObjectType<Array>()) {
50                 Array::Ptr resultArr = new Array();
51                 Array::Ptr arr = str;
52
53                 ObjectLock olock(arr);
54
55                 BOOST_FOREACH(const Value& arg, arr) {
56                         /* Note: don't escape macros here. */
57                         Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
58                             EscapeCallback(), resolvedMacros, useResolvedMacros);
59
60                         if (value.IsObjectType<Array>())
61                                 resultArr->Add(Utility::Join(value, ';'));
62                         else
63                                 resultArr->Add(value);
64                 }
65
66                 result = resultArr;
67         } else if (str.IsObjectType<Dictionary>()) {
68                 Dictionary::Ptr resultDict = new Dictionary();
69                 Dictionary::Ptr dict = str;
70
71                 ObjectLock olock(dict);
72
73                 BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
74                         /* Note: don't escape macros here. */
75                         resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
76                             EscapeCallback(), resolvedMacros, useResolvedMacros));
77                 }
78
79                 result = resultDict;
80         } else if (str.IsObjectType<Function>()) {
81                 result = EvaluateFunction(str, resolvers, cr, missingMacro, escapeFn, resolvedMacros, useResolvedMacros, 0);
82         } else {
83                 BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
84         }
85
86         return result;
87 }
88
89 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
90     const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
91 {
92         CONTEXT("Resolving macro '" + macro + "'");
93
94         *recursive_macro = false;
95
96         std::vector<String> tokens;
97         boost::algorithm::split(tokens, macro, boost::is_any_of("."));
98
99         String objName;
100         if (tokens.size() > 1) {
101                 objName = tokens[0];
102                 tokens.erase(tokens.begin());
103         }
104
105         BOOST_FOREACH(const ResolverSpec& resolver, resolvers) {
106                 if (!objName.IsEmpty() && objName != resolver.first)
107                         continue;
108
109                 if (objName.IsEmpty()) {
110                         CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.second);
111
112                         if (dobj) {
113                                 Dictionary::Ptr vars = dobj->GetVars();
114
115                                 if (vars && vars->Contains(macro)) {
116                                         *result = vars->Get(macro);
117                                         *recursive_macro = true;
118                                         return true;
119                                 }
120                         }
121                 }
122
123                 MacroResolver *mresolver = dynamic_cast<MacroResolver *>(resolver.second.get());
124
125                 if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
126                         return true;
127
128                 Value ref = resolver.second;
129                 bool valid = true;
130
131                 BOOST_FOREACH(const String& token, tokens) {
132                         if (ref.IsObjectType<Dictionary>()) {
133                                 Dictionary::Ptr dict = ref;
134                                 if (dict->Contains(token)) {
135                                         ref = dict->Get(token);
136                                         continue;
137                                 } else {
138                                         valid = false;
139                                         break;
140                                 }
141                         } else if (ref.IsObject()) {
142                                 Object::Ptr object = ref;
143
144                                 Type::Ptr type = object->GetReflectionType();
145
146                                 if (!type) {
147                                         valid = false;
148                                         break;
149                                 }
150
151                                 int field = type->GetFieldId(token);
152
153                                 if (field == -1) {
154                                         valid = false;
155                                         break;
156                                 }
157
158                                 ref = object->GetField(field);
159                         }
160                 }
161
162                 if (valid) {
163                         if (tokens[0] == "vars" ||
164                             tokens[0] == "action_url" ||
165                             tokens[0] == "notes_url" ||
166                             tokens[0] == "notes")
167                                 *recursive_macro = true;
168
169                         *result = ref;
170                         return true;
171                 }
172         }
173
174         return false;
175 }
176
177 Value MacroProcessor::InternalResolveMacrosShim(const std::vector<Value>& args, const ResolverList& resolvers,
178     const CheckResult::Ptr& cr, String *missingMacro,
179     const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
180     bool useResolvedMacros, int recursionLevel)
181 {
182         if (args.size() < 1)
183                 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
184
185         return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, missingMacro, escapeFn,
186             resolvedMacros, useResolvedMacros, recursionLevel);
187 }
188
189 Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
190     const CheckResult::Ptr& cr, String *missingMacro,
191     const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
192     bool useResolvedMacros, int recursionLevel)
193 {
194         Dictionary::Ptr resolvers_this = new Dictionary();
195
196         BOOST_FOREACH(const ResolverSpec& resolver, resolvers) {
197                 resolvers_this->Set(resolver.first, resolver.second);
198         }
199
200         resolvers_this->Set("macro", new Function(boost::bind(&MacroProcessor::InternalResolveMacrosShim,
201             _1, boost::cref(resolvers), cr, missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros,
202             recursionLevel + 1)));
203
204         ScriptFrame frame(resolvers_this);
205         return func->Invoke();
206 }
207
208 Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
209     const CheckResult::Ptr& cr, String *missingMacro,
210     const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
211     bool useResolvedMacros, int recursionLevel)
212 {
213         CONTEXT("Resolving macros for string '" + str + "'");
214
215         if (recursionLevel > 15)
216                 BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
217
218         size_t offset, pos_first, pos_second;
219         offset = 0;
220
221         Dictionary::Ptr resolvers_this;
222
223         String result = str;
224         while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
225                 pos_second = result.FindFirstOf("$", pos_first + 1);
226
227                 if (pos_second == String::NPos)
228                         BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
229
230                 String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
231
232                 Value resolved_macro;
233                 bool recursive_macro;
234                 bool found;
235
236                 if (useResolvedMacros) {
237                         recursive_macro = false;
238                         found = resolvedMacros->Contains(name);
239
240                         if (found)
241                                 resolved_macro = resolvedMacros->Get(name);
242                 } else
243                         found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
244
245                 /* $$ is an escape sequence for $. */
246                 if (name.IsEmpty()) {
247                         resolved_macro = "$";
248                         found = true;
249                 }
250
251                 if (resolved_macro.IsObjectType<Function>()) {
252                         resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, missingMacro, escapeFn,
253                             resolvedMacros, useResolvedMacros, recursionLevel);
254                 }
255
256                 if (!found) {
257                         if (!missingMacro)
258                                 Log(LogWarning, "MacroProcessor")
259                                     << "Macro '" << name << "' is not defined.";
260                         else
261                                 *missingMacro = name;
262                 }
263
264                 /* recursively resolve macros in the macro if it was a user macro */
265                 if (recursive_macro) {
266                         if (resolved_macro.IsObjectType<Array>()) {
267                                 Array::Ptr arr = resolved_macro;
268                                 Array::Ptr resolved_arr = new Array();
269
270                                 ObjectLock olock(arr);
271                                 BOOST_FOREACH(const Value& value, arr) {
272                                         if (value.IsScalar()) {
273                                                 resolved_arr->Add(InternalResolveMacros(value,
274                                                         resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
275                                                         false, recursionLevel + 1));
276                                         } else
277                                                 resolved_arr->Add(value);
278                                 }
279
280                                 resolved_macro = resolved_arr;
281                         } else if (resolved_macro.IsString()) {
282                                 resolved_macro = InternalResolveMacros(resolved_macro,
283                                         resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
284                                         false, recursionLevel + 1);
285                         }
286                 }
287
288                 if (!useResolvedMacros && found && resolvedMacros)
289                         resolvedMacros->Set(name, resolved_macro);
290
291                 if (escapeFn)
292                         resolved_macro = escapeFn(resolved_macro);
293
294                 /* we're done if this is the only macro and there are no other non-macro parts in the string */
295                 if (pos_first == 0 && pos_second == str.GetLength() - 1)
296                         return resolved_macro;
297                 else if (resolved_macro.IsObjectType<Array>())
298                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
299
300                 if (resolved_macro.IsObjectType<Array>()) {
301                         /* don't allow mixing strings and arrays in macro strings */
302                         if (pos_first != 0 || pos_second != str.GetLength() - 1)
303                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
304
305                         return resolved_macro;
306                 }
307
308                 String resolved_macro_str = resolved_macro;
309
310                 result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
311                 offset = pos_first + resolved_macro_str.GetLength();
312         }
313
314         return result;
315 }
316
317
318 bool MacroProcessor::ValidateMacroString(const String& macro)
319 {
320         if (macro.IsEmpty())
321                 return true;
322
323         size_t pos_first, pos_second, offset;
324         offset = 0;
325
326         while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
327                 pos_second = macro.FindFirstOf("$", pos_first + 1);
328
329                 if (pos_second == String::NPos)
330                         return false;
331
332                 offset = pos_second + 1;
333         }
334
335         return true;
336 }