1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
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. *
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. *
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 ******************************************************************************/
20 #include "remote/consolehandler.hpp"
21 #include "remote/httputility.hpp"
22 #include "remote/filterutility.hpp"
23 #include "config/configcompiler.hpp"
24 #include "base/configtype.hpp"
25 #include "base/configwriter.hpp"
26 #include "base/scriptglobal.hpp"
27 #include "base/logger.hpp"
28 #include "base/serializer.hpp"
29 #include "base/timer.hpp"
30 #include "base/initialize.hpp"
31 #include <boost/thread/once.hpp>
34 using namespace icinga;
36 REGISTER_URLHANDLER("/v1/console", ConsoleHandler);
38 static boost::mutex l_QueryMutex;
39 static std::map<String, ApiScriptFrame> l_ApiScriptFrames;
40 static Timer::Ptr l_FrameCleanupTimer;
41 static boost::mutex l_ApiScriptMutex;
43 static void ScriptFrameCleanupHandler()
45 boost::mutex::scoped_lock lock(l_ApiScriptMutex);
47 std::vector<String> cleanup_keys;
49 typedef std::pair<String, ApiScriptFrame> KVPair;
51 for (const KVPair& kv : l_ApiScriptFrames) {
52 if (kv.second.Seen < Utility::GetTime() - 1800)
53 cleanup_keys.push_back(kv.first);
56 for (const String& key : cleanup_keys)
57 l_ApiScriptFrames.erase(key);
60 static void EnsureFrameCleanupTimer()
62 static boost::once_flag once = BOOST_ONCE_INIT;
64 boost::call_once(once, []() {
65 l_FrameCleanupTimer = new Timer();
66 l_FrameCleanupTimer->OnTimerExpired.connect(std::bind(ScriptFrameCleanupHandler));
67 l_FrameCleanupTimer->SetInterval(30);
68 l_FrameCleanupTimer->Start();
72 bool ConsoleHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response, const Dictionary::Ptr& params)
74 if (request.RequestUrl->GetPath().size() > 3)
77 if (request.RequestMethod != "POST")
82 String methodName = request.RequestUrl->GetPath()[2];
84 FilterUtility::CheckPermission(user, "console");
86 String session = HttpUtility::GetLastParameter(params, "session");
88 if (session.IsEmpty())
89 session = Utility::NewUniqueID();
91 String command = HttpUtility::GetLastParameter(params, "command");
93 bool sandboxed = HttpUtility::GetLastParameter(params, "sandboxed");
95 if (methodName == "execute-script")
96 return ExecuteScriptHelper(request, response, params, command, session, sandboxed);
97 else if (methodName == "auto-complete-script")
98 return AutocompleteScriptHelper(request, response, params, command, session, sandboxed);
100 HttpUtility::SendJsonError(response, params, 400, "Invalid method specified: " + methodName);
104 bool ConsoleHandler::ExecuteScriptHelper(HttpRequest& request, HttpResponse& response,
105 const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
107 Log(LogNotice, "Console")
108 << "Executing expression: " << command;
110 EnsureFrameCleanupTimer();
112 ApiScriptFrame& lsf = l_ApiScriptFrames[session];
113 lsf.Seen = Utility::GetTime();
116 lsf.Locals = new Dictionary();
118 String fileName = "<" + Convert::ToString(lsf.NextLine) + ">";
121 lsf.Lines[fileName] = command;
123 Dictionary::Ptr resultInfo;
124 std::unique_ptr<Expression> expr;
128 expr = ConfigCompiler::CompileText(fileName, command);
130 ScriptFrame frame(true);
131 frame.Locals = lsf.Locals;
132 frame.Self = lsf.Locals;
133 frame.Sandboxed = sandboxed;
135 exprResult = expr->Evaluate(frame);
137 resultInfo = new Dictionary({
139 { "status", "Executed successfully." },
140 { "result", Serialize(exprResult, 0) }
142 } catch (const ScriptError& ex) {
143 DebugInfo di = ex.GetDebugInfo();
145 std::ostringstream msgbuf;
147 msgbuf << di.Path << ": " << lsf.Lines[di.Path] << "\n"
148 << String(di.Path.GetLength() + 2, ' ')
149 << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n"
150 << ex.what() << "\n";
152 resultInfo = new Dictionary({
154 { "status", String(msgbuf.str()) },
155 { "incomplete_expression", ex.IsIncompleteExpression() },
156 { "debug_info", new Dictionary({
158 { "first_line", di.FirstLine },
159 { "first_column", di.FirstColumn },
160 { "last_line", di.LastLine },
161 { "last_column", di.LastColumn }
166 Dictionary::Ptr result = new Dictionary({
167 { "results", new Array({ resultInfo }) }
170 response.SetStatus(200, "OK");
171 HttpUtility::SendJsonBody(response, params, result);
176 bool ConsoleHandler::AutocompleteScriptHelper(HttpRequest& request, HttpResponse& response,
177 const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
179 Log(LogInformation, "Console")
180 << "Auto-completing expression: " << command;
182 EnsureFrameCleanupTimer();
184 ApiScriptFrame& lsf = l_ApiScriptFrames[session];
185 lsf.Seen = Utility::GetTime();
188 lsf.Locals = new Dictionary();
191 ScriptFrame frame(true);
192 frame.Locals = lsf.Locals;
193 frame.Self = lsf.Locals;
194 frame.Sandboxed = sandboxed;
196 Dictionary::Ptr result1 = new Dictionary({
198 { "status", "Auto-completed successfully." },
199 { "suggestions", Array::FromVector(GetAutocompletionSuggestions(command, frame)) }
202 Dictionary::Ptr result = new Dictionary({
203 { "results", new Array({ result1 }) }
206 response.SetStatus(200, "OK");
207 HttpUtility::SendJsonBody(response, params, result);
212 static void AddSuggestion(std::vector<String>& matches, const String& word, const String& suggestion)
214 if (suggestion.Find(word) != 0)
217 matches.push_back(suggestion);
220 static void AddSuggestions(std::vector<String>& matches, const String& word, const String& pword, bool withFields, const Value& value)
224 if (!pword.IsEmpty())
225 prefix = pword + ".";
227 if (value.IsObjectType<Dictionary>()) {
228 Dictionary::Ptr dict = value;
230 ObjectLock olock(dict);
231 for (const Dictionary::Pair& kv : dict) {
232 AddSuggestion(matches, word, prefix + kv.first);
237 Type::Ptr type = value.GetReflectionType();
239 for (int i = 0; i < type->GetFieldCount(); i++) {
240 Field field = type->GetFieldInfo(i);
242 AddSuggestion(matches, word, prefix + field.Name);
246 Object::Ptr prototype = type->GetPrototype();
247 Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(prototype);
250 ObjectLock olock(dict);
251 for (const Dictionary::Pair& kv : dict) {
252 AddSuggestion(matches, word, prefix + kv.first);
256 type = type->GetBaseType();
261 std::vector<String> ConsoleHandler::GetAutocompletionSuggestions(const String& word, ScriptFrame& frame)
263 std::vector<String> matches;
265 for (const String& keyword : ConfigWriter::GetKeywords()) {
266 AddSuggestion(matches, word, keyword);
270 ObjectLock olock(frame.Locals);
271 for (const Dictionary::Pair& kv : frame.Locals) {
272 AddSuggestion(matches, word, kv.first);
277 ObjectLock olock(ScriptGlobal::GetGlobals());
278 for (const Dictionary::Pair& kv : ScriptGlobal::GetGlobals()) {
279 AddSuggestion(matches, word, kv.first);
284 Array::Ptr imports = ScriptFrame::GetImports();
285 ObjectLock olock(imports);
286 for (const Value& import : imports) {
287 AddSuggestions(matches, word, "", false, import);
291 String::SizeType cperiod = word.RFind(".");
293 if (cperiod != String::NPos) {
294 String pword = word.SubStr(0, cperiod);
299 std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("temp", pword);
302 value = expr->Evaluate(frame);
304 AddSuggestions(matches, word, pword, true, value);
305 } catch (...) { /* Ignore the exception */ }