diff options
-rw-r--r-- | core/modulemanager.lua | 9 | ||||
-rw-r--r-- | plugins/mod_muc.lua | 30 | ||||
-rw-r--r-- | plugins/mod_xmlrpc.lua | 117 | ||||
-rw-r--r-- | util/xmlrpc.lua | 163 |
4 files changed, 316 insertions, 3 deletions
diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 5afe3144..76ea0bc0 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -212,6 +212,7 @@ function handle_stanza(host, origin, stanza) 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 @@ addDiscoInfoHandler("*host", function(reply, to, from, node) 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); diff --git a/plugins/mod_muc.lua b/plugins/mod_muc.lua index 1f8d04e9..0a4a7b71 100644 --- a/plugins/mod_muc.lua +++ b/plugins/mod_muc.lua @@ -71,6 +71,30 @@ local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabbe 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 @@ function handle_to_occupant(origin, stanza) -- PM, vCards, etc 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 @@ function handle_to_occupant(origin, stanza) -- PM, vCards, etc 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 @@ function handle_to_room(origin, stanza) -- presence changes and groupchat messag 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 diff --git a/plugins/mod_xmlrpc.lua b/plugins/mod_xmlrpc.lua new file mode 100644 index 00000000..6fdfe8be --- /dev/null +++ b/plugins/mod_xmlrpc.lua @@ -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 } diff --git a/util/xmlrpc.lua b/util/xmlrpc.lua new file mode 100644 index 00000000..5a391754 --- /dev/null +++ b/util/xmlrpc.lua @@ -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; |