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 ) ;