Merge 0.7->trunk

Sun, 11 Apr 2010 01:30:31 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Sun, 11 Apr 2010 01:30:31 +0100
changeset 2973
61e103d99578
parent 2971
e21c546368bd (diff)
parent 2972
f9901175d535 (current diff)
child 2974
f37dc6f038f3
child 3011
1189a29cd846

Merge 0.7->trunk

--- a/core/loggingmanager.lua	Sun Apr 11 01:29:59 2010 +0100
+++ b/core/loggingmanager.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -33,7 +33,7 @@
 module "loggingmanager"
 
 -- The log config used if none specified in the config file
-local default_logging = { { to = "console" } };
+local default_logging = { { to = "console" , levels = { min = (debug_mode and "debug" ) } } };
 local default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
 local default_timestamp = "%b %d %H:%M:%S";
 -- The actual config loggingmanager is using
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_offline.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -0,0 +1,56 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local datamanager = require "util.datamanager";
+local st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
+
+module:add_feature("msgoffline");
+
+module:hook("message/offline/store", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local to = stanza.attr.to;
+	local node, host;
+	if to then
+		node, host = jid_split(to)
+	else
+		node, host = origin.username, origin.host;
+	end
+	
+	stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
+	local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+	stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+	
+	return true;
+end);
+
+module:hook("message/offline/broadcast", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+	
+	local data = datamanager.list_load(node, host, "offline");
+	if not data then return true; end
+	for _, stanza in ipairs(data) do
+		stanza = st.deserialize(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
+		stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
+		stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+		origin.send(stanza);
+	end
+	return true;
+end);
+
+module:hook("message/offline/delete", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+
+	return datamanager.list_store(node, host, "offline", nil);
+end);
--- a/plugins/muc/muc.lib.lua	Sun Apr 11 01:29:59 2010 +0100
+++ b/plugins/muc/muc.lib.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -122,9 +122,13 @@
 		local history = self._data['history'];
 		if not history then history = {}; self._data['history'] = history; end
 		stanza = st.clone(stanza);
-		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
+		stanza.attr.to = "";
+		local stamp = datetime.datetime();
+		local chars = #tostring(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
 		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-		t_insert(history, st.preserialize(stanza));
+		local entry = { stanza = stanza, stamp = stamp };
+		t_insert(history, entry);
 		while #history > history_length do t_remove(history, 1) end
 	end
 end
@@ -151,12 +155,46 @@
 		end
 	end
 end
-function room_mt:send_history(to)
+function room_mt:send_history(to, stanza)
 	local history = self._data['history']; -- send discussion history
 	if history then
-		for _, msg in ipairs(history) do
-			msg = st.deserialize(msg);
-			msg.attr.to=to;
+		local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
+		local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
+		
+		local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
+		if maxchars then maxchars = math.floor(maxchars); end
+		
+		local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
+		if not history_tag then maxstanzas = 20; end
+
+		local seconds = history_tag and tonumber(history_tag.attr.seconds);
+		if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
+
+		local since = history_tag and history_tag.attr.since;
+		if since and not since:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ$") then since = nil; end -- FIXME timezone support
+		if seconds and (not since or since < seconds) then since = seconds; end
+
+		local n = 0;
+		local charcount = 0;
+		local stanzacount = 0;
+		
+		for i=#history,1,-1 do
+			local entry = history[i];
+			if maxchars then
+				if not entry.chars then
+					entry.stanza.attr.to = "";
+					entry.chars = #tostring(entry.stanza);
+				end
+				charcount = charcount + entry.chars + #to;
+				if charcount > maxchars then break; end
+			end
+			if since and since > entry.stamp then break; end
+			if n + 1 > maxstanzas then break; end
+			n = n + 1;
+		end
+		for i=#history-n+1,#history do
+			local msg = history[i].stanza;
+			msg.attr.to = to;
 			self:_route_stanza(msg);
 		end
 	end
@@ -319,7 +357,7 @@
 								:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
 								:tag("status", {code='110'}));
 						end
-						self:send_history(from);
+						self:send_history(from, stanza);
 					else -- banned
 						local reply = st.error_reply(stanza, "auth", "forbidden"):up();
 						reply.tags[1].attr.code = "403";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/ejabberdstore.lib.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -0,0 +1,190 @@
