|
2 年之前 | |
---|---|---|
.. | ||
README.md | 3 年之前 | |
README.txt | 3 年之前 | |
description.txt | 3 年之前 | |
init.lua | 3 年之前 | |
mod.conf | 3 年之前 | |
samples.lua | 3 年之前 | |
screenshot.png | 2 年之前 |
active formspecs self-contained API that provides formspecs secure session tracking, formspecs session-based state tables, and localized event handling of formspecs
This mod is named formspecs
an improved version of dinamic secured forms
ActiveFormspecs (named formspecs
is a self-contained API that provides secure session tracking,
session-based state tables, and localized event handling of formspecs for individual mods as well
as entire games. It evolved out of a recurring need for secure "node formspecs" on my
server, while avoiding the burden of "reinventing the wheel" with every new project.
Since I've had many requests for source code from the Just Test Tribute subgame, I finally decided to release ActiveFormspecs with instructions and code examples so that other mod authors can start making use of this framework as well. It's easy to install and use, and arguably a more robust alternative to the builtin formspecs API.
ActiveFormspecs is intended to be compatible with all versions of Minetest as possible, currently tested with 0.4.16 and 5.2.
It has been in continuous use on my server since December 2016 with only minor revisions. So it should prove secure and stable enough for any production environment, as long as you follow the instructions below.
none, core minetest api
ActiveFormspecs is a framework that abstracts the builtin formspec API of Minetest. It is intended to address a number of known security issues related to formspecs:
Secure Session Tracking Formspec names have been deprecated as they can be easily forged. Now each formspec session is assigned a unique session ID. Due to the persistent nature of the Minetest client-server protocol (unlike HTTP, for example), all session tracking is performed server-side. Negotiation and validation with the client is entirely unnecessary. Thus, integrity of the session ID is always guaranteed.
Session-Based State Table Since the session ID token is retained throughout the lifetime of the formspec, it is therefore possible to update a formspec dynamically (e.g. in response to an event) with contextual data spanning multiple instances. This data is stored server-side via a session-based state table and it can even be initialized from within the formspec string itself using a new "hidden" element.
Localized Event Handling The minetest.register_on_player_receive_fields( ) method has also been deprecated. Instead, each formspec is assigned its own callback function at runtime, which allows for completely localized event handling. This callback function is invoked after any event associated with the formspec (hence the moniker "ActiveFormspecs"). Both the meta table and form fields are passed as arguments.
https://forum.minetest.net/viewtopic.php?f=9&t=19303
The project is a WIP and will be undergoing continuous development based upon your suggestions as well as my personal needs. Version 3.0 is already underway, and I am planning to introduce substantial improvements to the core functionality. New features and bug-fixes will be announced here as they become available. During significant milestones, I will include a roadmap so as to gauge your feedback about long-term goals. I will make every effort to ensure backward compatibility, when possible.
An interactive form session monitor can be accessed in-game via the /fs chat command (requires "server" privilege). A realtime summary of form sessions is displayed along with navigation buttons and related statistics.
The summary is sorted chronologically and divided into four columns per player:
A detached formspec can be opened using the minetest.create_form( ) method which returns a hashed session token (guaranteed to be unique):
The form can subsequently be re-opened or closed using the following methods:
The associated on_close( ) callback function will be automatically invoked with three arguments during a form-related event or signal, including closure. The return value is currently ignored, but in the future it may allow for additional behavior.
In the event of abnormal form session termination, the callback function will receive a signal indicating the specific condition that led to the closure. One of the following values will be stored in the "quit" field:
A non-trappable SIGSTOP signal can also be passed to minetest.destroy_form( ) or minetest.create_form( ) to forcibly kill the current form session, thereby avoiding recursion in callbacks during a procedural form closure.
local function open_alertbox( player_name, message )
local function get_formspec( )
local formspec = <generate a basic formspec string here>
return formspec
end
minetest.create_form( nil, player_name, get_formspec( ), nil, minetest.FORMSPEC_SIGSTOP )
end
When form closure may produce unanticipated side-effects, such as the example above, then the SIGSTOP signal can prove essential to avoid or even defer such actions.
Cascading forms such as dialog boxes, alerts, etc. can be implemented through the use of the SIGHOLD and SIGCONT signals. By passing a SIGHOLD signal to minetest.create_form(), the callback will be notified of the signal and the form session will be suspended (this also stops any form timers). As soon as the child form is closed, the previous form session will be restored and the callback will be notified of a SIGCONT signal. ActiveFormspecs manages all of the session housekeeping behind the scenes.
Here is an example of a popup alert that, when opened, will temporarily suspend the current form session until closed:
local function open_alert( player_name, message )
local get_formspec = function ( )
local formspec = "size[4,3]"
.. default.gui_bg_img
.. string.format( "label[0.5,0.5;%s]", minetest.formspec_escape( message ) )
.. "button_exit[1.0,2;2.0,1;close;Close]"
return formspec
end
minetest.create_form( nil, player_name, get_formspec( ), nil, minetest.FORMSPEC_SIGHOLD )
end
One caveat is that the parent form must be updated whenever a SIGCONT signal is received. This is for security reasons, given that element states (like dropdowns, scrollbars, etc.) cannot be preserved automatically, and some elements may even contain stale data (as with the case of textareas, fields, etc.) which should not be preserved anyway. Thankfully, only one line of code is required:
if fields.quit == minetest.FORMSPEC_SIGCONT then minetest.update_form( player_name, get_formspec( ) ) end
Formspecs are also supported within a node definition. If an on_open( ) method is defined, then it will be invoked whenever the player right-clicks the node. The return value must be a standard formspec string, or nil to abort.
The on_close( ) method is invoked during any formspec event or signal. If defined, it will receive three arguments. Currently, the return value is ignored, but in the future it may allow for additional behavior.
An optional before_open( ) method may also be defined. It should return a custom state table to be subsequently passed to the on_open( ) method in place of the default "pos" parameter.
The "hidden" formspec element of allows for a slightly different workflow, by presetting the state table from the formspec string itself. The key-value pairs are used only for initialization and will never be transmitted to the client. An optional data type can be specified within the element as follows:
If the data type is not specified, then no type-conversion will occur. Here are some examples:
Through the use of form timers, it possible to re-open a formspec at regular intervals, or even to close the formspec after a specified period. There is no need for complex chains of minetest.after( ) or additional globalstep registrations. A form timer persists for the duration of a form session.
Returns a form timer object associated with the form session of a given player. Three methods are exposed by the form timer object, providing similar behavior to node timers.
* timer.start( timeout ) Starts a timer with the given timeout in seconds.
* timer.stop( ) Cancels a running timer.
* timer.get_state( ) Returns a table with information about the running timer
The table returned including:
The associated on_close( ) callback function will be notified of timer expiration via a SIG_TIME signal.
To better understand this methodology, here are some working code examples. Let's say that we want to register an "uptime" chat command that displays the current server uptime to any player, with an option to automatically refresh the formspec each second.
minetest.register_chatcommand( "uptime", {
description = "View the uptime of the server interactively",
func = function( player_name, param )
local is_refresh = true
local get_formspec = function( )
local uptime = minetest.get_server_uptime( )
local formspec = "size[4,2]"
.. string.format( "label[0.5,0.5;%s %d secs]",
minetest.colorize( "#FFFF00", "Server Uptime:" ), uptime
)
.. "checkbox[0.5,1;is_refresh;Auto Refresh;" .. tostring( is_refresh ) .. "]"
return formspec
end
local on_close = function( state, player, fields )
if fields.quit == minetest.FORMSPEC_SIGTIME then
minetest.update_form( player_name, get_formspec( ) )
elseif fields.is_refresh then
is_refresh = fields.is_refresh == "true"
if is_refresh == true then
minetest.get_form_timer( player_name ).start( 1 )
else
minetest.get_form_timer( player_name ).stop( )
end
end
end
minetest.create_form( nil, player_name, get_formspec( ), on_close )
minetest.get_form_timer( player_name ).start( 1 )
end
} )
Of course, we could implement similar functionality without the need for a chat command. Perhaps we want to display the server uptime only when a privileged player clicks on a Nyan Cat while limiting the total number of refreshes to ten.
minetest.register_privilege( "uptime", "View the uptime of the server interactively" )
local function open_system_monitor( player_name, is_minutes )
local view_count = 0
local view_limit = 10
local function get_formspec( )
local uptime = minetest.get_server_uptime( )
local formspec = "size[4,3]"
.. string.format( "label[0.5,0.5;%s %0.1f %s]",
minetest.colorize( "#FFFF00", "Server Uptime:" ),
is_minutes and uptime / 60 or uptime,
is_minutes and "mins" or "secs"
)
.. "checkbox[0.5,1;is_minutes;Show Minutes;" .. tostring( is_minutes ) .. "]"
.. "button[0.5,2;2.5,1;update;Refresh]"
.. "hidden[view_count;1;number]"
.. "hidden[view_limit;10;number]"
return formspec
end
minetest.create_form( nil, player_name, get_formspec( ), function( state, player, fields )
if not minetest.check_player_privs( player_name, "uptime" ) then -- sanity check
return
end
if fields.update then
-- limit the number of refreshes!
if view_count == view_limit then
minetest.destroy_form( player_name )
minetest.chat_send_player( player_name, "You've exceeded the refresh limit." )
else
view_count = view_count + 1
minetest.update_form( player_name, get_formspec( ) )
end
elseif fields.is_minutes then
is_minutes = fields.is_minutes == "true"
minetest.update_form( player_name, get_formspec( ) )
end
end )
end
minetest.override_item( "nyancat:nyancat", {
description = "System Monitor",
on_rightclick = function( pos, node, player )
local player_name = player:get_player_name( )
if minetest.check_player_privs( player_name, "uptime" ) then
open_system_monitor( player_name, true )
else
minetest.chat_send_player( player_name, "Your privileges are insufficient." )
end
end,
} )
Version 1.0a (15-Dec-2016)
Version 1.1b (16-Dec-2016)
Version 1.2b (18-Dec-2016)
Version 1.3b (04-Jan-2017)
Version 1.4b (26-Jul-2017)
Version 2.0 (24-Dec-2017)
Version 2.1 (08-Jan-2018)
Version 2.2 (19-Jan-2018)
Version 2.3 (28-Jan-2018)
Version 2.4 (12-Feb-2018)
Version 2.5 (01-Feb-2019)
Version 2.6 (02-Feb-2020)
Minetest 0.4.15+ required or 5.X
The MIT License (MIT)
Copyright (c) 2016-2020, Leslie E. Krause (leslie@searstower.org) aka sorceredkid
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more details: https://opensource.org/licenses/MIT