1
0
Fork 0

Removed cWebPlugin, WebAdmin uses cLuaState::cCallback.

This commit is contained in:
Mattes D 2016-03-02 10:05:10 +01:00
parent 1f75d45222
commit af8c96026d
14 changed files with 649 additions and 642 deletions

View File

@ -2,7 +2,7 @@ return
{
cPlugin =
{
Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own Plugin object.
Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own cPlugin object.
]],
Functions =
{
@ -22,10 +22,10 @@ return
cPluginLua =
{
Desc = "",
Desc = "(<b>OBSOLETE</b>) This class is no longer useful in the API and will be removed as soon as all core plugins are migrated away from it. The {{cPlugin}} class serves as the main plugin instance's interface.",
Functions =
{
AddWebTab = { Params = "", Return = "", Notes = "Adds a new webadmin tab" },
AddWebTab = { Params = "Title, HandlerFn", Return = "", Notes = "<b>OBSOLETE</b> - Use {{cWebAdmin}}:AddWebTab() instead." },
},
Inherits = "cPlugin",
}, -- cPluginLua
@ -81,6 +81,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
GetNumLoadedPlugins = { Params = "", Return = "number", Notes = "Returns the number of loaded plugins (psLoaded only)" },
GetNumPlugins = { Params = "", Return = "number", Notes = "Returns the number of plugins, including the disabled, errored, unloaded and not-found ones" },
GetPlugin = { Params = "PluginName", Return = "{{cPlugin}}", Notes = "(<b>DEPRECATED, UNSAFE</b>) Returns a plugin handle of the specified plugin, or nil if such plugin is not loaded. Note thatdue to multithreading the handle is not guaranteed to be safe for use when stored - a single-plugin reload may have been triggered in the mean time for the requested plugin." },
GetPluginFolderName = { Params = "PluginName", Return = "string", Notes = "Returns the name of the folder from which the plugin was loaded (without the \"Plugins\" part). Used as a plugin's display name." },
GetPluginsPath = { Params = "", Return = "string", Notes = "Returns the path where the individual plugin folders are located. Doesn't include the path separator at the end of the returned string." },
IsCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if in-game Command is already bound (by any plugin)" },
IsConsoleCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if console Command is already bound (by any plugin)" },

View File

@ -5,7 +5,15 @@ return
Desc = "",
Functions =
{
AddWebTab = { Params = "Title, UrlPath, HandlerFn", Return = "", Notes = "(STATIC) Adds a new web tab to webadmin. The tab uses \"Title\" as its display string and is identified in the URL using the UrlPath (https://server.domain.com/webadmin/{PluginName}/{UrlPath}). The HandlerFn is the callback function that is called when the admin accesses the page, it has the following signature:<br/><pre class=\"prettyprint lang-lua\">function ({{a_Request|HTTPRequest}}, a_UrlPath)<br/> return Content, ContentType<br/>end</pre> URLPath must not contain a '/', the recommendation is to use only 7-bit-clean ASCII character set." },
GetAllWebTabs = { Params = "", Return = "array-table", Notes = "(STATIC) Returns an array-table with each item describing a web tab, for all web tabs registered in the WebAdmin, for all plugins. The returned table has the following format:<br/><pre class=\"prettyprint lang-lua\">{<br/> {<br/> PluginName = \"Plugin's API name\",<br/> UrlPath = \"UrlPath given to AddWebTab\",<br/> Title = \"Title given to AddWebTab\",<br/> },<br/> ...<br/>}"},
GetBaseURL = { Params = "URL", Return = "string", Notes = "(STATIC) Returns the string that is the path of the base webadmin (\"../../../webadmin\") relative to the given URL." },
GetContentTypeFromFileExt = { Params = "FileExt", Return = "string", Notes = "(STATIC) Returns the content-type that should be used for files with the specified extension (without the dot), such as \"text/plain\" for the \"txt\" extension. If the extension is not known, returns an empty string." },
GetHTMLEscapedString = { Params = "string", Return = "string", Notes = "(STATIC) Gets the HTML-escaped representation of a requested string. This is useful for user input and game data that is not guaranteed to be escaped already." },
GetPage = { Params = "{{Request|HTTPRequest}}", Return = "table", Notes = "(STATIC) Returns the (inner HTML) page contents for the specified request. Calls the appropriate WebTab handler registered via AddWebTab() and returns the information from that plugin wrapped in a table with the following structure:<br/><pre class=\"prettyprint lang-lua\">{<br/> Content = \"\", -- Content returned by the plugin<br/> ContentType = \"\", -- Content type returned by the plugin, or \"text/html\" if none returned<br/> UrlPath = \"\", -- UrlPath decoded from the request<br/> TabTitle = \"\", -- Title of the tab that handled the request, as given to AddWebTab()<br/> PluginName = \"\", -- API name of the plugin that handled the request<br/> PluginFolder = \"\", -- Folder name (= display name) of the plugin that handled the request<br/>}</pre>This function is mainly used in the webadmin template file." },
GetPorts = { Params = "", Return = "string", Notes = "Returns a comma-separated list of ports on which the webadmin is configured to listen. Note that this list does include ports that may currently be unavailable (another server was already listening on them prior to launching Cuberite)." },
GetURLEncodedString = { Params = "string", Return = "string", Notes = "(STATIC) Returns the string given to it escaped by URL encoding, which makes the string suitable for transmission in an URL. Invalid characters are turned into \"%xy\" values." },
Reload = { Params = "", Return = "", Notes = "Reloads the webadmin's config - the allowed logins, the template script and the login page. Note that reloading will not change the \"enabled\" state of the server, and it will not update listening ports. Existing WebTabs will be kept registered even after the reload." },
},
}, -- cWebAdmin

View File

