Mercurial > hg > orthanc
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 } |