comparison OrthancFramework/Sources/Lua/LuaContext.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/Lua/LuaContext.cpp@e3b3af80732d
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeaders.h"
35 #include "LuaContext.h"
36
37 #include "../Logging.h"
38 #include "../OrthancException.h"
39 #include "../Toolbox.h"
40
41 #include <set>
42 #include <cassert>
43 #include <boost/lexical_cast.hpp>
44
45 extern "C"
46 {
47 #include <lualib.h>
48 #include <lauxlib.h>
49 }
50
51 namespace Orthanc
52 {
53 static bool OnlyContainsDigits(const std::string& s)
54 {
55 for (size_t i = 0; i < s.size(); i++)
56 {
57 if (!isdigit(s[i]))
58 {
59 return false;
60 }
61 }
62
63 return true;
64 }
65
66 LuaContext& LuaContext::GetLuaContext(lua_State *state)
67 {
68 const void* value = GetGlobalVariable(state, "_LuaContext");
69 assert(value != NULL);
70
71 return *const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(value));
72 }
73
74 int LuaContext::PrintToLog(lua_State *state)
75 {
76 LuaContext& that = GetLuaContext(state);
77
78 // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/
79 int nArgs = lua_gettop(state);
80 lua_getglobal(state, "tostring");
81
82 // Make sure you start at 1 *NOT* 0 for arrays in Lua.
83 std::string result;
84
85 for (int i = 1; i <= nArgs; i++)
86 {
87 const char *s;
88 lua_pushvalue(state, -1);
89 lua_pushvalue(state, i);
90 lua_call(state, 1, 1);
91 s = lua_tostring(state, -1);
92
93 if (result.size() > 0)
94 result.append(", ");
95
96 if (s == NULL)
97 result.append("<No conversion to string>");
98 else
99 result.append(s);
100
101 lua_pop(state, 1);
102 }
103
104 LOG(WARNING) << "Lua says: " << result;
105 that.log_.append(result);
106 that.log_.append("\n");
107
108 return 0;
109 }
110
111
112 int LuaContext::ParseJson(lua_State *state)
113 {
114 LuaContext& that = GetLuaContext(state);
115
116 int nArgs = lua_gettop(state);
117 if (nArgs != 1 ||
118 !lua_isstring(state, 1)) // Password
119 {
120 lua_pushnil(state);
121 return 1;
122 }
123
124 const char* str = lua_tostring(state, 1);
125
126 Json::Value value;
127 Json::Reader reader;
128 if (reader.parse(str, str + strlen(str), value))
129 {
130 that.PushJson(value);
131 }
132 else
133 {
134 lua_pushnil(state);
135 }
136
137 return 1;
138 }
139
140
141 int LuaContext::DumpJson(lua_State *state)
142 {
143 LuaContext& that = GetLuaContext(state);
144
145 int nArgs = lua_gettop(state);
146 if ((nArgs != 1 && nArgs != 2) ||
147 (nArgs == 2 && !lua_isboolean(state, 2)))
148 {
149 lua_pushnil(state);
150 return 1;
151 }
152
153 bool keepStrings = false;
154 if (nArgs == 2)
155 {
156 keepStrings = lua_toboolean(state, 2) ? true : false;
157 }
158
159 Json::Value json;
160 that.GetJson(json, state, 1, keepStrings);
161
162 Json::FastWriter writer;
163 std::string s = writer.write(json);
164 lua_pushlstring(state, s.c_str(), s.size());
165
166 return 1;
167 }
168
169
170 #if ORTHANC_ENABLE_CURL == 1
171 int LuaContext::SetHttpCredentials(lua_State *state)
172 {
173 LuaContext& that = GetLuaContext(state);
174
175 // Check the types of the arguments
176 int nArgs = lua_gettop(state);
177 if (nArgs != 2 ||
178 !lua_isstring(state, 1) || // Username
179 !lua_isstring(state, 2)) // Password
180 {
181 LOG(ERROR) << "Lua: Bad parameters to SetHttpCredentials()";
182 }
183 else
184 {
185 // Configure the HTTP client
186 const char* username = lua_tostring(state, 1);
187 const char* password = lua_tostring(state, 2);
188 that.httpClient_.SetCredentials(username, password);
189 }
190
191 return 0;
192 }
193 #endif
194
195
196 #if ORTHANC_ENABLE_CURL == 1
197 bool LuaContext::AnswerHttpQuery(lua_State* state)
198 {
199 std::string str;
200
201 try
202 {
203 httpClient_.Apply(str);
204 }
205 catch (OrthancException&)
206 {
207 return false;
208 }
209
210 // Return the result of the HTTP request
211 lua_pushlstring(state, str.c_str(), str.size());
212
213 return true;
214 }
215 #endif
216
217
218 #if ORTHANC_ENABLE_CURL == 1
219 void LuaContext::SetHttpHeaders(int top)
220 {
221 std::map<std::string, std::string> headers;
222 GetDictionaryArgument(headers, lua_, top, false /* keep key case as provided by Lua script */);
223
224 httpClient_.ClearHeaders(); // always reset headers in case they have been set in a previous request
225
226 for (std::map<std::string, std::string>::const_iterator
227 it = headers.begin(); it != headers.end(); ++it)
228 {
229 httpClient_.AddHeader(it->first, it->second);
230 }
231 }
232 #endif
233
234
235 #if ORTHANC_ENABLE_CURL == 1
236 int LuaContext::CallHttpGet(lua_State *state)
237 {
238 LuaContext& that = GetLuaContext(state);
239
240 // Check the types of the arguments
241 int nArgs = lua_gettop(state);
242 if (nArgs < 1 || nArgs > 2 || // check args count
243 !lua_isstring(state, 1)) // URL is a string
244 {
245 LOG(ERROR) << "Lua: Bad parameters to HttpGet()";
246 lua_pushnil(state);
247 return 1;
248 }
249
250 // Configure the HTTP client class
251 const char* url = lua_tostring(state, 1);
252 that.httpClient_.SetMethod(HttpMethod_Get);
253 that.httpClient_.SetUrl(url);
254 that.httpClient_.GetBody().clear();
255 that.SetHttpHeaders(2);
256
257 // Do the HTTP GET request
258 if (!that.AnswerHttpQuery(state))
259 {
260 LOG(ERROR) << "Lua: Error in HttpGet() for URL " << url;
261 lua_pushnil(state);
262 }
263
264 return 1;
265 }
266 #endif
267
268
269 #if ORTHANC_ENABLE_CURL == 1
270 int LuaContext::CallHttpPostOrPut(lua_State *state,
271 HttpMethod method)
272 {
273 LuaContext& that = GetLuaContext(state);
274
275 // Check the types of the arguments
276 int nArgs = lua_gettop(state);
277 if ((nArgs < 1 || nArgs > 3) || // check arg count
278 !lua_isstring(state, 1) || // URL is a string
279 (nArgs >= 2 && (!lua_isstring(state, 2) && !lua_isnil(state, 2)))) // Body data is null or is a string
280 {
281 LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()";
282 lua_pushnil(state);
283 return 1;
284 }
285
286 // Configure the HTTP client class
287 const char* url = lua_tostring(state, 1);
288 that.httpClient_.SetMethod(method);
289 that.httpClient_.SetUrl(url);
290 that.SetHttpHeaders(3);
291
292 if (nArgs >= 2 && !lua_isnil(state, 2))
293 {
294 size_t bodySize = 0;
295 const char* bodyData = lua_tolstring(state, 2, &bodySize);
296
297 if (bodySize == 0)
298 {
299 that.httpClient_.GetBody().clear();
300 }
301 else
302 {
303 that.httpClient_.GetBody().assign(bodyData, bodySize);
304 }
305 }
306 else
307 {
308 that.httpClient_.GetBody().clear();
309 }
310
311 // Do the HTTP POST/PUT request
312 if (!that.AnswerHttpQuery(state))
313 {
314 LOG(ERROR) << "Lua: Error in HttpPost() or HttpPut() for URL " << url;
315 lua_pushnil(state);
316 }
317
318 return 1;
319 }
320 #endif
321
322
323 #if ORTHANC_ENABLE_CURL == 1
324 int LuaContext::CallHttpPost(lua_State *state)
325 {
326 return CallHttpPostOrPut(state, HttpMethod_Post);
327 }
328 #endif
329
330
331 #if ORTHANC_ENABLE_CURL == 1
332 int LuaContext::CallHttpPut(lua_State *state)
333 {
334 return CallHttpPostOrPut(state, HttpMethod_Put);
335 }
336 #endif
337
338
339 #if ORTHANC_ENABLE_CURL == 1
340 int LuaContext::CallHttpDelete(lua_State *state)
341 {
342 LuaContext& that = GetLuaContext(state);
343
344 // Check the types of the arguments
345 int nArgs = lua_gettop(state);
346 if (nArgs < 1 || nArgs > 2 || !lua_isstring(state, 1)) // URL
347 {
348 LOG(ERROR) << "Lua: Bad parameters to HttpDelete()";
349 lua_pushnil(state);
350 return 1;
351 }
352
353 // Configure the HTTP client class
354 const char* url = lua_tostring(state, 1);
355 that.httpClient_.SetMethod(HttpMethod_Delete);
356 that.httpClient_.SetUrl(url);
357 that.httpClient_.GetBody().clear();
358 that.SetHttpHeaders(2);
359
360 // Do the HTTP DELETE request
361 std::string s;
362 if (!that.httpClient_.Apply(s))
363 {
364 LOG(ERROR) << "Lua: Error in HttpDelete() for URL " << url;
365 lua_pushnil(state);
366 }
367 else
368 {
369 lua_pushstring(state, "SUCCESS");
370 }
371
372 return 1;
373 }
374 #endif
375
376
377 void LuaContext::PushJson(const Json::Value& value)
378 {
379 if (value.isString())
380 {
381 const std::string s = value.asString();
382 lua_pushlstring(lua_, s.c_str(), s.size());
383 }
384 else if (value.isDouble())
385 {
386 lua_pushnumber(lua_, value.asDouble());
387 }
388 else if (value.isInt())
389 {
390 lua_pushinteger(lua_, value.asInt());
391 }
392 else if (value.isUInt())
393 {
394 lua_pushinteger(lua_, value.asUInt());
395 }
396 else if (value.isBool())
397 {
398 lua_pushboolean(lua_, value.asBool());
399 }
400 else if (value.isNull())
401 {
402 lua_pushnil(lua_);
403 }
404 else if (value.isArray())
405 {
406 lua_newtable(lua_);
407
408 // http://lua-users.org/wiki/SimpleLuaApiExample
409 for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
410 {
411 // Push the table index (note the "+1" because of Lua conventions)
412 lua_pushnumber(lua_, i + 1);
413
414 // Push the value of the cell
415 PushJson(value[i]);
416
417 // Stores the pair in the table
418 lua_rawset(lua_, -3);
419 }
420 }
421 else if (value.isObject())
422 {
423 lua_newtable(lua_);
424
425 Json::Value::Members members = value.getMemberNames();
426
427 for (Json::Value::Members::const_iterator
428 it = members.begin(); it != members.end(); ++it)
429 {
430 // Push the index of the cell
431 lua_pushlstring(lua_, it->c_str(), it->size());
432
433 // Push the value of the cell
434 PushJson(value[*it]);
435
436 // Stores the pair in the table
437 lua_rawset(lua_, -3);
438 }
439 }
440 else
441 {
442 throw OrthancException(ErrorCode_JsonToLuaTable);
443 }
444 }
445
446
447 void LuaContext::GetJson(Json::Value& result,
448 lua_State* state,
449 int top,
450 bool keepStrings)
451 {
452 if (lua_istable(state, top))
453 {
454 Json::Value tmp = Json::objectValue;
455 bool isArray = true;
456 size_t size = 0;
457
458 // Code adapted from: http://stackoverflow.com/a/6142700/881731
459
460 // Push another reference to the table on top of the stack (so we know
461 // where it is, and this function can work for negative, positive and
462 // pseudo indices
463 lua_pushvalue(state, top);
464 // stack now contains: -1 => table
465 lua_pushnil(state);
466 // stack now contains: -1 => nil; -2 => table
467 while (lua_next(state, -2))
468 {
469 // stack now contains: -1 => value; -2 => key; -3 => table
470 // copy the key so that lua_tostring does not modify the original
471 lua_pushvalue(state, -2);
472 // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
473 std::string key(lua_tostring(state, -1));
474 Json::Value v;
475 GetJson(v, state, -2, keepStrings);
476
477 tmp[key] = v;
478
479 size += 1;
480 try
481 {
482 if (!OnlyContainsDigits(key) ||
483 boost::lexical_cast<size_t>(key) != size)
484 {
485 isArray = false;
486 }
487 }
488 catch (boost::bad_lexical_cast&)
489 {
490 isArray = false;
491 }
492
493 // pop value + copy of key, leaving original key
494 lua_pop(state, 2);
495 // stack now contains: -1 => key; -2 => table
496 }
497 // stack now contains: -1 => table (when lua_next returns 0 it pops the key
498 // but does not push anything.)
499 // Pop table
500 lua_pop(state, 1);
501
502 // Stack is now the same as it was on entry to this function
503
504 if (isArray)
505 {
506 result = Json::arrayValue;
507 for (size_t i = 0; i < size; i++)
508 {
509 result.append(tmp[boost::lexical_cast<std::string>(i + 1)]);
510 }
511 }
512 else
513 {
514 result = tmp;
515 }
516 }
517 else if (lua_isnil(state, top))
518 {
519 result = Json::nullValue;
520 }
521 else if (!keepStrings &&
522 lua_isboolean(state, top))
523 {
524 result = lua_toboolean(state, top) ? true : false;
525 }
526 else if (!keepStrings &&
527 lua_isnumber(state, top))
528 {
529 // Convert to "int" if truncation does not loose precision
530 double value = static_cast<double>(lua_tonumber(state, top));
531 int truncated = static_cast<int>(value);
532
533 if (std::abs(value - static_cast<double>(truncated)) <=
534 std::numeric_limits<double>::epsilon())
535 {
536 result = truncated;
537 }
538 else
539 {
540 result = value;
541 }
542 }
543 else if (lua_isstring(state, top))
544 {
545 // Caution: The "lua_isstring()" case must be the last, since
546 // Lua can convert most types to strings by default.
547 result = std::string(lua_tostring(state, top));
548 }
549 else if (lua_isboolean(state, top))
550 {
551 result = lua_toboolean(state, top) ? true : false;
552 }
553 else
554 {
555 LOG(WARNING) << "Unsupported Lua type when returning Json";
556 result = Json::nullValue;
557 }
558 }
559
560
561 LuaContext::LuaContext()
562 {
563 lua_ = luaL_newstate();
564 if (!lua_)
565 {
566 throw OrthancException(ErrorCode_CannotCreateLua);
567 }
568
569 luaL_openlibs(lua_);
570 lua_register(lua_, "print", PrintToLog);
571 lua_register(lua_, "ParseJson", ParseJson);
572 lua_register(lua_, "DumpJson", DumpJson);
573
574 #if ORTHANC_ENABLE_CURL == 1
575 lua_register(lua_, "HttpGet", CallHttpGet);
576 lua_register(lua_, "HttpPost", CallHttpPost);
577 lua_register(lua_, "HttpPut", CallHttpPut);
578 lua_register(lua_, "HttpDelete", CallHttpDelete);
579 lua_register(lua_, "SetHttpCredentials", SetHttpCredentials);
580 #endif
581
582 SetGlobalVariable("_LuaContext", this);
583 }
584
585
586 LuaContext::~LuaContext()
587 {
588 lua_close(lua_);
589 }
590
591
592 void LuaContext::ExecuteInternal(std::string* output,
593 const std::string& command)
594 {
595 log_.clear();
596 int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") ||
597 lua_pcall(lua_, 0, 0, 0));
598
599 if (error)
600 {
601 assert(lua_gettop(lua_) >= 1);
602
603 std::string description(lua_tostring(lua_, -1));
604 lua_pop(lua_, 1); /* pop error message from the stack */
605 throw OrthancException(ErrorCode_CannotExecuteLua, description);
606 }
607
608 if (output != NULL)
609 {
610 *output = log_;
611 }
612 }
613
614
615 bool LuaContext::IsExistingFunction(const char* name)
616 {
617 lua_settop(lua_, 0);
618 lua_getglobal(lua_, name);
619 return lua_type(lua_, -1) == LUA_TFUNCTION;
620 }
621
622
623 void LuaContext::Execute(Json::Value& output,
624 const std::string& command)
625 {
626 std::string s;
627 ExecuteInternal(&s, command);
628
629 Json::Reader reader;
630 if (!reader.parse(s, output))
631 {
632 throw OrthancException(ErrorCode_BadJson);
633 }
634 }
635
636
637 void LuaContext::RegisterFunction(const char* name,
638 lua_CFunction func)
639 {
640 lua_register(lua_, name, func);
641 }
642
643
644 void LuaContext::SetGlobalVariable(const char* name,
645 void* value)
646 {
647 lua_pushlightuserdata(lua_, value);
648 lua_setglobal(lua_, name);
649 }
650
651
652 const void* LuaContext::GetGlobalVariable(lua_State* state,
653 const char* name)
654 {
655 lua_getglobal(state, name);
656 assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA);
657 const void* value = lua_topointer(state, -1);
658 lua_pop(state, 1);
659 return value;
660 }
661
662
663 void LuaContext::GetDictionaryArgument(std::map<std::string, std::string>& target,
664 lua_State* state,
665 int top,
666 bool keyToLowerCase)
667 {
668 target.clear();
669
670 if (lua_gettop(state) >= top)
671 {
672 Json::Value headers;
673 GetJson(headers, state, top, true);
674
675 Json::Value::Members members = headers.getMemberNames();
676
677 for (size_t i = 0; i < members.size(); i++)
678 {
679 std::string key = members[i];
680
681 if (keyToLowerCase)
682 {
683 Toolbox::ToLowerCase(key);
684 }
685
686 target[key] = headers[members[i]].asString();
687 }
688 }
689 }
690 }