+
+local handlers = {};
+
+handlers.accounts = {
+	get = function(self, user)
+		local select = self:query("select password from users where username=?", user);
+		local row = select and select:fetch();
+		if row then return { password = row[1] }; end
+	end;
+	set = function(self, user, data)
+		if data and data.password then
+			return self:modify("update users set password=? where username=?", data.password, user)
+				or self:modify("insert into users (username, password) values (?, ?)", user, data.password);
+		else
+			return self:modify("delete from users where username=?", user);
+		end
+	end;
+};
+handlers.vcard = {
+	get = function(self, user)
+		local select = self:query("select vcard from vcard where username=?", user);
+		local row = select and select:fetch();
+		if row then return parse_xml(row[1]); end
+	end;
+	set = function(self, user, data)
+		if data then
+			data = unparse_xml(data);
+			return self:modify("update vcard set vcard=? where username=?", data, user)
+				or self:modify("insert into vcard (username, vcard) values (?, ?)", user, data);
+		else
+			return self:modify("delete from vcard where username=?", user);
+		end
+	end;
+};
+handlers.private = {
+	get = function(self, user)
+		local select = self:query("select namespace,data from private_storage where username=?", user);
+		if select then
+			local data = {};
+			for row in select:rows() do
+				data[row[1]] = parse_xml(row[2]);
+			end
+			return data;
+		end
+	end;
+	set = function(self, user, data)
+		if data then
+			self:modify("delete from private_storage where username=?", user);
+			for namespace,text in pairs(data) do
+				self:modify("insert into private_storage (username, namespace, data) values (?, ?, ?)", user, namespace, unparse_xml(text));
+			end
+			return true;
+		else
+			return self:modify("delete from private_storage where username=?", user);
+		end
+	end;
+	-- TODO map_set, map_get
+};
+local subscription_map = { N = "none", B = "both", F = "from", T = "to" };
+local subscription_map_reverse = { none = "N", both = "B", from = "F", to = "T" };
+handlers.roster = {
+	get = function(self, user)
+		local select = self:query("select jid,nick,subscription,ask,server,subscribe,type from rosterusers where username=?", user);
+		if select then
+			local roster = { pending = {} };
+			for row in select:rows() do
+				local jid,nick,subscription,ask,server,subscribe,typ = unpack(row);
+				local item = { groups = {} };
+				if nick == "" then nick = nil; end
+				item.nick = nick;
+				item.subscription = subscription_map[subscription];
+				if ask == "N" then ask = nil;
+				elseif ask == "O" then ask = "subscribe"
+				elseif ask == "I" then roster.pending[jid] = true; ask = nil;
+				elseif ask == "B" then roster.pending[jid] = true; ask = "subscribe";
+				else module:log("debug", "bad roster_item.ask: %s", ask); ask = nil; end
+				item.ask = ask;
+				roster[jid] = item;
+			end
+			
+			select = self:query("select jid,grp from rostergroups where username=?", user);
+			if select then
+				for row in select:rows() do
+					local jid,grp = unpack(rows);
+					if roster[jid] then roster[jid].groups[grp] = true; end
+				end
+			end
+			select = self:query("select version from roster_version where username=?", user);
+			local row = select and select:fetch();
+			if row then
+				roster[false] = { version = row[1]; };
+			end
+			return roster;
+		end
+	end;
+	set = function(self, user, data)
+		if data and next(data) ~= nil then
+			self:modify("delete from rosterusers where username=?", user);
+			self:modify("delete from rostergroups where username=?", user);
+			self:modify("delete from roster_version where username=?", user);
+			local done = {};
+			local pending = data.pending or {};
+			for jid,item in pairs(data) do
+				if jid and jid ~= "pending" then
+					local subscription = subscription_map_reverse[item.subscription];
+					local ask;
+					if pending[jid] then
+						if item.ask then ask = "B"; else ask = "I"; end
+					else
+						if item.ask then ask = "O"; else ask = "N"; end
+					end
+					local r = self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?, '', '', '')", user, jid, item.nick or "", subscription, ask);
+					if not r then module:log("debug", "--- :( %s", tostring(r)); end
+					done[jid] = true;
+					for group in pairs(item.groups) do
+						self:modify("insert into rostergroups (username,jid,grp) values (?, ?, ?)", user, jid, group);
+					end
+				end
+			end
+			for jid in pairs(pending) do
+				if not done[jid] then
+					self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?. ''. ''. '')", user, jid, "", "N", "I");
+				end
+			end
+			local version = data[false] and data[false].version;
+			if version then
+				self:modify("insert into roster_version (username,version) values (?, ?)", user, version);
+			end
+			return true;
+		else
+			self:modify("delete from rosterusers where username=?", user);
+			self:modify("delete from rostergroups where username=?", user);
+			self:modify("delete from roster_version where username=?", user);
+		end
+	end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+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.database:prepare(sql);
+	if not r then error("Unable to prepare SQL statement: "..err); end
+	self.sqlcache[sql] = r;
+	return r;
+end
+
+function driver:query(sql, ...)
+	local stmt = self:prepare(sql);
+	if stmt:execute(...) then return stmt; end
+end
+function driver:modify(sql, ...)
+	local stmt = self:query(sql, ...);
+	if stmt and stmt:affected() > 0 then return stmt; end
+end
+
+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(dbtype, dbname, ...)
+	local instance = setmetatable({}, driver);
+	instance.__index = instance;
+	instance.database = get_database(dbtype, dbname, ...);
+	instance.ds_cache = {};
+	return instance;
+end
+
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/mod_storage.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -0,0 +1,83 @@
+
+module:set_global();
+
+local cache = { data = {} };
+function cache:get(key) return self.data[key]; end
+function cache:set(key, val) self.data[key] = val; return val; end
+
+local DBI = require "DBI";
+function get_database(driver, db, ...)
+	local uri = "dbi:"..driver..":"..db;
+	return cache:get(uri) or cache:set(uri, (function(...)
+		module:log("debug", "Opening database: %s", uri);
+		prosody.unlock_globals();
+		local dbh = assert(DBI.Connect(...));
+		prosody.lock_globals();
+		dbh:autocommit(true)
+		return dbh;
+	end)(driver, db, ...));
+end
+
+local st = require "util.stanza";
+local _parse_xml = module:require("xmlparse");
+parse_xml_real = _parse_xml;
+function parse_xml(str)
+	local s = _parse_xml(str);
+	if s and not s.gsub then
+		return st.preserialize(s);
+	end
+end
+function unparse_xml(s)
+	return tostring(st.deserialize(s));
+end
+
+local drivers = {};
+
+--local driver = module:require("sqlbasic").new("SQLite3", "hello.sqlite");
+local option_datastore = module:get_option("datastore");
+local option_datastore_params = module:get_option("datastore_params") or {};
+if option_datastore then
+	local driver = module:require(option_datastore).new(unpack(option_datastore_params));
+	table.insert(drivers, driver);
+end
+
+local datamanager = require "util.datamanager";
+local olddm = {};
+local dm = {};
+for key,val in pairs(datamanager) do olddm[key] = val; end
+
+do -- driver based on old datamanager
+	local dmd = {};
+	dmd.__index = dmd;
+	function dmd:open(host, datastore)
+		return setmetatable({ host = host, datastore = datastore }, dmd);
+	end
+	function dmd:get(user) return olddm.load(user, self.host, self.datastore); end
+	function dmd:set(user, data) return olddm.store(user, self.host, self.datastore, data); end
+	table.insert(drivers, dmd);
+end
+
+local function open(...)
+	for _,driver in pairs(drivers) do
+		local ds = driver:open(...);
+		if ds then return ds; end
+	end
+end
+
+local _data_path;
+--function dm.set_data_path(path) _data_path = path; end
+--function dm.add_callback(...) end
+--function dm.remove_callback(...) end
+--function dm.getpath(...) end
+function dm.load(username, host, datastore)
+	local x = open(host, datastore);
+	return x:get(username);
+end
+function dm.store(username, host, datastore, data)
+	return open(host, datastore):set(username, data);
+end
+--function dm.list_append(...) return driver:list_append(...); end
+--function dm.list_store(...) return driver:list_store(...); end
+--function dm.list_load(...) return driver:list_load(...); end
+
+for key,val in pairs(dm) do datamanager[key] = val; end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/sqlbasic.lib.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/xep227store.lib.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/xmlparse.lib.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -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;
--- a/prosody	Sun Apr 11 01:29:59 2010 +0100
+++ b/prosody	Sun Apr 11 01:30:31 2010 +0100
@@ -22,9 +22,6 @@
 	package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
 end
 
