diff options
Diffstat (limited to 'plugins/storage')
-rw-r--r-- | plugins/storage/mod_xep0227.lua | 163 | ||||
-rw-r--r-- | plugins/storage/sqlbasic.lib.lua | 97 | ||||
-rw-r--r-- | plugins/storage/xep227store.lib.lua | 168 | ||||
-rw-r--r-- | plugins/storage/xmlparse.lib.lua | 56 |
4 files changed, 484 insertions, 0 deletions
diff --git a/plugins/storage/mod_xep0227.lua b/plugins/storage/mod_xep0227.lua new file mode 100644 index 00000000..b6d2e627 --- /dev/null +++ b/plugins/storage/mod_xep0227.lua @@ -0,0 +1,163 @@ + +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 st = require "util.stanza"; +local parse_xml_real = module:require("xmlparse"); + +local function getXml(user, host) + local jid = user.."@"..host; + local path = "data/"..jid..".xml"; + local f = io_open(path); + if not f then return; end + local s = f:read("*a"); + return parse_xml_real(s); +end +local function setXml(user, host, xml) + local jid = user.."@"..host; + local path = "data/"..jid..".xml"; + if xml then + local f = io_open(path, "w"); + if not f then return; end + local s = tostring(xml); + f:write(s); + f:close(); + return true; + else + 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='http://www.xmpp.org/extensions/xep-0227.html#ns'}) + :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 = {}; + +handlers.accounts = { + get = function(self, user) + local user = getUserElement(getXml(user, self.host)); + if user and user.attr.password then + return { password = user.attr.password }; + end + end; + set = function(self, user, data) + if data and data.password then + local xml = getXml(user, self.host); + if not xml then xml = createOuterXml(user, self.host); end + local usere = getUserElement(xml); + usere.attr.password = data.password; + return setXml(user, self.host, xml); + else + return setXml(user, self.host, nil); + end + end; +}; +handlers.vcard = { + get = function(self, user) + local 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) + local 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; +}; + +----------------------------- +local driver = {}; + +function driver:open(host, datastore, typ) + local instance = setmetatable({}, self); + instance.host = host; + instance.datastore = datastore; + local handler = handlers[datastore]; + if not handler then return nil; end + for key,val in pairs(handler) do + instance[key] = val; + end + if instance.init then instance:init(); end + return instance; +end + +module:add_item("data-driver", driver); diff --git a/plugins/storage/sqlbasic.lib.lua b/plugins/storage/sqlbasic.lib.lua new file mode 100644 index 00000000..f1202287 --- /dev/null +++ b/plugins/storage/sqlbasic.lib.lua @@ -0,0 +1,97 @@ + +-- Basic SQL driver +-- This driver stores data as simple key-values + +local ser = require "util.serialization".serialize; +local deser = function(data) + module:log("debug", "deser: %s", tostring(data)); + if not data then return nil; end + local f = loadstring("return "..data); + if not f then return nil; end + setfenv(f, {}); + local s, d = pcall(f); + if not s then return nil; end + return d; +end; + +local driver = {}; +driver.__index = driver; + +driver.item_table = "item"; +driver.list_table = "list"; + +function driver:prepare(sql) + module:log("debug", "query: %s", sql); + local err; + if not self.sqlcache then self.sqlcache = {}; end + local r = self.sqlcache[sql]; + if r then return r; end + r, err = self.connection:prepare(sql); + if not r then error("Unable to prepare SQL statement: "..err); end + self.sqlcache[sql] = r; + return r; +end + +function driver:load(username, host, datastore) + local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?"); + select:execute(username, host, datastore); + local row = select:fetch(); + return row and deser(row[1]) or nil; +end + +function driver:store(username, host, datastore, data) + if not data or next(data) == nil then + local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?"); + delete:execute(username, host, datastore); + return true; + else + local d = self:load(username, host, datastore); + if d then -- update + local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?"); + return update:execute(ser(data), username, host, datastore); + else -- insert + local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)"); + return insert:execute(username, host, datastore, ser(data)); + end + end +end + +function driver:list_append(username, host, datastore, data) + if not data then return; end + local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)"); + return insert:execute(username, host, datastore, ser(data)); +end + +function driver:list_store(username, host, datastore, data) + -- remove existing data + local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?"); + delete:execute(username, host, datastore); + if data and next(data) ~= nil then + -- add data + for _, d in ipairs(data) do + self:list_append(username, host, datastore, ser(d)); + end + end + return true; +end + +function driver:list_load(username, host, datastore) + local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?"); + select:execute(username, host, datastore); + local r = {}; + for row in select:rows() do + table.insert(r, deser(row[1])); + end + return r; +end + +local _M = {}; +function _M.new(dbtype, dbname, ...) + local d = {}; + setmetatable(d, driver); + local dbh = get_database(dbtype, dbname, ...); + --d:set_connection(dbh); + d.connection = dbh; + return d; +end +return _M; diff --git a/plugins/storage/xep227store.lib.lua b/plugins/storage/xep227store.lib.lua new file mode 100644 index 00000000..5ef8df54 --- /dev/null +++ b/plugins/storage/xep227store.lib.lua @@ -0,0 +1,168 @@ +
+local st = require "util.stanza";
+
+local function getXml(user, host)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ local f = io.open(path);
+ if not f then return; end
+ local s = f:read("*a");
+ return parse_xml_real(s);
+end
+local function setXml(user, host, xml)
+ local jid = user.."@"..host;
+ local path = "data/"..jid..".xml";
+ if xml then
+ local f = io.open(path, "w");
+ if not f then return; end
+ local s = tostring(xml);
+ f:write(s);
+ f:close();
+ return true;
+ else
+ 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='http://www.xmpp.org/extensions/xep-0227.html#ns'})
+ :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
+ table.remove(array, i);
+ return;
+ end
+ end
+end
+local function removeStanzaChild(s, child)
+ removeFromArray(s.tags, child);
+ removeFromArray(s, child);
+end
+
+local handlers = {};
+
+handlers.accounts = {
+ get = function(self, user)
+ local user = getUserElement(getXml(user, self.host));
+ if user and user.attr.password then
+ return { password = user.attr.password };
+ end
+ end;
+ set = function(self, user, data)
+ if data and data.password then
+ local xml = getXml(user, self.host);
+ if not xml then xml = createOuterXml(user, self.host); end
+ local usere = getUserElement(xml);
+ usere.attr.password = data.password;
+ return setXml(user, self.host, xml);
+ else
+ return setXml(user, self.host, nil);
+ end
+ end;
+};
+handlers.vcard = {
+ get = function(self, user)
+ local 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)
+ local 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;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:open(host, datastore, typ)
+ local cache_key = host.." "..datastore;
+ if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+ local instance = setmetatable({}, self);
+ instance.host = host;
+ instance.datastore = datastore;
+ local handler = handlers[datastore];
+ if not handler then return nil; end
+ for key,val in pairs(handler) do
+ instance[key] = val;
+ end
+ if instance.init then instance:init(); end
+ self.ds_cache[cache_key] = instance;
+ return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new()
+ local instance = setmetatable({}, driver);
+ instance.__index = instance;
+ instance.ds_cache = {};
+ return instance;
+end
+
+return _M;
diff --git a/plugins/storage/xmlparse.lib.lua b/plugins/storage/xmlparse.lib.lua new file mode 100644 index 00000000..91063995 --- /dev/null +++ b/plugins/storage/xmlparse.lib.lua @@ -0,0 +1,56 @@ +
+local st = require "util.stanza";
+
+-- XML parser
+local parse_xml = (function()
+ local entity_map = setmetatable({
+ ["amp"] = "&";
+ ["gt"] = ">";
+ ["lt"] = "<";
+ ["apos"] = "'";
+ ["quot"] = "\"";
+ }, {__index = function(_, s)
+ if s:sub(1,1) == "#" then
+ if s:sub(2,2) == "x" then
+ return string.char(tonumber(s:sub(3), 16));
+ else
+ return string.char(tonumber(s:sub(2)));
+ end
+ end
+ end
+ });
+ local function xml_unescape(str)
+ return (str:gsub("&(.-);", entity_map));
+ end
+ local function parse_tag(s)
+ local name,sattr=(s):gmatch("([^%s]+)(.*)")();
+ local attr = {};
+ for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end
+ return name, attr;
+ end
+ return function(xml)
+ local stanza = st.stanza("root");
+ local regexp = "<([^>]*)>([^<]*)";
+ for elem, text in xml:gmatch(regexp) do
+ if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
+ elseif elem:sub(1,1) == "/" then -- end tag
+ elem = elem:sub(2);
+ stanza:up(); -- TODO check for start-end tag name match
+ elseif elem:sub(-1,-1) == "/" then -- empty tag
+ elem = elem:sub(1,-2);
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr):up();
+ else -- start tag
+ local name,attr = parse_tag(elem);
+ stanza:tag(name, attr);
+ end
+ if #text ~= 0 then -- text
+ stanza:text(xml_unescape(text));
+ end
+ end
+ return stanza.tags[1];
+ end
+end)();
+-- end of XML parser
+
+return parse_xml;
|