-- IN-BAND BYTESTREAMS (XEP-0047)
-- library
require 'lm'
require 'iq'
require 'base64'
-- 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: --