-package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
-package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
-
 -- Substitute ~ with path to home directory in data path
 if CFG_DATADIR then
 	if os.getenv("HOME") then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/xmppstream.lua	Sun Apr 11 01:30:31 2010 +0100
@@ -0,0 +1,150 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local lxp = require "lxp";
+local st = require "util.stanza";
+
+local tostring = tostring;
+local t_insert = table.insert;
+local t_concat = table.concat;
+
+local default_log = require "util.logger".init("xmlhandlers");
+
+local error = error;
+
+module "xmppstream"
+
+local new_parser = lxp.new;
+
+local ns_prefixes = {
+	["http://www.w3.org/XML/1998/namespace"] = "xml";
+};
+
+local xmlns_streams = "http://etherx.jabber.org/streams";
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+
+function new_sax_handlers(session, stream_callbacks)
+	local chardata = {};
+	local xml_handlers = {};
+	local log = session.log or default_log;
+	
+	local cb_streamopened = stream_callbacks.streamopened;
+	local cb_streamclosed = stream_callbacks.streamclosed;
+	local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
+	local cb_handlestanza = stream_callbacks.handlestanza;
+	
+	local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
+	local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
+	local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
+	
+	local stream_default_ns = stream_callbacks.default_ns;
+	
+	local stanza;
+	function xml_handlers:StartElement(tagname, attr)
+		if stanza and #chardata > 0 then
+			-- We have some character data in the buffer
+			stanza:text(t_concat(chardata));
+			chardata = {};
+		end
+		local curr_ns,name = tagname:match(ns_pattern);
+		if name == "" then
+			curr_ns, name = "", curr_ns;
+		end
+
+		if curr_ns ~= stream_default_ns then
+			attr.xmlns = curr_ns;
+		end
+		
+		-- FIXME !!!!!
+		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
+		
+		if not stanza then --if we are not currently inside a stanza
+			if session.notopen then
+				if tagname == stream_tag then
+					if cb_streamopened then
+						cb_streamopened(session, attr);
+					end
+				else
+					-- Garbage before stream?
+					cb_error(session, "no-stream");
+				end
+				return;
+			end
+			if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
+				cb_error(session, "invalid-top-level-element");
+			end
+			
+			stanza = st.stanza(name, attr);
+		else -- we are inside a stanza, so add a tag
+			attr.xmlns = nil;
+			if curr_ns ~= stream_default_ns then
+				attr.xmlns = curr_ns;
+			end
+			stanza:tag(name, attr);
+		end
+	end
+	function xml_handlers:CharacterData(data)
+		if stanza then
+			t_insert(chardata, data);
+		end
+	end
+	function xml_handlers:EndElement(tagname)
+		if stanza then
+			if #chardata > 0 then
+				-- We have some character data in the buffer
+				stanza:text(t_concat(chardata));
+				chardata = {};
+			end
+			-- Complete stanza
+			if #stanza.last_add == 0 then
+				if tagname ~= stream_error_tag then
+					cb_handlestanza(session, stanza);
+				else
+					cb_error(session, "stream-error", stanza);
+				end
+				stanza = nil;
+			else
+				stanza:up();
+			end
+		else
+			if tagname == stream_tag then
+				if cb_streamclosed then
+					cb_streamclosed(session);
+				end
+			else
+				local curr_ns,name = tagname:match(ns_pattern);
+				if name == "" then
+					curr_ns, name = "", curr_ns;
+				end
+				cb_error(session, "parse-error", "unexpected-element-close", name);
+			end
+			stanza, chardata = nil, {};
+		end
+	end
+	return xml_handlers;
+end
+
+function new(session, stream_callbacks)
+	return new_parser(new_sax_handlers(session, stream_callbacks), ns_separator);
+end
+
+return _M;

mercurial