From 7ea839feb86822076c1fdfc7a0d8566a98169d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Sat, 14 Jan 2023 18:37:29 +0100 Subject: [PATCH] Move Lua HTTP out of LSI --- src/lua/LuaHttp.cpp | 312 +++++++++++++++++++++++++++++++++ src/lua/LuaHttp.h | 8 + src/lua/LuaScriptInterface.cpp | 304 +------------------------------- src/lua/LuaScriptInterface.h | 3 - src/lua/LuaTCPSocket.h | 2 - src/lua/meson.build | 1 + 6 files changed, 323 insertions(+), 307 deletions(-) create mode 100644 src/lua/LuaHttp.cpp create mode 100644 src/lua/LuaHttp.h diff --git a/src/lua/LuaHttp.cpp b/src/lua/LuaHttp.cpp new file mode 100644 index 000000000..d023b5340 --- /dev/null +++ b/src/lua/LuaHttp.cpp @@ -0,0 +1,312 @@ +#include "LuaHttp.h" +#include "client/http/Request.h" +#include "client/Client.h" +#include "json/json.h" +#include "LuaScriptInterface.h" +#include "Format.h" +#include +#include + +class RequestHandle +{ +public: + enum RequestType + { + normal, + getAuthToken, + }; + +private: + std::unique_ptr request; + bool dead = false; + RequestType type; + + RequestHandle() = default; + + std::pair FinishGetAuthToken(ByteString data) + { + std::istringstream ss(data); + Json::Value root; + try + { + ss >> root; + auto status = root["Status"].asString(); + if (status == "OK") + { + return { 200, root["Token"].asString() }; + } + return { 403, status }; + } + catch (std::exception &e) + { + std::cerr << "bad auth response: " << e.what() << std::endl; + } + return { 600, {} }; + } + +public: + static int Make(lua_State *l, const ByteString &uri, bool isPost, const ByteString &verb, RequestType type, const std::map &post_data, const std::vector &headers) + { + auto authUser = Client::Ref().GetAuthUser(); + if (type == getAuthToken && !authUser.UserID) + { + lua_pushnil(l); + lua_pushliteral(l, "not authenticated"); + return 2; + } + auto *rh = (RequestHandle *)lua_newuserdata(l, sizeof(RequestHandle)); + if (!rh) + { + return 0; + } + new(rh) RequestHandle(); + rh->type = type; + rh->request = std::make_unique(uri); + if (verb.size()) + { + rh->request->Verb(verb); + } + for (auto &header : headers) + { + rh->request->AddHeader(header); + } + if (isPost) + { + rh->request->AddPostData(post_data); + } + if (type == getAuthToken) + { + rh->request->AuthHeaders(ByteString::Build(authUser.UserID), authUser.SessionID); + } + rh->request->Start(); + luaL_newmetatable(l, "HTTPRequest"); + lua_setmetatable(l, -2); + return 1; + } + + ~RequestHandle() + { + if (!Dead()) + { + Cancel(); + } + } + + bool Dead() const + { + return dead; + } + + bool Done() const + { + return request->CheckDone(); + } + + void Progress(int *total, int *done) + { + if (!dead) + { + std::tie(*total, *done) = request->CheckProgress(); + } + } + + void Cancel() + { + if (!dead) + { + request.reset(); + dead = true; + } + } + + std::pair Finish(std::vector &headers) + { + int status = 0; + ByteString data; + if (!dead) + { + if (request->CheckDone()) + { + if (type != getAuthToken) + { + headers = request->ResponseHeaders(); + } + std::tie(status, data) = request->Finish(); + request.reset(); + if (type == getAuthToken) + { + if (status == 200) + { + std::tie(status, data) = FinishGetAuthToken(data); + } + } + dead = true; + } + } + return { status, data }; + } +}; + +static int http_request_gc(lua_State *l) +{ + auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); + rh->~RequestHandle(); + return 0; +} + +static int http_request_status(lua_State *l) +{ + auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); + if (rh->Dead()) + { + lua_pushliteral(l, "dead"); + } + else if (rh->Done()) + { + lua_pushliteral(l, "done"); + } + else + { + lua_pushliteral(l, "running"); + } + return 1; +} + +static int http_request_progress(lua_State *l) +{ + auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); + if (!rh->Dead()) + { + int total, done; + rh->Progress(&total, &done); + lua_pushinteger(l, total); + lua_pushinteger(l, done); + return 2; + } + return 0; +} + +static int http_request_cancel(lua_State *l) +{ + auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); + if (!rh->Dead()) + { + rh->Cancel(); + } + return 0; +} + +static int http_request_finish(lua_State *l) +{ + auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); + if (!rh->Dead()) + { + std::vector headers; + auto [ status, data ] = rh->Finish(headers); + tpt_lua_pushByteString(l, data); + lua_pushinteger(l, status); + lua_newtable(l); + for (auto i = 0; i < int(headers.size()); ++i) + { + lua_pushlstring(l, headers[i].data(), headers[i].size()); + lua_rawseti(l, -2, i + 1); + } + return 3; + } + return 0; +} + +static int http_request(lua_State *l, bool isPost) +{ + ByteString uri = tpt_lua_checkByteString(l, 1); + std::map post_data; + auto headersIndex = 2; + auto verbIndex = 3; + + if (isPost) + { + headersIndex += 1; + verbIndex += 1; + if (lua_istable(l, 2)) + { + lua_pushnil(l); + while (lua_next(l, 2)) + { + lua_pushvalue(l, -2); + post_data.emplace(tpt_lua_toByteString(l, -1), tpt_lua_toByteString(l, -2)); + lua_pop(l, 2); + } + } + } + + std::vector headers; + if (lua_istable(l, headersIndex)) + { + auto size = lua_objlen(l, headersIndex); + if (size) + { + for (auto i = 0U; i < size; ++i) + { + lua_rawgeti(l, headersIndex, i + 1); + headers.push_back(tpt_lua_toByteString(l, -1)); + lua_pop(l, 1); + } + } + else + { + // old dictionary format + lua_pushnil(l); + while (lua_next(l, headersIndex)) + { + lua_pushvalue(l, -2); + headers.push_back(tpt_lua_toByteString(l, -1) + ByteString(": ") + tpt_lua_toByteString(l, -2)); + lua_pop(l, 2); + } + } + } + + auto verb = tpt_lua_optByteString(l, verbIndex, ""); + return RequestHandle::Make(l, uri, isPost, verb, RequestHandle::normal, post_data, headers); +} + +static int http_get_auth_token(lua_State *l) +{ + return RequestHandle::Make(l, ByteString::Build(SCHEME, SERVER, "/ExternalAuth.api?Action=Get&Audience=", format::URLEncode(tpt_lua_checkByteString(l, 1))), false, {}, RequestHandle::getAuthToken, {}, {}); +} + +static int http_get(lua_State * l) +{ + return http_request(l, false); +} + +static int http_post(lua_State * l) +{ + return http_request(l, true); +} + +void LuaHttp::Open(lua_State *l) +{ + luaL_newmetatable(l, "HTTPRequest"); + lua_pushcfunction(l, http_request_gc); + lua_setfield(l, -2, "__gc"); + lua_newtable(l); + struct luaL_Reg httpRequestIndexMethods[] = { + { "status", http_request_status }, + { "progress", http_request_progress }, + { "cancel", http_request_cancel }, + { "finish", http_request_finish }, + { NULL, NULL } + }; + luaL_register(l, NULL, httpRequestIndexMethods); + lua_setfield(l, -2, "__index"); + lua_pop(l, 1); + lua_newtable(l); + struct luaL_Reg httpMethods[] = { + { "get", http_get }, + { "post", http_post }, + { "getAuthToken", http_get_auth_token }, + { NULL, NULL } + }; + luaL_register(l, NULL, httpMethods); + lua_setglobal(l, "http"); +} diff --git a/src/lua/LuaHttp.h b/src/lua/LuaHttp.h new file mode 100644 index 000000000..6e5bdff9c --- /dev/null +++ b/src/lua/LuaHttp.h @@ -0,0 +1,8 @@ +#pragma once +#include "Config.h" +#include "LuaCompat.h" + +namespace LuaHttp +{ + void Open(lua_State *l); +} diff --git a/src/lua/LuaScriptInterface.cpp b/src/lua/LuaScriptInterface.cpp index 47d8f81b9..ea6c7f2e4 100644 --- a/src/lua/LuaScriptInterface.cpp +++ b/src/lua/LuaScriptInterface.cpp @@ -1,6 +1,5 @@ #include "Config.h" -#include "client/http/Request.h" // includes curl.h, needs to come first to silence a warning on windows #include "bzip2/bz2wrap.h" #include "common/VariantIndex.h" @@ -24,6 +23,7 @@ #include "LuaTextbox.h" #include "LuaWindow.h" #include "LuaTCPSocket.h" +#include "LuaHttp.h" #include "LuaSDLKeys.h" #include "PowderToy.h" #include "TPTScriptInterface.h" @@ -4223,309 +4223,9 @@ int LuaScriptInterface::event_getmodifiers(lua_State * l) return 1; } -class RequestHandle -{ -public: - enum RequestType - { - normal, - getAuthToken, - }; - -private: - std::unique_ptr request; - bool dead = false; - RequestType type; - - RequestHandle() = default; - - void FinishGetAuthToken(ByteString &data, int &status_out, std::vector &headers) - { - headers.clear(); - std::istringstream ss(data); - Json::Value root; - try - { - ss >> root; - auto status = root["Status"].asString(); - if (status == "OK") - { - status_out = 200; - data = root["Token"].asString(); - } - else - { - status_out = 403; - data = status; - } - } - catch (std::exception &e) - { - std::cerr << "bad auth response: " << e.what() << std::endl; - status_out = 600; - data.clear(); - } - } - -public: - static int Make(lua_State *l, const ByteString &uri, bool isPost, const ByteString &verb, RequestType type, const std::map &post_data, const std::vector &headers) - { - auto authUser = Client::Ref().GetAuthUser(); - if (type == getAuthToken && !authUser.UserID) - { - lua_pushnil(l); - lua_pushliteral(l, "not authenticated"); - return 2; - } - auto *rh = (RequestHandle *)lua_newuserdata(l, sizeof(RequestHandle)); - if (!rh) - { - return 0; - } - new(rh) RequestHandle(); - rh->type = type; - rh->request = std::make_unique(uri); - if (verb.size()) - { - rh->request->Verb(verb); - } - for (auto &header : headers) - { - rh->request->AddHeader(header); - } - if (isPost) - { - rh->request->AddPostData(post_data); - } - if (type == getAuthToken) - { - rh->request->AuthHeaders(ByteString::Build(authUser.UserID), authUser.SessionID); - } - rh->request->Start(); - luaL_newmetatable(l, "HTTPRequest"); - lua_setmetatable(l, -2); - return 1; - } - - ~RequestHandle() - { - if (!Dead()) - { - Cancel(); - } - } - - bool Dead() const - { - return dead; - } - - bool Done() const - { - return request->CheckDone(); - } - - void Progress(int *total, int *done) - { - if (!dead) - { - std::tie(*total, *done) = request->CheckProgress(); - } - } - - void Cancel() - { - if (!dead) - { - request.reset(); - dead = true; - } - } - - ByteString Finish(int &status_out, std::vector &headers) - { - ByteString data; - if (!dead) - { - if (request->CheckDone()) - { - headers = request->ResponseHeaders(); - std::tie(status_out, data) = request->Finish(); - request.reset(); - if (type == getAuthToken && status_out == 200) - { - FinishGetAuthToken(data, status_out, headers); - } - dead = true; - } - } - return data; - } -}; - -static int http_request_gc(lua_State *l) -{ - auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); - rh->~RequestHandle(); - return 0; -} - -static int http_request_status(lua_State *l) -{ - auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); - if (rh->Dead()) - { - lua_pushliteral(l, "dead"); - } - else if (rh->Done()) - { - lua_pushliteral(l, "done"); - } - else - { - lua_pushliteral(l, "running"); - } - return 1; -} - -static int http_request_progress(lua_State *l) -{ - auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); - if (!rh->Dead()) - { - int total, done; - rh->Progress(&total, &done); - lua_pushinteger(l, total); - lua_pushinteger(l, done); - return 2; - } - return 0; -} - -static int http_request_cancel(lua_State *l) -{ - auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); - if (!rh->Dead()) - { - rh->Cancel(); - } - return 0; -} - -static int http_request_finish(lua_State *l) -{ - auto *rh = (RequestHandle *)luaL_checkudata(l, 1, "HTTPRequest"); - if (!rh->Dead()) - { - int status_out; - std::vector headers; - ByteString data = rh->Finish(status_out, headers); - lua_pushlstring(l, &data[0], data.size()); - lua_pushinteger(l, status_out); - lua_newtable(l); - for (auto i = 0; i < int(headers.size()); ++i) - { - lua_pushlstring(l, headers[i].data(), headers[i].size()); - lua_rawseti(l, -2, i + 1); - } - return 3; - } - return 0; -} - -static int http_request(lua_State *l, bool isPost) -{ - ByteString uri = tpt_lua_checkByteString(l, 1); - std::map post_data; - auto headersIndex = 2; - auto verbIndex = 3; - - if (isPost) - { - headersIndex += 1; - verbIndex += 1; - if (lua_istable(l, 2)) - { - lua_pushnil(l); - while (lua_next(l, 2)) - { - lua_pushvalue(l, -2); - post_data.emplace(tpt_lua_toByteString(l, -1), tpt_lua_toByteString(l, -2)); - lua_pop(l, 2); - } - } - } - - std::vector headers; - if (lua_istable(l, headersIndex)) - { - auto size = lua_objlen(l, headersIndex); - if (size) - { - for (auto i = 0U; i < size; ++i) - { - lua_rawgeti(l, headersIndex, i + 1); - headers.push_back(tpt_lua_toByteString(l, -1)); - lua_pop(l, 1); - } - } - else - { - // old dictionary format - lua_pushnil(l); - while (lua_next(l, headersIndex)) - { - lua_pushvalue(l, -2); - headers.push_back(tpt_lua_toByteString(l, -1) + ByteString(": ") + tpt_lua_toByteString(l, -2)); - lua_pop(l, 2); - } - } - } - - auto verb = tpt_lua_optByteString(l, verbIndex, ""); - return RequestHandle::Make(l, uri, isPost, verb, RequestHandle::normal, post_data, headers); -} - -static int http_get_auth_token(lua_State *l) -{ - return RequestHandle::Make(l, ByteString::Build(SCHEME, SERVER, "/ExternalAuth.api?Action=Get&Audience=", format::URLEncode(tpt_lua_checkByteString(l, 1))), false, {}, RequestHandle::getAuthToken, {}, {}); -} - -int LuaScriptInterface::http_get(lua_State * l) -{ - return http_request(l, false); -} - -int LuaScriptInterface::http_post(lua_State * l) -{ - return http_request(l, true); -} - void LuaScriptInterface::initHttpAPI() { - luaL_newmetatable(l, "HTTPRequest"); - lua_pushcfunction(l, http_request_gc); - lua_setfield(l, -2, "__gc"); - lua_newtable(l); - struct luaL_Reg httpRequestIndexMethods[] = { - { "status", http_request_status }, - { "progress", http_request_progress }, - { "cancel", http_request_cancel }, - { "finish", http_request_finish }, - { NULL, NULL } - }; - luaL_register(l, NULL, httpRequestIndexMethods); - lua_setfield(l, -2, "__index"); - lua_pop(l, 1); - lua_newtable(l); - struct luaL_Reg httpMethods[] = { - { "get", http_get }, - { "post", http_post }, - { "getAuthToken", http_get_auth_token }, - { NULL, NULL } - }; - luaL_register(l, NULL, httpMethods); - lua_setglobal(l, "http"); + LuaHttp::Open(l); } static int PushGameControllerEvent(lua_State * l, const GameControllerEvent &event) diff --git a/src/lua/LuaScriptInterface.h b/src/lua/LuaScriptInterface.h index 1320e33fe..e372a8e0d 100644 --- a/src/lua/LuaScriptInterface.h +++ b/src/lua/LuaScriptInterface.h @@ -186,9 +186,6 @@ class LuaScriptInterface: public TPTScriptInterface static int event_getmodifiers(lua_State * l); void initHttpAPI(); - static int http_get(lua_State * l); - static int http_post(lua_State * l); - void initSocketAPI(); std::vector lua_el_func_v, lua_gr_func_v, lua_cd_func_v; diff --git a/src/lua/LuaTCPSocket.h b/src/lua/LuaTCPSocket.h index 93d87fac5..fd2bdc2d0 100644 --- a/src/lua/LuaTCPSocket.h +++ b/src/lua/LuaTCPSocket.h @@ -1,7 +1,5 @@ #pragma once - #include "Config.h" - #include "LuaCompat.h" namespace LuaTCPSocket diff --git a/src/lua/meson.build b/src/lua/meson.build index d3ccf57ba..1a1457b69 100644 --- a/src/lua/meson.build +++ b/src/lua/meson.build @@ -5,6 +5,7 @@ luaconsole_files = files( 'LuaCheckbox.cpp', 'LuaCompat.c', 'LuaComponent.cpp', + 'LuaHttp.cpp', 'LuaLabel.cpp', 'LuaProgressBar.cpp', 'LuaScriptInterface.cpp',