diff options
Diffstat (limited to 'teal-src')
65 files changed, 1863 insertions, 589 deletions
diff --git a/teal-src/README.md b/teal-src/README.md new file mode 100644 index 00000000..e8c47035 --- /dev/null +++ b/teal-src/README.md @@ -0,0 +1,48 @@ +# Teal definitions and sources + +This directory contains files written in the +[Teal](https://github.com/teal-language/tl) language, a typed dialect of +Lua. There are two kinds of files, `.tl` Teal source code and `.d.tl` +type definitions files for modules written in Lua. The later allows +writing type-aware Teal using regular Lua or C code. + +## Setup + +The Teal compiler can be installed from LuaRocks using: + +```bash +luarocks install tl +``` + +## Checking types + +```bash +tl check teal-src/prosody/util/example.tl +``` + +Some editors and IDEs also have support, see [text editor +support](https://github.com/teal-language/tl#text-editor-support) + + +## Compiling to Lua + +`GNUmakefile` contains a rule for building Lua files from Teal sources. +It also applies [LuaFormat](https://github.com/Koihik/LuaFormatter) to +make the resulting code more readable, albeit this makes the line +numbers no longer match the original Teal source. Sometimes minor +`luacheck` issues remain, such as types being represented as unused +tables, which can be removed. + +```bash +sensible-editor teal-src/prosody/util/example.tl +# Write some code, remember to run tl check +make util/example.lua +sensible-editor util/example.lua +# Apply any minor tweaks that may be needed +``` + +## Files of note + +`module.d.tl` +: Describes the module environment. + diff --git a/teal-src/module.d.tl b/teal-src/module.d.tl index 67b2437c..22038bc4 100644 --- a/teal-src/module.d.tl +++ b/teal-src/module.d.tl @@ -1,4 +1,4 @@ -local st = require"util.stanza" +local st = require "prosody.util.stanza" global record moduleapi get_name : function (moduleapi) : string @@ -34,8 +34,11 @@ global record moduleapi get_option : config_getter<any> get_option_scalar : config_getter<nil | boolean | number | string> get_option_string : config_getter<string> - get_option_number : config_getter<number> + get_option_number : function (moduleapi, string, number, number, number) : number + get_option_integer : function (moduleapi, string, integer, integer, integer) : integer get_option_boolean : config_getter<boolean> + get_option_enum : function<A> (moduleapi, string, ... : A) : A + get_option_period : function (moduleapi, string|number, string|number, string|number, string|number) : number record util_array -- TODO import def { any } @@ -62,7 +65,12 @@ global record moduleapi send_iq : function (moduleapi, st.stanza_t, util_session, number) broadcast : function (moduleapi, { string }, st.stanza_t, function) type timer_callback = function (number, ... : any) : number - add_timer : function (moduleapi, number, timer_callback, ... : any) + record timer_wrapper + stop : function (timer_wrapper) + disarm : function (timer_wrapper) + reschedule : function (timer_wrapper, number) + end + add_timer : function (moduleapi, number, timer_callback, ... : any) : timer_wrapper get_directory : function (moduleapi) : string enum file_mode "r" "w" "a" "r+" "w+" "a+" @@ -70,6 +78,7 @@ global record moduleapi load_resource : function (moduleapi, string, file_mode) : FILE enum store_type "keyval" + "keyval+" "map" "archive" end @@ -121,12 +130,20 @@ global record moduleapi path : string resource_path : string + -- access control + may : function (moduleapi, string, table|string) + default_permission : function (string, string) + default_permissions : function (string, { string }) + -- methods the module can add load : function () add_host : function (moduleapi) save : function () : any restore : function (any) unload : function () + + -- added by mod_http + http_url : function (moduleapi, string, string, string) : string end global module : moduleapi diff --git a/teal-src/plugins/mod_cron.tl b/teal-src/plugins/mod_cron.tl deleted file mode 100644 index f3b8f62f..00000000 --- a/teal-src/plugins/mod_cron.tl +++ /dev/null @@ -1,106 +0,0 @@ -module:set_global(); - -local async = require "util.async"; -local datetime = require "util.datetime"; - -local record map_store<K,V> - -- TODO move to somewhere sensible - get : function (map_store<K,V>, string, K) : V - set : function (map_store<K,V>, string, K, V) -end - -local enum frequency - "hourly" - "daily" - "weekly" -end - -local record task_spec - id : string -- unique id - name : string -- name or short description - when : frequency - last : integer - run : function (task_spec, integer) - save : function (task_spec, integer) -end - -local record task_event - source : module - item : task_spec -end - -local periods : { frequency : integer } = { hourly = 3600, daily = 86400, weekly = 7*86400 } - -local active_hosts : { string : boolean } = { } - -function module.add_host(host_module : moduleapi) - - local last_run_times = host_module:open_store("cron", "map") as map_store<string,integer>; - active_hosts[host_module.host] = true; - - local function save_task(task : task_spec, started_at : integer) - last_run_times:set(nil, task.id, started_at); - end - - local function task_added(event : task_event) : boolean - local task = event.item; - if task.name == nil then - task.name = task.when; - end - if task.id == nil then - task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower(); - end - if task.last == nil then - task.last = last_run_times:get(nil, task.id); - end - task.save = save_task; - module:log("debug", "%s task %s added, last run %s", task.when, task.id, - task.last and datetime.datetime(task.last) or "never"); - if task.last == nil then - -- initialize new tasks so e.g. daily tasks run at ~midnight UTC for now - local now = os.time(); - task.last = now - now % periods[task.when]; - end - return true; - end - - local function task_removed(event : task_event) : boolean - local task = event.item; - host_module:log("debug", "Task %s removed", task.id); - return true; - end - - host_module:handle_items("task", task_added, task_removed, true); - - function host_module.unload() - active_hosts[host_module.host]=nil; - end -end - -local function should_run(when : frequency, last : integer) : boolean - return not last or last + periods[when]*0.995 <= os.time(); -end - -local function run_task(task : task_spec) - local started_at = os.time(); - task:run(started_at); - task.last = started_at; - task:save(started_at); -end - -local task_runner = async.runner(run_task); -module:add_timer(1, function() : integer - module:log("info", "Running periodic tasks"); - local delay = 3600; - for host in pairs(active_hosts) do - module:log("debug", "Running periodic tasks for host %s", host); - for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do - module:log("debug", "Considering %s task %s (%s)", task.when, task.id, task.run); - if should_run(task.when, task.last) then task_runner:run(task); end - end - end - module:log("debug", "Wait %ds", delay); - return delay; -end); - --- TODO measure load, pick a good time to do stuff diff --git a/teal-src/prosody/core/storagemanager.d.tl b/teal-src/prosody/core/storagemanager.d.tl new file mode 100644 index 00000000..bd82357c --- /dev/null +++ b/teal-src/prosody/core/storagemanager.d.tl @@ -0,0 +1,74 @@ +-- Storage local record API Description +-- +-- This is written as a TypedLua description + +-- Key-Value stores (the default) + +local stanza = require"prosody.util.stanza".stanza_t + +local record keyval_store + get : function ( keyval_store, string ) : any , string + set : function ( keyval_store, string, any ) : boolean, string +end + +-- Map stores (key-key-value stores) + +local record map_store + get : function ( map_store, string, any ) : any, string + set : function ( map_store, string, any, any ) : boolean, string + set_keys : function ( map_store, string, { any : any }) : boolean, string + remove : table +end + +-- Archive stores + +local record archive_query + start : number -- timestamp + ["end"]: number -- timestamp + with : string + after : string -- archive id + before : string -- archive id + total : boolean +end + +local record archive_store + -- Optional set of capabilities + caps : { + -- Optional total count of matching items returned as second return value from :find() + string : any + } + + -- Add to the archive + append : function ( archive_store, string, string, any, number, string ) : string, string + + -- Iterate over archive + type iterator = function () : string, any, number, string + find : function ( archive_store, string, archive_query ) : iterator, integer + + -- Removal of items. API like find. Optional + delete : function ( archive_store, string, archive_query ) : boolean | number, string + + -- Array of dates which do have messages (Optional) + dates : function ( archive_store, string ) : { string }, string + + -- Map of counts per "with" field + summary : function ( archive_store, string, archive_query ) : { string : integer }, string + + -- Map-store API + get : function ( archive_store, string, string ) : stanza, number, string + get : function ( archive_store, string, string ) : nil, string + set : function ( archive_store, string, string, stanza, number, string ) : boolean, string +end + +-- This represents moduleapi +local record coremodule + -- If the first string is omitted then the name of the module is used + -- The second string is one of "keyval" (default), "map" or "archive" + open_store : function (archive_store, string, string) : keyval_store, string + open_store : function (archive_store, string, string) : map_store, string + open_store : function (archive_store, string, string) : archive_store, string + + -- Other module methods omitted +end + +return coremodule diff --git a/teal-src/prosody/core/usermanager.d.tl b/teal-src/prosody/core/usermanager.d.tl new file mode 100644 index 00000000..fc055004 --- /dev/null +++ b/teal-src/prosody/core/usermanager.d.tl @@ -0,0 +1,46 @@ +local Role = require "prosody.util.roles".Role; + +local record usermanager + record AuthProvider + -- TODO + end + record AccountInfo + created : number + password_updated : any + enabled : boolean + end + + -- Users + test_password : function (username : string, host : string, password : string) : boolean + get_password : function (username : string, host : string) : string, string + set_password : function (username : string, host : string, password : string) : boolean, string + get_account_info : function (username : string, host : string) : AccountInfo + user_exists : function (username : string, host : string) : boolean + create_user : function (username : string, password : string, host : string) : boolean, string + delete_user : function (username : string, host : string) : boolean, string + user_is_enabled : function (username : string, host : string) : boolean, string + enable_user : function (username : string, host : string) : boolean, string + disable_user : function (username : string, host : string) : boolean, string + users : function (host : string) : function () : string + + -- Roles + get_user_role : function (username : string, host : string) : Role + set_user_role : function (username : string, host : string, role_name : string) : boolean, string + user_can_assume_role : function (username : string, host : string, role_name : string) : boolean + add_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string + remove_user_secondary_role : function (username : string, host: string, role_name : string) : boolean, string + get_user_secondary_roles : function (username : string, host : string) : { string : Role } + get_users_with_role : function (role : string, host : string) : { string } + get_jid_role : function (jid : string, host : string) : Role + set_jid_role : function (jid : string, host : string, role_name : string) : boolean + get_jids_with_role : function (role : string, host : string) : { string } + get_role_by_name : function (role_name : string) : Role + + -- Etc + get_provider : function (host : string) : AuthProvider + get_sasl_handler : function (host : string, session : table) : table + initialize_host : function (host : string) + new_null_provider : function () : AuthProvider +end + +return usermanager diff --git a/teal-src/prosody/net/http.d.tl b/teal-src/prosody/net/http.d.tl new file mode 100644 index 00000000..43b0bf4d --- /dev/null +++ b/teal-src/prosody/net/http.d.tl @@ -0,0 +1,86 @@ +local Promise = require "prosody.util.promise".Promise; + +local record sslctx -- from LuaSec +end + +local record lib + + enum http_method + "GET" + "HEAD" + "POST" + "PUT" + "OPTIONS" + "DELETE" + -- etc? + end + + record http_client_options + sslctx : sslctx + end + + record http_options + id : string + onlystatus : boolean + body : string + method : http_method + headers : { string : string } + insecure : boolean + suppress_errors : boolean + streaming_handler : function + suppress_url : boolean + sslctx : sslctx + end + + record http_request + host : string + port : string + enum Scheme + "http" + "https" + end + scheme : Scheme + url : string + userinfo : string + path : string + + method : http_method + headers : { string : string } + + insecure : boolean + suppress_errors : boolean + streaming_handler : function + http : http_client + time : integer + id : string + callback : http_callback + end + + record http_response + end + + type http_callback = function (string, number, http_response, http_request) + + record http_client + options : http_client_options + request : function (http_client, string, http_options, http_callback) + end + + request : function (string, http_options, http_callback) : Promise, string + default : http_client + new : function (http_client_options) : http_client + events : table + -- COMPAT + urlencode : function (string) : string + urldecode : function (string) : string + formencode : function ({ string : string }) : string + formdecode : function (string) : { string : string } + destroy_request : function (http_request) + + enum available_features + "sni" + end + features : { available_features : boolean } +end + +return lib diff --git a/teal-src/prosody/net/http/codes.d.tl b/teal-src/prosody/net/http/codes.d.tl new file mode 100644 index 00000000..65d004fc --- /dev/null +++ b/teal-src/prosody/net/http/codes.d.tl @@ -0,0 +1,2 @@ +local type response_codes = { integer : string } +return response_codes diff --git a/teal-src/prosody/net/http/errors.d.tl b/teal-src/prosody/net/http/errors.d.tl new file mode 100644 index 00000000..a9b6ea6c --- /dev/null +++ b/teal-src/prosody/net/http/errors.d.tl @@ -0,0 +1,22 @@ +local record http_errors + enum known_conditions + "cancelled" + "connection-closed" + "certificate-chain-invalid" + "certificate-verify-failed" + "connection failed" + "invalid-url" + "unable to resolve service" + end + type registry_keys = known_conditions | integer + record error + type : string + condition : string + code : integer + text : string + end + registry : { registry_keys : error } + new : function (integer, known_conditions, table) + new : function (integer, string, table) +end +return http_errors diff --git a/teal-src/prosody/net/http/files.d.tl b/teal-src/prosody/net/http/files.d.tl new file mode 100644 index 00000000..d0ba5c1c --- /dev/null +++ b/teal-src/prosody/net/http/files.d.tl @@ -0,0 +1,14 @@ +local record serve_options + path : string + mime_map : { string : string } + cache_size : integer + cache_max_file_size : integer + index_files : { string } + directory_index : boolean +end + +local record http_files + serve : function(serve_options|string) : function +end + +return http_files diff --git a/teal-src/prosody/net/http/parser.d.tl b/teal-src/prosody/net/http/parser.d.tl new file mode 100644 index 00000000..1cd6ccf4 --- /dev/null +++ b/teal-src/prosody/net/http/parser.d.tl @@ -0,0 +1,58 @@ +local record httpstream + feed : function(httpstream, string) +end + +local type sink_cb = function () + +local record httppacket + enum http_method + "HEAD" + "GET" + "POST" + "PUT" + "DELETE" + "OPTIONS" + -- etc + end + method : http_method + record url_details + path : string + query : string + end + url : url_details + path : string + enum http_version + "1.0" + "1.1" + end + httpversion : http_version + headers : { string : string } + body : string | boolean + body_sink : sink_cb + chunked : boolean + partial : boolean +end + +local enum error_conditions + "cancelled" + "connection-closed" + "certificate-chain-invalid" + "certificate-verify-failed" + "connection failed" + "invalid-url" + "unable to resolve service" +end + +local type success_cb = function (httppacket) +local type error_cb = function (error_conditions) + +local enum stream_mode + "client" + "server" +end + +local record lib + new : function (success_cb, error_cb, stream_mode) : httpstream +end + +return lib diff --git a/teal-src/prosody/net/http/server.d.tl b/teal-src/prosody/net/http/server.d.tl new file mode 100644 index 00000000..5a83af7e --- /dev/null +++ b/teal-src/prosody/net/http/server.d.tl @@ -0,0 +1,6 @@ + +local record http_server + -- TODO +end + +return http_server diff --git a/teal-src/prosody/net/server.d.tl b/teal-src/prosody/net/server.d.tl new file mode 100644 index 00000000..bb61f677 --- /dev/null +++ b/teal-src/prosody/net/server.d.tl @@ -0,0 +1,65 @@ +local record server + record LuaSocketTCP + end + record LuaSecCTX + end + + record extra_settings + end + + record interface + end + enum socket_type + "tcp" + "tcp6" + "tcp4" + end + + record listeners + onconnect : function (interface) + ondetach : function (interface) + onattach : function (interface, string) + onincoming : function (interface, string, string) + ondrain : function (interface) + onreadtimeout : function (interface) + onstarttls : function (interface) + onstatus : function (interface, string) + ondisconnect : function (interface, string) + end + + get_backend : function () : string + + type port = string | integer + enum read_mode + "*a" + "*l" + end + type read_size = read_mode | integer + addserver : function (string, port, listeners, read_size, LuaSecCTX) : interface + addclient : function (string, port, listeners, read_size, LuaSecCTX, socket_type, extra_settings) : interface + record listen_config + read_size : read_size + tls_ctx : LuaSecCTX + tls_direct : boolean + sni_hosts : { string : LuaSecCTX } + end + listen : function (string, port, listeners, listen_config) : interface + enum quitting + "quitting" + end + loop : function () : quitting + closeall : function () + setquitting : function (boolean | quitting) + + wrapclient : function (LuaSocketTCP, string, port, listeners, read_size, LuaSecCTX, extra_settings) : interface + wrapserver : function (LuaSocketTCP, string, port, listeners, listen_config) : interface + watchfd : function (integer | LuaSocketTCP, function (interface), function (interface)) : interface + link : function () + + record config + end + set_config : function (config) + +end + +return server diff --git a/teal-src/prosody/plugins/mod_cron.tl b/teal-src/prosody/plugins/mod_cron.tl new file mode 100644 index 00000000..4defc808 --- /dev/null +++ b/teal-src/prosody/plugins/mod_cron.tl @@ -0,0 +1,141 @@ +module:set_global(); + +local async = require "prosody.util.async"; + +local cron_initial_delay = module:get_option_number("cron_initial_delay", 1); +local cron_check_delay = module:get_option_number("cron_check_delay", 3600); +local cron_spread_factor = module:get_option_number("cron_spread_factor", 0); + +local record map_store<K,V> + -- TODO move to somewhere sensible + get : function (map_store<K,V>, string, K) : V + set : function (map_store<K,V>, string, K, V) +end + +local enum frequency + "hourly" + "daily" + "weekly" +end + +local record task_spec + id : string -- unique id + name : string -- name or short description + when : frequency + period : number + last : integer + run : function (task_spec, integer) + save : function (task_spec, integer) + restore : function (task_spec, integer) +end + +local record task_event + source : module + item : task_spec +end + +local active_hosts : { string : boolean } = { } + +function module.add_host(host_module : moduleapi) + + local last_run_times = host_module:open_store("cron", "map") as map_store<string,integer>; + active_hosts[host_module.host] = true; + + local function save_task(task : task_spec, started_at : integer) + last_run_times:set(nil, task.id, started_at); + end + + local function restore_task(task : task_spec) + if task.last == nil then + task.last = last_run_times:get(nil, task.id); + end + end + + local function task_added(event : task_event) : boolean + local task = event.item; + if task.name == nil then + task.name = task.when; + end + if task.id == nil then + task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower(); + end + task.period = host_module:get_option_period(task.id:gsub("/", "_") .. "_period", "1" .. task.when, 60, 86400*7*53); + task.restore = restore_task; + task.save = save_task; + module:log("debug", "%s task %s added", task.when, task.id); + return true; + end + + local function task_removed(event : task_event) : boolean + local task = event.item; + host_module:log("debug", "Task %s removed", task.id); + return true; + end + + host_module:handle_items("task", task_added, task_removed, true); + + function host_module.unload() + active_hosts[host_module.host]=nil; + end +end + +local function should_run(task : task_spec, last : integer) : boolean + return not last or last + task.period * 0.995 <= os.time(); +end + +local function run_task(task : task_spec) + task:restore(); + if not should_run(task, task.last) then + return; + end + local started_at = os.time(); + task:run(started_at); + task.last = started_at; + task:save(started_at); +end + +local function spread(t : number, factor : number) : number + return t * (1 - factor + 2*factor*math.random()); +end + +local task_runner : async.runner_t<task_spec> = async.runner(run_task); +scheduled = module:add_timer(cron_initial_delay, function() : number + module:log("info", "Running periodic tasks"); + local delay = spread(cron_check_delay, cron_spread_factor); + for host in pairs(active_hosts) do + module:log("debug", "Running periodic tasks for host %s", host); + for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do + task_runner:run(task); + end + end + module:log("debug", "Wait %gs", delay); + return delay; +end); + +-- TODO measure load, pick a good time to do stuff + +module:add_item("shell-command", { + section = "cron"; + section_desc = "View and manage recurring tasks"; + name = "tasks"; + desc = "View registered tasks"; + args = {}; + handler = function (self, filter_host : string) + local format_table = require "prosody.util.human.io".table; + local it = require "util.iterators"; + local row = format_table({ + { title = "Host", width = "2p" }; + { title = "Task", width = "3p" }; + { title = "Desc", width = "3p" }; + { title = "When", width = "1p" }; + { title = "Last run", width = "20" }; + }, self.session.width); + local print = self.session.print; + print(row()); + for host in it.sorted_pairs(filter_host and { [filter_host]=true } or active_hosts) do + for _, task in ipairs(module:context(host):get_host_items("task")) do + print(row { host, task.id, task.name, task.when, task.last and os.date("%Y-%m-%d %R:%S", task.last) or "never" }); + end + end + end; +}); diff --git a/teal-src/prosody/plugins/muc/muc.lib.d.tl b/teal-src/prosody/plugins/muc/muc.lib.d.tl new file mode 100644 index 00000000..6e9663a1 --- /dev/null +++ b/teal-src/prosody/plugins/muc/muc.lib.d.tl @@ -0,0 +1,178 @@ +local Stanza = require "prosody.util.stanza".stanza_t + +local record Room + jid : string + + enum Affiliation + "outcast" + "none" + "member" + "admin" + "owner" + end + + enum Role + "none" + "visitor" + "participant" + "moderator" + end + + record Occupant + bare_jid : string + nick : string + sessions : { string : Stanza } + role : Role + jid : string + + choose_new_primary : function (Occupant) : string + set_session : function (Occupant, string, Stanza, boolean) + remove_session : function (Occupant, string) + each_session : function (Occupant) -- TODO Iterator + + end + + -- Private properties + _jid_nick : { string : string } + _occupants : { string : Occupant } + _data : { string : any } + _affiliations : { string : Affiliation } + _affiliation_data : { string : { string : any } } + + -- Occupant methods + get_occupant_jid : function (Room, real_jid : string) : string + new_occupant : function (Room, bare_real_jid : string, nick : string) : Occupant + get_occupant_by_nick : function (Room, nick : string) : Occupant + type OccupantIterator = function ({string:Occupant}, occupant_jid : string) : string, Occupant + each_occupant : function (Room, read_only : boolean) : OccupantIterator, {string:Occupant}, nil + has_occupant : function (Room) : boolean + get_occupant_by_real_jid : function (Room, real_jid : string) : Occupant + save_occupant :function (Room, Occupant) : Occupant + + -- Affiliation methods + type AffiliationIterator = function (any, jid : string) : string, Affiliation + get_affiliation : function (Room, jid : string) : Affiliation + each_affiliation : function (Room, Affiliation) : AffiliationIterator, nil, nil + set_affiliation : function (Room, jid : string, Affiliation, reason : string, data : { string : any }) : boolean, string, string, string -- ok + error tripplet + get_affiliation_data : function (Room, jid : string, key : string) : any + set_affiliation_data : function (Room, jid : string, key : string, value : any) : boolean + get_registered_nick : function (Room, jid : string) : string + get_registered_jid : function (Room, nick : string) : string + + -- Role methods + get_default_role : function (Room, Affiliation) : Role, integer + get_role : function (Room, nick : string) : Role + may_set_role : function (Room, actor : string, Occupant, Role) : boolean + set_role : function (Room, actor : string, occupant_jid : string, Role, reason : string) : boolean, string, string, string + + -- Routing input, generally handled by mod_muc and hooked up to Prosody routing events + handle_first_presence : function (Room, table, Stanza) : boolean + handle_normal_presence : function (Room, table, Stanza) : boolean + handle_presence_to_room : function (Room, table, Stanza) : boolean + handle_presence_to_occupant : function (Room, table, Stanza) : boolean + handle_message_to_room : function (Room, table, Stanza) : boolean + handle_message_to_occupant : function (Room, table, Stanza) : boolean + handle_groupchat_to_room : function (Room, table, Stanza) : boolean + handle_iq_to_occupant : function (Room, table, Stanza) : boolean + handle_disco_info_get_query : function (Room, table, Stanza) : boolean + handle_disco_items_get_query : function (Room, table, Stanza) : boolean + handle_admin_query_set_command : function (Room, table, Stanza) : boolean + handle_admin_query_get_command : function (Room, table, Stanza) : boolean + handle_owner_query_get_to_room : function (Room, table, Stanza) : boolean + handle_owner_query_set_to_room : function (Room, table, Stanza) : boolean + handle_mediated_invite : function (Room, table, Stanza) : boolean + handle_mediated_decline : function (Room, table, Stanza) : boolean + handle_role_request : function (Room, table, Stanza) : boolean + handle_register_iq : function (Room, table, Stanza) : boolean + handle_kickable : function (Room, table, Stanza) : boolean + + -- Routing output + broadcast : function (Room, Stanza, function (nick : string, Occupant) : boolean) + broadcast_message : function (Room, Stanza) : boolean + route_stanza : function (Room, Stanza) + route_to_occupant : function (Room, Occupant, Stanza) + + -- Sending things to someone joining + publicise_occupant_status : function (Room, Occupant, x : Stanza, nick : string, actor : string, reason : string, prev_role : Role, force_unavailable : boolean, recipient : Occupant) + send_occupant_list : function (Room, to : string, filter : function (occupant_jid : string, Occupant) : boolean) + send_history : function (Room, Stanza) + send_subject : function (Room, to : string, time : number) + + respond_to_probe : function (Room, table, Stanza, Occupant) + + -- Constructors for various answer stanzas + get_disco_info : function (Room, Stanza) : Stanza + get_disco_items : function (Room, Stanza) : Stanza + + build_item_list : function (Room, Occupant, Stanza, is_anonymous : boolean, nick : string, actor_nick : string, actor_jid : string, reason : string) : Stanza + build_unavailable_presence : function (Room, from_muc_jid : string, to_jid : string) : Stanza + + -- Form handling + send_form : function (Room, table, Stanza) + get_form_layout : function (Room, actor : string) : table + process_form : function (Room, table, Stanza) : boolean + + -- Properties and configuration + get_name : function (Room) : string + set_name : function (Room, string) : boolean + get_description : function (Room) : string + set_description : function (Room, string) : boolean + get_language : function (Room) : string + set_language : function (Room, string) : boolean + get_hidden : function (Room) : boolean + set_hidden : function (Room, boolean) + get_public : function (Room) : boolean + set_public : function (Room, boolean) + get_password : function (Room) : string + set_password : function (Room, string) : boolean + get_members_only : function (Room) : boolean + set_members_only : function (Room, boolean) : boolean + get_allow_member_invites : function (Room) : boolean + set_allow_member_invites : function (Room, boolean) : boolean + get_moderated : function (Room) : boolean + set_moderated : function (Room, boolean) : boolean + get_persistent : function (Room) : boolean + set_persistent : function (Room, boolean) : boolean + get_changesubject : function (Room) : boolean + set_changesubject : function (Room, boolean) : boolean + get_subject : function (Room) : string + set_subject : function (Room, string) : boolean + get_historylength : function (Room) : integer + set_historylength : function (Room, integer) : boolean + get_presence_broadcast : function (Room) : { Role : boolean } + set_presence_broadcast : function (Room, { Role : boolean }) : boolean + + is_anonymous_for : function (Room, jid : string) : boolean + get_salt : function (Room) : string + get_occupant_id : function (Room, Occupant) + + -- Room teardown + clear : function (Room, x : Stanza) + destroy : function (Room, newjid : string, reason : string, password : string) : boolean + + -- Room state persistence + record FrozenRoom + _jid : string + _data : { string : any } + _affiliation_data : { string : { string : any } } + -- { string : Affiliation } + end + + record StateEntry + bare_jid : string + role : Role + jid : string + end + + save : function (Room, forced : boolean, savestate : boolean) : boolean + freeze : function (Room, live : boolean) : FrozenRoom, { string : StateEntry } +end + +local record lib + new_room : function (jid : string, config : { string : any }) : Room + restore_room : function (Room.FrozenRoom, { string : Room.StateEntry }) : Room + + room_mt : metatable +end + +return lib diff --git a/teal-src/prosody/util/array.d.tl b/teal-src/prosody/util/array.d.tl new file mode 100644 index 00000000..70bf2624 --- /dev/null +++ b/teal-src/prosody/util/array.d.tl @@ -0,0 +1,9 @@ +local record array_t<T> + { T } +end + +local record lib + metamethod __call : function () : array_t +end + +return lib diff --git a/teal-src/prosody/util/async.d.tl b/teal-src/prosody/util/async.d.tl new file mode 100644 index 00000000..a2e41cd6 --- /dev/null +++ b/teal-src/prosody/util/async.d.tl @@ -0,0 +1,42 @@ +local record lib + ready : function () : boolean + waiter : function (num : integer, allow_many : boolean) : function (), function () + guarder : function () : function (id : function ()) : function () | nil + record runner_t<T> + func : function (T) + thread : thread + enum state_e + -- from Lua manual + "running" + "suspended" + "normal" + "dead" + + -- from util.async + "ready" + "error" + end + state : state_e + notified_state : state_e + queue : { T } + type watcher_t = function (runner_t<T>, ... : any) + type watchers_t = { state_e : watcher_t } + data : any + id : string + + run : function (runner_t<T>, T) : boolean, state_e, integer + enqueue : function (runner_t<T>, T) : runner_t<T> + log : function (runner_t<T>, string, string, ... : any) + onready : function (runner_t<T>, function) : runner_t<T> + onready : function (runner_t<T>, function) : runner_t<T> + onwaiting : function (runner_t<T>, function) : runner_t<T> + onerror : function (runner_t<T>, function) : runner_t<T> + end + runner : function <T>(function (T), runner_t.watchers_t, any) : runner_t<T> + wait_for : function (any) : any, any + sleep : function (t:number) + + -- set_nexttick = function(new_next_tick) next_tick = new_next_tick; end; + -- set_schedule_function = function (new_schedule_function) schedule_task = new_schedule_function; end; +end +return lib diff --git a/teal-src/prosody/util/bitcompat.d.tl b/teal-src/prosody/util/bitcompat.d.tl new file mode 100644 index 00000000..18adf725 --- /dev/null +++ b/teal-src/prosody/util/bitcompat.d.tl @@ -0,0 +1,8 @@ +local record lib + band : function (integer, integer, ... : integer) : integer + bor : function (integer, integer, ... : integer) : integer + bxor : function (integer, integer, ... : integer) : integer + lshift : function (integer, integer) : integer + rshift : function (integer, integer) : integer +end +return lib diff --git a/teal-src/util/compat.d.tl b/teal-src/prosody/util/compat.d.tl index da9c6083..da9c6083 100644 --- a/teal-src/util/compat.d.tl +++ b/teal-src/prosody/util/compat.d.tl diff --git a/teal-src/util/crand.d.tl b/teal-src/prosody/util/crand.d.tl index b40cb67e..b40cb67e 100644 --- a/teal-src/util/crand.d.tl +++ b/teal-src/prosody/util/crand.d.tl diff --git a/teal-src/prosody/util/crypto.d.tl b/teal-src/prosody/util/crypto.d.tl new file mode 100644 index 00000000..866185d0 --- /dev/null +++ b/teal-src/prosody/util/crypto.d.tl @@ -0,0 +1,57 @@ +local record lib + record key + private_pem : function (key) : string + public_pem : function (key) : string + get_type : function (key) : string + end + + type base_evp_sign = function (key, message : string) : string + type base_evp_verify = function (key, message : string, signature : string) : boolean + + ed25519_sign : base_evp_sign + ed25519_verify : base_evp_verify + + ecdsa_sha256_sign : base_evp_sign + ecdsa_sha256_verify : base_evp_verify + ecdsa_sha384_sign : base_evp_sign + ecdsa_sha384_verify : base_evp_verify + ecdsa_sha512_sign : base_evp_sign + ecdsa_sha512_verify : base_evp_verify + + rsassa_pkcs1_sha256_sign : base_evp_sign + rsassa_pkcs1_sha256_verify : base_evp_verify + rsassa_pkcs1_sha384_sign : base_evp_sign + rsassa_pkcs1_sha384_verify : base_evp_verify + rsassa_pkcs1_sha512_sign : base_evp_sign + rsassa_pkcs1_sha512_verify : base_evp_verify + + rsassa_pss_sha256_sign : base_evp_sign + rsassa_pss_sha256_verify : base_evp_verify + rsassa_pss_sha384_sign : base_evp_sign + rsassa_pss_sha384_verify : base_evp_verify + rsassa_pss_sha512_sign : base_evp_sign + rsassa_pss_sha512_verify : base_evp_verify + + type Levp_encrypt = function (key : string, iv : string, plaintext : string) : string + type Levp_decrypt = function (key : string, iv : string, ciphertext : string) : string, string + + aes_128_gcm_encrypt : Levp_encrypt + aes_128_gcm_decrypt : Levp_decrypt + aes_256_gcm_encrypt : Levp_encrypt + aes_256_gcm_decrypt : Levp_decrypt + + aes_256_ctr_encrypt : Levp_encrypt + aes_256_ctr_decrypt : Levp_decrypt + + generate_ed25519_keypair : function () : key + + import_private_pem : function (string) : key + import_public_pem : function (string) : key + + parse_ecdsa_signature : function (string, integer) : string, string + build_ecdsa_signature : function (r : string, s : string) : string + + version : string + _LIBCRYPTO_VERSION : string +end +return lib diff --git a/teal-src/prosody/util/dataforms.d.tl b/teal-src/prosody/util/dataforms.d.tl new file mode 100644 index 00000000..3dad2776 --- /dev/null +++ b/teal-src/prosody/util/dataforms.d.tl @@ -0,0 +1,54 @@ +local stanza_t = require "prosody.util.stanza".stanza_t + +local record lib + record dataform + title : string + instructions : string + + record form_field + + enum field_type + "boolean" + "fixed" + "hidden" + "jid-multi" + "jid-single" + "list-multi" + "list-single" + "text-multi" + "text-private" + "text-single" + end + + type : field_type + var : string -- protocol name + name : string -- internal name + + label : string + desc : string + + datatype : string + range_min : number + range_max : number + + value : any -- depends on field_type + options : table + end + + { form_field } + + enum form_type + "form" + "submit" + "cancel" + "result" + end + + form : function ( dataform, { string : any }, form_type ) : stanza_t + data : function ( dataform, stanza_t ) : { string : any } + end + + new : function ( dataform ) : dataform +end + +return lib diff --git a/teal-src/util/datamapper.tl b/teal-src/prosody/util/datamapper.tl index 73b1dfc0..89d5e29b 100644 --- a/teal-src/util/datamapper.tl +++ b/teal-src/prosody/util/datamapper.tl @@ -19,12 +19,14 @@ -- TODO s/number/integer/ once we have appropriate math.type() compat -- -local st = require "util.stanza"; -local json = require"util.json" -local pointer = require"util.jsonpointer"; +if not math.type then require "prosody.util.mathcompat" end + +local st = require "prosody.util.stanza"; +local json = require "prosody.util.json" +local pointer = require "prosody.util.jsonpointer"; local json_type_name = json.json_type_name; -local json_schema_object = require "util.jsonschema" +local json_schema_object = require "prosody.util.jsonschema" local type schema_t = boolean | json_schema_object local function toboolean ( s : string ) : boolean @@ -133,10 +135,6 @@ local function unpack_propschema( propschema : schema_t, propname : string, curr end end - if current_ns == "urn:xmpp:reactions:0" and name == "reactions" then - assert(proptype=="array") - end - return proptype, value_where, name, namespace, prefix, single_attribute, enums end diff --git a/teal-src/prosody/util/datetime.d.tl b/teal-src/prosody/util/datetime.d.tl new file mode 100644 index 00000000..9f770a73 --- /dev/null +++ b/teal-src/prosody/util/datetime.d.tl @@ -0,0 +1,9 @@ +local record lib + date : function (t : number) : string + datetime : function (t : number) : string + time : function (t : number) : string + legacy : function (t : number) : string + parse : function (t : string) : number +end + +return lib diff --git a/teal-src/util/encodings.d.tl b/teal-src/prosody/util/encodings.d.tl index 58aefa5e..58aefa5e 100644 --- a/teal-src/util/encodings.d.tl +++ b/teal-src/prosody/util/encodings.d.tl diff --git a/teal-src/util/error.d.tl b/teal-src/prosody/util/error.d.tl index 05f52405..4c3a7196 100644 --- a/teal-src/util/error.d.tl +++ b/teal-src/prosody/util/error.d.tl @@ -38,7 +38,7 @@ local record protoerror code : integer end -local record error +local record Error type : error_type condition : error_condition text : string @@ -55,10 +55,10 @@ local type context = { string : any } local record error_registry_wrapper source : string registry : registry - new : function (string, context) : error - coerce : function (any, string) : any, error - wrap : function (error) : error - wrap : function (string, context) : error + new : function (string, context) : Error + coerce : function (any, string) : any, Error + wrap : function (Error) : Error + wrap : function (string, context) : Error is_error : function (any) : boolean end @@ -66,12 +66,12 @@ local record lib record configure_opt auto_inject_traceback : boolean end - new : function (protoerror, context, { string : protoerror }, string) : error + new : function (protoerror, context, { string : protoerror }, string) : Error init : function (string, string, registry | compact_registry) : error_registry_wrapper init : function (string, registry | compact_registry) : error_registry_wrapper is_error : function (any) : boolean - coerce : function (any, string) : any, error - from_stanza : function (table, context, string) : error + coerce : function (any, string) : any, Error + from_stanza : function (table, context, string) : Error configure : function end diff --git a/teal-src/util/format.d.tl b/teal-src/prosody/util/format.d.tl index 1ff77c97..1ff77c97 100644 --- a/teal-src/util/format.d.tl +++ b/teal-src/prosody/util/format.d.tl diff --git a/teal-src/util/hashes.d.tl b/teal-src/prosody/util/hashes.d.tl index cbb06f8e..64c5a12b 100644 --- a/teal-src/util/hashes.d.tl +++ b/teal-src/prosody/util/hashes.d.tl @@ -4,18 +4,30 @@ local type kdf = function (pass : string, salt : string, i : integer) : string local record lib sha1 : hash - sha256 : hash sha224 : hash + sha256 : hash sha384 : hash sha512 : hash md5 : hash + sha3_256 : hash + sha3_512 : hash + blake2s256 : hash + blake2b512 : hash hmac_sha1 : hmac + hmac_sha224 : hmac hmac_sha256 : hmac + hmac_sha384 :hmac hmac_sha512 : hmac hmac_md5 : hmac + hmac_sha3_256 : hmac + hmac_sha3_512 : hmac + hmac_blake2s256 : hmac + hmac_blake2b512 : hmac scram_Hi_sha1 : kdf pbkdf2_hmac_sha1 : kdf pbkdf2_hmac_sha256 : kdf + hkdf_hmac_sha256 : kdf + hkdf_hmac_sha384 : kdf equals : function (string, string) : boolean version : string _LIBCRYPTO_VERSION : string diff --git a/teal-src/util/hex.d.tl b/teal-src/prosody/util/hex.d.tl index 3b216a88..9d84540b 100644 --- a/teal-src/util/hex.d.tl +++ b/teal-src/prosody/util/hex.d.tl @@ -2,5 +2,7 @@ local type s2s = function (s : string) : string local record lib to : s2s from : s2s + encode : s2s + decode : s2s end return lib diff --git a/teal-src/util/http.d.tl b/teal-src/prosody/util/http.d.tl index ecbe35c3..ecbe35c3 100644 --- a/teal-src/util/http.d.tl +++ b/teal-src/prosody/util/http.d.tl diff --git a/teal-src/prosody/util/human/io.d.tl b/teal-src/prosody/util/human/io.d.tl new file mode 100644 index 00000000..e4f64cd1 --- /dev/null +++ b/teal-src/prosody/util/human/io.d.tl @@ -0,0 +1,28 @@ +local record lib + getchar : function (n : integer) : string + getline : function () : string + getpass : function () : string + show_yesno : function (prompt : string) : boolean + read_password : function () : string + show_prompt : function (prompt : string) : boolean + printf : function (fmt : string, ... : any) + padleft : function (s : string, width : integer) : string + padright : function (s : string, width : integer) : string + + -- {K:V} vs T ? + record tablerow<K,V> + width : integer | string -- generate an 1..100 % enum? + title : string + mapper : function (V, {K:V}) : string + key : K + enum alignments + "left" + "right" + end + align : alignments + end + type getrow = function<K,V> ({ K : V }) : string + table : function<K,V> ({ tablerow<K,V> }, width : integer) : getrow<K,V> +end + +return lib diff --git a/teal-src/util/human/units.d.tl b/teal-src/prosody/util/human/units.d.tl index f6568d90..3db17c3a 100644 --- a/teal-src/util/human/units.d.tl +++ b/teal-src/prosody/util/human/units.d.tl @@ -1,5 +1,8 @@ local lib = record + enum logbase + "b" -- 1024 + end adjust : function (number, string) : number, string - format : function (number, string, string) : string + format : function (number, string, logbase) : string end return lib diff --git a/teal-src/util/id.d.tl b/teal-src/prosody/util/id.d.tl index 4b6c93b7..4b6c93b7 100644 --- a/teal-src/util/id.d.tl +++ b/teal-src/prosody/util/id.d.tl diff --git a/teal-src/util/interpolation.d.tl b/teal-src/prosody/util/interpolation.d.tl index fb653edf..fb653edf 100644 --- a/teal-src/util/interpolation.d.tl +++ b/teal-src/prosody/util/interpolation.d.tl diff --git a/teal-src/prosody/util/ip.d.tl b/teal-src/prosody/util/ip.d.tl new file mode 100644 index 00000000..79e901c6 --- /dev/null +++ b/teal-src/prosody/util/ip.d.tl @@ -0,0 +1,20 @@ +local record iplib + enum protocol + "IPv6" + "IPv4" + end + record ip_t + addr : string + packed : string + proto : protocol + zone : string + end + + new_ip : function (string, protocol) : ip_t + commonPrefixLength : function (ip_t, ip_t) : integer + parse_cidr : function (string) : ip_t, integer + match : function (ip_t, ip_t, integer) : boolean + is_ip : function (any) : boolean + truncate : function (ip_t, integer) : ip_t +end +return iplib diff --git a/teal-src/util/jid.d.tl b/teal-src/prosody/util/jid.d.tl index 897318d9..897318d9 100644 --- a/teal-src/util/jid.d.tl +++ b/teal-src/prosody/util/jid.d.tl diff --git a/teal-src/util/json.d.tl b/teal-src/prosody/util/json.d.tl index a1c25004..a1c25004 100644 --- a/teal-src/util/json.d.tl +++ b/teal-src/prosody/util/json.d.tl diff --git a/teal-src/util/jsonpointer.tl b/teal-src/prosody/util/jsonpointer.tl index c21e1fbf..5ea99732 100644 --- a/teal-src/util/jsonpointer.tl +++ b/teal-src/prosody/util/jsonpointer.tl @@ -9,7 +9,7 @@ local function unescape_token(escaped_token : string) : string return unescaped end -local function resolve_json_pointer(ref : table, path : string) : any, ptr_error +local function resolve_json_pointer(ref : any, path : string) : any, ptr_error local ptr_len = #path+1 for part, pos in path:gmatch("/([^/]*)()") do local token = unescape_token(part) @@ -23,7 +23,7 @@ local function resolve_json_pointer(ref : table, path : string) : any, ptr_error new_ref = ref[token] elseif idx is integer then local i = tonumber(token) - if token == "-" then i = #ref + 1 end + if token == "-" then i = #(ref as {any}) + 1 end new_ref = ref[i+1] else return nil, "invalid-table" diff --git a/teal-src/prosody/util/jsonschema.tl b/teal-src/prosody/util/jsonschema.tl new file mode 100644 index 00000000..1cc2314f --- /dev/null +++ b/teal-src/prosody/util/jsonschema.tl @@ -0,0 +1,505 @@ +-- Copyright (C) Kim Alvefur +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- +-- Based on +-- https://json-schema.org/draft/2020-12/json-schema-core.html +-- https://json-schema.org/draft/2020-12/json-schema-validation.html +-- + +if not math.type then require "prosody.util.mathcompat" end + + +local utf8_enc = rawget(_G, "utf8") or require"prosody.util.encodings".utf8; +local utf8_len = utf8_enc.len or function(s : string) : integer + local _, count = s:gsub("[%z\001-\127\194-\253][\128-\191]*", ""); + return count; +end; + +local json = require "prosody.util.json" +local null = json.null; + +local pointer = require "prosody.util.jsonpointer" + +local type json_type_name = json.json_type_name + +-- json_type_name here is non-standard +local type schema_t = boolean | json_schema_object + +local record json_schema_object + type json_type_name = json.json_type_name + type schema_object = json_schema_object + + -- json-schema-core meta stuff + ["$schema"] : string + ["$vocabulary"] : { string : boolean } + ["$id"] : string + ["$comment"] : string + ["$defs"] : { string : schema_t } + ["$anchor"] : string -- NYI + ["$dynamicAnchor"] : string -- NYI + ["$ref"] : string + ["$dynamicRef"] : string -- NYI + + -- combinations + allOf : { schema_t } + anyOf : { schema_t } + oneOf : { schema_t } + + -- conditional logic + ["not"] : schema_t + ["if"] : schema_t + ["then"] : schema_t + ["else"] : schema_t + + dependentRequired : { string : { string } } + + -- arrays + prefixItems : { schema_t } + items : schema_t + contains : schema_t + + -- objects + properties : { string : schema_t } + patternProperties: { string : schema_t } -- NYI + additionalProperties: schema_t + propertyNames : schema_t + + -- unevaluated + unevaluatedItems : schema_t -- NYI + unevaluatedProperties : schema_t -- NYI + + -- json-schema-validation + type : json_type_name | { json_type_name } + enum : { any } + const : any + + -- numbers + multipleOf : number + maximum : number + exclusiveMaximum : number + minimum : number + exclusiveMinimum : number + + -- strings + maxLength : integer + minLength : integer + pattern : string -- NYI + + -- arrays + maxItems : integer + minItems : integer + uniqueItems : boolean + maxContains : integer + minContains : integer + + -- objects + maxProperties : integer -- NYI + minProperties : integer -- NYI + required : { string } + dependentSchemas : { string : schema_t } + + -- semantic format + format : string + + -- for Lua + luaPatternProperties: { string : schema_t } + luaPattern : string + + -- xml + record xml_t + name : string + namespace : string + prefix : string + attribute : boolean + wrapped : boolean + + -- nonstantard, maybe in the future + text : boolean + x_name_is_value : boolean + x_single_attribute : string + end + + xml : xml_t + + -- descriptive + title : string + description : string + deprecated : boolean + readOnly : boolean + writeOnly : boolean + + -- methods + validate : function (schema : schema_t, data : any, root : json_schema_object, sloc : string, iloc : string, errs:errors) : boolean, errors +end + +-- TODO validator function per schema property + +local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean + if schema == nil then + return true + elseif schema == "object" and data is table then + return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string") + elseif schema == "array" and data is table then + return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number") + elseif schema == "integer" then + return math.type(data) == schema + elseif schema == "null" then + return data == null + elseif schema is { json_type_name } then + for _, one in ipairs(schema as { json_type_name }) do + if simple_validate(one, data) then + return true + end + end + return false + else + return type(data) == schema + end +end + +local record validation_error + instanceLocation : string + schemaLocation : string + error : string +end +local type errors = { validation_error } +local function mkerr(sloc:string,iloc:string,err:string) : validation_error + return { schemaLocation = sloc; instanceLocation = iloc; error = err }; +end + +local function validate (schema : schema_t, data : any, root : json_schema_object, sloc : string, iloc : string, errs:errors) : boolean, errors + if schema is boolean then + return schema + end + + if root == nil then + root = schema as json_schema_object + iloc = "" + sloc = "" + errs = {}; + end + + if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then + local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t + if referenced ~= nil and referenced ~= root and referenced ~= schema then + if not validate(referenced, data, root, schema["$ref"], iloc, errs) then + table.insert(errs, mkerr(sloc.."/$ref", iloc, "Subschema failed validation")) + return false, errs; + end + end + end + + if not simple_validate(schema.type, data) then + table.insert(errs, mkerr(sloc.."/type", iloc, "unexpected type")); + return false, errs; + end + + if schema.type == "object" then + if data is table then + -- just check that there the keys are all strings + for k in pairs(data) do + if not k is string then + table.insert(errs, mkerr(sloc.."/type", iloc, "'object' had non-string keys")); + return false, errs; + end + end + end + end + + if schema.type == "array" then + if data is table then + -- just check that there the keys are all numbers + for i in pairs(data) do + if not i is integer then + table.insert(errs, mkerr(sloc.."/type", iloc, "'array' had non-integer keys")); + return false, errs; + end + end + end + end + + if schema["enum"] ~= nil then + local match = false + for _, v in ipairs(schema["enum"]) do + if v == data then + -- FIXME supposed to do deep-compare + match = true + break + end + end + if not match then + table.insert(errs, mkerr(sloc.."/enum", iloc, "not one of the enumerated values")); + return false, errs; + end + end + + -- XXX this is measured in byte, while JSON measures in ... bork + -- TODO use utf8.len? + if data is string then + if schema.maxLength and utf8_len(data) > schema.maxLength then + table.insert(errs, mkerr(sloc.."/maxLength", iloc, "string too long")) + return false, errs; + end + if schema.minLength and utf8_len(data) < schema.minLength then + table.insert(errs, mkerr(sloc.."/maxLength", iloc, "string too short")) + return false, errs; + end + if schema.luaPattern and not data:match(schema.luaPattern) then + table.insert(errs, mkerr(sloc.."/luaPattern", iloc, "string does not match pattern")) + return false, errs; + end + end + + if data is number then + if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then + table.insert(errs, mkerr(sloc.."/luaPattern", iloc, "not a multiple")) + return false, errs; + end + + if schema.maximum and not ( data <= schema.maximum ) then + table.insert(errs, mkerr(sloc.."/maximum", iloc, "number exceeds maximum")) + return false, errs; + end + + if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then + table.insert(errs, mkerr(sloc.."/exclusiveMaximum", iloc, "number exceeds exclusive maximum")) + return false, errs; + end + + if schema.minimum and not ( data >= schema.minimum ) then + table.insert(errs, mkerr(sloc.."/minimum", iloc, "number below minimum")) + return false, errs; + end + + if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then + table.insert(errs, mkerr(sloc.."/exclusiveMinimum", iloc, "number below exclusive minimum")) + return false, errs; + end + end + + if schema.allOf then + for i, sub in ipairs(schema.allOf) do + if not validate(sub, data, root, sloc.."/allOf/"..i, iloc, errs) then + table.insert(errs, mkerr(sloc.."/allOf", iloc, "did not match all subschemas")) + return false, errs; + end + end + end + + if schema.oneOf then + local valid = 0 + for i, sub in ipairs(schema.oneOf) do + if validate(sub, data, root, sloc.."/oneOf"..i, iloc, errs) then + valid = valid + 1 + end + end + if valid ~= 1 then + table.insert(errs, mkerr(sloc.."/oneOf", iloc, "did not match exactly one subschema")) + return false, errs; + end + end + + if schema.anyOf then + local match = false + for i, sub in ipairs(schema.anyOf) do + if validate(sub, data, root, sloc.."/anyOf/"..i, iloc, errs) then + match = true + break + end + end + if not match then + table.insert(errs, mkerr(sloc.."/anyOf", iloc, "did not match any subschema")) + return false, errs; + end + end + + if schema["not"] then + if validate(schema["not"], data, root, sloc.."/not", iloc, errs) then + table.insert(errs, mkerr(sloc.."/not", iloc, "did match subschema")) + return false, errs; + end + end + + if schema["if"] ~= nil then + if validate(schema["if"], data, root, sloc.."/if", iloc, errs) then + if schema["then"] then + if not validate(schema["then"], data, root, sloc.."/then", iloc, errs) then + table.insert(errs, mkerr(sloc.."/then", iloc, "did not match subschema")) + return false, errs; + end + end + else + if schema["else"] then + if not validate(schema["else"], data, root, sloc.."/else", iloc, errs) then + table.insert(errs, mkerr(sloc.."/else", iloc, "did not match subschema")) + return false, errs; + end + end + end + end + + if schema.const ~= nil and schema.const ~= data then + table.insert(errs, mkerr(sloc.."/const", iloc, "did not match constant value")) + return false, errs; + end + + if data is table then + -- tables combine object and array behavior, thus we do both kinds of + -- validations in this block, which could be useful for validating Lua + -- tables + + if schema.maxItems and #(data as {any}) > schema.maxItems then + table.insert(errs, mkerr(sloc.."/maxItems", iloc, "too many items")) + return false, errs; + end + + if schema.minItems and #(data as {any}) < schema.minItems then + table.insert(errs, mkerr(sloc.."/minItems", iloc, "too few items")) + return false, errs; + end + + if schema.required then + for _, k in ipairs(schema.required) do + if data[k] == nil then + table.insert(errs, mkerr(sloc.."/required", iloc.."/"..tostring(k), "missing required property")) + return false, errs; + end + end + end + + if schema.dependentRequired then + for k, reqs in pairs(schema.dependentRequired) do + if data[k] ~= nil then + for _, req in ipairs(reqs) do + if data[req] == nil then + table.insert(errs, mkerr(sloc.."/dependentRequired", iloc, "missing dependent required property")) + return false, errs; + end + end + end + end + end + + if schema.propertyNames ~= nil then + -- could be used to validate non-string keys of Lua tables + for k in pairs(data) do + if not validate(schema.propertyNames, k, root, sloc.."/propertyNames", iloc.."/"..tostring(k), errs) then + table.insert(errs, mkerr(sloc.."/propertyNames", iloc.."/"..tostring(k), "a property name did not match subschema")) + return false, errs; + end + end + end + + -- additionalProperties applies to properties not validated by properties + -- or patternProperties, so we must keep track of properties validated by + -- the later + local seen_properties : { string : boolean } = {} + + if schema.properties then + for k, sub in pairs(schema.properties) do + if data[k] ~= nil and not validate(sub, data[k], root, sloc.."/"..tostring(k), iloc.."/"..tostring(k), errs) then + table.insert(errs, mkerr(sloc.."/"..tostring(k), iloc.."/"..tostring(k), "a property did not match subschema")) + return false, errs; + end + seen_properties[k] = true + end + end + + if schema.luaPatternProperties then + -- like patternProperties, but Lua patterns + for pattern, sub in pairs(schema.luaPatternProperties) do + for k in pairs(data) do + if k is string and k:match(pattern) then + if not validate(sub, data[k], root, sloc.."/luaPatternProperties", iloc, errs) then + table.insert(errs, mkerr(sloc.."/luaPatternProperties/"..pattern, iloc.."/"..tostring(k), "a property did not match subschema")) + return false, errs; + end + seen_properties[k] = true + end + end + end + end + + if schema.additionalProperties ~= nil then + for k, v in pairs(data) do + if not seen_properties[k as string] then + if not validate(schema.additionalProperties, v, root, sloc.."/additionalProperties", iloc.."/"..tostring(k), errs) then + table.insert(errs, mkerr(sloc.."/additionalProperties", iloc.."/"..tostring(k), "additional property did not match subschema")) + return false, errs; + end + end + end + end + + if schema.dependentSchemas then + for k, sub in pairs(schema.dependentSchemas) do + if data[k] ~= nil and not validate(sub, data, root, sloc.."/dependentSchemas/"..k, iloc, errs) then + table.insert(errs, mkerr(sloc.."/dependentSchemas", iloc.."/"..tostring(k), "did not match dependent subschema")) + return false, errs; + end + end + end + + if schema.uniqueItems then + -- only works for scalars, would need to deep-compare for objects/arrays/tables + local values : { any : boolean } = {} + for _, v in pairs(data) do + if values[v] then + table.insert(errs, mkerr(sloc.."/uniqueItems", iloc, "had duplicate items")) + return false, errs; + end + values[v] = true + end + end + + local p = 0 + if schema.prefixItems ~= nil then + for i, s in ipairs(schema.prefixItems) do + if data[i] == nil then + break + elseif validate(s, data[i], root, sloc.."/prefixItems/"..i, iloc.."/"..i, errs) then + p = i + else + table.insert(errs, mkerr(sloc.."/prefixItems/"..i, iloc.."/"..tostring(i), "did not match subschema")) + return false, errs; + end + end + end + + if schema.items ~= nil then + for i = p+1, #(data as {any}) do + if not validate(schema.items, data[i], root, sloc, iloc.."/"..i, errs) then + table.insert(errs, mkerr(sloc.."/prefixItems/"..i, iloc.."/"..i, "did not match subschema")) + return false, errs; + end + end + end + + if schema.contains ~= nil then + local found = 0 + for i = 1, #(data as {any}) do + if validate(schema.contains, data[i], root, sloc.."/contains", iloc.."/"..i, errs) then + found = found + 1 + else + table.insert(errs, mkerr(sloc.."/contains", iloc.."/"..i, "did not match subschema")) + end + end + if found < (schema.minContains or 1) then + table.insert(errs, mkerr(sloc.."/minContains", iloc, "too few matches")) + return false, errs; + elseif found > (schema.maxContains or math.huge) then + table.insert(errs, mkerr(sloc.."/maxContains", iloc, "too many matches")) + return false, errs; + end + end + end + + return true; +end + + +json_schema_object.validate = validate; + +return json_schema_object; diff --git a/teal-src/prosody/util/jwt.d.tl b/teal-src/prosody/util/jwt.d.tl new file mode 100644 index 00000000..b3d0cd9e --- /dev/null +++ b/teal-src/prosody/util/jwt.d.tl @@ -0,0 +1,38 @@ +local crypto = require "prosody.util.crypto" +local record jwtlib + enum algorithm + "HS256" + "HS384" + "HS512" + "ES256" + "ES512" + "RS256" + "RS384" + "RS512" + "PS256" + "PS384" + "PS512" + end + type payload = { string : any } + type signer_t = function (payload : payload) : string + type verifier_t = function (token : string) : payload + enum key_type + "rsaEncryption" + "id-ecPublicKey" + end + record algorithm_t + sign : signer_t + verify : verifier_t + load_key : function (key : string) : crypto.key + end + init : function (algorithm, private_key : string, public_key : string, table) : signer_t, verifier_t + new_signer : function (algorithm, string, table) : signer_t + new_verifier : function (algorithm, string, table) : verifier_t + _algorithms : { + algorithm : algorithm_t + } + -- Deprecated + sign : function (private_key : string, payload) : string + verify : function (string) : payload +end +return jwtlib diff --git a/teal-src/prosody/util/logger.d.tl b/teal-src/prosody/util/logger.d.tl new file mode 100644 index 00000000..db29adfd --- /dev/null +++ b/teal-src/prosody/util/logger.d.tl @@ -0,0 +1,18 @@ +local record util + enum loglevel + "debug" + "info" + "warn" + "error" + end + type logger = function ( loglevel, string, ...:any ) + type sink = function ( string, loglevel, string, ...:any ) + type simple_sink = function ( string, loglevel, string ) + init : function ( string ) : logger + make_logger : function ( string, loglevel ) : function ( string, ...:any ) + reset : function () + add_level_sink : function ( loglevel, sink ) + add_simple_sink : function ( simple_sink, { loglevel } ) +end + +return util diff --git a/teal-src/prosody/util/mathcompat.tl b/teal-src/prosody/util/mathcompat.tl new file mode 100644 index 00000000..1e3f9bab --- /dev/null +++ b/teal-src/prosody/util/mathcompat.tl @@ -0,0 +1,15 @@ +if not math.type then + local enum number_subtype + "float" "integer" + end + local function math_type(t:any) : number_subtype + if t is number then + if t % 1 == 0 and t ~= t+1 and t ~= t-1 then + return "integer" + else + return "float" + end + end + end + _G.math.type = math_type +end diff --git a/teal-src/util/net.d.tl b/teal-src/prosody/util/net.d.tl index 1040fcef..1040fcef 100644 --- a/teal-src/util/net.d.tl +++ b/teal-src/prosody/util/net.d.tl diff --git a/teal-src/util/poll.d.tl b/teal-src/prosody/util/poll.d.tl index 8df56d57..8df56d57 100644 --- a/teal-src/util/poll.d.tl +++ b/teal-src/prosody/util/poll.d.tl diff --git a/teal-src/util/pposix.d.tl b/teal-src/prosody/util/pposix.d.tl index 68f49730..d7780835 100644 --- a/teal-src/util/pposix.d.tl +++ b/teal-src/prosody/util/pposix.d.tl @@ -97,6 +97,7 @@ local record pposix meminfo : function () : memoryinfo atomic_append : function (f : FILE, s : string) : boolean, string, integer + remove_blocks : function (f : FILE, integer, integer) isatty : function(FILE) : boolean diff --git a/teal-src/prosody/util/promise.d.tl b/teal-src/prosody/util/promise.d.tl new file mode 100644 index 00000000..d895a828 --- /dev/null +++ b/teal-src/prosody/util/promise.d.tl @@ -0,0 +1,22 @@ + +local record lib + type resolve_func = function (any) + type promise_body = function (resolve_func, resolve_func) + + record Promise<A, B> + type on_resolved = function (A) : any + type on_rejected = function (B) : any + next : function (Promise, on_resolved, on_rejected) : Promise<any, any> + end + + new : function (promise_body) : Promise + resolve : function (any) : Promise + reject : function (any) : Promise + all : function ({ Promise }) : Promise + all_settled : function ({ Promise }) : Promise + race : function ({ Promise }) : Promise + try : function + is_promise : function(any) : boolean +end + +return lib diff --git a/teal-src/prosody/util/queue.d.tl b/teal-src/prosody/util/queue.d.tl new file mode 100644 index 00000000..cb8458e7 --- /dev/null +++ b/teal-src/prosody/util/queue.d.tl @@ -0,0 +1,21 @@ +local record lib + record queue<T> + size : integer + count : function (queue<T>) : integer + enum push_errors + "queue full" + end + + push : function (queue<T>, T) : boolean, push_errors + pop : function (queue<T>) : T + peek : function (queue<T>) : T + replace : function (queue<T>, T) : boolean, push_errors + type iterator = function (T, integer) : integer, T + items : function (queue<T>) : iterator, T, integer + type consume_iter = function (queue<T>) : T + consume : function (queue<T>) : consume_iter + end + + new : function<T> (size:integer, allow_wrapping:boolean) : queue<T> +end +return lib; diff --git a/teal-src/util/random.d.tl b/teal-src/prosody/util/random.d.tl index 83ff2fcc..83ff2fcc 100644 --- a/teal-src/util/random.d.tl +++ b/teal-src/prosody/util/random.d.tl diff --git a/teal-src/util/ringbuffer.d.tl b/teal-src/prosody/util/ringbuffer.d.tl index e4726d68..e4726d68 100644 --- a/teal-src/util/ringbuffer.d.tl +++ b/teal-src/prosody/util/ringbuffer.d.tl diff --git a/teal-src/prosody/util/roles.d.tl b/teal-src/prosody/util/roles.d.tl new file mode 100644 index 00000000..fef4f88a --- /dev/null +++ b/teal-src/prosody/util/roles.d.tl @@ -0,0 +1,32 @@ +local record util_roles + + type context = any + + record Role + id : string + name : string + description : string + default : boolean + priority : number -- or integer? + permissions : { string : boolean } + + may : function (Role, string, context) + clone : function (Role, role_config) + set_permission : function (Role, string, boolean, boolean) + end + + is_role : function (any) : boolean + + record role_config + name : string + description : string + default : boolean + priority : number -- or integer? + inherits : { Role } + permissions : { string : boolean } + end + + new : function (role_config, Role) : Role +end + +return util_roles diff --git a/teal-src/prosody/util/serialization.d.tl b/teal-src/prosody/util/serialization.d.tl new file mode 100644 index 00000000..528c9705 --- /dev/null +++ b/teal-src/prosody/util/serialization.d.tl @@ -0,0 +1,34 @@ +local record _M + enum preset + "debug" + "oneline" + "compact" + "pretty" + end + type fallback = function (any, string) : string + record config + preset : preset + fallback : fallback + fatal : boolean + keywords : { string : boolean } + indentwith : string + itemstart : string + itemsep : string + itemlast : string + tstart : string + tend : string + kstart : string + kend : string + equals : string + unquoted : boolean | string + hex : string + freeze : boolean + maxdepth : integer + multirefs : boolean + table_pairs : function + end + type serializer = function (any) : string + new : function (config|preset) : serializer + serialize : function (any, config|preset) : string +end +return _M diff --git a/teal-src/prosody/util/set.d.tl b/teal-src/prosody/util/set.d.tl new file mode 100644 index 00000000..a23dd258 --- /dev/null +++ b/teal-src/prosody/util/set.d.tl @@ -0,0 +1,22 @@ +local record lib + record Set<T> + add : function<T> (Set<T>, T) + contains : function<T> (Set<T>, T) : boolean + contains_set : function<T> (Set<T>, Set<T>) : boolean + items : function<T> (Set<T>) : function<T> (Set<T>, T) : T + remove : function<T> (Set<T>, T) + add_list : function<T> (Set<T>, { T }) + include : function<T> (Set<T>, Set<T>) + exclude : function<T> (Set<T>, Set<T>) + empty : function<T> (Set<T>) : boolean + end + + new : function<T> ({ T }) : Set<T> + is_set : function (any) : boolean + union : function<T> (Set<T>, Set<T>) : Set <T> + difference : function<T> (Set<T>, Set<T>) : Set <T> + intersection : function<T> (Set<T>, Set<T>) : Set <T> + xor : function<T> (Set<T>, Set<T>) : Set <T> +end + +return lib diff --git a/teal-src/util/signal.d.tl b/teal-src/prosody/util/signal.d.tl index 8610aa7f..290cf08f 100644 --- a/teal-src/util/signal.d.tl +++ b/teal-src/prosody/util/signal.d.tl @@ -1,5 +1,5 @@ local record lib - enum signal + enum Signal "SIGABRT" "SIGALRM" "SIGBUS" @@ -33,9 +33,9 @@ local record lib "SIGXCPU" "SIGXFSZ" end - signal : function (integer | signal, function, boolean) : boolean - raise : function (integer | signal) - kill : function (integer, integer | signal) + signal : function (integer | Signal, function, boolean) : boolean + raise : function (integer | Signal) + kill : function (integer, integer | Signal) -- enum : integer end return lib diff --git a/teal-src/util/smqueue.tl b/teal-src/prosody/util/smqueue.tl index e149dde7..821aee57 100644 --- a/teal-src/util/smqueue.tl +++ b/teal-src/prosody/util/smqueue.tl @@ -1,4 +1,4 @@ -local queue = require "util.queue"; +local queue = require "prosody.util.queue"; local record lib -- T would typically be util.stanza diff --git a/teal-src/util/stanza.d.tl b/teal-src/prosody/util/stanza.d.tl index a358248a..e1ab2105 100644 --- a/teal-src/util/stanza.d.tl +++ b/teal-src/prosody/util/stanza.d.tl @@ -4,6 +4,39 @@ local record lib type childtags_iter = function () : stanza_t type maptags_cb = function ( stanza_t ) : stanza_t + + enum stanza_error_type + "auth" + "cancel" + "continue" + "modify" + "wait" + end + enum stanza_error_condition + "bad-request" + "conflict" + "feature-not-implemented" + "forbidden" + "gone" + "internal-server-error" + "item-not-found" + "jid-malformed" + "not-acceptable" + "not-allowed" + "not-authorized" + "policy-violation" + "recipient-unavailable" + "redirect" + "registration-required" + "remote-server-not-found" + "remote-server-timeout" + "resource-constraint" + "service-unavailable" + "subscription-required" + "undefined-condition" + "unexpected-request" + end + record stanza_t name : string attr : { string : string } @@ -16,6 +49,7 @@ local record lib tag : function ( stanza_t, string, { string : string } ) : stanza_t text : function ( stanza_t, string ) : stanza_t up : function ( stanza_t ) : stanza_t + at_top : function ( stanza_t ) : boolean reset : function ( stanza_t ) : stanza_t add_direct_child : function ( stanza_t, stanza_t ) add_child : function ( stanza_t, stanza_t ) @@ -24,6 +58,8 @@ local record lib get_child : function ( stanza_t, string, string ) : stanza_t get_text : function ( stanza_t ) : string get_child_text : function ( stanza_t, string, string ) : string + get_child_attr : function ( stanza_t, string, string ) : string + get_child_with_attr : function ( stanza_t, string, string, string, function (string) : boolean ) : string child_with_name : function ( stanza_t, string, string ) : stanza_t child_with_ns : function ( stanza_t, string, string ) : stanza_t children : function ( stanza_t ) : children_iter, stanza_t, integer @@ -35,7 +71,9 @@ local record lib pretty_print : function ( stanza_t ) : string pretty_top_tag : function ( stanza_t ) : string - get_error : function ( stanza_t ) : string, string, string, stanza_t + -- FIXME Represent util.error support + get_error : function ( stanza_t ) : stanza_error_type, stanza_error_condition, string, stanza_t + add_error : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string ) indent : function ( stanza_t, integer, string ) : stanza_t end @@ -45,16 +83,61 @@ local record lib { serialized_stanza_t | string } end + record message_attr + ["xml:lang"] : string + from : string + id : string + to : string + type : message_type + enum message_type + "chat" + "error" + "groupchat" + "headline" + "normal" + end + end + + record presence_attr + ["xml:lang"] : string + from : string + id : string + to : string + type : presence_type + enum presence_type + "error" + "probe" + "subscribe" + "subscribed" + "unsubscribe" + "unsubscribed" + end + end + + record iq_attr + ["xml:lang"] : string + from : string + id : string + to : string + type : iq_type + enum iq_type + "error" + "get" + "result" + "set" + end + end + stanza : function ( string, { string : string } ) : stanza_t is_stanza : function ( any ) : boolean preserialize : function ( stanza_t ) : serialized_stanza_t deserialize : function ( serialized_stanza_t ) : stanza_t clone : function ( stanza_t, boolean ) : stanza_t - message : function ( { string : string }, string ) : stanza_t - iq : function ( { string : string } ) : stanza_t + message : function ( message_attr, string ) : stanza_t + iq : function ( iq_attr ) : stanza_t reply : function ( stanza_t ) : stanza_t - error_reply : function ( stanza_t, string, string, string, string ) - presence : function ( { string : string } ) : stanza_t + error_reply : function ( stanza_t, stanza_error_type, stanza_error_condition, string, string ) : stanza_t + presence : function ( presence_attr ) : stanza_t xml_escape : function ( string ) : string pretty_print : function ( string ) : string end diff --git a/teal-src/util/strbitop.d.tl b/teal-src/prosody/util/strbitop.d.tl index 010efdb8..86577ef2 100644 --- a/teal-src/util/strbitop.d.tl +++ b/teal-src/prosody/util/strbitop.d.tl @@ -2,5 +2,6 @@ local record mod sand : function (string, string) : string sor : function (string, string) : string sxor : function (string, string) : string + common_prefix_bits : function (string, string) : integer end return mod diff --git a/teal-src/prosody/util/struct.d.tl b/teal-src/prosody/util/struct.d.tl new file mode 100644 index 00000000..201aaa23 --- /dev/null +++ b/teal-src/prosody/util/struct.d.tl @@ -0,0 +1,6 @@ +local record lib + pack : function (string, ...:any) : string + unpack : function(string, string, integer) : any... + size : function(string) : integer +end +return lib diff --git a/teal-src/util/table.d.tl b/teal-src/prosody/util/table.d.tl index 0ff5ed95..67e5d0f0 100644 --- a/teal-src/util/table.d.tl +++ b/teal-src/prosody/util/table.d.tl @@ -1,6 +1,7 @@ local record lib create : function (narr:integer, nrec:integer):table pack : function (...:any):{any} + move : function (table, integer, integer, integer, table) : table end return lib diff --git a/teal-src/prosody/util/termcolours.d.tl b/teal-src/prosody/util/termcolours.d.tl new file mode 100644 index 00000000..226259aa --- /dev/null +++ b/teal-src/prosody/util/termcolours.d.tl @@ -0,0 +1,7 @@ +local record lib + getstring : function (string, string) : string + getstyle : function (...:string) : string + setstyle : function (string) : string + tohtml : function (string) : string +end +return lib diff --git a/teal-src/util/time.d.tl b/teal-src/prosody/util/time.d.tl index e159706b..e159706b 100644 --- a/teal-src/util/time.d.tl +++ b/teal-src/prosody/util/time.d.tl diff --git a/teal-src/prosody/util/timer.d.tl b/teal-src/prosody/util/timer.d.tl new file mode 100644 index 00000000..a6394cf3 --- /dev/null +++ b/teal-src/prosody/util/timer.d.tl @@ -0,0 +1,8 @@ +local record util_timer + record task end + type timer_callback = function (number) : number + add_task : function ( number, timer_callback, any ) : task + stop : function ( task ) + reschedule : function ( task, number ) : task +end +return util_timer diff --git a/teal-src/util/uuid.d.tl b/teal-src/prosody/util/uuid.d.tl index 45fd4312..284a4e4c 100644 --- a/teal-src/util/uuid.d.tl +++ b/teal-src/prosody/util/uuid.d.tl @@ -1,5 +1,5 @@ local record lib - get_nibbles : (number) : string + get_nibbles : function (number) : string generate : function () : string seed : function (string) diff --git a/teal-src/util/xtemplate.tl b/teal-src/prosody/util/xtemplate.tl index cdc913bf..4d293359 100644 --- a/teal-src/util/xtemplate.tl +++ b/teal-src/prosody/util/xtemplate.tl @@ -14,17 +14,25 @@ local s_match = string.match; local s_sub = string.sub; local t_concat = table.concat; -local st = require "util.stanza"; +local st = require "prosody.util.stanza"; local type escape_t = function (string) : string -local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean +local type filter_t = function (string | st.stanza_t, string | st.stanza_t, string) : string | st.stanza_t, boolean local type filter_coll = { string : filter_t } local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string escape = escape or st.xml_escape; - return (s_gsub(template, "%b{}", function(block : string) : string + return (s_gsub(template, "(%s*)(%b{})(%s*)", function(pre_blank : string, block : string, post_blank : string) : string local inner = s_sub(block, 2, -2); + if inner:sub(1, 1) == "-" then + pre_blank = ""; + inner = inner:sub(2); + end + if inner:sub(-1, -1) == "-" then + post_blank = ""; + inner = inner:sub(1, -2); + end local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()"); if not path is string then return end local value : string | st.stanza_t @@ -48,7 +56,7 @@ local function render(template : string, root : st.stanza_t, escape : escape_t, if func == "each" and tmpl then if not st.is_stanza(value) then - return ""; + return pre_blank..post_blank; end if not args then value, args = root, path; end local ns, name = s_match(args, "^(%b{})(.*)$"); @@ -77,11 +85,7 @@ local function render(template : string, root : st.stanza_t, escape : escape_t, end elseif filters and filters[func] then local f = filters[func]; - if args == nil then - value, is_escaped = f(value, tmpl); - else - value, is_escaped = f(args, value, tmpl); - end + value, is_escaped = f(value, args, tmpl); else error("No such filter function: " .. func); end @@ -90,14 +94,14 @@ local function render(template : string, root : st.stanza_t, escape : escape_t, if value is string then if not is_escaped then value = escape(value); end - return value; + return pre_blank .. value .. post_blank; elseif st.is_stanza(value) then value = value:get_text(); if value then - return escape(value); + return pre_blank .. escape(value) .. post_blank; end end - return ""; + return pre_blank .. post_blank; end)); end diff --git a/teal-src/util/dataforms.d.tl b/teal-src/util/dataforms.d.tl deleted file mode 100644 index 9e4170fa..00000000 --- a/teal-src/util/dataforms.d.tl +++ /dev/null @@ -1,52 +0,0 @@ -local stanza_t = require "util.stanza".stanza_t - -local enum form_type - "form" - "submit" - "cancel" - "result" -end - -local enum field_type - "boolean" - "fixed" - "hidden" - "jid-multi" - "jid-single" - "list-multi" - "list-single" - "text-multi" - "text-private" - "text-single" -end - -local record form_field - - type : field_type - var : string -- protocol name - name : string -- internal name - - label : string - desc : string - - datatype : string - range_min : number - range_max : number - - value : any -- depends on field_type - options : table -end - -local record dataform - title : string - instructions : string - { form_field } -- XXX https://github.com/teal-language/tl/pull/415 - - form : function ( dataform, table, form_type ) : stanza_t -end - -local record lib - new : function ( dataform ) : dataform -end - -return lib diff --git a/teal-src/util/datetime.d.tl b/teal-src/util/datetime.d.tl deleted file mode 100644 index 971e8f9c..00000000 --- a/teal-src/util/datetime.d.tl +++ /dev/null @@ -1,11 +0,0 @@ --- TODO s/number/integer/ once Teal gets support for that - -local record lib - date : function (t : integer) : string - datetime : function (t : integer) : string - time : function (t : integer) : string - legacy : function (t : integer) : string - parse : function (t : string) : integer -end - -return lib diff --git a/teal-src/util/jsonschema.tl b/teal-src/util/jsonschema.tl deleted file mode 100644 index 160c164c..00000000 --- a/teal-src/util/jsonschema.tl +++ /dev/null @@ -1,374 +0,0 @@ --- Copyright (C) 2021 Kim Alvefur --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- --- Based on --- https://json-schema.org/draft/2020-12/json-schema-core.html --- https://json-schema.org/draft/2020-12/json-schema-validation.html --- - -local json = require"util.json" -local null = json.null; - -local pointer = require "util.jsonpointer" - -local type json_type_name = json.json_type_name - --- json_type_name here is non-standard -local type schema_t = boolean | json_schema_object - -local record json_schema_object - type json_type_name = json.json_type_name - type schema_object = json_schema_object - - type : json_type_name | { json_type_name } - enum : { any } - const : any - - allOf : { schema_t } - anyOf : { schema_t } - oneOf : { schema_t } - - ["not"] : schema_t - ["if"] : schema_t - ["then"] : schema_t - ["else"] : schema_t - - ["$ref"] : string - - -- numbers - multipleOf : number - maximum : number - exclusiveMaximum : number - minimum : number - exclusiveMinimum : number - - -- strings - maxLength : integer - minLength : integer - pattern : string -- NYI - format : string - - -- arrays - prefixItems : { schema_t } - items : schema_t - contains : schema_t - maxItems : integer - minItems : integer - uniqueItems : boolean - maxContains : integer -- NYI - minContains : integer -- NYI - - -- objects - properties : { string : schema_t } - maxProperties : integer -- NYI - minProperties : integer -- NYI - required : { string } - dependentRequired : { string : { string } } - additionalProperties: schema_t - patternProperties: schema_t -- NYI - propertyNames : schema_t - - -- xml - record xml_t - name : string - namespace : string - prefix : string - attribute : boolean - wrapped : boolean - - -- nonstantard, maybe in the future - text : boolean - x_name_is_value : boolean - x_single_attribute : string - end - - xml : xml_t - - -- descriptive - title : string - description : string - deprecated : boolean - readOnly : boolean - writeOnly : boolean - - -- methods - validate : function ( schema_t, any, json_schema_object ) : boolean -end - --- TODO validator function per schema property - -local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean - if schema == nil then - return true - elseif schema == "object" and data is table then - return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string") - elseif schema == "array" and data is table then - return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number") - elseif schema == "integer" then - return math.type(data) == schema - elseif schema == "null" then - return data == null - elseif schema is { json_type_name } then - for _, one in ipairs(schema as { json_type_name }) do - if simple_validate(one, data) then - return true - end - end - return false - else - return type(data) == schema - end -end - -local complex_validate : function ( json_schema_object, any, json_schema_object ) : boolean - -local function validate (schema : schema_t, data : any, root : json_schema_object) : boolean - if schema is boolean then - return schema - else - return complex_validate(schema, data, root) - end -end - -function complex_validate (schema : json_schema_object, data : any, root : json_schema_object) : boolean - - if root == nil then - root = schema - end - - if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then - local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t - if referenced ~= nil and referenced ~= root and referenced ~= schema then - if not validate(referenced, data, root) then - return false; - end - end - end - - if not simple_validate(schema.type, data) then - return false; - end - - if schema.type == "object" then - if data is table then - -- just check that there the keys are all strings - for k in pairs(data) do - if not k is string then - return false - end - end - end - end - - if schema.type == "array" then - if data is table then - -- just check that there the keys are all numbers - for i in pairs(data) do - if not i is integer then - return false - end - end - end - end - - if schema["enum"] ~= nil then - local match = false - for _, v in ipairs(schema["enum"]) do - if v == data then - -- FIXME supposed to do deep-compare - match = true - break - end - end - if not match then - return false - end - end - - -- XXX this is measured in byte, while JSON measures in ... bork - -- TODO use utf8.len? - if data is string then - if schema.maxLength and #data > schema.maxLength then - return false - end - if schema.minLength and #data < schema.minLength then - return false - end - end - - if data is number then - if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then - return false - end - - if schema.maximum and not ( data <= schema.maximum ) then - return false - end - - if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then - return false - end - - if schema.minimum and not ( data >= schema.minimum ) then - return false - end - - if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then - return false - end - end - - if schema.allOf then - for _, sub in ipairs(schema.allOf) do - if not validate(sub, data, root) then - return false - end - end - end - - if schema.oneOf then - local valid = 0 - for _, sub in ipairs(schema.oneOf) do - if validate(sub, data, root) then - valid = valid + 1 - end - end - if valid ~= 1 then - return false - end - end - - if schema.anyOf then - local match = false - for _, sub in ipairs(schema.anyOf) do - if validate(sub, data, root) then - match = true - break - end - end - if not match then - return false - end - end - - if schema["not"] then - if validate(schema["not"], data, root) then - return false - end - end - - if schema["if"] ~= nil then - if validate(schema["if"], data, root) then - if schema["then"] then - return validate(schema["then"], data, root) - end - else - if schema["else"] then - return validate(schema["else"], data, root) - end - end - end - - if schema.const ~= nil and schema.const ~= data then - return false - end - - if data is table then - - if schema.maxItems and #data > schema.maxItems then - return false - end - - if schema.minItems and #data < schema.minItems then - return false - end - - if schema.required then - for _, k in ipairs(schema.required) do - if data[k] == nil then - return false - end - end - end - - if schema.propertyNames ~= nil then - for k in pairs(data) do - if not validate(schema.propertyNames, k, root) then - return false - end - end - end - - if schema.properties then - for k, sub in pairs(schema.properties) do - if data[k] ~= nil and not validate(sub, data[k], root) then - return false - end - end - end - - if schema.additionalProperties ~= nil then - for k, v in pairs(data) do - if schema.properties == nil or schema.properties[k as string] == nil then - if not validate(schema.additionalProperties, v, root) then - return false - end - end - end - end - - if schema.uniqueItems then - -- only works for scalars, would need to deep-compare for objects/arrays/tables - local values : { any : boolean } = {} - for _, v in pairs(data) do - if values[v] then - return false - end - values[v] = true - end - end - - local p = 0 - if schema.prefixItems ~= nil then - for i, s in ipairs(schema.prefixItems) do - if data[i] == nil then - break - elseif validate(s, data[i], root) then - p = i - else - return false - end - end - end - - if schema.items ~= nil then - for i = p+1, #data do - if not validate(schema.items, data[i], root) then - return false - end - end - end - - if schema.contains ~= nil then - local found = false - for i = 1, #data do - if validate(schema.contains, data[i], root) then - found = true - break - end - end - if not found then - return false - end - end - end - - return true; -end - - -json_schema_object.validate = validate; - -return json_schema_object; |