Changeset

881:c508fb40552a

Automated merge with http://waqas.ath.cx:8000/
author Matthew Wild <mwild1@gmail.com>
date Wed, 04 Mar 2009 18:48:29 +0000
parents 880:ff4a08d73772 (diff) 873:e7ecde5d50e3 (current diff)
children 882:e362bafbbb68 888:1059230969c3
files
diffstat 4 files changed, 316 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/core/modulemanager.lua	Wed Mar 04 18:24:43 2009 +0000
+++ b/core/modulemanager.lua	Wed Mar 04 18:48:29 2009 +0000
@@ -212,6 +212,7 @@
 		end
 	end
 	local handlers = stanza_handlers:get(host, origin_type, name, xmlns);
+	if not handlers then handlers = stanza_handlers:get("*", origin_type, name, xmlns); end
 	if handlers then
 		log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
 		(handlers[1])(origin, stanza);
@@ -299,6 +300,14 @@
 				end
 			end
 		end
+		for module, features in pairs(features_table:get("*") or NULL) do -- for each module
+			for feature in pairs(features) do
+				if not done[feature] then
+					reply:tag("feature", {var = feature}):up(); -- TODO cache
+					done[feature] = true;
+				end
+			end
+		end
 		return next(done) ~= nil;
 	end
 end);
--- a/plugins/mod_muc.lua	Wed Mar 04 18:24:43 2009 +0000
+++ b/plugins/mod_muc.lua	Wed Mar 04 18:48:29 2009 +0000
@@ -71,6 +71,30 @@
 function get_filtered_presence(stanza)
 	return filter_xmlns_from_stanza(st.clone(stanza), presence_filters);
 end
+local kickable_error_conditions = {
+	["gone"] = true;
+	["internal-server-error"] = true;
+	["item-not-found"] = true;
+	["jid-malformed"] = true;
+	["recipient-unavailable"] = true;
+	["redirect"] = true;
+	["remote-server-not-found"] = true;
+	["remote-server-timeout"] = true;
+	["service-unavailable"] = true;
+};
+function get_kickable_error(stanza)
+	for _, tag in ipairs(stanza.tags) do
+		if tag.name == "error" and tag.attr.xmlns == "jabber:client" then
+			for _, cond in ipairs(tag.tags) do
+				if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
+					return kickable_error_conditions[cond.name] and cond.name;
+				end
+			end
+			return true; -- malformed error message
+		end
+	end
+	return true; -- malformed error message
+end
 function getUsingPath(stanza, path, getText)
 	local tag = stanza;
 	for _, name in ipairs(path) do
@@ -291,7 +315,7 @@
 		origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
 	elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
 		origin.send(st.error_reply(stanza, "modify", "bad-request"));
-	elseif stanza.name == "message" and type == "error" then
+	elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then
 		log("debug", "%s kicked from %s for sending an error message", current_nick, room);
 		handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable
 	else -- private stanza
@@ -302,7 +326,7 @@
 			if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then jid = jid_bare(jid); end
 			stanza.attr.to, stanza.attr.from = jid, current_nick;
 			core_route_stanza(component, stanza);
-		else -- recipient not in room
+		elseif type ~= "error" and type ~= "result" then -- recipient not in room
 			origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
 		end
 	end
@@ -342,7 +366,7 @@
 			stanza.attr.to = current_nick;
 			handle_to_occupant(origin, stanza);
 			stanza.attr.to = to;
