--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/ibb.lua Sat Mar 21 03:42:49 2009 +0200
@@ -0,0 +1,325 @@
+
+-- IN-BAND BYTESTREAMS (XEP-0047)
+
+-- library
+
+require 'lm'
+require 'base64'
+
+iq = { }
+
+function iq.send ( conn, to, smtype, data, success, fail )
+ data.mtype = 'iq-' .. smtype
+ data.to = to
+ conn:send ( lm.message.create ( data ),
+ function ( conn, mess )
+ local mtype, smtype = mess:type ()
+ if smtype == 'result' then
+ success ()
+ elseif smtype == 'error' then
+ fail ( mess:child( 'error' ):children():name () ) -- FIXME
+ else
+ fail ( mess:xml () )
+ return false
+ end
+ return true
+ end )
+end
+
+-- public
+
+ibb = {
+ block_size = 4096,
+ streamhandler =
+ function ( accept, reject )
+ reject ()
+ end,
+}
+
+local ibb_sid = 0 -- private
+
+function ibb.send ( conn, to, success, fail, id )
+ local bs = ibb.block_size -- local instance
+ local sid = id
+ if not sid then
+ ibb_sid = ibb_sid + 1
+ sid = 'ibb_' .. ibb_sid
+ end
+ iq.send ( conn, to, 'set',
+ {
+ open = { sid = sid, ['block-size'] = bs, xmlns = 'http://jabber.org/protocol/ibb' }
+ },
+ function ()
+ local seq = 0
+ local noerr = true
+ success (
+ function ( data, success, fail )
+ if not data then
+ iq.send ( conn, to, 'set',
+ {
+ close = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb' },
+ },
+ function ()
+ success ( 'end' ) -- XXX
+ end,
+ fail )
+ elseif data ~= '' then
+ local encoded = base64.encode ( data )
+ while encoded:len () > 0 and noerr do
+ local chunk = encoded:sub ( 1, bs )
+ local cseq = seq -- local instance for closure
+ encoded = encoded:sub ( bs + 1 )
+ seq = seq + 1
+ iq.send ( conn, to, 'set',
+ {
+ data = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
+ chunk,
+ },
+ },
+ function ()
+ success ( cseq )
+ end,
+ function ( mesg )
+ noerr = false
+ fail ( mesg )
+ end )
+ end
+ end
+ end )
+ end,
+ fail )
+end
+
+-- private
+
+local ibb_files = {}
+local ibb_handler_registered = false
+local ibb_incoming_iq_handler = lm.message_handler.new (
+ function ( conn, mess )
+ local mtype, smtype = mess:type ()
+ if smtype ~= 'set' then
+ return false
+ end
+
+ local child = mess:children ()
+ if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
+ return false
+ end
+
+ local id = mess:attribute ( 'id' )
+ local from = mess:attribute ( 'from' )
+ local action = child:name ()
+ local sid = child:attribute ( 'sid' )
+
+ if action == 'open' then
+ if not ibb_files[sid] then
+ ibb.streamhandler ( from,
+ function ( success, fail )
+ ibb_files[sid] = { from = from, success = success, fail = fail }
+ conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+ end,
+ function ()
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '405', type = 'cancel',
+ ['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ end )
+ else
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '409', type = 'cancel',
+ conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ end
+ elseif action == 'data' then
+ local seq = child:attribute ( 'seq' )
+ if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
+ local data = child:value ()
+ conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+ ibb_files[sid][tonumber(seq)+1] = data
+-- XXX ibb_files[sid].success ( seq )
+ else
+ if ibb_files[sid] then
+ ibb_files[sid].fail ( 'conflict' )
+ ibb_files[sid] = nil -- invalidate session
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '409', type = 'cancel',
+ conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ else
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '404', type = 'cancel', -- XXX: check
+ ['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ end
+ end
+ elseif action == 'close' then
+ if ibb_files[sid] and from == ibb_files[sid].from then
+ local data = ''
+ for seq, chunk in ipairs ( ibb_files[sid] ) do
+ data = data .. chunk
+ end
+ local decoded = base64.decode ( data )
+ ibb_files[sid].success ( decoded )
+ conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+ ibb_files[sid] = nil
+ else
+ if ibb_files[sid] then
+ ibb_files[sid].fail ( 'conflict' )
+ ibb_files[sid] = nil -- invalidate session
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '409', type = 'cancel',
+ conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ else
+ conn:send (
+ lm.message.create { to = from, mtype = 'iq-error', id = id,
+ error = { code = '404', type = 'cancel', -- XXX: check
+ ['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+ }
+ } )
+ end
+ end
+ else
+ return false
+ end
+
+ return true
+ end )
+
+-- mcabber
+
+local mc_incoming_files = { }
+
+ibb.streamhandler =
+ function ( from, accept, reject )
+ local fid = #mc_incoming_files + 1
+ mc_incoming_files[fid] = {
+ from = from,
+ accept =
+ function ( name )
+ mc_incoming_files[fid].name = name
+ accept (
+ function ( data )
+ local h = io.open ( mc_incoming_files[fid].name, 'w' )
+ if not h then
+ print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name )
+ return
+ end
+ h:write ( data )
+ h:close ()
+ print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name )
+ mc_incoming_files[fid] = nil
+ end,
+ function ( mesg )
+ main.print_info ( from, 'Stream error: ' .. mesg )
+ mc_incoming_files[fid] = nil -- XXX
+ end )
+ end,
+ reject =
+ function ()
+ reject ()
+ print ( 'Stream ' .. fid .. ' rejected' )
+ mc_incoming_files[fid] = nil
+ end,
+ }
+ main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' )
+ end
+
+main.command ( 'ibb',
+ function ( args )
+ local action = args[1]
+ if action == 'send' then
+ local who
+ if args.t then
+ who = args.t
+ else
+ who = main.full_jid ()
+ end
+ local fname = args[2]
+ ibb.send ( lm.connection.bless ( main.connection () ), who,
+ function ( sender )
+ main.print_info ( who, 'Stream accepted' )
+ local noerr = true
+ local h = io.open ( fname, 'r' )
+ if not h then
+ print ( 'Cannot open file ' .. fname )
+ return
+ end
+ local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/
+ h:close ()
+ local fail =
+ function ( mesg )
+ noerr = false
+ main.print_info ( who, 'Stream error: ' .. mesg )
+ end
+ sender ( data,
+ function ( seq )
+ main.print_info ( who, 'Delivery notification of chunk #' .. seq )
+ end, fail )
+ if noerr then
+ sender ( nil,
+ function ( seq )
+ main.print_info ( who, 'Stream finalizing notification' )
+ end, fail )
+ end
+ if noerr then
+ main.print_info ( who, 'Stream sent' )
+ else
+ main.print_info ( who, 'Stream error occured' )
+ end
+ end,
+ function ( mesg )
+ main.print_info ( who, 'Stream initiation error: ' .. mesg )
+ end )
+ elseif action == 'accept' then
+ local id = tonumber(args[2])
+ if mc_incoming_files[id] then
+ mc_incoming_files[id].accept ( args[3] )
+ end
+ elseif action == 'reject' then
+ local id = tonumber(args[2])
+ if mc_incoming_files[id] then
+ mc_incoming_files[id].reject ()
+ end
+ else
+ local text = ''
+ for sid, data in pairs ( mc_incoming_files ) do
+ text = text .. '\n' .. sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' )
+ end
+ if text ~= '' then
+ print ( 'List of incoming streams:' .. text )
+ else
+ print ( 'No streams' )
+ end
+ end
+ end, true, { "send", "accept", "reject" } )
+
+
+commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
+
+hooks_d['hook-post-connect'].ibb =
+ function ( args )
+ lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq', 'normal' )
+ ibb_handler_registered = true
+ hooks_d['hook-post-connect'].ibb = nil
+ hooks_d['hook-quit'].ibb =
+ function ( args )
+ if ibb_handler_registered then
+ lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq' )
+ end
+ end
+ end
+
+main.add_feature ( 'http://jabber.org/protocol/ibb' )
+
+-- vim: se ts=4: --