Diff

tools/migration/migrator/jabberd14.lua @ 4417:33c149394dcb

migrator/jabberd14: Support for reading jabberd14 spool files.
author Waqas Hussain <waqas20@gmail.com>
date Sat, 19 Nov 2011 21:12:23 +0500
child 4445:0434eb77d18c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/migration/migrator/jabberd14.lua	Sat Nov 19 21:12:23 2011 +0500
@@ -0,0 +1,194 @@
+
+local lfs = require "lfs";
+local lxp = require "lxp";
+local st = require "util.stanza";
+local os_getenv = os.getenv;
+local io_open = io.open;
+local assert = assert;
+local ipairs = ipairs;
+local coroutine = coroutine;
+local print = print;
+
+module "jabberd14"
+
+local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end
+local function is_file(path) return lfs.attributes(path, "mode") == "file"; end
+local function clean_path(path)
+	return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~");
+end
+
+local parse_xml = (function()
+	local ns_prefixes = {
+		["http://www.w3.org/XML/1998/namespace"] = "xml";
+	};
+	local ns_separator = "\1";
+	local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+	return function(xml)
+		local handler = {};
+		local stanza = st.stanza("root");
+		function handler:StartElement(tagname, attr)
+			local curr_ns,name = tagname:match(ns_pattern);
+			if name == "" then
+				curr_ns, name = "", curr_ns;
+			end
+			if curr_ns ~= "" then
+				attr.xmlns = curr_ns;
+			end
+			for i=1,#attr do
+				local k = attr[i];
+				attr[i] = nil;
+				local ns, nm = k:match(ns_pattern);
+				if nm ~= "" then
+					ns = ns_prefixes[ns]; 
+					if ns then 
+						attr[ns..":"..nm] = attr[k];
+						attr[k] = nil;
+					end
+				end
+			end
+			stanza:tag(name, attr);
+		end
+		function handler:CharacterData(data)
+			stanza:text(data);
+		end
+		function handler:EndElement(tagname)
+			stanza:up();
+		end
+		local parser = lxp.new(handler, "\1");
+		local ok, err, line, col = parser:parse(xml);
+		if ok then ok, err, line, col = parser:parse(); end
+		--parser:close();
+		if ok then
+			return stanza.tags[1];
+		else
+			return ok, err.." (line "..line..", col "..col..")";
+		end
+	end;
+end)();
+
+local function load_xml(path)
+	if path then
+		local f, err = io_open(path);
+		if not f then return f, err; end
+		local data = f:read("*a");
+		f:close();
+		if data then
+			return parse_xml(data);
+		end
+	end
+end
+
+local function load_spool_file(host, filename, path)
+	local xml = load_xml(path);
+	if not xml then return; end
+
+	local register_element = xml:get_child("query", "jabber:iq:register");
+	local username_element = register_element and register_element:get_child("username", "jabber:iq:register");
+	local password_element = register_element and register_element:get_child("password", "jabber:iq:auth");
+	local username = username_element and username_element:get_text();
+	local password = password_element and password_element:get_text();
+	if not username then
+		print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename)
+		return;
+	elseif username..".xml" ~= filename then
+		print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename);
+		return;
+	end
+
+	local userdata = {
+		user = username;
+		host = host;
+		stores = {};
+	};
+	local stores = userdata.stores;
+	stores.accounts = { password = password };
+
+	for i=1,#xml.tags do
+		local tag = xml.tags[i];
+		local xname = (tag.attr.xmlns or "")..":"..tag.name;
+		if tag.attr.j_private_flag == "1" and tag.attr.xmlns then
+			-- Private XML
+			stores.private = stores.private or {};
+			tag.attr.j_private_flag = nil;
+			stores.private[tag.attr.xmlns] = st.preserialize(tag);
+		elseif xname == "jabber:iq:auth:password" then
+			if stores.accounts.password ~= tag:get_text() then
+				if password then
+					print("[warn] conflicting passwords")
+				else
+					stores.accounts.password = tag:get_text();
+				end
+			end
+		elseif xname == "jabber:iq:register:query" then
+			-- already processed
+		elseif xname == "jabber:xdb:nslist:foo" then
+			-- ignore
+		elseif xname == "jabber:iq:auth:0k:zerok" then
+			-- ignore
+		elseif xname == "jabber:iq:roster:query" then
+			-- Roster
+			local roster = {};
+			local subscription_types = { from = true, to = true, both = true, none = true };
+			for _,item_element in ipairs(tag.tags) do
+				assert(item_element.name == "item");
+				assert(item_element.attr.jid);
+				assert(subscription_types[item_element.attr.subscription]);
+				assert((item_element.attr.ask or "subscribe") == "subscribe")
+				if item_element.name == "item" then
+					local groups = {};
+					for _,group_element in ipairs(item_element.tags) do
+						assert(group_element.name == "group");
+						groups[group_element:get_text()] = true;
+					end
+					local item = {
+						name = item_element.attr.name;
+						subscription = item_element.attr.subscription;
+						ask = item_element.attr.ask;
+						groups = groups;
+					};
+					roster[item_element.attr.jid] = item;
+				end
+			end
+			stores.roster = roster;
+		elseif xname == "jabber:iq:last:query" then
+			-- Last activity
+		elseif xname == "jabber:x:offline:foo" then
+			-- Offline messages
+		elseif xname == "vcard-temp:vCard" then
+			-- vCards
+			stores.vcard = st.preserialize(tag);
+		else
+			print("[warn] Unknown tag: "..xname);
+		end
+	end
+	return userdata;
+end
+
+local function loop_over_users(path, host, cb)
+	for file in lfs.dir(path) do
+		if file:match("%.xml$") then
+			local user = load_spool_file(host, file, path.."/"..file);
+			if user then cb(user); end
+		end
+	end
+end
+local function loop_over_hosts(path, cb)
+	for host in lfs.dir(path) do
+		if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then
+			loop_over_users(path.."/"..host, host, cb);
+		end
+	end
+end
+
+function reader(input)
+	local path = clean_path(assert(input.path, "no input.path specified"));
+	assert(is_dir(path), "input.path is not a directory");
+
+	if input.host then
+		return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end);
+	else
+		return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end);
+	end
+end
+
+return _M;