# HG changeset patch # User Tobias Markmann # Date 1226753237 -3600 # Node ID 80e7de32b61883949e982cce10d18ed3a858bcf3 # Parent 516f4c9019912cfcaf43d75cee778bc3d6b91064# Parent 826308c076270449ac7cd0e295b4a417f400b388 Merging my new SASL code with Waqas' adjusted saslauth module. diff -r 516f4c901991 -r 80e7de32b618 core/componentmanager.lua --- a/core/componentmanager.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/core/componentmanager.lua Sat Nov 15 13:47:17 2008 +0100 @@ -25,8 +25,9 @@ if not hosts[host] then -- TODO check for host well-formedness components[host] = component; - hosts[host] = {type = "component", host = host, connected = true}; + hosts[host] = {type = "component", host = host, connected = true, s2sout = {} }; log("debug", "component added: "..host); + return hosts[host]; else log("error", "Attempt to set component for existing host: "..host); end diff -r 516f4c901991 -r 80e7de32b618 core/offlinemanager.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/offlinemanager.lua Sat Nov 15 13:47:17 2008 +0100 @@ -0,0 +1,32 @@ + +local datamanager = require "util.datamanager"; +local st = require "util.stanza"; +local datetime = require "util.datetime"; +local ipairs = ipairs; + +module "offlinemanager" + +function store(node, host, stanza) + stanza.attr.stamp = datetime.datetime(); + stanza.attr.stamp_legacy = datetime.legacy(); + return datamanager.list_append(node, host, "offline", st.preserialize(stanza)); +end + +function load(node, host) + local data = datamanager.list_load(node, host, "offline"); + if not data then return; end + for k, v in ipairs(data) do + local stanza = st.deserialize(v); + 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; + data[k] = stanza; + end + return data; +end + +function deleteAll(node, host) + return datamanager.list_store(node, host, "offline", nil); +end + +return _M; diff -r 516f4c901991 -r 80e7de32b618 core/rostermanager.lua --- a/core/rostermanager.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/core/rostermanager.lua Sat Nov 15 13:47:17 2008 +0100 @@ -1,8 +1,6 @@ -local mainlog = log; -local function log(type, message) - mainlog(type, "rostermanager", message); -end + +local log = require "util.logger".init("rostermanager"); local setmetatable = setmetatable; local format = string.format; @@ -234,7 +232,7 @@ if item.subscription == "from" then item.subscription = "none"; changed = true; - elseif item.subscription == both then + elseif item.subscription == "both" then item.subscription = "to"; changed = true; end diff -r 516f4c901991 -r 80e7de32b618 core/s2smanager.lua --- a/core/s2smanager.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/core/s2smanager.lua Sat Nov 15 13:47:17 2008 +0100 @@ -4,12 +4,15 @@ local socket = require "socket"; local format = string.format; local t_insert = table.insert; +local get_traceback = debug.traceback; local tostring, pairs, ipairs, getmetatable, print, newproxy, error, tonumber = tostring, pairs, ipairs, getmetatable, print, newproxy, error, tonumber; local connlisteners_get = require "net.connlisteners".get; local wraptlsclient = require "net.server".wraptlsclient; local modulemanager = require "core.modulemanager"; +local st = require "stanza"; +local stanza = st.stanza; local uuid_gen = require "util.uuid".generate; @@ -21,28 +24,37 @@ local dialback_secret = "This is very secret!!! Ha!"; -local srvmap = { ["gmail.com"] = "talk.google.com", ["identi.ca"] = "longlance.controlezvous.ca", ["cdr.se"] = "jabber.cdr.se" }; +local srvmap = { ["gmail.com"] = "talk.google.com", ["identi.ca"] = "hampton.controlezvous.ca", ["cdr.se"] = "jabber.cdr.se" }; module "s2smanager" -function connect_host(from_host, to_host) -end - function send_to_host(from_host, to_host, data) - local host = hosts[to_host]; + local host = hosts[from_host].s2sout[to_host]; if host then - -- Write to connection - if host.type == "s2sout_unauthed" and not host.notopen and not host.dialback_key then - log("debug", "trying to send over unauthed s2sout to "..to_host..", authing it now..."); - initiate_dialback(host); - if not host.sendq then host.sendq = { data }; - else t_insert(host.sendq, data); end + -- We have a connection to this host already + if host.type == "s2sout_unauthed" then + host.log("debug", "trying to send over unauthed s2sout to "..to_host..", authing it now..."); + if not host.notopen and not host.dialback_key then + host.log("debug", "dialback had not been initiated"); + initiate_dialback(host); + end + + -- Queue stanza until we are able to send it + if host.sendq then t_insert(host.sendq, data); + else host.sendq = { data }; end + elseif host.type == "local" or host.type == "component" then + log("error", "Trying to send a stanza to ourselves??") + log("error", "Traceback: %s", get_traceback()); + log("error", "Stanza: %s", tostring(data)); else - log("debug", "going to send stanza to "..to_host.." from "..from_host); + (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host); -- FIXME - if hosts[to_host].from_host ~= from_host then log("error", "WARNING! This might, possibly, be a bug, but it might not..."); end - hosts[to_host].sends2s(data); - log("debug", "stanza sent over "..hosts[to_host].type); + if host.from_host ~= from_host then + log("error", "WARNING! This might, possibly, be a bug, but it might not..."); + log("error", "We are going to send from %s instead of %s", host.from_host, from_host); + end + host.sends2s(data); + host.log("debug", "stanza sent over "..host.type); end else log("debug", "opening a new outgoing connection for this stanza"); @@ -52,10 +64,6 @@ end end -function disconnect_host(host) - -end - local open_sessions = 0; function new_incoming(conn) @@ -72,23 +80,27 @@ function new_outgoing(from_host, to_host) local host_session = { to_host = to_host, from_host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" }; - hosts[to_host] = host_session; + hosts[from_host].s2sout[to_host] = host_session; local cl = connlisteners_get("xmppserver"); local conn, handler = socket.tcp() + --FIXME: Below parameters (ports/ip) are incorrect (use SRV) + to_host = srvmap[to_host] or to_host; + + conn:settimeout(0); + local success, err = conn:connect(to_host, 5269); + if not success then + log("warn", "s2s connect() failed: %s", err); + end + + conn = wraptlsclient(cl, conn, to_host, 5269, 0, 1, hosts[from_host].ssl_ctx ); + host_session.conn = conn; -- Register this outgoing connection so that xmppserver_listener knows about it -- otherwise it will assume it is a new incoming connection cl.register_outgoing(conn, host_session); - --FIXME: Below parameters (ports/ip) are incorrect (use SRV) - to_host = srvmap[to_host] or to_host; - conn:settimeout(0.1); - conn:connect(to_host, 5269); - conn = wraptlsclient(cl, conn, to_host, 5269, 0, 1, hosts[from_host].ssl_ctx ); - host_session.conn = conn; - do local conn_name = "s2sout"..tostring(conn):match("[a-f0-9]*$"); host_session.log = logger_init(conn_name); @@ -103,7 +115,6 @@ end function streamopened(session, attr) - session.log("debug", "s2s stream opened"); local send = session.sends2s; session.version = tonumber(attr.version) or 0; @@ -124,7 +135,7 @@ session.streamid = uuid_gen(); print(session, session.from_host, "incoming s2s stream opened"); send(""); - send(format("", session.streamid, session.to_host)); + send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag()); elseif session.direction == "outgoing" then -- If we are just using the connection for verifying dialback keys, we won't try and auth it if not attr.id then error("stream response did not give us a streamid!!!"); end @@ -147,7 +158,7 @@ end send("");]] - log("info", "s2s stream opened successfully"); + session.notopen = nil; end @@ -194,7 +205,7 @@ if session.direction == "outgoing" then if sendq then - session.log("debug", "sending queued stanzas across new outgoing connection to "..session.to_host); + session.log("debug", "sending "..#sendq.." queued stanzas across new outgoing connection to "..session.to_host); for i, data in ipairs(sendq) do send(data); sendq[i] = nil; @@ -207,7 +218,7 @@ function destroy_session(session) (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)); if session.direction == "outgoing" then - hosts[session.to_host] = nil; + hosts[session.from_host].s2sout[session.to_host] = nil; end session.conn = nil; session.disconnect = nil; diff -r 516f4c901991 -r 80e7de32b618 core/stanza_router.lua --- a/core/stanza_router.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/core/stanza_router.lua Sat Nov 15 13:47:17 2008 +0100 @@ -13,6 +13,7 @@ local rostermanager = require "core.rostermanager"; local sessionmanager = require "core.sessionmanager"; +local offlinemanager = require "core.offlinemanager"; local s2s_verify_dialback = require "core.s2smanager".verify_dialback; local s2s_make_authenticated = require "core.s2smanager".make_authenticated; @@ -31,7 +32,7 @@ local print = print; function core_process_stanza(origin, stanza) - log("debug", "Received["..origin.type.."]: "..tostring(st.reply(st.reply(stanza)))) + log("debug", "Received[%s]: %s", origin.type, stanza:pretty_top_tag()) if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end -- FIXME Hack. This should be removed when we fix namespace handling. -- TODO verify validity of stanza (as well as JID validity) @@ -149,6 +150,10 @@ core_route_stanza(origin, request); end end + for _, msg in ipairs(offlinemanager.load(node, host) or {}) do + origin.send(msg); -- FIXME do we need to modify to/from in any way? + end + offlinemanager.deleteAll(node, host); end origin.priority = 0; if stanza.attr.type == "unavailable" then @@ -168,11 +173,23 @@ end stanza.attr.to = nil; -- reset it else - -- TODO error, bad type + log("warn", "Unhandled c2s presence: %s", tostring(stanza)); + if stanza.attr.type ~= "error" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? + end + end + else + log("warn", "Unhandled c2s stanza: %s", tostring(stanza)); + if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? end end -- TODO handle other stanzas else - log("warn", "Unhandled origin: %s", origin.type); -- FIXME reply with error + log("warn", "Unhandled origin: %s", origin.type); + if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then + -- s2s stanzas can get here + (origin.sends2s or origin.send)(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error? + end end end @@ -328,8 +345,14 @@ t_insert(recipients, session); end end + local count = 0; for _, session in pairs(recipients) do session.send(stanza); + count = count + 1; + end + if count == 0 then + offlinemanager.store(node, host, stanza); + -- TODO deal with storage errors end else -- TODO send IQ error @@ -349,7 +372,12 @@ -- TODO send unavailable presence or unsubscribed end elseif stanza.name == "message" then - -- TODO send message error, or store offline messages + if stanza.attr.type == "chat" or stanza.attr.type == "normal" or not stanza.attr.type then + offlinemanager.store(node, host, stanza); + -- FIXME don't store messages with only chat state notifications + end + -- TODO allow configuration of offline storage + -- TODO send error if not storing offline elseif stanza.name == "iq" then -- TODO send IQ error end diff -r 516f4c901991 -r 80e7de32b618 main.lua --- a/main.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/main.lua Sat Nov 15 13:47:17 2008 +0100 @@ -17,7 +17,7 @@ if config.hosts and #config.hosts > 0 then for _, host in pairs(config.hosts) do - hosts[host] = {type = "local", connected = true, sessions = {}, host = host}; + hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {} }; end else error("No hosts defined in the configuration file"); end @@ -32,6 +32,9 @@ require "core.sessionmanager" require "core.stanza_router" +pcall(require, "remdebug.engine"); +if remdebug then remdebug.engine.start() end + local start = require "net.connlisteners".start; require "util.stanza" require "util.jid" diff -r 516f4c901991 -r 80e7de32b618 net/xmppclient_listener.lua --- a/net/xmppclient_listener.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/net/xmppclient_listener.lua Sat Nov 15 13:47:17 2008 +0100 @@ -78,7 +78,7 @@ if session then if session.last_presence and session.last_presence.attr.type ~= "unavailable" then local pres = st.presence{ type = "unavailable" }; - if err == "closed" then err = "connection closed"; end + if err == "closed" then err = "connection closed"; end --FIXME where did err come from? pres:tag("status"):text("Disconnected: "..err); session.stanza_dispatch(pres); end diff -r 516f4c901991 -r 80e7de32b618 plugins/mod_dialback.lua --- a/plugins/mod_dialback.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/plugins/mod_dialback.lua Sat Nov 15 13:47:17 2008 +0100 @@ -22,6 +22,7 @@ type = "invalid" log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to); end + log("debug", "verifyied dialback key... it is %s", type); origin.sends2s(format("%s", attr.to, attr.from, attr.id, type, stanza[1])); end); @@ -38,7 +39,7 @@ send_s2s(origin.to_host, origin.from_host, format("%s", origin.to_host, origin.from_host, origin.streamid, origin.dialback_key)); - hosts[origin.from_host].dialback_verifying = origin; + hosts[origin.to_host].s2sout[origin.from_host].dialback_verifying = origin; end); add_handler({ "s2sout_unauthed", "s2sout" }, "verify", xmlns_dialback, diff -r 516f4c901991 -r 80e7de32b618 plugins/mod_saslauth.lua --- a/plugins/mod_saslauth.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/plugins/mod_saslauth.lua Sat Nov 15 13:47:17 2008 +0100 @@ -2,6 +2,7 @@ local st = require "util.stanza"; local send = require "core.sessionmanager".send_to_session; local sm_bind_resource = require "core.sessionmanager".bind_resource; +local jid local usermanager_validate_credentials = require "core.usermanager".validate_credentials; local t_concat, t_insert = table.concat, table.insert; @@ -15,48 +16,100 @@ local new_sasl = require "util.sasl".new; -add_handler("c2s_unauthed", "auth", xmlns_sasl, function (session, stanza) - if not session.sasl_handler then - session.sasl_handler = new_sasl(stanza.attr.mechanism, - function (username, password) - -- onAuth - require "core.usermanager" - if usermanager_validate_credentials(session.host, username, password) then - return true; - end - return false; - end, - function (username) - -- onSuccess - local success, err = sessionmanager.make_authenticated(session, username); - if not success then - sessionmanager.destroy_session(session); - return; - end - session.sasl_handler = nil; - session:reset_stream(); - end, - function (reason) - -- onFail - log("debug", "SASL failure, reason: %s", reason); - end, - function (stanza) - -- onWrite - log("debug", "SASL writes: %s", tostring(stanza)); - send(session, stanza); +local function build_reply(status, ret) + local reply = st.stanza(status, {xmlns = xmlns_sasl}); + if status == "challenge" then + reply:text(ret or ""); + elseif status == "failure" then + reply:tag(ret):up(); + elseif status == "success" then + reply:text(ret or ""); + else + error("Unknown sasl status: "..status); + end + return reply; +end + +local function handle_status(session, status) + if status == "failure" then + session.sasl_handler = nil; + elseif status == "success" then + session.sasl_handler = nil; + session:reset_stream(); + end +end + +local function password_callback(jid, mechanism) + local node, host = jid_split(jid); + local password = (datamanager.load(node, host, "accounts") or {}).password; -- FIXME handle hashed passwords + local func = function(x) return x; end; + if password then + if mechanism == "PLAIN" then + return func, password; + elseif mechanism == "DIGEST-MD5" then + return func, require "hashes".md5(node.."::"..password); + end + end + return func, nil; +end + +add_handler("c2s_unauthed", "auth", xmlns_sasl, + function (session, stanza) + if not session.sasl_handler then + session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, password_callback); + local status, ret = session.sasl_handler:feed(stanza[1]); + handle_status(session, status); + session.send(build_reply(status, ret)); + --[[session.sasl_handler = new_sasl(stanza.attr.mechanism, + function (username, password) + -- onAuth + require "core.usermanager" + if usermanager_validate_credentials(session.host, username, password) then + return true; + end + return false; + end, + function (username) + -- onSuccess + local success, err = sessionmanager.make_authenticated(session, username); + if not success then + sessionmanager.destroy_session(session); + return; + end + session.sasl_handler = nil; + session:reset_stream(); + end, + function (reason) + -- onFail + log("debug", "SASL failure, reason: %s", reason); + end, + function (stanza) + -- onWrite + log("debug", "SASL writes: %s", tostring(stanza)); + send(session, stanza); + end + ); + session.sasl_handler:feed(stanza); ]] + else + error("Client tried to negotiate SASL again", 0); end - ); - session.sasl_handler:feed(stanza); - else - error("Client tried to negotiate SASL again", 0); - end -end); + end); -add_handler("c2s_unauthed", "response", xmlns_sasl, function (session, stanza) - if session.sasl_handler then - session.sasl_handler:feed(stanza); - end -end); +add_handler("c2s_unauthed", "abort", xmlns_sasl, + function(session, stanza) + if not session.sasl_handler then error("Attempt to abort when sasl has not started"); end + local status, ret = session.sasl_handler:feed(stanza[1]); + handle_status(session, status); + session.send(build_reply(status, ret)); + end); + +add_handler("c2s_unauthed", "response", xmlns_sasl, + function(session, stanza) + if not session.sasl_handler then error("Attempt to respond when sasl has not started"); end + local status, ret = session.sasl_handler:feed(stanza[1]); + handle_status(session, status); + session.send(build_reply(status, ret)); + end); add_event_hook("stream-features", function (session, features) diff -r 516f4c901991 -r 80e7de32b618 util/datamanager.lua --- a/util/datamanager.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/util/datamanager.lua Sat Nov 15 13:47:17 2008 +0100 @@ -1,14 +1,15 @@ local format = string.format; local setmetatable, type = setmetatable, type; -local pairs = pairs; +local pairs, ipairs = pairs, ipairs; local char = string.char; local loadfile, setfenv, pcall = loadfile, setfenv, pcall; local log = log; local io_open = io.open; local os_remove = os.remove; -local tostring = tostring; +local tostring, tonumber = tostring, tonumber; local error = error; local next = next; +local t_insert = table.insert; local indent = function(f, i) for n = 1, i do @@ -69,13 +70,14 @@ ------- API ------------- -function getpath(username, host, datastore) +function getpath(username, host, datastore, ext) + ext = ext or "dat"; if username then - return format("data/%s/%s/%s.dat", encode(host), datastore, encode(username)); + return format("data/%s/%s/%s.%s", encode(host), datastore, encode(username), ext); elseif host then - return format("data/%s/%s.dat", encode(host), datastore); + return format("data/%s/%s.%s", encode(host), datastore, ext); else - return format("data/%s.dat", datastore); + return format("data/%s.%s", datastore, ext); end end @@ -115,4 +117,59 @@ return true; end -return _M; \ No newline at end of file +function list_append(username, host, datastore, data) + if not data then return; end + -- save the datastore + local f, msg = io_open(getpath(username, host, datastore, "list"), "a+"); + if not f then + log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil")); + return; + end + f:write("item("); + simplesave(f, data, 1); + f:write(");\n"); + f:close(); + return true; +end + +function list_store(username, host, datastore, data) + if not data then + data = {}; + end + -- save the datastore + local f, msg = io_open(getpath(username, host, datastore, "list"), "w+"); + if not f then + log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil")); + return; + end + for _, d in ipairs(data) do + f:write("item("); + simplesave(f, d, 1); + f:write(");\n"); + end + f:close(); + if not next(data) then -- try to delete empty datastore + os_remove(getpath(username, host, datastore, "list")); + end + -- we write data even when we are deleting because lua doesn't have a + -- platform independent way of checking for non-exisitng files + return true; +end + +function list_load(username, host, datastore) + local data, ret = loadfile(getpath(username, host, datastore, "list")); + if not data then + log("warn", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); + return nil; + end + local items = {}; + setfenv(data, {item = function(i) t_insert(items, i); end}); + local success, ret = pcall(data); + if not success then + log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil")); + return nil; + end + return items; +end + +return _M; diff -r 516f4c901991 -r 80e7de32b618 util/datetime.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util/datetime.lua Sat Nov 15 13:47:17 2008 +0100 @@ -0,0 +1,28 @@ +-- XEP-0082: XMPP Date and Time Profiles + +local os_date = os.date; +local error = error; + +module "datetime" + +function date() + return os_date("!%Y-%m-%d"); +end + +function datetime() + return os_date("!%Y-%m-%dT%H:%M:%SZ"); +end + +function time() + return os_date("!%H:%M:%S"); +end + +function legacy() + return os_date("!%Y%m%dT%H:%M:%S"); +end + +function parse(s) + error("datetime.parse: Not implemented"); -- TODO +end + +return _M; diff -r 516f4c901991 -r 80e7de32b618 util/logger.lua --- a/util/logger.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/util/logger.lua Sat Nov 15 13:47:17 2008 +0100 @@ -3,8 +3,21 @@ local print = print; local debug = debug; local tostring = tostring; + +local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; +local do_pretty_printing = not os.getenv("WINDIR"); + module "logger" +local logstyles = {}; + +--TODO: This should be done in config, but we don't have proper config yet +if do_pretty_printing then + logstyles["info"] = getstyle("bold"); + logstyles["warn"] = getstyle("bold", "yellow"); + logstyles["error"] = getstyle("bold", "red"); +end + function init(name) --name = nil; -- While this line is not commented, will automatically fill in file/line number info return function (level, message, ...) @@ -13,9 +26,9 @@ level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline; end if ... then - print(level, format(message, ...)); + print(name, getstring(logstyles[level], level), format(message, ...)); else - print(level, message); + print(name, getstring(logstyles[level], level), message); end end end diff -r 516f4c901991 -r 80e7de32b618 util/stanza.lua --- a/util/stanza.lua Wed Nov 12 21:38:46 2008 +0100 +++ b/util/stanza.lua Sat Nov 15 13:47:17 2008 +0100 @@ -10,8 +10,11 @@ local print = print; local unpack = unpack; local s_gsub = string.gsub; +local os = os; -local debug = debug; +local do_pretty_printing = not os.getenv("WINDIR"); +local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; + local log = require "util.logger".init("stanza"); module "stanza" @@ -105,6 +108,14 @@ return s_format("<%s%s>%s", t.name, attr_string, children_text, t.name); end +function stanza_mt.top_tag(t) + local attr_string = ""; + if t.attr then + for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, tostring(v)); end end + end + return s_format("<%s%s>", t.name, attr_string); +end + function stanza_mt.__add(s1, s2) return s1:add_direct_child(s2); end @@ -184,4 +195,44 @@ return stanza("presence", attr); end +if do_pretty_printing then + local style_attrk = getstyle("yellow"); + local style_attrv = getstyle("red"); + local style_tagname = getstyle("red"); + local style_punc = getstyle("magenta"); + + local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'"); + local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">"); + --local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, ""); + local tag_format = top_tag_format.."%s"..getstring(style_punc, ""); + function stanza_mt.pretty_print(t) + local children_text = ""; + for n, child in ipairs(t) do + if type(child) == "string" then + children_text = children_text .. xml_escape(child); + else + children_text = children_text .. child:pretty_print(); + end + end + + local attr_string = ""; + if t.attr then + for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end + end + return s_format(tag_format, t.name, attr_string, children_text, t.name); + end + + function stanza_mt.pretty_top_tag(t) + local attr_string = ""; + if t.attr then + for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end + end + return s_format(top_tag_format, t.name, attr_string); + end +else + -- Sorry, fresh out of colours for you guys ;) + stanza_mt.pretty_print = stanza_mt.__tostring; + stanza_mt.pretty_top_tag = stanza_mt.top_tag; +end + return _M; diff -r 516f4c901991 -r 80e7de32b618 util/termcolours.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util/termcolours.lua Sat Nov 15 13:47:17 2008 +0100 @@ -0,0 +1,33 @@ +local t_concat, t_insert = table.concat, table.insert; +local char, format = string.char, string.format; +local ipairs = ipairs; +module "termcolours" + +local stylemap = { + reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8; + black = 30; red = 31; green = 32; yellow = 33; blue = 34; magenta = 35; cyan = 36; white = 37; + ["black background"] = 40; ["red background"] = 41; ["green background"] = 42; ["yellow background"] = 43; ["blue background"] = 44; ["magenta background"] = 45; ["cyan background"] = 46; ["white background"] = 47; + bold = 1, dark = 2, underline = 4, underlined = 4, normal = 0; + } + +local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m"; +function getstring(style, text) + if style then + return format(fmt_string, style, text); + else + return text; + end +end + +function getstyle(...) + local styles, result = { ... }, {}; + for i, style in ipairs(styles) do + style = stylemap[style]; + if style then + t_insert(result, style); + end + end + return t_concat(result, ";"); +end + +return _M;