-		else
+		elseif type ~= "error" and type ~= "result" then
 			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 		end
 	else
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_xmlrpc.lua	Wed Mar 04 18:48:29 2009 +0000
@@ -0,0 +1,117 @@
+-- Prosody IM v0.3
+-- 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.
+--
+
+
+module.host = "*" -- Global module
+
+local httpserver = require "net.httpserver";
+local st = require "util.stanza";
+local pcall = pcall;
+local unpack = unpack;
+local tostring = tostring;
+
+local translate_request = require "util.xmlrpc".translate_request;
+local create_response = require "util.xmlrpc".create_response;
+local create_error_response = require "util.xmlrpc".create_error_response;
+
+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_xml(xml)
+	local stanza = st.stanza("root");
+	local regexp = "<([^>]*)>([^<]*)";
+	for elem, text in xml:gmatch(regexp) do
+		--print("[<"..elem..">|"..text.."]");
+		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);
+			stanza:tag(elem):up();
+		else -- start tag
+			stanza:tag(elem);
+		end
+		if #text ~= 0 then -- text
+			stanza:text(xml_unescape(text));
+		end
+	end
+	return stanza.tags[1];
+end
+
+local function get_method(method)
+	return function(...)
+		return {method = method; args = {...}};
+	end
+end
+
+local function handle_xmlrpc_request(method, args)
+	method = get_method(method);
+	if not method then return create_error_response(404, "method not found"); end
+	args = args or {};
+	local success, result = pcall(method, unpack(args));
+	if success then
+		success, result = pcall(create_response, result or "nil");
+		if success then
+			return result;
+		end
+		return create_error_response(500, "Error in creating response: "..result);
+	end
+	return create_error_response(0, result or "nil");
+end
+
+local function handle_xmpp_request(origin, stanza)
+	local query = stanza.tags[1];
+	if query.name == "query" then
+		if #query.tags == 1 then
+			local success, method, args = pcall(translate_request, query.tags[1]);
+			if success then
+				local result = handle_xmlrpc_request(method, args);
+				origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
+			else
+				origin.send(st.error_reply(stanza, "modify", "bad-request", method));
+			end
+		else
+			origin.send(st.error_reply(stanza, "modify", "bad-request", "No content in XML-RPC request"));
+		end
+	else
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+	end
+end
+module:add_iq_handler({"c2s", "s2sin"}, "jabber:iq:rpc", handle_xmpp_request);
+module:add_feature("jabber:iq:rpc");
+
+local default_headers = { ["Content-Type"] = "text/xml" };
+local function handle_http_request(method, body, request)
+	local stanza = body and parse_xml(body);
+	if (not stanza) or request.method ~= "POST" then
+		return "<html><body>You really don't look like an XML-RPC client to me... what do you want?</body></html>";
+	end
+	local success, method, args = pcall(translate_request, stanza);
+	if success then
+		return { headers = default_headers; body = tostring(handle_xmlrpc_request(method, args)) };
+	end
+	return "<html><body>Error parsing XML-RPC request: "..tostring(method).."</body></html>";
+end
+httpserver.new{ port = 9000, base = "xmlrpc", handler = handle_http_request }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/xmlrpc.lua	Wed Mar 04 18:48:29 2009 +0000
@@ -0,0 +1,163 @@
+-- Prosody IM v0.3
+-- 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 pairs = pairs;
+local type = type;
+local error = error;
+local t_concat = table.concat;
+local t_insert = table.insert;
+local tostring = tostring;
+local tonumber = tonumber;
+local st = require "util.stanza";
+
+module "xmlrpc"
+
+local _lua_to_xmlrpc;
+local map = {
+	table=function(stanza, object)
+		stanza:tag("struct");
+		for name, value in pairs(object) do
+			stanza:tag("member");
+				stanza:tag("name"):text(tostring(name)):up();
+				stanza:tag("value");
+					_lua_to_xmlrpc(stanza, value);
+				stanza:up();
+			stanza:up();
+		end
+		stanza:up();
+	end;
+	boolean=function(stanza, object)
+		stanza:tag("boolean"):text(object and "1" or "0"):up();
+	end;
+	string=function(stanza, object)
+		stanza:tag("string"):text(object):up();
+	end;
+	number=function(stanza, object)
+		stanza:tag("int"):text(tostring(object)):up();
+	end;
+};
+_lua_to_xmlrpc = function(stanza, object)
+	local h = map[type(object)];
+	if h then
+		h(stanza, object);
+	else
+		error("Type not supported by XML-RPC: " .. type(object));
+	end
+end
+function create_response(object)
+	local stanza = st.stanza("methodResponse"):tag("params"):tag("param"):tag("value");
+	_lua_to_xmlrpc(stanza, object);
+	stanza:up():up():up();
+	return stanza;
+end
+function create_error_response(faultCode, faultString)
+	local stanza = st.stanza("methodResponse"):tag("fault"):tag("value");
+	_lua_to_xmlrpc(stanza, {faultCode=faultCode, faultString=faultString});
+	stanza:up():up();
+	return stanza;
+end
+
+
+local _xmlrpc_to_lua;
+local int_parse = function(stanza)
+	if #stanza.tags ~= 0 or #stanza == 0 then error("<"..stanza.name.."> must have a single text child"); end
+	local n = tonumber(t_concat(stanza));
+	if n then return n; end
+	error("Failed to parse content of <"..stanza.name..">");
+end
+local rmap = {
+	methodCall=function(stanza)
+		if #stanza.tags ~= 2 then error("<methodCall> must have exactly two subtags"); end -- FIXME <params> is optional
+		if stanza.tags[1].name ~= "methodName" then error("First <methodCall> child tag must be <methodName>") end
+		if stanza.tags[2].name ~= "params" then error("Second <methodCall> child tag must be <params>") end
+		return _xmlrpc_to_lua(stanza.tags[1]), _xmlrpc_to_lua(stanza.tags[2]);
+	end;
+	methodName=function(stanza)
+		if #stanza.tags ~= 0 then error("<methodName> must not have any subtags"); end
+		if #stanza == 0 then error("<methodName> must have text content"); end
+		return t_concat(stanza);
+	end;
+	params=function(stanza)
+		local t = {};
+		for _, child in pairs(stanza.tags) do
+			if child.name ~= "param" then error("<params> can only have <param> children"); end;
+			t_insert(t, _xmlrpc_to_lua(child));
+		end
+		return t;
+	end;
+	param=function(stanza)
+		if not(#stanza.tags == 1 and stanza.tags[1].name == "value") then error("<param> must have exactly one <value> child"); end
+		return _xmlrpc_to_lua(stanza.tags[1]);
+	end;
+	value=function(stanza)
+		if #stanza.tags == 0 then return t_concat(stanza); end
+		if #stanza.tags ~= 1 then error("<value> must have a single child"); end
+		return _xmlrpc_to_lua(stanza.tags[1]);
+	end;
+	int=int_parse;
+	i4=int_parse;
+	double=int_parse;
+	boolean=function(stanza)
+		if #stanza.tags ~= 0 or #stanza == 0 then error("<boolean> must have a single text child"); end
+		local b = t_concat(stanza);
+		if b ~= "1" and b ~= "0" then error("Failed to parse content of <boolean>"); end
+		return b == "1" and true or false;
+	end;
+	string=function(stanza)
+		if #stanza.tags ~= 0 then error("<string> must have a single text child"); end
+		return t_concat(stanza);
+	end;
+	array=function(stanza)
+		if #stanza.tags ~= 1 then error("<array> must have a single <data> child"); end
+		return _xmlrpc_to_lua(stanza.tags[1]);
+	end;
+	data=function(stanza)
+		local t = {};
+		for _,child in pairs(stanza.tags) do
+			if child.name ~= "value" then error("<data> can only have <value> children"); end
+			t_insert(t, _xmlrpc_to_lua(child));
+		end
+		return t;
+	end;
+	struct=function(stanza)
+		local t = {};
+		for _,child in pairs(stanza.tags) do
+			if child.name ~= "member" then error("<struct> can only have <member> children"); end
+			local name, value = _xmlrpc_to_lua(child);
+			t[name] = value;
+		end
+		return t;
+	end;
+	member=function(stanza)
+		if #stanza.tags ~= 2 then error("<member> must have exactly two subtags"); end -- FIXME <params> is optional
+		if stanza.tags[1].name ~= "name" then error("First <member> child tag must be <name>") end
+		if stanza.tags[2].name ~= "value" then error("Second <member> child tag must be <value>") end
+		return _xmlrpc_to_lua(stanza.tags[1]), _xmlrpc_to_lua(stanza.tags[2]);
+	end;
+	name=function(stanza)
+		if #stanza.tags ~= 0 then error("<name> must have a single text child"); end
+		local n = t_concat(stanza)
+		if tostring(tonumber(n)) == n then n = tonumber(n); end
+		return n;
+	end;
+}
+_xmlrpc_to_lua = function(stanza)
+	local h = rmap[stanza.name];
+	if h then
+		return h(stanza);
+	else
+		error("Unknown element: "..stanza.name);
+	end
+end
+function translate_request(stanza)
+	if stanza.name ~= "methodCall" then error("XML-RPC requests must have <methodCall> as root element"); end
+	return _xmlrpc_to_lua(stanza);
+end
+
+return _M;