# HG changeset patch # User Sebastien Jodogne # Date 1561385207 -7200 # Node ID dd1e68f2d0c06cd70e9d64bd878d56de3ea2deb9 # Parent 6cc72ebfd6efdb60e74d499f0d07b9f6754aefc8 Fix issue #106 (Unable to export preview as jpeg from Lua script) diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Core/HttpServer/HttpToolbox.cpp --- a/Core/HttpServer/HttpToolbox.cpp Mon Jun 24 12:37:52 2019 +0200 +++ b/Core/HttpServer/HttpToolbox.cpp Mon Jun 24 16:06:47 2019 +0200 @@ -225,25 +225,15 @@ } - bool HttpToolbox::SimpleGet(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri) - { - IHttpHandler::Arguments headers; // No HTTP header - return SimpleGet(result, handler, origin, uri, headers); - } - - static bool SimplePostOrPut(std::string& result, IHttpHandler& handler, RequestOrigin origin, HttpMethod method, const std::string& uri, const void* bodyData, - size_t bodySize) + size_t bodySize, + const IHttpHandler::Arguments& httpHeaders) { - IHttpHandler::Arguments headers; // No HTTP header IHttpHandler::GetArguments getArguments; // No GET argument for POST/PUT UriComponents curi; @@ -253,7 +243,7 @@ HttpOutput http(stream, false /* no keep alive */); if (handler.Handle(http, origin, LOCALHOST, "", method, curi, - headers, getArguments, bodyData, bodySize)) + httpHeaders, getArguments, bodyData, bodySize)) { stream.GetOutput(result); return true; @@ -270,9 +260,10 @@ RequestOrigin origin, const std::string& uri, const void* bodyData, - size_t bodySize) + size_t bodySize, + const IHttpHandler::Arguments& httpHeaders) { - return SimplePostOrPut(result, handler, origin, HttpMethod_Post, uri, bodyData, bodySize); + return SimplePostOrPut(result, handler, origin, HttpMethod_Post, uri, bodyData, bodySize, httpHeaders); } @@ -281,26 +272,27 @@ RequestOrigin origin, const std::string& uri, const void* bodyData, - size_t bodySize) + size_t bodySize, + const IHttpHandler::Arguments& httpHeaders) { - return SimplePostOrPut(result, handler, origin, HttpMethod_Put, uri, bodyData, bodySize); + return SimplePostOrPut(result, handler, origin, HttpMethod_Put, uri, bodyData, bodySize, httpHeaders); } bool HttpToolbox::SimpleDelete(IHttpHandler& handler, RequestOrigin origin, - const std::string& uri) + const std::string& uri, + const IHttpHandler::Arguments& httpHeaders) { UriComponents curi; Toolbox::SplitUriComponents(curi, uri); - IHttpHandler::Arguments headers; // No HTTP header IHttpHandler::GetArguments getArguments; // No GET argument for DELETE StringHttpOutput stream; HttpOutput http(stream, false /* no keep alive */); return handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Delete, curi, - headers, getArguments, NULL /* no body for DELETE */, 0); + httpHeaders, getArguments, NULL /* no body for DELETE */, 0); } } diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Core/HttpServer/HttpToolbox.h --- a/Core/HttpServer/HttpToolbox.h Mon Jun 24 12:37:52 2019 +0200 +++ b/Core/HttpServer/HttpToolbox.h Mon Jun 24 16:06:47 2019 +0200 @@ -64,11 +64,6 @@ static bool SimpleGet(std::string& result, IHttpHandler& handler, RequestOrigin origin, - const std::string& uri); - - static bool SimpleGet(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, const std::string& uri, const IHttpHandler::Arguments& httpHeaders); @@ -77,17 +72,20 @@ RequestOrigin origin, const std::string& uri, const void* bodyData, - size_t bodySize); + size_t bodySize, + const IHttpHandler::Arguments& httpHeaders); static bool SimplePut(std::string& result, IHttpHandler& handler, RequestOrigin origin, const std::string& uri, const void* bodyData, - size_t bodySize); + size_t bodySize, + const IHttpHandler::Arguments& httpHeaders); static bool SimpleDelete(IHttpHandler& handler, RequestOrigin origin, - const std::string& uri); + const std::string& uri, + const IHttpHandler::Arguments& httpHeaders); }; } diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Core/Lua/LuaContext.cpp --- a/Core/Lua/LuaContext.cpp Mon Jun 24 12:37:52 2019 +0200 +++ b/Core/Lua/LuaContext.cpp Mon Jun 24 16:06:47 2019 +0200 @@ -36,6 +36,7 @@ #include "../Logging.h" #include "../OrthancException.h" +#include "../Toolbox.h" #include #include @@ -156,7 +157,7 @@ } Json::Value json; - that.GetJson(json, 1, keepStrings); + that.GetJson(json, state, 1, keepStrings); Json::FastWriter writer; std::string s = writer.write(json); @@ -208,27 +209,21 @@ return true; } - - void LuaContext::SetHttpHeaders(lua_State *state, int top) - { - this->httpClient_.ClearHeaders(); // always reset headers in case they have been set in a previous request - - if (lua_gettop(state) >= top) - { - Json::Value headers; - this->GetJson(headers, top, true); + - Json::Value::Members members = headers.getMemberNames(); + void LuaContext::SetHttpHeaders(int top) + { + std::map headers; + GetDictionaryArgument(headers, lua_, top, false /* keep key case as provided by Lua script */); + + httpClient_.ClearHeaders(); // always reset headers in case they have been set in a previous request - for (Json::Value::Members::const_iterator - it = members.begin(); it != members.end(); ++it) - { - this->httpClient_.AddHeader(*it, headers[*it].asString()); - } + for (std::map::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + httpClient_.AddHeader(it->first, it->second); } - - } - + } int LuaContext::CallHttpGet(lua_State *state) @@ -237,8 +232,8 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if ((nArgs < 1 || nArgs > 2) || // check args count - !lua_isstring(state, 1)) // URL is a string + if (nArgs < 1 || nArgs > 2 || // check args count + !lua_isstring(state, 1)) // URL is a string { LOG(ERROR) << "Lua: Bad parameters to HttpGet()"; lua_pushnil(state); @@ -250,7 +245,7 @@ that.httpClient_.SetMethod(HttpMethod_Get); that.httpClient_.SetUrl(url); that.httpClient_.GetBody().clear(); - that.SetHttpHeaders(state, 2); + that.SetHttpHeaders(2); // Do the HTTP GET request if (!that.AnswerHttpQuery(state)) @@ -283,7 +278,7 @@ const char* url = lua_tostring(state, 1); that.httpClient_.SetMethod(method); that.httpClient_.SetUrl(url); - that.SetHttpHeaders(state, 3); + that.SetHttpHeaders(3); if (nArgs >= 2 && !lua_isnil(state, 2)) { @@ -345,7 +340,7 @@ that.httpClient_.SetMethod(HttpMethod_Delete); that.httpClient_.SetUrl(url); that.httpClient_.GetBody().clear(); - that.SetHttpHeaders(state, 2); + that.SetHttpHeaders(2); // Do the HTTP DELETE request std::string s; @@ -434,10 +429,11 @@ void LuaContext::GetJson(Json::Value& result, + lua_State* state, int top, bool keepStrings) { - if (lua_istable(lua_, top)) + if (lua_istable(state, top)) { Json::Value tmp = Json::objectValue; bool isArray = true; @@ -448,19 +444,19 @@ // Push another reference to the table on top of the stack (so we know // where it is, and this function can work for negative, positive and // pseudo indices - lua_pushvalue(lua_, top); + lua_pushvalue(state, top); // stack now contains: -1 => table - lua_pushnil(lua_); + lua_pushnil(state); // stack now contains: -1 => nil; -2 => table - while (lua_next(lua_, -2)) + while (lua_next(state, -2)) { // stack now contains: -1 => value; -2 => key; -3 => table // copy the key so that lua_tostring does not modify the original - lua_pushvalue(lua_, -2); + lua_pushvalue(state, -2); // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table - std::string key(lua_tostring(lua_, -1)); + std::string key(lua_tostring(state, -1)); Json::Value v; - GetJson(v, -2, keepStrings); + GetJson(v, state, -2, keepStrings); tmp[key] = v; @@ -479,13 +475,13 @@ } // pop value + copy of key, leaving original key - lua_pop(lua_, 2); + lua_pop(state, 2); // stack now contains: -1 => key; -2 => table } // stack now contains: -1 => table (when lua_next returns 0 it pops the key // but does not push anything.) // Pop table - lua_pop(lua_, 1); + lua_pop(state, 1); // Stack is now the same as it was on entry to this function @@ -502,20 +498,20 @@ result = tmp; } } - else if (lua_isnil(lua_, top)) + else if (lua_isnil(state, top)) { result = Json::nullValue; } else if (!keepStrings && - lua_isboolean(lua_, top)) + lua_isboolean(state, top)) { - result = lua_toboolean(lua_, top) ? true : false; + result = lua_toboolean(state, top) ? true : false; } else if (!keepStrings && - lua_isnumber(lua_, top)) + lua_isnumber(state, top)) { // Convert to "int" if truncation does not loose precision - double value = static_cast(lua_tonumber(lua_, top)); + double value = static_cast(lua_tonumber(state, top)); int truncated = static_cast(value); if (std::abs(value - static_cast(truncated)) <= @@ -528,15 +524,15 @@ result = value; } } - else if (lua_isstring(lua_, top)) + else if (lua_isstring(state, top)) { // Caution: The "lua_isstring()" case must be the last, since // Lua can convert most types to strings by default. - result = std::string(lua_tostring(lua_, top)); + result = std::string(lua_tostring(state, top)); } - else if (lua_isboolean(lua_, top)) + else if (lua_isboolean(state, top)) { - result = lua_toboolean(lua_, top) ? true : false; + result = lua_toboolean(state, top) ? true : false; } else { @@ -653,4 +649,33 @@ lua_pop(state, 1); return value; } + + + void LuaContext::GetDictionaryArgument(std::map& target, + lua_State* state, + int top, + bool keyToLowerCase) + { + target.clear(); + + if (lua_gettop(state) >= top) + { + Json::Value headers; + GetJson(headers, state, top, true); + + Json::Value::Members members = headers.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + std::string key = members[i]; + + if (keyToLowerCase) + { + Toolbox::ToLowerCase(key); + } + + target[key] = headers[members[i]].asString(); + } + } + } } diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Core/Lua/LuaContext.h --- a/Core/Lua/LuaContext.h Mon Jun 24 12:37:52 2019 +0200 +++ b/Core/Lua/LuaContext.h Mon Jun 24 16:06:47 2019 +0200 @@ -87,11 +87,12 @@ void ExecuteInternal(std::string* output, const std::string& command); - void GetJson(Json::Value& result, - int top, - bool keepStrings); + static void GetJson(Json::Value& result, + lua_State* state, + int top, + bool keepStrings); - void SetHttpHeaders(lua_State* state, int top); + void SetHttpHeaders(int top); public: LuaContext(); @@ -136,5 +137,10 @@ const char* name); void PushJson(const Json::Value& value); + + static void GetDictionaryArgument(std::map& target, + lua_State* state, + int top, + bool keyToLowerCase); }; } diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Core/Lua/LuaFunctionCall.cpp --- a/Core/Lua/LuaFunctionCall.cpp Mon Jun 24 12:37:52 2019 +0200 +++ b/Core/Lua/LuaFunctionCall.cpp Mon Jun 24 16:06:47 2019 +0200 @@ -134,7 +134,7 @@ bool keepStrings) { ExecuteInternal(1); - context_.GetJson(result, lua_gettop(context_.lua_), keepStrings); + context_.GetJson(result, context_.lua_, lua_gettop(context_.lua_), keepStrings); } diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 NEWS --- a/NEWS Mon Jun 24 12:37:52 2019 +0200 +++ b/NEWS Mon Jun 24 16:06:47 2019 +0200 @@ -27,6 +27,7 @@ * Allow the serialization of signed 16bpp images in PAM format * HTTP header "Accept-Encoding" is honored for streams without built-in support for compression * The default HTTP timeout is now 60 seconds (instead of 10 seconds in previous versions) +* Fix issue #106 (Unable to export preview as jpeg from Lua script) * Fix issue #136 (C-FIND request fails when found DICOM file does not have certain tags) * Fix issue #137 (C-STORE fails for unknown SOP Class although server is configured to accept any) * Fix issue #138 (POST to modalities/{name} accepts invalid characters) diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Mon Jun 24 12:37:52 2019 +0200 +++ b/OrthancServer/LuaScripting.cpp Mon Jun 24 16:06:47 2019 +0200 @@ -279,9 +279,9 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if ((nArgs != 1 && nArgs != 2) || + if (nArgs < 1 || nArgs > 3 || !lua_isstring(state, 1) || // URI - (nArgs == 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? + (nArgs >= 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? { LOG(ERROR) << "Lua: Bad parameters to RestApiGet()"; lua_pushnil(state); @@ -291,11 +291,14 @@ const char* uri = lua_tostring(state, 1); bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false); + std::map headers; + LuaContext::GetDictionaryArgument(headers, state, 3, true /* HTTP header key to lower case */); + try { std::string result; if (HttpToolbox::SimpleGet(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), - RequestOrigin_Lua, uri)) + RequestOrigin_Lua, uri, headers)) { lua_pushlstring(state, result.c_str(), result.size()); return 1; @@ -325,10 +328,10 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if ((nArgs != 2 && nArgs != 3) || + if (nArgs < 2 || nArgs > 4 || !lua_isstring(state, 1) || // URI !lua_isstring(state, 2) || // Body - (nArgs == 3 && !lua_isboolean(state, 3))) // Restrict to built-in API? + (nArgs >= 3 && !lua_isboolean(state, 3))) // Restrict to built-in API? { LOG(ERROR) << "Lua: Bad parameters to " << (isPost ? "RestApiPost()" : "RestApiPut()"); lua_pushnil(state); @@ -340,14 +343,17 @@ const char* bodyData = lua_tolstring(state, 2, &bodySize); bool builtin = (nArgs == 3 ? lua_toboolean(state, 3) != 0 : false); + std::map headers; + LuaContext::GetDictionaryArgument(headers, state, 4, true /* HTTP header key to lower case */); + try { std::string result; if (isPost ? HttpToolbox::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), - RequestOrigin_Lua, uri, bodyData, bodySize) : + RequestOrigin_Lua, uri, bodyData, bodySize, headers) : HttpToolbox::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), - RequestOrigin_Lua, uri, bodyData, bodySize)) + RequestOrigin_Lua, uri, bodyData, bodySize, headers)) { lua_pushlstring(state, result.c_str(), result.size()); return 1; @@ -391,9 +397,9 @@ // Check the types of the arguments int nArgs = lua_gettop(state); - if ((nArgs != 1 && nArgs != 2) || + if (nArgs < 1 || nArgs > 3 || !lua_isstring(state, 1) || // URI - (nArgs == 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? + (nArgs >= 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? { LOG(ERROR) << "Lua: Bad parameters to RestApiDelete()"; lua_pushnil(state); @@ -403,10 +409,13 @@ const char* uri = lua_tostring(state, 1); bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false); + std::map headers; + LuaContext::GetDictionaryArgument(headers, state, 3, true /* HTTP header key to lower case */); + try { if (HttpToolbox::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), - RequestOrigin_Lua, uri)) + RequestOrigin_Lua, uri, headers)) { lua_pushboolean(state, 1); return 1; diff -r 6cc72ebfd6ef -r dd1e68f2d0c0 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Mon Jun 24 12:37:52 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Jun 24 16:06:47 2019 +0200 @@ -1948,8 +1948,10 @@ handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); } + std::map httpHeaders; + std::string result; - if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri)) + if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, httpHeaders)) { CopyToMemoryBuffer(*p.target, result); } @@ -2013,10 +2015,12 @@ handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); } + std::map httpHeaders; + std::string result; if (isPost ? - HttpToolbox::SimplePost(result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize) : - HttpToolbox::SimplePut (result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize)) + HttpToolbox::SimplePost(result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders) : + HttpToolbox::SimplePut (result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders)) { CopyToMemoryBuffer(*p.target, result); } @@ -2041,7 +2045,9 @@ handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); } - if (!HttpToolbox::SimpleDelete(*handler, RequestOrigin_Plugins, uri)) + std::map httpHeaders; + + if (!HttpToolbox::SimpleDelete(*handler, RequestOrigin_Plugins, uri, httpHeaders)) { throw OrthancException(ErrorCode_UnknownResource); }