mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
762 lines
22 KiB
Lua
762 lines
22 KiB
Lua
|
|
local ipairs, pairs = ipairs, pairs;
|
|
local setmetatable = setmetatable;
|
|
local tostring = tostring;
|
|
local next, unpack = next, table.unpack;
|
|
local os_remove = os.remove;
|
|
local io_open = io.open;
|
|
local jid_bare = require "prosody.util.jid".bare;
|
|
local jid_prep = require "prosody.util.jid".prep;
|
|
local jid_join = require "prosody.util.jid".join;
|
|
|
|
local array = require "prosody.util.array";
|
|
local base64 = require "prosody.util.encodings".base64;
|
|
local dt = require "prosody.util.datetime";
|
|
local hex = require "prosody.util.hex";
|
|
local it = require "prosody.util.iterators";
|
|
local paths = require"prosody.util.paths";
|
|
local set = require "prosody.util.set";
|
|
local st = require "prosody.util.stanza";
|
|
local parse_xml_real = require "prosody.util.xml".parse;
|
|
|
|
local lfs = require "lfs";
|
|
|
|
local function default_get_user_xml(self, user, host) --luacheck: ignore 212/self
|
|
local jid = jid_join(user, host);
|
|
local path = paths.join(prosody.paths.data, jid..".xml");
|
|
local f, err = io_open(path);
|
|
if not f then
|
|
module:log("debug", "Unable to load XML file for <%s>: %s", jid, err);
|
|
return;
|
|
end
|
|
module:log("debug", "Loaded %s", path);
|
|
local s = f:read("*a");
|
|
f:close();
|
|
return parse_xml_real(s);
|
|
end
|
|
local function default_set_user_xml(self, user, host, xml) --luacheck: ignore 212/self
|
|
local jid = jid_join(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
|
|
module:log("warn", "Unable to find user element in %s", xml and xml:top_tag() or "nothing");
|
|
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 hex_to_base64(s)
|
|
return base64.encode(hex.decode(s));
|
|
end
|
|
|
|
local function base64_to_hex(s)
|
|
return hex.encode(base64.decode(s));
|
|
end
|
|
|
|
local handlers = {};
|
|
|
|
-- In order to support custom account properties
|
|
local extended = "http://prosody.im/protocol/extended-xep0227\1";
|
|
|
|
local scram_hash_name = module:get_option_enum("password_hash", "SHA-1", "SHA-256");
|
|
local scram_properties = set.new({ "server_key", "stored_key", "iteration_count", "salt" });
|
|
|
|
handlers.accounts = {
|
|
get = function(self, user)
|
|
user = getUserElement(self:_get_user_xml(user, self.host));
|
|
local scram_credentials = user and user:get_child_with_attr(
|
|
"scram-credentials", "urn:xmpp:pie:0#scram",
|
|
"mechanism", "SCRAM-"..scram_hash_name
|
|
);
|
|
if scram_credentials then
|
|
return {
|
|
iteration_count = tonumber(scram_credentials:get_child_text("iter-count"));
|
|
server_key = base64_to_hex(scram_credentials:get_child_text("server-key"));
|
|
stored_key = base64_to_hex(scram_credentials:get_child_text("stored-key"));
|
|
salt = base64.decode(scram_credentials:get_child_text("salt"));
|
|
};
|
|
elseif 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 not data then
|
|
return self:_set_user_xml(user, self.host, nil);
|
|
end
|
|
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
if not xml then xml = createOuterXml(user, self.host); end
|
|
local usere = getUserElement(xml);
|
|
|
|
local account_properties = set.new(it.to_array(it.keys(data)));
|
|
|
|
-- Include SCRAM credentials if known
|
|
if account_properties:contains_set(scram_properties) then
|
|
local scram_el = st.stanza("scram-credentials", { xmlns = "urn:xmpp:pie:0#scram", mechanism = "SCRAM-"..scram_hash_name })
|
|
:text_tag("server-key", hex_to_base64(data.server_key))
|
|
:text_tag("stored-key", hex_to_base64(data.stored_key))
|
|
:text_tag("iter-count", ("%d"):format(data.iteration_count))
|
|
:text_tag("salt", base64.encode(data.salt));
|
|
usere:add_child(scram_el);
|
|
account_properties:exclude(scram_properties);
|
|
end
|
|
|
|
-- Include the password if present
|
|
if account_properties:contains("password") then
|
|
usere.attr.password = data.password;
|
|
account_properties:remove("password");
|
|
end
|
|
|
|
-- Preserve remaining properties as namespaced attributes
|
|
for property in account_properties do
|
|
usere.attr[extended..property] = data[property];
|
|
end
|
|
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end;
|
|
};
|
|
handlers.vcard = {
|
|
get = function(self, user)
|
|
user = getUserElement(self:_get_user_xml(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 = self:_get_user_xml(user, self.host);
|
|
local usere = xml and getUserElement(xml);
|
|
if usere then
|
|
usere:remove_children("vCard", "vcard-temp");
|
|
if not data or not data.attr then
|
|
-- No data to set, old one deleted, success
|
|
return true;
|
|
end
|
|
local vcard = st.deserialize(data);
|
|
usere:add_child(vcard);
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end
|
|
return true;
|
|
end;
|
|
};
|
|
handlers.private = {
|
|
get = function(self, user)
|
|
user = getUserElement(self:_get_user_xml(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 = self:_get_user_xml(user, self.host);
|
|
local usere = xml and getUserElement(xml);
|
|
if usere then
|
|
usere:remove_children("query", "jabber:iq:private");
|
|
if data and next(data) ~= nil then
|
|
local 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 self:_set_user_xml(user, self.host, xml);
|
|
end
|
|
return true;
|
|
end;
|
|
};
|
|
|
|
handlers.roster = {
|
|
get = function(self, user)
|
|
user = getUserElement(self:_get_user_xml(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 = self:_get_user_xml(user, self.host);
|
|
local usere = xml and getUserElement(xml);
|
|
if usere then
|
|
local user_jid = jid_join(usere.name, self.host);
|
|
usere:remove_children("query", "jabber:iq:roster");
|
|
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
|
|
local roster = st.stanza("query", {xmlns='jabber:iq:roster'});
|
|
usere:add_child(roster);
|
|
for contact_jid, item in pairs(data) do
|
|
if contact_jid ~= false then
|
|
contact_jid = jid_bare(jid_prep(contact_jid));
|
|
if contact_jid ~= user_jid then -- Skip self-contacts
|
|
roster:tag("item", {
|
|
jid = contact_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
|
|
end
|
|
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 self:_set_user_xml(user, self.host, xml);
|
|
end
|
|
return true;
|
|
end;
|
|
};
|
|
|
|
-- PEP node configuration/etc. (not items)
|
|
local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
|
|
local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
|
|
local lib_pubsub = module:require "pubsub";
|
|
handlers.pep = {
|
|
get = function (self, user)
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return nil;
|
|
end
|
|
local nodes = {
|
|
--[[
|
|
[node_name] = {
|
|
name = node_name;
|
|
config = {};
|
|
affiliations = {};
|
|
subscribers = {};
|
|
};
|
|
]]
|
|
};
|
|
local owner_el = user_el:get_child("pubsub", xmlns_pubsub_owner);
|
|
if not owner_el then
|
|
local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub);
|
|
if not pubsub_el then
|
|
return nil;
|
|
end
|
|
for node_el in pubsub_el:childtags("items") do
|
|
nodes[node_el.attr.node] = true; -- relies on COMPAT behavior in mod_pep
|
|
end
|
|
return nodes;
|
|
end
|
|
for node_el in owner_el:childtags() do
|
|
local node_name = node_el.attr.node;
|
|
local node = nodes[node_name];
|
|
if not node then
|
|
node = {
|
|
name = node_name;
|
|
config = {};
|
|
affiliations = {};
|
|
subscribers = {};
|
|
};
|
|
nodes[node_name] = node;
|
|
end
|
|
if node_el.name == "configure" then
|
|
local form = node_el:get_child("x", "jabber:x:data");
|
|
if form then
|
|
node.config = lib_pubsub.node_config_form:data(form);
|
|
end
|
|
elseif node_el.name == "affiliations" then
|
|
for affiliation_el in node_el:childtags("affiliation") do
|
|
local aff_jid = jid_prep(affiliation_el.attr.jid);
|
|
local aff_value = affiliation_el.attr.affiliation;
|
|
if aff_jid and aff_value then
|
|
node.affiliations[aff_jid] = aff_value;
|
|
end
|
|
end
|
|
elseif node_el.name == "subscriptions" then
|
|
for subscription_el in node_el:childtags("subscription") do
|
|
local sub_jid = jid_prep(subscription_el.attr.jid);
|
|
local sub_state = subscription_el.attr.subscription;
|
|
if sub_jid and sub_state == "subscribed" then
|
|
local options;
|
|
local subscription_options_el = subscription_el:get_child("options");
|
|
if subscription_options_el then
|
|
local options_form = subscription_options_el:get_child("x", "jabber:x:data");
|
|
if options_form then
|
|
options = lib_pubsub.subscription_options_form:data(options_form);
|
|
end
|
|
end
|
|
node.subscribers[sub_jid] = options or true;
|
|
end
|
|
end
|
|
else
|
|
module:log("warn", "Ignoring unknown pubsub element: %s", node_el.name);
|
|
end
|
|
end
|
|
return nodes;
|
|
end;
|
|
set = function(self, user, data)
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return true;
|
|
end
|
|
-- Remove existing data, if any
|
|
user_el:remove_children("pubsub", xmlns_pubsub_owner);
|
|
|
|
-- Generate new data
|
|
local owner_el = st.stanza("pubsub", { xmlns = xmlns_pubsub_owner });
|
|
|
|
for node_name, node_data in pairs(data) do
|
|
if node_data == true then
|
|
node_data = { config = {} };
|
|
end
|
|
local configure_el = st.stanza("configure", { node = node_name })
|
|
:add_child(lib_pubsub.node_config_form:form(node_data.config, "submit"));
|
|
owner_el:add_child(configure_el);
|
|
if node_data.affiliations and next(node_data.affiliations) ~= nil then
|
|
local affiliations_el = st.stanza("affiliations", { node = node_name });
|
|
for aff_jid, aff_value in pairs(node_data.affiliations) do
|
|
affiliations_el:tag("affiliation", { jid = aff_jid, affiliation = aff_value }):up();
|
|
end
|
|
owner_el:add_child(affiliations_el);
|
|
end
|
|
if node_data.subscribers and next(node_data.subscribers) ~= nil then
|
|
local subscriptions_el = st.stanza("subscriptions", { node = node_name });
|
|
for sub_jid, sub_data in pairs(node_data.subscribers) do
|
|
local sub_el = st.stanza("subscription", { jid = sub_jid, subscribed = "subscribed" });
|
|
if sub_data ~= true then
|
|
local options_form = lib_pubsub.subscription_options_form:form(sub_data, "submit");
|
|
sub_el:tag("options"):add_child(options_form):up();
|
|
end
|
|
subscriptions_el:add_child(sub_el);
|
|
end
|
|
owner_el:add_child(subscriptions_el);
|
|
end
|
|
end
|
|
|
|
user_el:add_child(owner_el);
|
|
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end;
|
|
};
|
|
|
|
-- PEP items
|
|
handlers.pep_ = {
|
|
_stores = function (self, xml) --luacheck: ignore 212/self
|
|
local store_names = set.new();
|
|
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return store_names;
|
|
end
|
|
|
|
-- Locate existing pubsub element, if any
|
|
local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub);
|
|
if not pubsub_el then
|
|
return store_names;
|
|
end
|
|
|
|
-- Find node items element, if any
|
|
for items_el in pubsub_el:childtags("items") do
|
|
store_names:add("pep_"..items_el.attr.node);
|
|
end
|
|
return store_names;
|
|
end;
|
|
find = function (self, user, query)
|
|
-- query keys: limit, reverse, key (id)
|
|
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return nil, "no 227 user element found";
|
|
end
|
|
|
|
local node_name = self.datastore:match("^pep_(.+)$");
|
|
|
|
-- Locate existing pubsub element, if any
|
|
local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub);
|
|
if not pubsub_el then
|
|
return nil;
|
|
end
|
|
|
|
-- Find node items element, if any
|
|
local node_items_el;
|
|
for items_el in pubsub_el:childtags("items") do
|
|
if items_el.attr.node == node_name then
|
|
node_items_el = items_el;
|
|
break;
|
|
end
|
|
end
|
|
|
|
if not node_items_el then
|
|
return nil;
|
|
end
|
|
|
|
local user_jid = user.."@"..self.host;
|
|
|
|
local results = {};
|
|
for item_el in node_items_el:childtags("item") do
|
|
if query and query.key then
|
|
if item_el.attr.id == query.key then
|
|
table.insert(results, { item_el.attr.id, item_el.tags[1], 0, user_jid });
|
|
break;
|
|
end
|
|
else
|
|
table.insert(results, { item_el.attr.id, item_el.tags[1], 0, user_jid });
|
|
end
|
|
if query and query.limit and #results >= query.limit then
|
|
break;
|
|
end
|
|
end
|
|
if query and query.reverse then
|
|
return array.reverse(results);
|
|
end
|
|
local i = 0;
|
|
return function ()
|
|
i = i + 1;
|
|
local v = results[i];
|
|
if v == nil then return nil; end
|
|
return unpack(v, 1, 4);
|
|
end;
|
|
end;
|
|
append = function (self, user, key, payload, when, with) --luacheck: ignore 212/when 212/with 212/key
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return true;
|
|
end
|
|
|
|
local node_name = self.datastore:match("^pep_(.+)$");
|
|
|
|
-- Locate existing pubsub element, if any
|
|
local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub);
|
|
if not pubsub_el then
|
|
pubsub_el = st.stanza("pubsub", { xmlns = xmlns_pubsub });
|
|
user_el:add_child(pubsub_el);
|
|
end
|
|
|
|
-- Find node items element, if any
|
|
local node_items_el;
|
|
for items_el in pubsub_el:childtags("items") do
|
|
if items_el.attr.node == node_name then
|
|
node_items_el = items_el;
|
|
break;
|
|
end
|
|
end
|
|
|
|
if not node_items_el then
|
|
-- Doesn't exist yet, create one
|
|
node_items_el = st.stanza("items", { node = node_name });
|
|
pubsub_el:add_child(node_items_el);
|
|
end
|
|
|
|
-- Append item to pubsub_el
|
|
local item_el = st.stanza("item", { id = key })
|
|
:add_child(payload);
|
|
node_items_el:add_child(item_el);
|
|
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end;
|
|
delete = function (self, user, query)
|
|
-- query keys: limit, reverse, key (id)
|
|
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return nil, "no 227 user element found";
|
|
end
|
|
|
|
local node_name = self.datastore:match("^pep_(.+)$");
|
|
|
|
-- Locate existing pubsub element, if any
|
|
local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub);
|
|
if not pubsub_el then
|
|
return nil;
|
|
end
|
|
|
|
-- Find node items element, if any
|
|
local node_items_el;
|
|
for items_el in pubsub_el:childtags("items") do
|
|
if items_el.attr.node == node_name then
|
|
node_items_el = items_el;
|
|
break;
|
|
end
|
|
end
|
|
|
|
if not node_items_el then
|
|
return nil;
|
|
end
|
|
|
|
local results = array();
|
|
for item_el in pubsub_el:childtags("item") do
|
|
if query and query.key then
|
|
if item_el.attr.id == query.key then
|
|
table.insert(results, item_el);
|
|
break;
|
|
end
|
|
else
|
|
table.insert(results, item_el);
|
|
end
|
|
if query and query.limit and #results >= query.limit then
|
|
break;
|
|
end
|
|
end
|
|
if query and query.truncate then
|
|
results:sub(-query.truncate);
|
|
end
|
|
|
|
-- Actually remove the matching items
|
|
local delete_keys = set.new(results:map(function (item) return item.attr.id; end));
|
|
pubsub_el:maptags(function (item_el)
|
|
if delete_keys:contains(item_el.attr.id) then
|
|
return nil;
|
|
end
|
|
return item_el;
|
|
end);
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end;
|
|
};
|
|
|
|
-- MAM archives
|
|
local xmlns_pie_mam = "urn:xmpp:pie:0#mam";
|
|
handlers.archive = {
|
|
find = function (self, user, query)
|
|
assert(query == nil, "XEP-0313 queries are not supported on XEP-0227 files");
|
|
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return nil, "no 227 user element found";
|
|
end
|
|
|
|
-- Locate existing archive element, if any
|
|
local archive_el = user_el:get_child("archive", xmlns_pie_mam);
|
|
if not archive_el then
|
|
return nil;
|
|
end
|
|
|
|
local user_jid = user.."@"..self.host;
|
|
|
|
|
|
local f, s, result_el = archive_el:childtags("result", "urn:xmpp:mam:2");
|
|
return function ()
|
|
result_el = f(s, result_el);
|
|
if not result_el then return nil; end
|
|
|
|
local id = result_el.attr.id;
|
|
local item = result_el:find("{urn:xmpp:forward:0}forwarded/{jabber:client}message");
|
|
assert(item, "Invalid stanza in XEP-0227 archive");
|
|
local when = dt.parse(result_el:find("{urn:xmpp:forward:0}forwarded/{urn:xmpp:delay}delay@stamp"));
|
|
local to_bare, from_bare = jid_bare(item.attr.to), jid_bare(item.attr.from);
|
|
local with = to_bare == user_jid and from_bare or to_bare;
|
|
-- id, item, when, with
|
|
return id, item, when, with;
|
|
end;
|
|
end;
|
|
append = function (self, user, key, payload, when, with) --luacheck: ignore 212/when 212/with 212/key
|
|
local xml = self:_get_user_xml(user, self.host);
|
|
local user_el = xml and getUserElement(xml);
|
|
if not user_el then
|
|
return true;
|
|
end
|
|
|
|
-- Locate existing archive element, if any
|
|
local archive_el = user_el:get_child("archive", xmlns_pie_mam);
|
|
if not archive_el then
|
|
archive_el = st.stanza("archive", { xmlns = xmlns_pie_mam });
|
|
user_el:add_child(archive_el);
|
|
end
|
|
|
|
local item = st.clone(payload);
|
|
item.attr.xmlns = "jabber:client";
|
|
|
|
local result_el = st.stanza("result", { xmlns = "urn:xmpp:mam:2", id = key })
|
|
:tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
|
|
:tag("delay", { xmlns = "urn:xmpp:delay", stamp = dt.datetime(when) }):up()
|
|
:add_child(item)
|
|
:up();
|
|
|
|
-- Append item to archive_el
|
|
archive_el:add_child(result_el);
|
|
|
|
return self:_set_user_xml(user, self.host, xml);
|
|
end;
|
|
};
|
|
|
|
-----------------------------
|
|
local driver = {};
|
|
|
|
local function users(self)
|
|
local file_patt = "^.*@"..(self.host:gsub("%p", "%%%1")).."%.xml$";
|
|
|
|
local f, s, filename = lfs.dir(prosody.paths.data);
|
|
|
|
return function ()
|
|
filename = f(s, filename);
|
|
while filename and not filename:match(file_patt) do
|
|
filename = f(s, filename);
|
|
end
|
|
if not filename then return nil; end
|
|
return filename:match("^[^@]+");
|
|
end;
|
|
end
|
|
|
|
function driver:open(datastore, typ) -- luacheck: ignore 212/self
|
|
if typ and typ ~= "keyval" and typ ~= "archive" then return nil, "unsupported-store"; end
|
|
local handler = handlers[datastore];
|
|
if not handler and datastore:match("^pep_") then
|
|
handler = handlers.pep_;
|
|
end
|
|
if not handler then return nil, "unsupported-datastore"; end
|
|
local instance = setmetatable({
|
|
host = module.host;
|
|
datastore = datastore;
|
|
users = users;
|
|
_get_user_xml = assert(default_get_user_xml);
|
|
_set_user_xml = default_set_user_xml;
|
|
}, {
|
|
__index = handler;
|
|
}
|
|
);
|
|
if instance.init then instance:init(); end
|
|
return instance;
|
|
end
|
|
|
|
-- Custom API that allows some configuration
|
|
function driver:open_xep0227(datastore, typ, options)
|
|
local instance, err = self:open(datastore, typ);
|
|
if not instance then
|
|
return instance, err;
|
|
end
|
|
if options then
|
|
instance._set_user_xml = assert(options.set_user_xml);
|
|
instance._get_user_xml = assert(options.get_user_xml);
|
|
end
|
|
return instance;
|
|
end
|
|
|
|
local function get_store_names_from_xml(self, user_xml)
|
|
local stores = set.new();
|
|
for handler_name, handler_funcs in pairs(handlers) do
|
|
if handler_funcs._stores then
|
|
stores:include(handler_funcs._stores(self, user_xml));
|
|
else
|
|
stores:add(handler_name);
|
|
end
|
|
end
|
|
return stores;
|
|
end
|
|
|
|
local function get_store_names(self, path)
|
|
local stores = set.new();
|
|
local f, err = io_open(paths.join(prosody.paths.data, path));
|
|
if not f then
|
|
module:log("warn", "Unable to load XML file for <%s>: %s", "store listing", err);
|
|
return stores;
|
|
end
|
|
module:log("info", "Loaded %s", path);
|
|
local s = f:read("*a");
|
|
f:close();
|
|
local user_xml = parse_xml_real(s);
|
|
return get_store_names_from_xml(self, user_xml);
|
|
end
|
|
|
|
function driver:stores(username)
|
|
local store_dir = prosody.paths.data;
|
|
|
|
local mode, err = lfs.attributes(store_dir, "mode");
|
|
if not mode then
|
|
return function() module:log("debug", "Could not iterate over stores in %s: %s", store_dir, err); end
|
|
end
|
|
|
|
local file_patt = "^.*@"..(module.host:gsub("%p", "%%%1")).."%.xml$";
|
|
|
|
local all_users = username == true;
|
|
|
|
local store_names = set.new();
|
|
|
|
for filename in lfs.dir(prosody.paths.data) do
|
|
if filename:match(file_patt) then
|
|
if all_users or filename == username.."@"..module.host..".xml" then
|
|
store_names:include(get_store_names(self, filename));
|
|
if not all_users then break; end
|
|
end
|
|
end
|
|
end
|
|
|
|
return store_names:items();
|
|
end
|
|
|
|
function driver:xep0227_user_stores(username, host)
|
|
local user_xml = self:_get_user_xml(username, host);
|
|
if not user_xml then
|
|
return nil;
|
|
end
|
|
local store_names = get_store_names_from_xml(username, host);
|
|
return store_names:items();
|
|
end
|
|
|
|
module:provides("storage", driver);
|