@ -20,7 +20,7 @@ end
function GetDefaultPage()
local function GetDefaultPage()
local PM = cRoot:Get():GetPluginManager()
local SubTitle = "Current Game"
@ -55,30 +55,31 @@ end
function ShowPage(WebAdmin, TemplateRequest)
SiteContent = {}
local BaseURL = WebAdmin:GetBaseURL(TemplateRequest.Request.Path)
local BaseURL = cWebAdmin:GetBaseURL(TemplateRequest.Request.Path)
local Title = "Cuberite WebAdmin"
local NumPlayers = cRoot:Get():GetServer():GetNumPlayers()
local MemoryUsageKiB = cRoot:GetPhysicalRAMUsage()
local NumChunks = cRoot:Get():GetTotalChunkCount()
local PluginPage = WebAdmin:GetPage(TemplateRequest.Request)
local PluginPage = cWebAdmin:GetPage(TemplateRequest.Request)
local PageContent = PluginPage.Content
local SubTitle = PluginPage.PluginName
if (PluginPage.TabName ~= "") then
SubTitle = PluginPage.PluginName .. " - " .. PluginPage.TabName
local SubTitle = PluginPage.PluginFolder
if (PluginPage.UrlPath ~= "") then
SubTitle = PluginPage.PluginFolder .. " - " .. PluginPage.TabTitle
end
if (PageContent == "") then
PageContent, SubTitle = GetDefaultPage()
end
--[[
-- 2016-01-15 Mattes: This wasn't used anywhere in the code, no idea what it was supposed to do
local reqParamsClass = ""
for key,value in pairs(TemplateRequest.Request.Params) do
for key, value in pairs(TemplateRequest.Request.Params) do
reqParamsClass = reqParamsClass .. " param-" .. string.lower(string.gsub(key, "[^a-zA-Z0-9]+", "-") .. "-" .. string.gsub(value, "[^a-zA-Z0-9]+", "-"))
end
if (string.gsub(reqParamsClass, "%s", "") == "") then
reqParamsClass = " no-param"
end
--]]
Output([[
<!-- Copyright Justin S and Cuberite Team, licensed under CC-BY-SA 3.0 -->
@ -133,20 +134,39 @@ function ShowPage(WebAdmin, TemplateRequest)
<td class="trow1 smalltext">
]])
local AllPlugins = WebAdmin:GetPlugins()
for key,value in pairs(AllPlugins) do
local PluginWebTitle = value:GetWebTitle()
local TabNames = value:GetTabNames()
if (GetTableSize(TabNames) > 0) then
Output("<div><a class='usercp_nav_item usercp_nav_pmfolder' style='text-decoration:none;'><b>"..PluginWebTitle.."</b></a></div>\n");
for webname,prettyname in pairs(TabNames) do
Output("<div><a href='" .. BaseURL .. PluginWebTitle .. "/" .. webname .. "' class='usercp_nav_item usercp_nav_sub_pmfolder'>" .. prettyname .. "</a></div>\n")
-- Get all tabs:
local perPluginTabs = {}
for _, tab in ipairs(cWebAdmin:GetAllWebTabs()) do
local pluginTabs = perPluginTabs[tab.PluginName] or {};
perPluginTabs[tab.PluginName] = pluginTabs
table.insert(pluginTabs, tab)
end
-- Sort by plugin:
local pluginNames = {}
for pluginName, pluginTabs in pairs(perPluginTabs) do
table.insert(pluginNames, pluginName)
end
table.sort(pluginNames)
-- Output by plugin, then alphabetically:
for _, pluginName in ipairs(pluginNames) do
local pluginTabs = perPluginTabs[pluginName]
table.sort(pluginTabs,
function(a_Tab1, a_Tab2)
return ((a_Tab1.Title or "") < (a_Tab2.Title or ""))
end
)
-- Translate the plugin name into the folder name (-> title)
local pluginWebTitle = cPluginManager:Get():GetPluginFolderName(pluginName) or pluginName
Output("<div><a class='usercp_nav_item usercp_nav_pmfolder' style='text-decoration:none;'><b>" .. pluginWebTitle .. "</b></a></div>\n");
Output("<br>\n");
-- Output each tab:
for _, tab in pairs(pluginTabs) do
Output("<div><a href='" .. BaseURL .. pluginName .. "/" .. tab.UrlPath .. "' class='usercp_nav_item usercp_nav_sub_pmfolder'>" .. tab.Title .. "</a></div>\n")
end
Output("<br>\n");
end

View File

@ -38,7 +38,6 @@ $cfile "LuaFunctions.h"
$cfile "PluginManager.h"
$cfile "Plugin.h"
$cfile "PluginLua.h"
$cfile "WebPlugin.h"
$cfile "LuaWindow.h"
$cfile "../BlockID.h"

View File

