Software /
code /
prosody
File
plugins/mod_storage_xep0227.lua @ 10813:4a9ff4f61796
mod_presence: Send unavailable presence in current thread run
`session:dispatch_stanza(pres)` enqueues processing of the stanza in the
sessions async thread, but becasue the entire stream close handling is
now in that thread it would process the presence after the stream and
session was completely closed, leading to weird errors "sent to a
resting session".
We call core_process_stanza() since this is what :dispatch_stanza calls
in the end.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 09 May 2020 00:28:10 +0200 |
parent | 8352:6ff50541d2a6 |
child | 11789:f3085620b6ff |
line wrap: on
line source
local ipairs, pairs = ipairs, pairs; local setmetatable = setmetatable; local tostring = tostring; local next = next; local t_remove = table.remove; local os_remove = os.remove; local io_open = io.open; local paths = require"util.paths"; local st = require "util.stanza"; local parse_xml_real = require "util.xml".parse; local function getXml(user, host) local jid = user.."@"..host; local path = paths.join(prosody.paths.data, jid..".xml"); local f = io_open(path); if not f then return; end local s = f:read("*a"); f:close(); return parse_xml_real(s); end local function setXml(user, host, xml) local jid = user.."@"..host; local path = paths.join(prosody.paths.data, jid..".xml"); local f, err = io_open(path, "w"); if not f then return f, err; end if xml then local s = tostring(xml); f:write(s); f:close(); return true; else f:close(); return os_remove(path); end end local function getUserElement(xml) if xml and xml.name == "server-data" then local host = xml.tags[1]; if host and host.name == "host" then local user = host.tags[1]; if user and user.name == "user" then return user; end end end end local function createOuterXml(user, host) return st.stanza("server-data", {xmlns='urn:xmpp:pie:0'}) :tag("host", {jid=host}) :tag("user", {name = user}); end local function removeFromArray(array, value) for i,item in ipairs(array) do if item == value then t_remove(array, i); return; end end end local function removeStanzaChild(s, child) removeFromArray(s.tags, child); removeFromArray(s, child); end local handlers = {}; -- In order to support mod_auth_internal_hashed local extended = "http://prosody.im/protocol/extended-xep0227\1"; handlers.accounts = { get = function(self, user) user = getUserElement(getXml(user, self.host)); if user and user.attr.password then return { password = user.attr.password }; elseif user then local data = {}; for k, v in pairs(user.attr) do if k:sub(1, #extended) == extended then data[k:sub(#extended+1)] = v; end end return data; end end; set = function(self, user, data) if data then local xml = getXml(user, self.host); if not xml then xml = createOuterXml(user, self.host); end local usere = getUserElement(xml); for k, v in pairs(data) do if k == "password" then usere.attr.password = v; else usere.attr[extended..k] = v; end end return setXml(user, self.host, xml); else return setXml(user, self.host, nil); end end; }; handlers.vcard = { get = function(self, user) user = getUserElement(getXml(user, self.host)); if user then local vcard = user:get_child("vCard", 'vcard-temp'); if vcard then return st.preserialize(vcard); end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local vcard = usere:get_child("vCard", 'vcard-temp'); if vcard then removeStanzaChild(usere, vcard); elseif not data then return true; end if data then vcard = st.deserialize(data); usere:add_child(vcard); end return setXml(user, self.host, xml); end return true; end; }; handlers.private = { get = function(self, user) user = getUserElement(getXml(user, self.host)); if user then local private = user:get_child("query", "jabber:iq:private"); if private then local r = {}; for _, tag in ipairs(private.tags) do r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag); end return r; end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local private = usere:get_child("query", 'jabber:iq:private'); if private then removeStanzaChild(usere, private); end if data and next(data) ~= nil then private = st.stanza("query", {xmlns='jabber:iq:private'}); for _,tag in pairs(data) do private:add_child(st.deserialize(tag)); end usere:add_child(private); end return setXml(user, self.host, xml); end return true; end; }; handlers.roster = { get = function(self, user) user = getUserElement(getXml(user, self.host)); if user then local roster = user:get_child("query", "jabber:iq:roster"); if roster then local r = { [false] = { version = roster.attr.version; pending = {}; } }; for item in roster:childtags("item") do r[item.attr.jid] = { jid = item.attr.jid, subscription = item.attr.subscription, ask = item.attr.ask, name = item.attr.name, groups = {}; }; for group in item:childtags("group") do r[item.attr.jid].groups[group:get_text()] = true; end for pending in user:childtags("presence", "jabber:client") do r[false].pending[pending.attr.from] = true; end end return r; end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local roster = usere:get_child("query", 'jabber:iq:roster'); if roster then removeStanzaChild(usere, roster); end usere:maptags(function (tag) if tag.attr.xmlns == "jabber:client" and tag.name == "presence" and tag.attr.type == "subscribe" then return nil; end return tag; end); if data and next(data) ~= nil then roster = st.stanza("query", {xmlns='jabber:iq:roster'}); usere:add_child(roster); for jid, item in pairs(data) do if jid then roster:tag("item", { jid = jid, subscription = item.subscription, ask = item.ask, name = item.name, }); for group in pairs(item.groups) do roster:tag("group"):text(group):up(); end roster:up(); -- move out from item else roster.attr.version = item.version; for pending_jid in pairs(item.pending) do usere:add_child(st.presence({ from = pending_jid, type = "subscribe" })); end end end end return setXml(user, self.host, xml); end return true; end; }; ----------------------------- local driver = {}; function driver:open(datastore, typ) -- luacheck: ignore 212/self if typ and typ ~= "keyval" then return nil, "unsupported-store"; end local handler = handlers[datastore]; if not handler then return nil, "unsupported-datastore"; end local instance = setmetatable({ host = module.host; datastore = datastore; }, { __index = handler }); if instance.init then instance:init(); end return instance; end module:provides("storage", driver);