--- a/examples/ibb.lua Mon Mar 23 09:22:04 2009 +0200
+++ b/examples/ibb.lua Fri Mar 27 01:46:53 2009 +0200
@@ -1,304 +1,184 @@
-- IN-BAND BYTESTREAMS (XEP-0047)
--- library
-
-require 'lm'
-require 'iq'
-require 'base64'
+-- TODO bidirectionality
+-- thus, on stream accept we can add our sid to incoming files structure,
+-- as if we received and accepted incoming request.
+-- message stanzas
--- public
+require 'lm' -- FIXME
+local iq = require 'iq'
+local base64 = require 'base64'
-ibb = {
- block_size = 4096,
- streamhandler =
+local F = { }
+local M = { }
+M.__index = M
+local O = {
+ handler =
function ( accept, reject )
reject ()
end,
}
-local ibb_sid = 0 -- private
+function F.new ( conn, to, bs, sid )
+ local obj = {
+ conn = conn,
+ to = to,
+ sbs = bs,
+ bs = math.floor ( bs * 3 / 4 ),
+ sid = sid,
+ seq = 0,
+ }
+ setmetatable ( obj, M )
+ return obj
+end
-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',
+function M.open ( obj, success, fail )
+ iq.send ( obj.conn, obj.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
+ open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' }
+ }, success, fail )
+end
+
+function M.send ( obj, data, success, fail )
+ if data and data ~= '' then
+ local start = 0
+ while start < data:len () do
+ local chunk = base64.encode ( data:sub ( start, obj.bs ) )
+ local cseq = obj.seq -- local instance
+ iq.send ( obj.conn, obj.to, 'set',
+ {
+ data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
+ chunk,
+ },
+ },
+ function ()
+ success ( cseq )
+ end,
+ function ( mesg )
+ noerr = false
+ fail ( mesg )
end )
- end, fail )
+ start = start + obj.bs
+ obj.seq = obj.seq + 1
+ end
+ end
+end
+
+function M.close ( obj, success, fail )
+ iq.send ( obj.conn, obj.to, 'set',
+ {
+ close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' },
+ }, success, fail )
end
--- private
+function F.handler ( handler )
+ O.handler = handler
+end
+
+local ibb_files = {}
-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
+function F.iq_handler ( 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 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' )
- 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
+ if action == 'open' then
+ if not ibb_files[sid] then
+ O.handler ( 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' },
},
} )
- 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
+ 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
- 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.' )
+ 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
-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" } )
+ return true
+end
-
-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' )
+return F
-- vim: se ts=4: --