@ -23,7 +23,6 @@ SET (SRCS
Plugin.cpp
PluginLua.cpp
PluginManager.cpp
WebPlugin.cpp
)
SET (HDRS
@ -44,7 +43,6 @@ SET (HDRS
Plugin.h
PluginLua.h
PluginManager.h
WebPlugin.h
tolua++.h
)
@ -66,7 +64,6 @@ set(BINDING_DEPENDENCIES
../Bindings/Plugin.h
../Bindings/PluginLua.h
../Bindings/PluginManager.h
../Bindings/WebPlugin.h
../BiomeDef.h
../BlockArea.h
../BlockEntities/BeaconEntity.h

View File

@ -1702,46 +1702,70 @@ static int tolua_SetObjectCallback(lua_State * tolua_S)
// Callback class used for the WebTab:
class cWebTabCallback:
public cWebAdmin::cWebTabCallback
{
public:
/** The Lua callback to call to generate the page contents. */
cLuaState::cCallback m_Callback;
virtual bool Call(
const HTTPRequest & a_Request,
const AString & a_UrlPath,
AString & a_Content,
AString & a_ContentType
) override
{
AString content, contentType;
return m_Callback.Call(&a_Request, a_UrlPath, cLuaState::Return, a_Content, a_ContentType);
}
};
static int tolua_cPluginLua_AddWebTab(lua_State * tolua_S)
{
cLuaState LuaState(tolua_S);
cPluginLua * self = nullptr;
// OBSOLETE, use cWebAdmin:AddWebTab() instead!
// Function signature:
// cPluginLua:AddWebTab(Title, CallbackFn, [UrlPath])
if (!LuaState.GetStackValue(1, self))
// TODO: Warn about obsolete API usage
// Only implement after merging the new API change and letting some time for changes in the plugins
// Check params:
cLuaState LuaState(tolua_S);
cPluginLua * self = cManualBindings::GetLuaPlugin(tolua_S);
if (self == nullptr)
{
return 0;
}
if (
!LuaState.CheckParamString(2) ||
!LuaState.CheckParamFunction(3) ||
// Optional string as param 4
!LuaState.CheckParamEnd(5)
)
{
LOGWARNING("cPluginLua:AddWebTab: invalid self as first argument");
return 0;
}
tolua_Error tolua_err;
tolua_err.array = 0;
tolua_err.index = 3;
tolua_err.type = "function";
std::string Title;
int Reference = LUA_REFNIL;
if (LuaState.CheckParamString(2) && LuaState.CheckParamFunction(3))
// Read the params:
AString title, urlPath;
auto callback = std::make_shared<cWebTabCallback>();
if (!LuaState.GetStackValues(2, title, callback->m_Callback))
{
Reference = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
LuaState.GetStackValue(2, Title);
LOGWARNING("cPlugin:AddWebTab(): Cannot read required parameters");
return 0;
}
else
if (!LuaState.GetStackValue(4, urlPath))
{
return cManualBindings::tolua_do_error(tolua_S, "#ferror calling function '#funcname#'", &tolua_err);
urlPath = cWebAdmin::GetURLEncodedString(title);
}
if (Reference != LUA_REFNIL)
{
if (!self->AddWebTab(Title.c_str(), tolua_S, Reference))
{
luaL_unref(tolua_S, LUA_REGISTRYINDEX, Reference);
}
}
else
{
LOGWARNING("cPluginLua:AddWebTab: invalid function reference in 2nd argument (Title: \"%s\")", Title.c_str());
}
cRoot::Get()->GetWebAdmin()->AddWebTab(title, urlPath, self->GetName(), callback);
return 0;
}
@ -2107,22 +2131,68 @@ static int tolua_cUrlParser_ParseAuthorityPart(lua_State * a_LuaState)
static int tolua_cWebAdmin_GetPlugins(lua_State * tolua_S)
static int tolua_cWebAdmin_AddWebTab(lua_State * tolua_S)
{
cWebAdmin * self = reinterpret_cast<cWebAdmin *>(tolua_tousertype(tolua_S, 1, nullptr));
// Function signatures:
// cWebAdmin:AddWebTab(Title, UrlPath, CallbackFn)
const cWebAdmin::PluginList & AllPlugins = self->GetPlugins();
// Check params:
cLuaState LuaState(tolua_S);
cPluginLua * self = cManualBindings::GetLuaPlugin(tolua_S);
if (self == nullptr)
{
return 0;
}
if (
// Don't care whether the first param is a cWebAdmin instance or class
!LuaState.CheckParamString(2, 3) ||
!LuaState.CheckParamFunction(4) ||
!LuaState.CheckParamEnd(5)
)
{
return 0;
}
lua_createtable(tolua_S, static_cast<int>(AllPlugins.size()), 0);
// Read the params:
AString title, urlPath;
auto callback = std::make_shared<cWebTabCallback>();
if (!LuaState.GetStackValues(2, title, urlPath, callback->m_Callback))
{
LOGWARNING("cWebAdmin:AddWebTab(): Cannot read required parameters");
return 0;
}
cRoot::Get()->GetWebAdmin()->AddWebTab(title, urlPath, self->GetName(), callback);
return 0;
}
static int tolua_cWebAdmin_GetAllWebTabs(lua_State * tolua_S)
{
// Function signature:
// cWebAdmin:GetAllWebTabs() -> { {"PluginName", "UrlPath", "Title"}, {"PluginName", "UrlPath", "Title"}, ...}
// Don't care about params at all
auto webTabs = cRoot::Get()->GetWebAdmin()->GetAllWebTabs();
lua_createtable(tolua_S, static_cast<int>(webTabs.size()), 0);
int newTable = lua_gettop(tolua_S);
int index = 1;
cWebAdmin::PluginList::const_iterator iter = AllPlugins.begin();
while (iter != AllPlugins.end())
cLuaState L(tolua_S);
for (const auto & wt: webTabs)
{
const cWebPlugin * Plugin = *iter;
tolua_pushusertype(tolua_S, reinterpret_cast<void *>(const_cast<cWebPlugin*>(Plugin)), "const cWebPlugin");
lua_createtable(tolua_S, 0, 3);
L.Push(wt->m_PluginName);
lua_setfield(tolua_S, -2, "PluginName");
L.Push(wt->m_UrlPath);
lua_setfield(tolua_S, -2, "UrlPath");
L.Push(wt->m_Title);
lua_setfield(tolua_S, -2, "Title");
lua_rawseti(tolua_S, newTable, index);
++iter;
++index;
}
return 1;
@ -2132,14 +2202,70 @@ static int tolua_cWebAdmin_GetPlugins(lua_State * tolua_S)
/** Binding for cWebAdmin::GetHTMLEscapedString.
/** Binding for cWebAdmin::GetBaseURL.
Manual code required because ToLua generates an extra return value */
static int tolua_AllToLua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
static int tolua_cWebAdmin_GetBaseURL(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
!S.CheckParamUserTable(1, "cWebAdmin") ||
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
AString Input;
S.GetStackValue(2, Input);
// Convert and return:
S.Push(cWebAdmin::GetBaseURL(Input));
return 1;
}
/** Binding for cWebAdmin::GetContentTypeFromFileExt.
Manual code required because ToLua generates an extra return value */
static int tolua_cWebAdmin_GetContentTypeFromFileExt(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
AString Input;
S.GetStackValue(2, Input);
// Convert and return:
S.Push(cWebAdmin::GetContentTypeFromFileExt(Input));
return 1;
}
/** Binding for cWebAdmin::GetHTMLEscapedString.
Manual code required because ToLua generates an extra return value */
static int tolua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
@ -2160,14 +2286,71 @@ static int tolua_AllToLua_cWebAdmin_GetHTMLEscapedString(lua_State * tolua_S)
/** Binding for cWebAdmin::GetPage. */
static int tolua_cWebAdmin_GetPage(lua_State * tolua_S)
{
/*
Function signature:
cWebAdmin:GetPage(a_HTTPRequest) ->
{
Content = "", // Content generated by the plugin
ContentType = "", // Content type generated by the plugin (default: "text/html")
UrlPath = "", // URL path of the tab
TabTitle = "", // Tab's title, as register via cWebAdmin:AddWebTab()
PluginName = "", // Plugin's API name
PluginFolder = "", // Plugin's folder name (display name)
}
*/
// Check the param types:
cLuaState S(tolua_S);
if (
// Don't care about first param, whether it's cWebAdmin instance or class
!S.CheckParamUserType(2, "HTTPRequest") ||
!S.CheckParamEnd(3)
)
{
return 0;
}
// Get the parameters:
HTTPRequest * request = nullptr;
if (!S.GetStackValue(2, request))
{
LOGWARNING("cWebAdmin:GetPage(): Cannot read the HTTPRequest parameter.");
return 0;
}
// Generate the page and push the results as a dictionary-table:
auto page = cRoot::Get()->GetWebAdmin()->GetPage(*request);
lua_createtable(S, 0, 6);
S.Push(page.Content);
lua_setfield(S, -2, "Content");
S.Push(page.ContentType);
lua_setfield(S, -2, "ContentType");
S.Push(page.TabUrlPath);
lua_setfield(S, -2, "UrlPath");
S.Push(page.TabTitle);
lua_setfield(S, -2, "TabTitle");
S.Push(page.PluginName);
lua_setfield(S, -2, "PluginName");
S.Push(cPluginManager::Get()->GetPluginFolderName(page.PluginName));
lua_setfield(S, -2, "PluginFolder");
return 1;
}
/** Binding for cWebAdmin::GetURLEncodedString.
Manual code required because ToLua generates an extra return value */
static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
static int tolua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
{
// Check the param types:
cLuaState S(tolua_S);
if (
!S.CheckParamUserTable(1, "cWebAdmin") ||
// Don't care whether the first param is a cWebAdmin instance or class
!S.CheckParamString(2) ||
!S.CheckParamEnd(3)
)
@ -2188,27 +2371,6 @@ static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
static int tolua_cWebPlugin_GetTabNames(lua_State * tolua_S)
{
// Returns a map of (SafeTitle -> Title) for the plugin's web tabs.
auto self = reinterpret_cast<cWebPlugin *>(tolua_tousertype(tolua_S, 1, nullptr));
auto TabNames = self->GetTabNames();
lua_newtable(tolua_S);
int index = 1;
for (auto itr = TabNames.cbegin(), end = TabNames.cend(); itr != end; ++itr)
{
tolua_pushstring(tolua_S, itr->second.c_str()); // Because the SafeTitle is supposed to be unique, use it as key
tolua_pushstring(tolua_S, itr->first.c_str());
lua_rawset(tolua_S, -3);
++index;
}
return 1;
}
static int tolua_cClientHandle_SendPluginMessage(lua_State * L)
{
cLuaState S(L);
@ -3655,13 +3817,13 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cWebAdmin");
tolua_function(tolua_S, "GetHTMLEscapedString", tolua_AllToLua_cWebAdmin_GetHTMLEscapedString);
tolua_function(tolua_S, "GetPlugins", tolua_cWebAdmin_GetPlugins);
tolua_function(tolua_S, "GetURLEncodedString", tolua_AllToLua_cWebAdmin_GetURLEncodedString);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cWebPlugin");
tolua_function(tolua_S, "GetTabNames", tolua_cWebPlugin_GetTabNames);
tolua_function(tolua_S, "AddWebTab", tolua_cWebAdmin_AddWebTab);
tolua_function(tolua_S, "GetAllWebTabs", tolua_cWebAdmin_GetAllWebTabs);
tolua_function(tolua_S, "GetBaseURL", tolua_cWebAdmin_GetBaseURL);
tolua_function(tolua_S, "GetContentTypeFromFileExt", tolua_cWebAdmin_GetContentTypeFromFileExt);
tolua_function(tolua_S, "GetHTMLEscapedString", tolua_cWebAdmin_GetHTMLEscapedString);
tolua_function(tolua_S, "GetPage", tolua_cWebAdmin_GetPage);
tolua_function(tolua_S, "GetURLEncodedString", tolua_cWebAdmin_GetURLEncodedString);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "HTTPRequest");

View File

@ -15,6 +15,8 @@
#include "../CommandOutput.h"
#include "PluginManager.h"
#include "../Item.h"
#include "../Root.h"
#include "../WebAdmin.h"
extern "C"
{
@ -66,7 +68,7 @@ void cPluginLua::Close(void)
// Remove the command bindings and web tabs:
ClearCommands();
ClearConsoleCommands();
ClearTabs();
ClearWebTabs();
// Notify and remove all m_Resettables (unlock the m_CriticalSection while resetting them):
cResettablePtrs resettables;
@ -205,7 +207,7 @@ bool cPluginLua::Load(void)
void cPluginLua::Unload(void)
{
ClearTabs();
ClearWebTabs();
super::Unload();
Close();
}
@ -2126,51 +2128,6 @@ void cPluginLua::AddResettable(cPluginLua::cResettablePtr a_Resettable)
AString cPluginLua::HandleWebRequest(const HTTPRequest & a_Request)
{
// Find the tab to use for the request:
auto TabName = GetTabNameForRequest(a_Request);
AString SafeTabTitle = TabName.second;
if (SafeTabTitle.empty())
{
return "";
}
auto Tab = GetTabBySafeTitle(SafeTabTitle);
if (Tab == nullptr)
{
return "";
}
// Get the page content from the plugin:
cCSLock Lock(m_CriticalSection);
AString Contents = Printf("WARNING: WebPlugin tab '%s' did not return a string!", Tab->m_Title.c_str());
if (!m_LuaState.Call(Tab->m_UserData, &a_Request, cLuaState::Return, Contents))
{
return "Lua encountered error while processing the page request";
}
return Contents;
}
bool cPluginLua::AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference)
{
cCSLock Lock(m_CriticalSection);
if (a_LuaState != m_LuaState)
{
LOGERROR("Only allowed to add a tab to a WebPlugin of your own Plugin!");
return false;
}
AddNewWebTab(a_Title, a_FunctionReference);
return true;
}
void cPluginLua::BindCommand(const AString & a_Command, int a_FnRef)
{
ASSERT(m_Commands.find(a_Command) == m_Commands.end());
@ -2227,6 +2184,19 @@ void cPluginLua::CallbackWindowSlotChanged(int a_FnRef, cWindow & a_Window, int
void cPluginLua::ClearWebTabs(void)
{
auto webAdmin = cRoot::Get()->GetWebAdmin();
if (webAdmin != nullptr) // can be nullptr when shutting down the server
{
webAdmin->RemoveAllPluginWebTabs(m_Name);
}
}
////////////////////////////////////////////////////////////////////////////////
// cPluginLua::cResettable:

View File

@ -10,7 +10,6 @@
#pragma once
#include "Plugin.h"
#include "WebPlugin.h"
#include "LuaState.h"
// Names for the global variables through which the plugin is identified in its LuaState
@ -29,8 +28,7 @@ class cWindow;
// tolua_begin
class cPluginLua :
public cPlugin,
public cWebPlugin
public cPlugin
{
typedef cPlugin super;
@ -181,14 +179,6 @@ public:
/** Returns true if the plugin contains the function for the specified hook type, using the old-style registration (#121) */
bool CanAddOldStyleHook(int a_HookType);
// cWebPlugin overrides
virtual const AString GetWebTitle(void) const override {return GetName(); }
virtual AString HandleWebRequest(const HTTPRequest & a_Request) override;
/** Adds a new web tab to webadmin.
Displaying the tab calls the referenced function. */
bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // Exported in ManualBindings.cpp
/** Binds the command to call the function specified by a Lua function reference. Simply adds to CommandMap. */
void BindCommand(const AString & a_Command, int a_FnRef);
@ -270,6 +260,9 @@ protected:
/** Releases all Lua references, notifies and removes all m_Resettables[] and closes the m_LuaState. */
void Close(void);
/** Removes all WebTabs currently registered for this plugin from the WebAdmin. */
void ClearWebTabs(void);
} ; // tolua_export

View File

@ -1965,6 +1965,23 @@ bool cPluginManager::ForEachPlugin(cPluginCallback & a_Callback)
AString cPluginManager::GetPluginFolderName(const AString & a_PluginName)
{
// TODO: Implement locking for plugins
for (auto & plugin: m_Plugins)
{
if (plugin->GetName() == a_PluginName)
{
return plugin->GetFolderName();
}
}
return AString();
}
void cPluginManager::AddHook(cPlugin * a_Plugin, int a_Hook)
{
if (a_Plugin == nullptr)

View File

@ -332,6 +332,9 @@ public:
Returns true if all plugins have been reported, false if the callback has aborted the enumeration by returning true. */
bool ForEachPlugin(cPluginCallback & a_Callback);
/** Returns the name of the folder (cPlugin::GetFolderName()) from which the specified plugin was loaded. */
AString GetPluginFolderName(const AString & a_PluginName); // tolua_export
/** Returns the path where individual plugins' folders are expected.
The path doesn't end in a slash. */
static AString GetPluginsPath(void) { return FILE_IO_PREFIX + AString("Plugins"); } // tolua_export

View File

@ -1,152 +0,0 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "WebPlugin.h"
#include "../WebAdmin.h"
#include "../Root.h"
cWebPlugin::cWebPlugin()
{
cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin();
if (WebAdmin != nullptr)
{
WebAdmin->AddPlugin(this);
}
}
cWebPlugin::~cWebPlugin()
{
ASSERT(m_Tabs.empty()); // Has ClearTabs() been called?
// Remove from WebAdmin:
cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin();
if (WebAdmin != nullptr)
{
WebAdmin->RemovePlugin(this);
}
}
cWebPlugin::cTabNames cWebPlugin::GetTabNames(void) const
{
std::list< std::pair<AString, AString>> NameList;
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
NameList.push_back(std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle));
}
return NameList;
}
cWebPlugin::cTabPtr cWebPlugin::GetTabBySafeTitle(const AString & a_SafeTitle) const
{
cCSLock Lock(m_CSTabs);
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
if ((*itr)->m_SafeTitle == a_SafeTitle)
{
return *itr;
}
}
return nullptr;
}
std::pair<AString, AString> cWebPlugin::GetTabNameForRequest(const HTTPRequest & a_Request)
{
AStringVector Split = StringSplit(a_Request.Path, "/");
if (Split.empty())
{
return std::make_pair(AString(), AString());
}
cCSLock Lock(m_CSTabs);
cTabPtr Tab;
if (Split.size() > 2) // If we got the tab name, show that page
{
for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
if ((*itr)->m_SafeTitle.compare(Split[2]) == 0) // This is the one!
{
return std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle);
}
}
// Tab name not found, display an "empty" page:
return std::make_pair(AString(), AString());
}
// Show the first tab:
if (!m_Tabs.empty())
{
return std::make_pair(m_Tabs.front()->m_SafeTitle, m_Tabs.front()->m_SafeTitle);
}
// No tabs at all:
return std::make_pair(AString(), AString());
}
AString cWebPlugin::SafeString(const AString & a_String)
{
AString RetVal;
auto len = a_String.size();
RetVal.reserve(len);
for (size_t i = 0; i < len; ++i)
{
char c = a_String[i];
if (c == ' ')
{
c = '_';
}
RetVal.push_back(c);
}
return RetVal;
}
void cWebPlugin::AddNewWebTab(const AString & a_Title, int a_UserData)
{
auto Tab = std::make_shared<cTab>(a_Title, a_UserData);
cCSLock Lock(m_CSTabs);
m_Tabs.push_back(Tab);
}
void cWebPlugin::ClearTabs(void)
{
// Remove the webadmin tabs:
cTabPtrs Tabs;
{
cCSLock Lock(m_CSTabs);
std::swap(Tabs, m_Tabs);
}
}

View File

@ -1,80 +0,0 @@
#pragma once
struct HTTPRequest;
// tolua_begin
class cWebPlugin
{
public:
// tolua_end
struct cTab
{
AString m_Title;
AString m_SafeTitle;
int m_UserData;
cTab(const AString & a_Title, int a_UserData):
m_Title(a_Title),
m_SafeTitle(cWebPlugin::SafeString(a_Title)),
m_UserData(a_UserData)
{
}
};
typedef SharedPtr<cTab> cTabPtr;
typedef std::list<cTabPtr> cTabPtrs;
typedef std::list<std::pair<AString, AString>> cTabNames;
cWebPlugin();
virtual ~cWebPlugin();
// tolua_begin
/** Returns the title of the plugin, as it should be presented in the webadmin's pages tree. */
virtual const AString GetWebTitle(void) const = 0;
/** Sanitizes the input string, replacing spaces with underscores. */
static AString SafeString(const AString & a_String);
// tolua_end
virtual AString HandleWebRequest(const HTTPRequest & a_Request) = 0;
/** Adds a new web tab with the specified contents. */
void AddNewWebTab(const AString & a_Title, int a_UserData);
/** Removes all the tabs. */
void ClearTabs(void);
/** Returns all the tabs that this plugin has registered. */
const cTabPtrs & GetTabs(void) const { return m_Tabs; }
/** Returns all of the tabs that this plugin has registered. */
cTabNames GetTabNames(void) const; // Exported in ManualBindings.cpp
/** Returns the tab that has the specified SafeTitle.
Returns nullptr if no such tab. */
cTabPtr GetTabBySafeTitle(const AString & a_SafeTitle) const;
std::pair<AString, AString> GetTabNameForRequest(const HTTPRequest & a_Request);
private:
/** All tabs that this plugin has registered.
Protected against multithreaded access by m_CSTabs. */
cTabPtrs m_Tabs;
/** Protects m_Tabs against multithreaded access. */
mutable cCriticalSection m_CSTabs;
}; // tolua_export

View File

@ -2,10 +2,6 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "WebAdmin.h"
#include "Bindings/WebPlugin.h"
#include "Bindings/PluginManager.h"
#include "Bindings/Plugin.h"
#include "World.h"
#include "Entities/Player.h"
@ -87,9 +83,9 @@ public:
// cWebAdmin:
cWebAdmin::cWebAdmin(void) :
m_TemplateScript("<webadmin_template>"),
m_IsInitialized(false),
m_IsRunning(false),
m_TemplateScript("<webadmin_template>")
m_IsRunning(false)
{
}
@ -106,40 +102,9 @@ cWebAdmin::~cWebAdmin()
void cWebAdmin::AddPlugin(cWebPlugin * a_Plugin)
{
m_Plugins.remove(a_Plugin);
m_Plugins.push_back(a_Plugin);
}
void cWebAdmin::RemovePlugin(cWebPlugin * a_Plugin)
{
m_Plugins.remove(a_Plugin);
}
bool cWebAdmin::Init(void)
{
if (!m_IniFile.ReadFile("webadmin.ini"))
{
LOGWARN("Regenerating webadmin.ini, all settings will be reset");
m_IniFile.AddHeaderComment(" This file controls the webadmin feature of Cuberite");
m_IniFile.AddHeaderComment(" Username format: [User:*username*]");
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
if (!m_IniFile.GetValueSetB("WebAdmin", "Enabled", true))
if (!LoadIniFile())
{
// WebAdmin is disabled, bail out faking a success
return true;
@ -147,31 +112,7 @@ bool cWebAdmin::Init(void)
LOGD("Initialising WebAdmin...");
// Initialize the WebAdmin template script and load the file
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
m_HTTPServer.Stop();
return false;
}
// Load the login template, provide a fallback default if not found:
if (!LoadLoginTemplate())
{
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Sets the fallback template:
m_LoginTemplate = \
"<h1>Cuberite WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
Reload();
// Read the ports to be used:
// Note that historically the ports were stored in the "Port" and "PortsIPv6" values
@ -224,7 +165,7 @@ void cWebAdmin::Stop(void)
bool cWebAdmin::LoadLoginTemplate(void)
bool cWebAdmin::LoadLoginPage(void)
{
cFile File(FILE_IO_PREFIX "webadmin/login_template.html", cFile::fmRead);
if (!File.IsOpen())
@ -238,7 +179,8 @@ bool cWebAdmin::LoadLoginTemplate(void)
return false;
}
m_LoginTemplate = TemplateContent;
cCSLock Lock(m_CS);
m_LoginPage = TemplateContent;
return true;
}
@ -246,6 +188,89 @@ bool cWebAdmin::LoadLoginTemplate(void)
void cWebAdmin::RemoveAllPluginWebTabs(const AString & a_PluginName)
{
cCSLock lock(m_CS);
m_WebTabs.erase(std::remove_if(m_WebTabs.begin(), m_WebTabs.end(), [=](cWebTabPtr a_CBWebTab)
{
return (a_CBWebTab->m_PluginName == a_PluginName);
}),
m_WebTabs.end()
);
}
void cWebAdmin::Reload(void)
{
cCSLock lock(m_CS);
if (!LoadIniFile())
{
// We are asked to disable the webadmin, cannot do that, so warn the admin:
LOGWARNING(
"WebAdmin was previously enabled and now the settings say to disable it."
" This will not take effect until you restart the server."
);
}
// Initialize the WebAdmin template script and reload the file:
if (m_TemplateScript.IsValid())
{
m_TemplateScript.Close();
}
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
{
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin will not work properly!", FILE_IO_PREFIX "webadmin/template.lua");
m_TemplateScript.Close();
}
// Load the login template, provide a fallback default if not found:
if (!LoadLoginPage())
{
LOGWARN("Could not load WebAdmin login page \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
// Set the fallback:
m_LoginPage = \
"<h1>Cuberite WebAdmin</h1>" \
"<center>" \
"<form method='get' action='webadmin/'>" \
"<input type='submit' value='Log in'>" \
"</form>" \
"</center>";
}
}
bool cWebAdmin::LoadIniFile(void)
{
m_IniFile.Clear();
if (!m_IniFile.ReadFile("webadmin.ini"))
{
LOGWARN("Regenerating webadmin.ini, all settings will be reset");
m_IniFile.AddHeaderComment(" This file controls the webadmin feature of Cuberite");
m_IniFile.AddHeaderComment(" It specifies whether webadmin is enabled, and what logins are allowed. ");
m_IniFile.AddHeaderComment(" Username format: [User:*username*]");
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
return m_IniFile.GetValueSetB("WebAdmin", "Enabled", true);
}
void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request)
{
if (!a_Request.HasAuth())
@ -255,17 +280,20 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
}
// Check auth:
AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", "");
if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin - bad username or password");
return;
cCSLock Lock(m_CS);
AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", "");
if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword))
{
a_Connection.SendNeedAuth("Cuberite WebAdmin - bad username or password");
return;
}
}
// Check if the contents should be wrapped in the template:
auto BareURL = a_Request.GetURLPath();
ASSERT(BareURL.length() > 0);
bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~'));
bool ShouldWrapInTemplate = (!BareURL.empty() && (BareURL[1] != '~'));
// Retrieve the request data:
auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData());
@ -312,6 +340,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
// Try to get the template from the Lua template script
if (ShouldWrapInTemplate)
{
cCSLock Lock(m_CS);
if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template))
{
cHTTPOutgoingResponse Resp;
@ -325,59 +354,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT
return;
}
AString BaseURL = GetBaseURL(BareURL);
AString Menu;
Template = "{CONTENT}";
AString FoundPlugin;
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
{
cWebPlugin * WebPlugin = *itr;
std::list< std::pair<AString, AString> > NameList = WebPlugin->GetTabNames();
for (std::list< std::pair<AString, AString> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names)
{
Menu += "<li><a href='" + BaseURL + WebPlugin->GetWebTitle().c_str() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
}
}
sWebAdminPage Page = GetPage(TemplateRequest.Request);
AString Content = Page.Content;
FoundPlugin = Page.PluginName;
if (!Page.TabName.empty())
{
FoundPlugin += " - " + Page.TabName;
}
if (FoundPlugin.empty()) // Default page
{
Content = GetDefaultPage();
}
int MemUsageKiB = cRoot::GetPhysicalRAMUsage();
if (MemUsageKiB > 0)
{
ReplaceString(Template, "{MEM}", Printf("%.02f", static_cast<double>(MemUsageKiB) / 1024));
ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB));
}
else
{
ReplaceString(Template, "{MEM}", "unknown");
ReplaceString(Template, "{MEMKIB}", "unknown");
}
ReplaceString(Template, "{USERNAME}", a_Request.GetAuthUsername());
ReplaceString(Template, "{MENU}", Menu);
ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin);
ReplaceString(Template, "{CONTENT}", Content);
ReplaceString(Template, "{TITLE}", "Cuberite");
AString NumChunks;
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(Template.c_str(), Template.length());
// Send the un-decorated page content:
auto page = GetPage(TemplateRequest.Request);
cHTTPOutgoingResponse resp;
resp.SetContentType(page.ContentType);
a_Connection.Send(resp);
a_Connection.Send(page.Content.c_str(), page.Content.length());
a_Connection.FinishResponse();
}
@ -392,7 +374,7 @@ void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPInc
cHTTPOutgoingResponse Resp;
Resp.SetContentType("text/html");
a_Connection.Send(Resp);
a_Connection.Send(m_LoginTemplate);
a_Connection.Send(m_LoginPage);
a_Connection.FinishResponse();
}
@ -406,7 +388,7 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
std::replace(FileURL.begin(), FileURL.end(), '\\', '/');
// Remove all leading backslashes:
if (FileURL[0] == '/')
if (!FileURL.empty() && (FileURL[0] == '/'))
{
size_t FirstCharToRead = FileURL.find_first_not_of('/');
if (FirstCharToRead != AString::npos)
@ -418,8 +400,9 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
// Remove all "../" strings:
ReplaceString(FileURL, "../", "");
bool LoadedSuccessfull = false;
// Read the file contents and guess its mime-type, based on the extension:
AString Content = "<h2>404 Not Found</h2>";
AString ContentType;
AString Path = Printf(FILE_IO_PREFIX "webadmin/files/%s", FileURL.c_str());
if (cFile::IsFile(Path))
{
@ -427,18 +410,17 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
AString FileContent;
if (File.IsOpen() && (File.ReadRestOfFile(FileContent) != -1))
{
LoadedSuccessfull = true;
Content = FileContent;
std::swap(Content, FileContent);
size_t LastPointPosition = Path.find_last_of('.');
if (LastPointPosition != AString::npos)
{
ContentType = GetContentTypeFromFileExt(Path.substr(LastPointPosition + 1));
}
}
}
// Find content type (The currently method is very bad. We should change it later)
AString ContentType = "text/html";
size_t LastPointPosition = Path.find_last_of('.');
if (LoadedSuccessfull && (LastPointPosition != AString::npos) && (LastPointPosition < Path.length()))
if (ContentType.empty())
{
AString FileExtension = Path.substr(LastPointPosition + 1);
ContentType = GetContentTypeFromFileExt(FileExtension);
ContentType = "application/unknown";
}
// Send the response:
@ -456,32 +438,36 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc
AString cWebAdmin::GetContentTypeFromFileExt(const AString & a_FileExtension)
{
static bool IsInitialized = false;
static std::map<AString, AString> ContentTypeMap;
static AStringMap ContentTypeMap;
if (!IsInitialized)
{
// Initialize the ContentTypeMap:
ContentTypeMap["png"] = "image/png";
ContentTypeMap["fif"] = "image/fif";
ContentTypeMap["gif"] = "image/gif";
ContentTypeMap["jpeg"] = "image/jpeg";
ContentTypeMap["jpg"] = "image/jpeg";
ContentTypeMap["jpe"] = "image/jpeg";
ContentTypeMap["tiff"] = "image/tiff";
ContentTypeMap["ico"] = "image/ico";
ContentTypeMap["csv"] = "image/comma-separated-values";
ContentTypeMap["css"] = "text/css";
ContentTypeMap["js"] = "text/javascript";
ContentTypeMap["txt"] = "text/plain";
ContentTypeMap["rtx"] = "text/richtext";
ContentTypeMap["xml"] = "text/xml";
ContentTypeMap["png"] = "image/png";
ContentTypeMap["fif"] = "image/fif";
ContentTypeMap["gif"] = "image/gif";
ContentTypeMap["jpeg"] = "image/jpeg";
ContentTypeMap["jpg"] = "image/jpeg";
ContentTypeMap["jpe"] = "image/jpeg";
ContentTypeMap["tiff"] = "image/tiff";
ContentTypeMap["ico"] = "image/ico";
ContentTypeMap["csv"] = "text/csv";
ContentTypeMap["css"] = "text/css";
ContentTypeMap["js"] = "text/javascript";
ContentTypeMap["txt"] = "text/plain";
ContentTypeMap["rtx"] = "text/richtext";
ContentTypeMap["rtf"] = "text/richtext";
ContentTypeMap["xml"] = "text/xml";
ContentTypeMap["html"] = "text/html";
ContentTypeMap["htm"] = "text/html";
ContentTypeMap["xhtml"] = "application/xhtml+xml"; // Not recomended for IE6, but no-one uses that anymore
}
AString FileExtension = StrToLower(a_FileExtension);
if (ContentTypeMap.find(a_FileExtension) == ContentTypeMap.end())
auto itr = ContentTypeMap.find(StrToLower(a_FileExtension));
if (itr == ContentTypeMap.end())
{
return "text/html";
return AString();
}
return ContentTypeMap[FileExtension];
return itr->second;
}
@ -490,77 +476,49 @@ AString cWebAdmin::GetContentTypeFromFileExt(const AString & a_FileExtension)
sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request)
{
sWebAdminPage Page;
AStringVector Split = StringSplit(a_Request.Path, "/");
sWebAdminPage page;
auto split = StringSplit(a_Request.Path, "/");
// Find the plugin that corresponds to the requested path
AString FoundPlugin;
if (Split.size() > 1)
// If no specific page was requested, return an empty object:
if (split.size() <= 2)
{
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
return page;
}
// Find the WebTab handler responsible for the request:
cWebTabPtr tab;
{
cCSLock Lock(m_CS);
for (auto & wt: m_WebTabs)
{
if ((*itr)->GetWebTitle() == Split[1])
if (
(wt->m_PluginName == split[1]) &&
(wt->m_UrlPath == split[2])
)
{
Page.Content = (*itr)->HandleWebRequest(a_Request);
cWebPlugin * WebPlugin = *itr;
FoundPlugin = WebPlugin->GetWebTitle();
AString TabName = WebPlugin->GetTabNameForRequest(a_Request).first;
Page.PluginName = FoundPlugin;
Page.TabName = TabName;
tab = wt;
break;
}
}
} // for wt - m_WebTabs[]
}
// Return the page contents
return Page;
}
AString cWebAdmin::GetDefaultPage(void)
{
AString Content;
Content += "<h4>Server Name:</h4>";
Content += "<p>" + AString( cRoot::Get()->GetServer()->GetServerID()) + "</p>";
// Display a list of all plugins:
Content += "<h4>Plugins:</h4><ul>";
struct cPluginCallback:
public cPluginManager::cPluginCallback
// If a WebTab handler was found, call it:
if (tab != nullptr)
{
AString & m_Content;
cPluginCallback(AString & a_Content):
m_Content(a_Content)
page.ContentType = "text/html"; // Default to HTML content type, unless overridden by a plugin
if (!tab->m_Callback->Call(a_Request, split[1], page.Content, page.ContentType))
{
page.Content = GetHTMLEscapedString(Printf(
"WebTab callback for plugin %s, page %s has failed.",
tab->m_PluginName.c_str(), tab->m_Title.c_str()
));
}
virtual bool Item(cPlugin * a_Plugin) override
{
if (a_Plugin->IsLoaded())
{
AppendPrintf(m_Content, "<li>%s V.%i</li>", a_Plugin->GetName().c_str(), a_Plugin->GetVersion());
}
return false;
}
} Callback(Content);
cPluginManager::Get()->ForEachPlugin(Callback);
Content += "</ul>";
// Display a list of all players:
Content += "<h4>Players:</h4><ul>";
cPlayerAccum PlayerAccum;
cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
if (World != nullptr)
{
World->ForEachPlayer(PlayerAccum);
Content.append(PlayerAccum.m_Contents);
page.PluginName = tab->m_PluginName;
page.TabTitle = tab->m_Title;
page.TabUrlPath = split[1];
}
Content += "</ul><br>";
return Content;
return page;
}
@ -576,6 +534,41 @@ AString cWebAdmin::GetBaseURL(const AString & a_URL)
void cWebAdmin::AddWebTab(
const AString & a_Title,
const AString & a_UrlPath,
const AString & a_PluginName,
SharedPtr<cWebAdmin::cWebTabCallback> a_Callback
)
{
cCSLock lock(m_CS);
m_WebTabs.emplace_back(std::make_shared<cWebTab>(a_Title, a_UrlPath, a_PluginName, a_Callback));
}
bool cWebAdmin::DelWebTab(const AString & a_UrlPath)
{
cCSLock lock(m_CS);
for (auto itr = m_WebTabs.begin(), end = m_WebTabs.end(); itr != end; ++itr)
{
if ((*itr)->m_UrlPath == a_UrlPath)
{
m_WebTabs.erase(itr);
return true;
}
} // for itr - m_WebTabs[]
// Not found:
return false;
}
AString cWebAdmin::GetHTMLEscapedString(const AString & a_Input)
{
AString dst;

View File

@ -89,14 +89,14 @@ struct HTTPTemplateRequest
// tolua_begin
struct sWebAdminPage
{
AString Content;
AString PluginName;
AString TabName;
AString TabTitle;
AString TabUrlPath;
AString ContentType;
};
// tolua_end
@ -111,7 +111,49 @@ class cWebAdmin :
public:
// tolua_end
typedef std::list< cWebPlugin* > PluginList;
/** Interface for getting the content of a single WebTab. */
class cWebTabCallback abstract
{
public:
// Force a virtual destructor in descendants
virtual ~cWebTabCallback() {}
/** Returns the contents for the specified request.
Returns true if the call was successful, false on an error.
a_Request is the full HTTP request object, as received from the client.
a_UrlPath is the UrlPath of the WebTab registered for this request, as parsed from a_Request.
Descendants should fill a_Content with the page contents
and optionally set a_ContentType [defaults to "text/html"] */
virtual bool Call(
const HTTPRequest & a_Request,
const AString & a_UrlPath,
AString & a_Content,
AString & a_ContentType
) = 0;
};
/** Container for a single web tab.
Each web tab has a title, URL path and an associated plugin's name.
Each web tab is registered with a callback to provide the content. */
class cWebTab
{
public:
AString m_Title;
AString m_UrlPath;
AString m_PluginName;
SharedPtr<cWebTabCallback> m_Callback;
cWebTab(const AString & a_Title, const AString & a_UrlPath, const AString & a_PluginName, SharedPtr<cWebTabCallback> a_Callback):
m_Title(a_Title),
m_UrlPath(a_UrlPath),
m_PluginName(a_PluginName),
m_Callback(a_Callback)
{
}
};
typedef SharedPtr<cWebTab> cWebTabPtr;
typedef std::vector<cWebTabPtr> cWebTabPtrs;
cWebAdmin(void);
@ -120,81 +162,115 @@ public:
/** Initializes the object. Returns true if successfully initialized and ready to start */
bool Init(void);
/** Starts the HTTP server taking care of the admin. Returns true if successful */
/** Starts the HTTP server taking care of the webadmin. Returns true if successful */
bool Start(void);
/** Stops the HTTP server, if it was started. */
void Stop(void);
/** Loads the login template. Returns true if the loading succeeds, false if not. */
bool LoadLoginTemplate(void);
/** Loads the login template into m_LoginPage.
Returns true if the loading succeeds, false if not. */
bool LoadLoginPage(void);
void AddPlugin(cWebPlugin * a_Plugin);
void RemovePlugin(cWebPlugin * a_Plugin);
/** Returns a copy of all the registered web tabs.
Exported to Lua in ManualBindings.cpp. */
cWebTabPtrs GetAllWebTabs(void) { return m_WebTabs; }
// TODO: Convert this to the auto-locking callback mechanism used for looping players in worlds and such
PluginList GetPlugins() const { return m_Plugins; } // >> EXPORTED IN MANUALBINDINGS <<
/** Removes all WebTabs registered by the specified plugin. */
void RemoveAllPluginWebTabs(const AString & a_PluginName);
/** Returns the (inner) page contents for the specified request.
Calls the appropriate WebTab handler to get the contents.
Exported to Lua in ManualBindings.cpp. */
sWebAdminPage GetPage(const HTTPRequest & a_Request);
// tolua_begin
sWebAdminPage GetPage(const HTTPRequest & a_Request);
/** Reloads m_IniFile, m_LoginPage and m_TemplateScript.
Note that reloading will not change the "enabled" state of the server, and it will not update listening ports. */
void Reload(void);
/** Returns the contents of the default page - the list of plugins and players */
AString GetDefaultPage(void);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
AString GetBaseURL(const AString & a_URL);
/** Returns the list of ports used for the webadmin. */
/** Returns the list of ports on which the webadmin is configured to listen. */
AString GetPorts(void) const { return StringsConcat(m_Ports, ','); }
/** OBSOLETE: Returns the list of IPv4 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv4Ports(void) const { return GetPorts(); }
/** OBSOLETE: Returns the list of IPv6 ports used for the webadmin.
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
AString GetIPv6Ports(void) const { return GetPorts(); }
// tolua_end
/** Escapes text passed into it, so it can be embedded into html. */
/** Adds a new WebTab handler.
a_Title is the display title of the tab
a_UrlPath is the part of the URL that uniquely identifies this tab.
a_PluginName is the display name of the plugin creating this tab.
a_Callback is used to provide the actual WebTab contents, when requested.
Exported in ManualBindings.cpp. */
void AddWebTab(
const AString & a_Title,
const AString & a_UrlPath,
const AString & a_PluginName,
SharedPtr<cWebTabCallback> a_Callback
);
/** Removes the WebTab with the specified URL path.
Returns true if WebTab was found and removed, false if not found.
Exported in ManualBindings.cpp */
bool DelWebTab(const AString & a_UrlPath);
/** Escapes text passed into it, so it can be embedded into html.
Exported to Lua in ManualBindings.cpp. */
static AString GetHTMLEscapedString(const AString & a_Input);
/** Escapes the string for use in an URL */
/** Escapes the string for use in an URL
Exported to Lua in ManualBindings.cpp. */
static AString GetURLEncodedString(const AString & a_Input);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style).
Exported to Lua in ManualBindings.cpp. */
static AString GetBaseURL(const AString & a_URL);
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
static AString GetBaseURL(const AStringVector & a_URLSplit);
/** Returns the content type from the file extension. If the extension isn't in the list, the function returns "text/html" */
/** Returns the content type from the file extension.
If the extension isn't in the list, the function returns an empty string.
Exported to Lua in ManualBindings.cpp. */
static AString GetContentTypeFromFileExt(const AString & a_FileExtension);
protected:
/** Protects m_WebTabs, m_TemplateScript, m_LoginTemplate and m_IniFile against multithreaded access. */
cCriticalSection m_CS;
/** All registered WebTab handlers.
Protected against multithreaded access by m_CS. */
cWebTabPtrs m_WebTabs;
/** The Lua template script to provide templates.
Protected against multithreaded access by m_CS. */
cLuaState m_TemplateScript;
/** The HTML page that provides the login.
Protected against multithreaded access by m_CS. */
AString m_LoginPage;
/** The webadmin.ini file, used for the settings and allowed logins.
Protected against multithreaded access by m_CS. */
cIniFile m_IniFile;
/** Set to true if Init() succeeds and the webadmin isn't to be disabled */
bool m_IsInitialized;
/** Set to true if Start() succeeds in starting the server, reset back to false in Stop(). */
bool m_IsRunning;
/** The webadmin.ini file, used for the settings and allowed logins */
cIniFile m_IniFile;
PluginList m_Plugins;
/** The ports on which the webadmin is running. */
AStringVector m_Ports;
/** The Lua template script to provide templates: */
cLuaState m_TemplateScript;
/** The template that provides the login site: */
AString m_LoginTemplate;
/** The HTTP server which provides the underlying HTTP parsing, serialization and events */
cHTTPServer m_HTTPServer;
/** Loads webadmin.ini into m_IniFile.
Creates a default file if it doesn't exist.
Returns true if webadmin is enabled, false if disabled. */
bool LoadIniFile(void);
/** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */
void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request);