xep227toprosody: Initial commit (thanks stefan).
authorWaqas Hussain <waqas20@gmail.com>
Wed, 27 Jan 2010 18:04:13 +0000 (23:04 +0500)
committerWaqas Hussain <waqas20@gmail.com>
Wed, 27 Jan 2010 18:04:13 +0000 (23:04 +0500)
tools/xep227toprosody.lua [new file with mode: 0644]

diff --git a/tools/xep227toprosody.lua b/tools/xep227toprosody.lua
new file mode 100644 (file)
index 0000000..d3c8924
--- /dev/null
@@ -0,0 +1,262 @@
+#!/usr/bin/env lua
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- Copyright (C) 2010      Stefan Gehn
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+-- FIXME: XEP-0227 supports XInclude but luaexpat does not
+--
+-- XEP-227 elements and their current level of support:
+-- Hosts : supported
+-- Users : supported
+-- Rosters : supported, needs testing
+-- Offline Messages : supported, needs testing
+-- Private XML Storage : supported, needs testing
+-- vCards : supported, needs testing
+-- Privacy Lists: UNSUPPORTED
+--   http://xmpp.org/extensions/xep-0227.html#privacy-lists
+--   mod_privacy uses dm.load(username, host, "privacy"); and stores stanzas 1:1
+-- Incoming Subscription Requests : supported
+
+package.path = package.path..";../?.lua";
+package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
+
+-- ugly workaround for getting datamanager to work outside of prosody :(
+prosody = { };
+prosody.platform = "unknown";
+if os.getenv("WINDIR") then
+       prosody.platform = "windows";
+elseif package.config:sub(1,1) == "/" then
+       prosody.platform = "posix";
+end
+
+local lxp = require "lxp";
+local st = require "util.stanza";
+local init_xmlhandlers = require "core.xmlhandlers";
+local dm = require "util.datamanager"
+dm.set_data_path("data");
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+local ns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
+
+-----------------------------------------------------------------------
+
+function store_vcard(username, host, stanza)
+       -- create or update vCard for username@host
+       local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza));
+       print("["..(err or "success").."] stored vCard: "..username.."@"..host);
+end
+
+function store_password(username, host, password)
+       -- create or update account for username@host
+       local ret, err = dm.store(username, host, "accounts", {password = password});
+       print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
+end
+
+function store_roster(username, host, roster_items)
+       -- fetch current roster-table for username@host if he already has one
+       local roster = dm.load(username, host, "roster") or {};
+       -- merge imported roster-items with loaded roster
+       for item_tag in roster_items:childtags() do
+               -- jid for this roster-item
+               local item_jid = item_tag.attr.jid
+               -- validate item stanzas
+               if (item_tag.name == "item") and (item_jid ~= "") then
+                       -- prepare roster item
+                       -- TODO: is the subscription attribute optional?
+                       local item = {subscription = item_tag.attr.subscription, groups = {}};
+                       -- optional: give roster item a real name
+                       if item_tag.attr.name then
+                               item.name = item_tag.attr.name;
+                       end
+                       -- optional: iterate over group stanzas inside item stanza
+                       for group_tag in item_tag:childtags() do
+                               local group_name = group_tag:get_text();
+                               if (group_tag.name == "group") and (group_name ~= "") then
+                                       item.groups[group_name] = true;
+                               else
+                                       print("[error] invalid group stanza: "..group_tag:pretty_print());
+                               end
+                       end
+                       -- store item in roster
+                       roster[item_jid] = item;
+                       print("[success] roster entry: " ..username.."@"..host.." - "..item_jid);
+               else
+                       print("[error] invalid roster stanza: " ..item_tag:pretty_print());
+               end
+
+       end
+       -- store merged roster-table
+       local ret, err = dm.store(username, host, "roster", roster);
+       print("["..(err or "success").."] stored roster: " ..username.."@"..host);
+end
+
+function store_private(username, host, private_items)
+       local private = dm.load(username, host, "private") or {};
+       for ch in private_items:childtags() do
+               --print("private :"..ch:pretty_print());
+               private[ch.name..":"..ch.attr.xmlns] = st.preserialize(ch);
+               print("[success] private item: " ..username.."@"..host.." - "..ch.name);
+       end
+       local ret, err = dm.store(username, host, "private", private);
+       print("["..(err or "success").."] stored private: " ..username.."@"..host);
+end
+
+function store_offline_messages(username, host, offline_messages)
+       -- TODO: maybe use list_load(), append and list_store() instead
+       --       of constantly reopening the file with list_append()?
+       for ch in offline_messages:childtags() do
+               --print("message :"..ch:pretty_print());
+               local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch));
+               print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from);
+       end
+end
+
+
+function store_subscription_request(username, host, presence_stanza)
+       local from_bare = presence_stanza.attr.from;
+
+       -- fetch current roster-table for username@host if he already has one
+       local roster = dm.load(username, host, "roster") or {};
+
+       local item = roster[from_bare];
+       if item and (item.subscription == "from" or item.subscription == "both") then
+               return; -- already subscribed, do nothing
+       end
+
+       -- add to table of pending subscriptions
+       if not roster.pending then roster.pending = {}; end
+       roster.pending[from_bare] = true;
+
+       -- store updated roster-table
+       local ret, err = dm.store(username, host, "roster", roster);
+       print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare);
+end
+
+-----------------------------------------------------------------------
+
+local curr_host = "";
+
+
+
+local cb = {
+       stream_tag = "user",
+       stream_ns = ns_xep227,
+       user_name = ""
+};
+function cb.streamopened(session, attr)
+       session.notopen = false;
+       user_name = attr.name;
+       store_password(user_name, curr_host, attr.password);
+end
+function cb.streamclosed(session)
+       session.notopen = true;
+       user_name = "";
+end
+function cb.handlestanza(session, stanza)
+       --print("Parsed stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
+       if (stanza.name == "vCard") and (stanza.attr.xmlns == "vcard-temp") then
+               store_vcard(user_name, curr_host, stanza);
+       elseif (stanza.name == "query") then
+               if (stanza.attr.xmlns == "jabber:iq:roster") then
+                       store_roster(user_name, curr_host, stanza);
+               elseif (stanza.attr.xmlns == "jabber:iq:private") then
+                       store_private(user_name, curr_host, stanza);
+               end
+       elseif (stanza.name == "offline-messages") then
+               store_offline_messages(user_name, curr_host, stanza);
+       elseif (stanza.name == "presence") and (stanza.attr.xmlns == "jabber:client") then
+               store_subscription_request(user_name, curr_host, stanza);
+       else
+               print("UNHANDLED stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
+       end
+end
+
+local user_handlers = init_xmlhandlers({ notopen = true, }, cb);
+
+-----------------------------------------------------------------------
+
+local lxp_handlers = {
+       --count = 0
+};
+
+-- TODO: error handling for invalid opening elements if curr_host is empty
+function lxp_handlers.StartElement(parser, elementname, attributes)
+       local curr_ns, name = elementname:match(ns_pattern);
+       if name == "" then
+               curr_ns, name = "", curr_ns;
+       end
+       --io.write("+ ", string.rep(" ", count), name, "  (", curr_ns, ")", "\n")
+       --count = count + 1;
+       if curr_host ~= "" then
+               -- forward to xmlhandlers
+               user_handlers:StartElement(elementname, attributes);
+       elseif (curr_ns == ns_xep227) and (name == "host") then
+               curr_host = attributes["jid"]; -- start of host element
+               print("Begin parsing host "..curr_host);
+       elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
+               io.stderr:write("Unhandled XML element: ", name, "\n");
+               os.exit(1);
+       end
+end
+
+-- TODO: error handling for invalid closing elements if host is empty
+function lxp_handlers.EndElement(parser, elementname)
+       local curr_ns, name = elementname:match(ns_pattern);
+       if name == "" then
+               curr_ns, name = "", curr_ns;
+       end
+       --count = count - 1;
+       --io.write("- ", string.rep(" ", count), name, "  (", curr_ns, ")", "\n")
+       if curr_host ~= "" then
+               if (curr_ns == ns_xep227) and (name == "host") then
+                       print("End parsing host "..curr_host);
+                       curr_host = "" -- end of host element
+               else
+                       -- forward to xmlhandlers
+                       user_handlers:EndElement(elementname, attributes);
+               end
+       elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
+               io.stderr:write("Unhandled XML element: ", name, "\n");
+               os.exit(1);
+       end
+end
+
+function lxp_handlers.CharacterData(parser, string)
+       if curr_host ~= "" then
+               -- forward to xmlhandlers
+               user_handlers:CharacterData(string);
+       end
+end
+
+-----------------------------------------------------------------------
+
+local arg = ...;
+local help = "/? -? ? /h -h /help -help --help";
+if not arg or help:find(arg, 1, true) then
+       print([[XEP-227 importer for Prosody
+
+  Usage: xep227toprosody.lua filename.xml
+
+]]);
+       os.exit(1);
+end
+
+local file = io.open(arg);
+if not file then
+       io.stderr:write("Could not open file: ", arg, "\n");
+       os.exit(0);
+end
+
+local parser = lxp.new(lxp_handlers, ns_separator);
+for l in file:lines() do
+       parser:parse(l);
+end
+parser:parse();
+parser:close();
+file:close();